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/3, check/3]).
-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", 84).
?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", 144).
?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", 276).
?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", 236).
?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()
) -> list({graded@internal@types:resolved_call(),
graded@internal@types:effect_set()}).
resolve_unknown_local(
Local_call,
Visited,
Function_map,
Context,
Knowledge_base
) ->
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)
),
collect_effects(
erlang:element(3, Local_definition),
Function_map,
Context,
Knowledge_base,
New_visited,
[]
)
end
end.
-file("src/graded/internal/checker.gleam", 173).
?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())
) -> list({graded@internal@types:resolved_call(),
graded@internal@types:effect_set()}).
collect_effects(
Function,
Function_map,
Context,
Knowledge_base,
Visited,
Param_bounds
) ->
Result = graded@internal@extract:extract_calls(
erlang:element(7, Function),
Context
),
Resolved_effects = gleam@list:map(
erlang:element(2, Result),
fun(Call) ->
{Call,
graded@internal@effects:lookup_effects(
Knowledge_base,
erlang:element(2, Call)
)}
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} ->
resolve_unknown_local(
Local_call,
Visited,
Function_map,
Context,
Knowledge_base
)
end
end
),
Field_effects = gleam@list:map(
erlang:element(4, Result),
fun(Field_call) ->
Synthetic_call@1 = {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 = resolve_field_call(
Field_call,
Function,
Knowledge_base
),
{Synthetic_call@1, Effect_set}
end
),
lists:append([Resolved_effects, Local_effects, Field_effects]).
-file("src/graded/internal/checker.gleam", 35).
?DOC(false).
-spec infer(
glance:module_(),
graded@internal@effects:knowledge_base(),
list(graded@internal@types:effect_annotation())
) -> list(graded@internal@types:effect_annotation()).
infer(Module, Knowledge_base, Existing_checks) ->
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,
All_effects = collect_effects(
erlang:element(3, Definition@1),
Function_map,
Context,
Knowledge_base,
gleam@set:new(),
Param_bounds
),
Effect_set = gleam@list:fold(
All_effects,
graded@internal@types:empty(),
fun(Combined, Pair) ->
graded@internal@types:union(
Combined,
erlang:element(2, Pair)
)
end
),
{effect_annotation,
effects,
erlang:element(3, erlang:element(3, Definition@1)),
[],
Effect_set}
end
).
-file("src/graded/internal/checker.gleam", 90).
?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()
) -> {list(graded@internal@types:violation()),
list(graded@internal@types:warning())}.
check_annotation(Annotation, Function_map, Context, Knowledge_base) ->
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)
),
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", 16).
?DOC(false).
-spec check(
glance:module_(),
list(graded@internal@types:effect_annotation()),
graded@internal@effects:knowledge_base()
) -> {list(graded@internal@types:violation()),
list(graded@internal@types:warning())}.
check(Module, Annotations, Knowledge_base) ->
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)
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}.