Current section

Files

Jump to
graded src graded@internal@checker.erl
Raw

src/graded@internal@checker.erl

-module(graded@internal@checker).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/graded/internal/checker.gleam").
-export([infer/4, check/4]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(false).
-file("src/graded/internal/checker.gleam", 112).
?DOC(false).
-spec polymorphic_param_bounds(
graded@internal@types:effect_set(),
gleam@set:set(binary())
) -> list(graded@internal@types:param_bound()).
polymorphic_param_bounds(Effect_set, Fn_typed_params) ->
case Effect_set of
{polymorphic, _, Variables} ->
_pipe = Variables,
_pipe@1 = gleam@set:to_list(_pipe),
_pipe@2 = gleam@list:filter(
_pipe@1,
fun(V) -> gleam@set:contains(Fn_typed_params, V) end
),
_pipe@3 = gleam@list:sort(_pipe@2, fun gleam@string:compare/2),
gleam@list:map(
_pipe@3,
fun(V@1) ->
{param_bound,
V@1,
{polymorphic,
gleam@set:new(),
gleam@set:from_list([V@1])}}
end
);
_ ->
[]
end.
-file("src/graded/internal/checker.gleam", 131).
?DOC(false).
-spec build_function_map(glance:module_()) -> gleam@dict:dict(binary(), glance:definition(glance:function_())).
build_function_map(Module) ->
_pipe = erlang:element(6, Module),
_pipe@1 = gleam@list:map(
_pipe,
fun(Definition) ->
{erlang:element(3, erlang:element(3, Definition)), Definition}
end
),
maps:from_list(_pipe@1).
-file("src/graded/internal/checker.gleam", 194).
?DOC(false).
-spec collect_reference_warnings(
binary(),
list(graded@internal@types:resolved_call()),
graded@internal@effects:knowledge_base()
) -> list(graded@internal@types:warning()).
collect_reference_warnings(Function_name, References, Knowledge_base) ->
gleam@list:filter_map(
References,
fun(Ref) ->
case graded@internal@effects:lookup(
Knowledge_base,
erlang:element(2, Ref)
) of
{known, Effect_set} ->
case Effect_set =:= graded@internal@types:empty() of
true ->
{error, nil};
false ->
{ok,
{untracked_effect_warning,
Function_name,
erlang:element(2, Ref),
erlang:element(3, Ref),
Effect_set}}
end;
unknown ->
{error, nil}
end
end
).
-file("src/graded/internal/checker.gleam", 393).
?DOC(false).
-spec local_polymorphic_bounds(glance:function_()) -> list(graded@internal@types:param_bound()).
local_polymorphic_bounds(Function) ->
_pipe = graded@internal@signatures:fn_typed_params_from_function(Function),
_pipe@1 = gleam@set:to_list(_pipe),
gleam@list:map(
_pipe@1,
fun(Name) ->
{param_bound,
Name,
{polymorphic, gleam@set:new(), gleam@set:from_list([Name])}}
end
).
-file("src/graded/internal/checker.gleam", 463).
?DOC(false).
-spec variables_in(graded@internal@types:effect_set()) -> list(binary()).
variables_in(Effect_set) ->
case Effect_set of
{polymorphic, _, Variables} ->
gleam@set:to_list(Variables);
_ ->
[]
end.
-file("src/graded/internal/checker.gleam", 493).
?DOC(false).
-spec find_arg_by_label(list(graded@internal@types:call_argument()), binary()) -> gleam@option:option(graded@internal@types:call_argument()).
find_arg_by_label(Args, Label) ->
_pipe = gleam@list:find(
Args,
fun(Arg) -> erlang:element(3, Arg) =:= {some, Label} end
),
gleam@option:from_result(_pipe).
-file("src/graded/internal/checker.gleam", 501).
?DOC(false).
-spec find_arg_at_position(
list(graded@internal@types:call_argument()),
integer()
) -> gleam@option:option(graded@internal@types:call_argument()).
find_arg_at_position(Args, Position) ->
_pipe = gleam@list:find(
Args,
fun(Arg) ->
(erlang:element(2, Arg) =:= Position) andalso (erlang:element(
3,
Arg
)
=:= none)
end
),
gleam@option:from_result(_pipe).
-file("src/graded/internal/checker.gleam", 528).
?DOC(false).
-spec find_param_position(
list(graded@internal@signatures:parameter_info()),
fun((graded@internal@signatures:parameter_info()) -> boolean())
) -> gleam@option:option(integer()).
find_param_position(Params, Predicate) ->
_pipe = gleam@list:find(Params, Predicate),
_pipe@1 = gleam@result:map(_pipe, fun(P) -> erlang:element(2, P) end),
gleam@option:from_result(_pipe@1).
-file("src/graded/internal/checker.gleam", 512).
?DOC(false).
-spec position_from_registry(
graded@internal@types:qualified_name(),
binary(),
graded@internal@signatures:signature_registry()
) -> gleam@option:option(integer()).
position_from_registry(Callee_name, Param_name, Registry) ->
gleam@option:then(
graded@internal@signatures:lookup(Registry, Callee_name),
fun(Params) ->
By_name = find_param_position(
Params,
fun(P) -> erlang:element(4, P) =:= {some, Param_name} end
),
gleam@option:lazy_or(
By_name,
fun() ->
find_param_position(
Params,
fun(P@1) ->
erlang:element(3, P@1) =:= {some, Param_name}
end
)
end
)
end
).
-file("src/graded/internal/checker.gleam", 473).
?DOC(false).
-spec find_matching_arg(
graded@internal@types:qualified_name(),
graded@internal@types:param_bound(),
list(graded@internal@types:call_argument()),
graded@internal@signatures:signature_registry()
) -> gleam@option:option(graded@internal@types:call_argument()).
find_matching_arg(Callee_name, Bound, Args, Registry) ->
By_label = find_arg_by_label(Args, erlang:element(2, Bound)),
gleam@option:lazy_or(
By_label,
fun() ->
_pipe = position_from_registry(
Callee_name,
erlang:element(2, Bound),
Registry
),
gleam@option:then(
_pipe,
fun(Pos) -> find_arg_at_position(Args, Pos) end
)
end
).
-file("src/graded/internal/checker.gleam", 540).
?DOC(false).
-spec resolve_argument_effects(
graded@internal@types:call_argument(),
graded@internal@effects:knowledge_base(),
list(graded@internal@types:param_bound())
) -> graded@internal@types:effect_set().
resolve_argument_effects(Arg, Knowledge_base, Caller_param_bounds) ->
case erlang:element(4, Arg) of
{function_ref, Name} ->
graded@internal@effects:lookup_effects(Knowledge_base, Name);
constructor_ref ->
graded@internal@types:empty();
{local_ref, Name@1} ->
case gleam@list:find(
Caller_param_bounds,
fun(B) -> erlang:element(2, B) =:= Name@1 end
) of
{ok, Bound} ->
erlang:element(3, Bound);
{error, nil} ->
graded@internal@types:from_labels([<<"Unknown"/utf8>>])
end;
other_expression ->
graded@internal@types:from_labels([<<"Unknown"/utf8>>])
end.
-file("src/graded/internal/checker.gleam", 434).
?DOC(false).
-spec bind_variables(
graded@internal@types:qualified_name(),
list(graded@internal@types:param_bound()),
list(graded@internal@types:call_argument()),
graded@internal@effects:knowledge_base(),
list(graded@internal@types:param_bound()),
graded@internal@signatures:signature_registry()
) -> gleam@dict:dict(binary(), graded@internal@types:effect_set()).
bind_variables(
Callee_name,
Callee_bounds,
Args,
Knowledge_base,
Caller_param_bounds,
Registry
) ->
gleam@list:fold(
Callee_bounds,
maps:new(),
fun(Acc, Bound) ->
Matched = find_matching_arg(Callee_name, Bound, Args, Registry),
case Matched of
{some, Arg} ->
Arg_effects = resolve_argument_effects(
Arg,
Knowledge_base,
Caller_param_bounds
),
Var_names = variables_in(erlang:element(3, Bound)),
gleam@list:fold(
Var_names,
Acc,
fun(D, Var) ->
gleam@dict:insert(D, Var, Arg_effects)
end
);
none ->
Acc
end
end
).
-file("src/graded/internal/checker.gleam", 340).
?DOC(false).
-spec substitute_local_call_effects(
list({graded@internal@types:resolved_call(),
graded@internal@types:effect_set()}),
graded@internal@types:local_call(),
gleam@dict:dict(integer(), list(graded@internal@types:call_argument())),
gleam@dict:dict(binary(), glance:definition(glance:function_())),
graded@internal@effects:knowledge_base(),
list(graded@internal@types:param_bound()),
graded@internal@signatures:signature_registry()
) -> list({graded@internal@types:resolved_call(),
graded@internal@types:effect_set()}).
substitute_local_call_effects(
Recursive,
Local_call,
Call_args,
Function_map,
Knowledge_base,
Caller_param_bounds,
Registry
) ->
Any_polymorphic = gleam@list:any(
Recursive,
fun(P) -> graded@internal@types:has_variables(erlang:element(2, P)) end
),
gleam@bool:guard(
not Any_polymorphic,
Recursive,
fun() ->
case gleam_stdlib:map_get(
Function_map,
erlang:element(2, Local_call)
) of
{error, nil} ->
Recursive;
{ok, Local_definition} ->
Bounds = local_polymorphic_bounds(
erlang:element(3, Local_definition)
),
Args = begin
_pipe = gleam_stdlib:map_get(
Call_args,
erlang:element(2, erlang:element(3, Local_call))
),
gleam@result:unwrap(_pipe, [])
end,
Callee_name = {qualified_name,
<<"<local>"/utf8>>,
erlang:element(2, Local_call)},
Local_registry = graded@internal@signatures:from_glance_module(
<<"<local>"/utf8>>,
{module, [], [], [], [], [Local_definition]}
),
Merged_registry = graded@internal@signatures:merge(
Registry,
Local_registry
),
Bindings = bind_variables(
Callee_name,
Bounds,
Args,
Knowledge_base,
Caller_param_bounds,
Merged_registry
),
gleam@list:map(
Recursive,
fun(Pair) ->
{Call, Effects} = Pair,
{Call,
graded@internal@types:substitute(
Effects,
Bindings
)}
end
)
end
end
).
-file("src/graded/internal/checker.gleam", 407).
?DOC(false).
-spec substitute_at_call_site(
graded@internal@types:resolved_call(),
graded@internal@types:effect_set(),
gleam@dict:dict(integer(), list(graded@internal@types:call_argument())),
graded@internal@effects:knowledge_base(),
list(graded@internal@types:param_bound()),
graded@internal@signatures:signature_registry()
) -> graded@internal@types:effect_set().
substitute_at_call_site(
Call,
Effect_set,
Call_args,
Knowledge_base,
Caller_param_bounds,
Registry
) ->
gleam@bool:guard(
not graded@internal@types:has_variables(Effect_set),
Effect_set,
fun() ->
Callee_bounds = graded@internal@effects:lookup_param_bounds(
Knowledge_base,
erlang:element(2, Call)
),
Args = begin
_pipe = gleam_stdlib:map_get(
Call_args,
erlang:element(2, erlang:element(3, Call))
),
gleam@result:unwrap(_pipe, [])
end,
Bindings = bind_variables(
erlang:element(2, Call),
Callee_bounds,
Args,
Knowledge_base,
Caller_param_bounds,
Registry
),
graded@internal@types:substitute(Effect_set, Bindings)
end
).
-file("src/graded/internal/checker.gleam", 607).
?DOC(false).
-spec resolve_field_call(
graded@internal@types:field_call(),
glance:function_(),
graded@internal@effects:knowledge_base()
) -> graded@internal@types:effect_set().
resolve_field_call(Field_call, Function, Knowledge_base) ->
Unknown = graded@internal@types:from_labels([<<"Unknown"/utf8>>]),
Param@1 = gleam@list:find(
erlang:element(5, Function),
fun(Param) -> case erlang:element(3, Param) of
{named, Name} ->
Name =:= erlang:element(2, Field_call);
{discarded, _} ->
false
end end
),
case Param@1 of
{ok,
{function_parameter, _, _, {some, {named_type, _, Type_name, _, _}}}} ->
case graded@internal@effects:lookup_type_field(
Knowledge_base,
Type_name,
erlang:element(3, Field_call)
) of
{known, Effect_set} ->
Effect_set;
unknown ->
Unknown
end;
_ ->
Unknown
end.
-file("src/graded/internal/checker.gleam", 557).
?DOC(false).
-spec resolve_unknown_local(
graded@internal@types:local_call(),
gleam@set:set(binary()),
gleam@dict:dict(binary(), glance:definition(glance:function_())),
graded@internal@extract:import_context(),
graded@internal@effects:knowledge_base(),
graded@internal@signatures:signature_registry()
) -> list({graded@internal@types:resolved_call(),
graded@internal@types:effect_set()}).
resolve_unknown_local(
Local_call,
Visited,
Function_map,
Context,
Knowledge_base,
Registry
) ->
case gleam@set:contains(Visited, erlang:element(2, Local_call)) of
true ->
[];
false ->
case gleam_stdlib:map_get(
Function_map,
erlang:element(2, Local_call)
) of
{error, nil} ->
Synthetic_call = {resolved_call,
{qualified_name,
<<"<local>"/utf8>>,
erlang:element(2, Local_call)},
erlang:element(3, Local_call)},
[{Synthetic_call,
graded@internal@types:from_labels(
[<<"Unknown"/utf8>>]
)}];
{ok, Local_definition} ->
New_visited = gleam@set:insert(
Visited,
erlang:element(2, Local_call)
),
Nested_fn_typed = graded@internal@signatures:fn_typed_params_from_function(
erlang:element(3, Local_definition)
),
collect_effects(
erlang:element(3, Local_definition),
Function_map,
Context,
Knowledge_base,
New_visited,
[],
Nested_fn_typed,
Registry
)
end
end.
-file("src/graded/internal/checker.gleam", 223).
?DOC(false).
-spec collect_effects(
glance:function_(),
gleam@dict:dict(binary(), glance:definition(glance:function_())),
graded@internal@extract:import_context(),
graded@internal@effects:knowledge_base(),
gleam@set:set(binary()),
list(graded@internal@types:param_bound()),
gleam@set:set(binary()),
graded@internal@signatures:signature_registry()
) -> list({graded@internal@types:resolved_call(),
graded@internal@types:effect_set()}).
collect_effects(
Function,
Function_map,
Context,
Knowledge_base,
Visited,
Param_bounds,
Fn_typed_params,
Registry
) ->
Result = graded@internal@extract:extract_calls(
erlang:element(7, Function),
Context
),
Resolved_effects = gleam@list:map(
erlang:element(2, Result),
fun(Call) ->
Effect_set = graded@internal@effects:lookup_effects(
Knowledge_base,
erlang:element(2, Call)
),
Concrete = substitute_at_call_site(
Call,
Effect_set,
erlang:element(6, Result),
Knowledge_base,
Param_bounds,
Registry
),
{Call, Concrete}
end
),
Local_effects = gleam@list:flat_map(
erlang:element(3, Result),
fun(Local_call) ->
case gleam@list:find(
Param_bounds,
fun(Param) ->
erlang:element(2, Param) =:= erlang:element(2, Local_call)
end
) of
{ok, Bound} ->
Synthetic_call = {resolved_call,
{qualified_name,
<<"<param>"/utf8>>,
erlang:element(2, Local_call)},
erlang:element(3, Local_call)},
[{Synthetic_call, erlang:element(3, Bound)}];
{error, nil} ->
case gleam@set:contains(
Fn_typed_params,
erlang:element(2, Local_call)
) of
true ->
Synthetic_call@1 = {resolved_call,
{qualified_name,
<<"<param>"/utf8>>,
erlang:element(2, Local_call)},
erlang:element(3, Local_call)},
[{Synthetic_call@1,
{polymorphic,
gleam@set:new(),
gleam@set:from_list(
[erlang:element(2, Local_call)]
)}}];
false ->
_pipe = resolve_unknown_local(
Local_call,
Visited,
Function_map,
Context,
Knowledge_base,
Registry
),
substitute_local_call_effects(
_pipe,
Local_call,
erlang:element(6, Result),
Function_map,
Knowledge_base,
Param_bounds,
Registry
)
end
end
end
),
Field_effects = gleam@list:map(
erlang:element(4, Result),
fun(Field_call) ->
Synthetic_call@2 = {resolved_call,
{qualified_name,
<<"<field>"/utf8>>,
<<<<(erlang:element(2, Field_call))/binary, "."/utf8>>/binary,
(erlang:element(3, Field_call))/binary>>},
erlang:element(4, Field_call)},
Effect_set@1 = resolve_field_call(
Field_call,
Function,
Knowledge_base
),
{Synthetic_call@2, Effect_set@1}
end
),
lists:append([Resolved_effects, Local_effects, Field_effects]).
-file("src/graded/internal/checker.gleam", 45).
?DOC(false).
-spec infer(
glance:module_(),
graded@internal@effects:knowledge_base(),
list(graded@internal@types:effect_annotation()),
graded@internal@signatures:signature_registry()
) -> list(graded@internal@types:effect_annotation()).
infer(Module, Knowledge_base, Existing_checks, Registry) ->
Context = graded@internal@extract:build_import_context(Module),
Function_map = build_function_map(Module),
Bounds_map = begin
_pipe = Existing_checks,
_pipe@1 = gleam@list:filter(
_pipe,
fun(Annotation) -> erlang:element(4, Annotation) /= [] end
),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Annotation@1) ->
{erlang:element(3, Annotation@1),
erlang:element(4, Annotation@1)}
end
),
maps:from_list(_pipe@2)
end,
_pipe@3 = erlang:element(6, Module),
_pipe@4 = gleam@list:filter(
_pipe@3,
fun(Definition) ->
erlang:element(4, erlang:element(3, Definition)) =:= public
end
),
gleam@list:map(
_pipe@4,
fun(Definition@1) ->
Param_bounds = begin
_pipe@5 = gleam_stdlib:map_get(
Bounds_map,
erlang:element(3, erlang:element(3, Definition@1))
),
gleam@result:unwrap(_pipe@5, [])
end,
Declared_bound_names = begin
_pipe@6 = Param_bounds,
_pipe@7 = gleam@list:map(
_pipe@6,
fun(B) -> erlang:element(2, B) end
),
gleam@set:from_list(_pipe@7)
end,
Fn_typed_params = begin
_pipe@8 = graded@internal@signatures:fn_typed_params_from_function(
erlang:element(3, Definition@1)
),
gleam@set:filter(
_pipe@8,
fun(Name) ->
not gleam@set:contains(Declared_bound_names, Name)
end
)
end,
All_effects = collect_effects(
erlang:element(3, Definition@1),
Function_map,
Context,
Knowledge_base,
gleam@set:new(),
Param_bounds,
Fn_typed_params,
Registry
),
Effect_set = gleam@list:fold(
All_effects,
graded@internal@types:empty(),
fun(Combined, Pair) ->
graded@internal@types:union(
Combined,
erlang:element(2, Pair)
)
end
),
Inferred_params = polymorphic_param_bounds(
Effect_set,
Fn_typed_params
),
{effect_annotation,
effects,
erlang:element(3, erlang:element(3, Definition@1)),
Inferred_params,
Effect_set}
end
).
-file("src/graded/internal/checker.gleam", 137).
?DOC(false).
-spec check_annotation(
graded@internal@types:effect_annotation(),
gleam@dict:dict(binary(), glance:definition(glance:function_())),
graded@internal@extract:import_context(),
graded@internal@effects:knowledge_base(),
graded@internal@signatures:signature_registry()
) -> {list(graded@internal@types:violation()),
list(graded@internal@types:warning())}.
check_annotation(Annotation, Function_map, Context, Knowledge_base, Registry) ->
case gleam_stdlib:map_get(Function_map, erlang:element(3, Annotation)) of
{error, nil} ->
{[], []};
{ok, Function_definition} ->
Body_effects = collect_effects(
erlang:element(3, Function_definition),
Function_map,
Context,
Knowledge_base,
gleam@set:new(),
erlang:element(4, Annotation),
gleam@set:new(),
Registry
),
Violations = begin
_pipe = Body_effects,
_pipe@1 = gleam@list:filter(
_pipe,
fun(Pair) ->
{_, Call_effects} = Pair,
not graded@internal@types:is_subset(
Call_effects,
erlang:element(5, Annotation)
)
end
),
gleam@list:map(
_pipe@1,
fun(Pair@1) ->
{Call, Call_effects@1} = Pair@1,
{violation,
erlang:element(3, Annotation),
erlang:element(2, Call),
erlang:element(3, Call),
erlang:element(5, Annotation),
Call_effects@1}
end
)
end,
Extract_result = graded@internal@extract:extract_calls(
erlang:element(7, erlang:element(3, Function_definition)),
Context
),
Warnings = collect_reference_warnings(
erlang:element(3, Annotation),
erlang:element(5, Extract_result),
Knowledge_base
),
{Violations, Warnings}
end.
-file("src/graded/internal/checker.gleam", 19).
?DOC(false).
-spec check(
glance:module_(),
list(graded@internal@types:effect_annotation()),
graded@internal@effects:knowledge_base(),
graded@internal@signatures:signature_registry()
) -> {list(graded@internal@types:violation()),
list(graded@internal@types:warning())}.
check(Module, Annotations, Knowledge_base, Registry) ->
Context = graded@internal@extract:build_import_context(Module),
Function_map = build_function_map(Module),
Results = gleam@list:map(
Annotations,
fun(Annotation) ->
check_annotation(
Annotation,
Function_map,
Context,
Knowledge_base,
Registry
)
end
),
Violations = gleam@list:flat_map(
Results,
fun(R) -> erlang:element(1, R) end
),
Warnings = gleam@list:flat_map(
Results,
fun(R@1) -> erlang:element(2, R@1) end
),
{Violations, Warnings}.