-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, <<""/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, <<""/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, <<""/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}.