-module(graded). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/graded.gleam"). -export([infer_path_dep/2, run/1, run_format/1, run_format_check/1, run_infer/1, main/0]). -export_type([graded_error/0]). -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( " Effect checker for Gleam via sidecar `.graded` annotation files.\n" "\n" " graded verifies that your Gleam functions respect their declared effect\n" " budgets. Annotations live in `.graded` sidecar files alongside your source\n" " — your Gleam code stays clean.\n" "\n" " ## Usage\n" "\n" " ```sh\n" " gleam run -m graded check [directory] # enforce check annotations (default)\n" " gleam run -m graded infer [directory] # infer and write effect annotations\n" " gleam run -m graded format [directory] # normalize .graded file formatting\n" " ```\n" "\n" " ## Programmatic API\n" "\n" " Use `run` to check a directory and get back a list of `CheckResult` values,\n" " each containing any violations found per file. Use `run_infer` to infer\n" " effects and write `.graded` files.\n" "\n" ). -type graded_error() :: {directory_read_error, binary(), simplifile:file_error()} | {file_read_error, binary(), simplifile:file_error()} | {file_write_error, binary(), simplifile:file_error()} | {directory_create_error, binary(), simplifile:file_error()} | {gleam_parse_error, binary(), glance:error()} | {graded_parse_error, binary(), graded@internal@annotation:parse_error()} | {format_check_failed, list(binary())} | {cyclic_imports, list(binary())}. -file("src/graded.gleam", 1083). -spec target_directory(list(binary())) -> binary(). target_directory(Arguments) -> case Arguments of [Directory | _] -> Directory; [] -> <<"src"/utf8>> end. -file("src/graded.gleam", 1150). -spec format_error(graded_error()) -> binary(). format_error(Error) -> case Error of {directory_read_error, Path, _} -> <<"Could not read directory: "/utf8, Path/binary>>; {file_read_error, Path@1, _} -> <<"Could not read: "/utf8, Path@1/binary>>; {file_write_error, Path@2, _} -> <<"Could not write: "/utf8, Path@2/binary>>; {directory_create_error, Path@3, _} -> <<"Could not create directory: "/utf8, Path@3/binary>>; {gleam_parse_error, Path@4, _} -> <<"Could not parse: "/utf8, Path@4/binary>>; {graded_parse_error, Path@5, _} -> <<"Parse error in .graded file for: "/utf8, Path@5/binary>>; {format_check_failed, Paths} -> <<"Unformatted .graded files:\n"/utf8, (gleam@string:join( gleam@list:map( Paths, fun(Path@6) -> <<" "/utf8, Path@6/binary>> end ), <<"\n"/utf8>> ))/binary>>; {cyclic_imports, Modules} -> <<"Cyclic project imports detected (this should be unreachable — Gleam disallows circular imports):\n"/utf8, (gleam@string:join( gleam@list:map( Modules, fun(M) -> <<" "/utf8, M/binary>> end ), <<"\n"/utf8>> ))/binary>> end. -file("src/graded.gleam", 1173). -spec print_violation(binary(), graded@internal@types:violation()) -> nil. print_violation(File, Violation) -> Base = <<<<<<<<<<<<<<<<<<<>/binary, (erlang:element(2, Violation))/binary>>/binary, " calls "/utf8>>/binary, (erlang:element(2, erlang:element(3, Violation)))/binary>>/binary, "."/utf8>>/binary, (erlang:element(3, erlang:element(3, Violation)))/binary>>/binary, " with effects "/utf8>>/binary, (graded@internal@effects:format_effect_set( erlang:element(6, Violation) ))/binary>>/binary, " but declared "/utf8>>/binary, (graded@internal@effects:format_effect_set(erlang:element(5, Violation)))/binary>>, Hint = case graded@internal@types:has_variables( erlang:element(6, Violation) ) of true -> <<<<<<"\n hint: actual effects contain unresolved variables; add a `check "/utf8, (erlang:element(2, Violation))/binary>>/binary, "(: [...])` bound, or pass a function reference / constructor"/utf8>>/binary, " whose effects are known"/utf8>>; false -> <<""/utf8>> end, gleam_stdlib:println(<>). -file("src/graded.gleam", 1167). -spec print_violations(graded@internal@types:check_result()) -> nil. print_violations(Check_result) -> gleam@list:each( erlang:element(3, Check_result), fun(Violation) -> print_violation(erlang:element(2, Check_result), Violation) end ). -file("src/graded.gleam", 1206). -spec print_warning(binary(), graded@internal@types:warning()) -> nil. print_warning(File, Warning) -> gleam_stdlib:println( <<<<<<<<<<<<<<<<<>/binary, (erlang:element(2, Warning))/binary>>/binary, " passes "/utf8>>/binary, (erlang:element(2, erlang:element(3, Warning)))/binary>>/binary, "."/utf8>>/binary, (erlang:element(3, erlang:element(3, Warning)))/binary>>/binary, " as a value — its effects "/utf8>>/binary, (graded@internal@effects:format_effect_set( erlang:element(5, Warning) ))/binary>>/binary, " won't be tracked"/utf8>> ). -file("src/graded.gleam", 1200). -spec print_warnings(graded@internal@types:check_result()) -> nil. print_warnings(Check_result) -> gleam@list:each( erlang:element(4, Check_result), fun(Warning) -> print_warning(erlang:element(2, Check_result), Warning) end ). -file("src/graded.gleam", 884). ?DOC( " Run the checker against one source file using the slice of `check`\n" " annotations from the spec file that mention this file's module.\n" ). -spec check_one_file( binary(), glance:module_(), list(graded@internal@types:effect_annotation()), graded@internal@effects:knowledge_base(), graded@internal@signatures:signature_registry(), gleam@dict:dict({integer(), integer()}, girard@types:type()), gleam@dict:dict(binary(), gleam@set:set(binary())) ) -> graded@internal@types:check_result(). check_one_file( Gleam_path, Module, Module_checks, Knowledge_base, Registry, Module_types, Girard_fn_typed ) -> {Violations, Warnings} = graded@internal@checker:check( Module, Module_checks, Knowledge_base, Registry, Module_types, Girard_fn_typed ), {check_result, Gleam_path, Violations, Warnings}. -file("src/graded.gleam", 402). ?DOC( " Build a package-wide map keyed by `#(defining module, name)` from a per-module\n" " `name -> value` map, qualifying each entry with the module it came from.\n" ). -spec qualify_by_module( gleam@dict:dict(binary(), {binary(), glance:module_()}), fun((glance:module_()) -> gleam@dict:dict(binary(), UNF)) ) -> gleam@dict:dict({binary(), binary()}, UNF). qualify_by_module(Index, Per_module) -> gleam@dict:fold( Index, maps:new(), fun(Acc, Path, Entry) -> {_, Module} = Entry, gleam@dict:fold( Per_module(Module), Acc, fun(Inner, Name, Value) -> gleam@dict:insert(Inner, {Path, Name}, Value) end ) end ). -file("src/graded.gleam", 501). ?DOC( " Union two field-effect contributions for the same field across sites. Keeps\n" " the first polymorphic source — conflicting polymorphism across sites is rare,\n" " and unbound variables collapse to `[Unknown]` at the call site.\n" ). -spec merge_field_effect( graded@internal@types:type_field_effect(), graded@internal@types:type_field_effect() ) -> graded@internal@types:type_field_effect(). merge_field_effect(Existing, New) -> {Bounds, Source} = case erlang:element(4, Existing) of {some, _} -> {erlang:element(3, Existing), erlang:element(4, Existing)}; none -> {erlang:element(3, New), erlang:element(4, New)} end, {type_field_effect, graded@internal@effect_term:normalize( {t_union, [erlang:element(2, Existing), erlang:element(2, New)]} ), Bounds, Source}. -file("src/graded.gleam", 487). ?DOC( " The qualified function a field value refers to, if any: a `FunctionRef`\n" " directly, or a `LocalRef` (a bare same-module name) qualified by the current\n" " module. `None` for constructors and inline expressions.\n" ). -spec field_value_function(graded@internal@types:argument_value(), binary()) -> gleam@option:option(graded@internal@types:qualified_name()). field_value_function(Value, Module_path) -> case Value of {function_ref, Name} -> {some, Name}; {local_ref, Name@1} -> {some, {qualified_name, Module_path, Name@1}}; _ -> none end. -file("src/graded.gleam", 449). ?DOC( " The effect a constructor field's value contributes. A function reference (or\n" " a same-module function, qualified by `module_path`) resolves via the\n" " knowledge base — capturing its param bounds + identity when it is\n" " effect-polymorphic. A constructor is pure; anything else is `[Unknown]`.\n" ). -spec field_effect_of( graded@internal@effects:knowledge_base(), graded@internal@types:argument_value(), binary(), fun((list(binary()), list(glance:statement())) -> graded@internal@types:effect_term()) ) -> graded@internal@types:type_field_effect(). field_effect_of(Knowledge_base, Value, Module_path, Closure_effect) -> case field_value_function(Value, Module_path) of {some, Name} -> Field_effects = graded@internal@effects:lookup_effects( Knowledge_base, Name ), case not gleam@set:is_empty( graded@internal@effect_term:free_vars(Field_effects) ) of true -> {type_field_effect, Field_effects, graded@internal@effects:lookup_param_bounds( Knowledge_base, Name ), {some, Name}}; false -> {type_field_effect, Field_effects, [], none} end; none -> case Value of {closure, Params, Body} -> {type_field_effect, Closure_effect(Params, Body), [], none}; _ -> {type_field_effect, graded@internal@effects:argument_value_effects( Knowledge_base, Value ), [], none} end end. -file("src/graded.gleam", 419). ?DOC( " Fold one constructor call's field bindings into the (module, type, field) ->\n" " effect accumulator, unioning with any effect already recorded for that field.\n" " The defining module is the call's resolved module (qualified) or the current\n" " module (unqualified) — so same-named constructors in different modules don't\n" " collide.\n" ). -spec accumulate_constructor_binding( gleam@dict:dict({binary(), binary(), binary()}, graded@internal@types:type_field_effect()), graded@internal@extract:constructor_binding(), gleam@dict:dict({binary(), binary()}, binary()), graded@internal@effects:knowledge_base(), binary(), fun((list(binary()), list(glance:statement())) -> graded@internal@types:effect_term()) ) -> gleam@dict:dict({binary(), binary(), binary()}, graded@internal@types:type_field_effect()). accumulate_constructor_binding( Acc, Binding, Constructor_types, Knowledge_base, Module_path, Closure_effect ) -> {constructor_binding, Binding_module, Constructor, Fields} = Binding, Module = gleam@option:unwrap(Binding_module, Module_path), case gleam_stdlib:map_get(Constructor_types, {Module, Constructor}) of {error, nil} -> Acc; {ok, Type_name} -> gleam@dict:fold( Fields, Acc, fun(Inner, Label, Value) -> Field_effect = field_effect_of( Knowledge_base, Value, Module_path, Closure_effect ), Key = {Module, Type_name, Label}, Merged = case gleam_stdlib:map_get(Inner, Key) of {ok, Existing} -> merge_field_effect(Existing, Field_effect); {error, nil} -> Field_effect end, gleam@dict:insert(Inner, Key, Merged) end ) end. -file("src/graded.gleam", 344). ?DOC( " Stage C: derive `type Foo.field : [...]` annotations from constructor call\n" " sites across the package. `Validator(to_error: io.println)` anywhere makes\n" " `Validator.to_error` carry io.println's effects (unioned across all sites),\n" " so a field call resolves without a hand-written annotation. Resolved via\n" " girard's receiver typing at the use site; hand-written `type` lines still\n" " win, since they are merged over these.\n" ). -spec build_constructor_field_index( gleam@dict:dict(binary(), {binary(), glance:module_()}), graded@internal@effects:knowledge_base() ) -> list({{binary(), binary(), binary()}, graded@internal@types:type_field_effect()}). build_constructor_field_index(Index, Knowledge_base) -> Constructor_types = qualify_by_module( Index, fun graded@internal@extract:build_constructor_type_map/1 ), Cross_constructors = qualify_by_module( Index, fun graded@internal@extract:constructor_label_map/1 ), _pipe@2 = gleam@dict:fold( Index, maps:new(), fun(Acc, Path, Entry) -> {_, Module} = Entry, Context = begin _pipe = graded@internal@extract:build_import_context(Module), graded@internal@extract:with_cross_constructors( _pipe, Cross_constructors ) end, Function_map = graded@internal@checker:build_function_map(Module), Scc_ids = graded@internal@checker:build_scc_ids( Module, Context, maps:new(), false ), Closure_effect = fun(Params, Body) -> graded@internal@checker:closure_field_operator( Params, Body, Context, Function_map, Knowledge_base, Scc_ids ) end, _pipe@1 = graded@internal@extract:collect_constructor_bindings( Module, Context ), gleam@list:fold( _pipe@1, Acc, fun(Inner, Binding) -> accumulate_constructor_binding( Inner, Binding, Constructor_types, Knowledge_base, Path, Closure_effect ) end ) end ), maps:to_list(_pipe@2). -file("src/graded.gleam", 787). ?DOC( " Infer one module against `kb` and fold its effects, param bounds, and\n" " returned operators (qualified by `module_path`) into the knowledge base, with\n" " existing entries winning. The per-module step of `infer_project_in_memory`.\n" ). -spec fold_inferred_module( graded@internal@effects:knowledge_base(), glance:module_(), binary(), graded@internal@signatures:signature_registry(), graded@internal@typeinfo:type_info() ) -> graded@internal@effects:knowledge_base(). fold_inferred_module(Kb, Module, Module_path, Registry, Type_info) -> {Inferred, Returned_operators} = graded@internal@checker:infer_with_returns( Module, Kb, [], Registry, graded@internal@typeinfo:for_module(Type_info, Module_path), graded@internal@typeinfo:fn_typed_for_module(Type_info, Module_path) ), Qualify = fun(Function) -> {qualified_name, Module_path, Function} end, Effects_dict = gleam@list:fold( Inferred, maps:new(), fun(Acc, Ann) -> gleam@dict:insert( Acc, Qualify(erlang:element(3, Ann)), erlang:element(5, Ann) ) end ), Params_dict = gleam@list:fold( Inferred, maps:new(), fun(Acc@1, Ann@1) -> case erlang:element(4, Ann@1) of [] -> Acc@1; Params -> gleam@dict:insert( Acc@1, Qualify(erlang:element(3, Ann@1)), Params ) end end ), Returns_dict = gleam@dict:fold( Returned_operators, maps:new(), fun(Acc@2, Function@1, Op) -> gleam@dict:insert(Acc@2, Qualify(Function@1), Op) end ), _pipe = Kb, _pipe@1 = graded@internal@effects:with_inferred(_pipe, Effects_dict), _pipe@2 = graded@internal@effects:with_inferred_params(_pipe@1, Params_dict), graded@internal@effects:with_inferred_returned_operators( _pipe@2, Returns_dict ). -file("src/graded.gleam", 644). ?DOC( " For every project module, derive its set of project-internal imports.\n" " Imports of stdlib/dep modules (anything not in `index`) are filtered out\n" " — those are leaves with effects already resolved via the knowledge base\n" " and don't belong in the topological sort.\n" ). -spec build_dependency_graph( gleam@dict:dict(binary(), {binary(), glance:module_()}) ) -> gleam@dict:dict(binary(), gleam@set:set(binary())). build_dependency_graph(Index) -> gleam@dict:map_values( Index, fun(_, Entry) -> {_, Module} = Entry, Context = graded@internal@extract:build_import_context(Module), _pipe = erlang:element(2, Context), _pipe@1 = maps:values(_pipe), _pipe@2 = gleam@list:filter( _pipe@1, fun(Imported) -> gleam@dict:has_key(Index, Imported) end ), gleam@set:from_list(_pipe@2) end ). -file("src/graded.gleam", 765). ?DOC( " Infer project modules in topological order, in memory, folding their\n" " effects, param bounds, and returned operators into `base_kb` — with existing\n" " (spec / dependency) entries taking priority, so committed effects are never\n" " overridden. This lets `check` resolve calls into project modules that haven't\n" " been `graded infer`-ed yet, without writing the cache. Falls back to\n" " `base_kb` unchanged when the import graph has a cycle (the real\n" " `graded infer` reports that error; `check` just degrades to spec-only).\n" ). -spec infer_project_in_memory( graded@internal@effects:knowledge_base(), gleam@dict:dict(binary(), {binary(), glance:module_()}), graded@internal@signatures:signature_registry(), graded@internal@typeinfo:type_info() ) -> graded@internal@effects:knowledge_base(). infer_project_in_memory(Base_kb, Index, Registry, Type_info) -> case graded@internal@topo:sort(build_dependency_graph(Index)) of {error, _} -> Base_kb; {ok, Sorted} -> gleam@list:fold( Sorted, Base_kb, fun(Kb, Module_path) -> case gleam_stdlib:map_get(Index, Module_path) of {error, _} -> Kb; {ok, {_, Module}} -> fold_inferred_module( Kb, Module, Module_path, Registry, Type_info ) end end ) end. -file("src/graded.gleam", 1051). -spec infer_path_dep_module( {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_term()), graded@internal@effects:knowledge_base()}, binary(), gleam@dict:dict(binary(), {glance:module_(), list(graded@internal@types:effect_annotation())}) ) -> {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_term()), graded@internal@effects:knowledge_base()}. infer_path_dep_module(State, Module_path, Index) -> {Acc, Kb} = State, case gleam_stdlib:map_get(Index, Module_path) of {error, _} -> {Acc, Kb}; {ok, {Module, Checks}} -> Annotations = graded@internal@checker:infer( Module, Kb, Checks, graded@internal@signatures:empty(), maps:new(), maps:new() ), Module_dict = gleam@list:fold( Annotations, maps:new(), fun(D, Annotation) -> gleam@dict:insert( D, {qualified_name, Module_path, erlang:element(3, Annotation)}, erlang:element(5, Annotation) ) end ), {maps:merge(Acc, Module_dict), graded@internal@effects:with_inferred(Kb, Module_dict)} end. -file("src/graded.gleam", 1131). -spec read_and_parse_gleam(binary()) -> {ok, glance:module_()} | {error, graded_error()}. read_and_parse_gleam(Gleam_path) -> gleam@result:'try'( begin _pipe = simplifile:read(Gleam_path), gleam@result:map_error( _pipe, fun(_capture) -> {file_read_error, Gleam_path, _capture} end ) end, fun(Source) -> _pipe@1 = glance:module(Source), gleam@result:map_error( _pipe@1, fun(_capture@1) -> {gleam_parse_error, Gleam_path, _capture@1} end ) end ). -file("src/graded.gleam", 1004). ?DOC( " Build the dependency-graph index for a single path dep, topo-sort it,\n" " then infer every module in dependency order. Returns the union of all\n" " inferred effects keyed by `QualifiedName` so the caller can fold them\n" " into the global knowledge base. Errors are swallowed (returned as\n" " `Error(Nil)`) to preserve the existing tolerance: a malformed dep\n" " shouldn't break the whole project.\n" "\n" " Exposed (pub) primarily so tests can exercise the topological-order path\n" " inference on a temporary directory tree without going through\n" " `gleam.toml` resolution. Production callers go through\n" " `enrich_with_path_deps` which reads `gleam.toml` to discover dep paths.\n" ). -spec infer_path_dep(binary(), graded@internal@effects:knowledge_base()) -> {ok, gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_term())} | {error, nil}. infer_path_dep(Dep_path, Base_kb) -> Source_dir = <>, Gleam_files = case simplifile:get_files(Source_dir) of {ok, Found} -> gleam@list:filter( Found, fun(Path) -> gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) end ); {error, _} -> [] end, Entries = gleam@list:filter_map( Gleam_files, fun(Gleam_path) -> gleam@result:'try'( begin _pipe = read_and_parse_gleam(Gleam_path), gleam@result:map_error(_pipe, fun(_) -> nil end) end, fun(Module) -> Module_path = graded@internal@config:module_path_for_source( Gleam_path, Source_dir ), {ok, {Module_path, Module, []}} end ) end ), Index = gleam@list:fold( Entries, maps:new(), fun(Acc, Entry) -> {Module_path@1, Module@1, Checks} = Entry, gleam@dict:insert(Acc, Module_path@1, {Module@1, Checks}) end ), Graph = gleam@dict:map_values( Index, fun(_, Entry@1) -> {Module@2, _} = Entry@1, Context = graded@internal@extract:build_import_context(Module@2), _pipe@1 = erlang:element(2, Context), _pipe@2 = maps:values(_pipe@1), _pipe@3 = gleam@list:filter( _pipe@2, fun(Imported) -> gleam@dict:has_key(Index, Imported) end ), gleam@set:from_list(_pipe@3) end ), gleam@result:'try'( begin _pipe@4 = graded@internal@topo:sort(Graph), gleam@result:map_error(_pipe@4, fun(_) -> nil end) end, fun(Sorted) -> {Inferred, _} = gleam@list:fold( Sorted, {maps:new(), Base_kb}, fun(State, Module_path@2) -> infer_path_dep_module(State, Module_path@2, Index) end ), {ok, Inferred} end ). -file("src/graded.gleam", 976). ?DOC( " For each path dependency declared in `gleam.toml`:\n" "\n" " 1. Try to load its spec file (via the dep's own `[tools.graded]`\n" " config, defaulting to `.graded`) and fold its\n" " annotations into the knowledge base. This is the fast, intended\n" " path: the dep author already ran `graded infer`, committed the\n" " spec file, and the consumer just reads it.\n" "\n" " 2. If the dep has no spec file, fall back to inferring from source via\n" " `infer_path_dep` so path deps without graded set up still work.\n" " Cross-path-dep imports are not currently merged into a single graph\n" " — each dep is processed sequentially.\n" ). -spec enrich_with_path_deps(graded@internal@effects:knowledge_base()) -> graded@internal@effects:knowledge_base(). enrich_with_path_deps(Knowledge_base) -> Path_deps = graded@internal@effects:parse_path_dependencies( <<"gleam.toml"/utf8>> ), gleam@list:fold( Path_deps, Knowledge_base, fun(Kb, Dep) -> {Name, Dep_path} = Dep, Spec_path = graded@internal@config:spec_file_for(Dep_path, Name), case simplifile_erl:is_file(Spec_path) of {ok, true} -> graded@internal@effects:with_inferred( Kb, graded@internal@effects:load_spec_effects(Spec_path) ); _ -> case infer_path_dep(Dep_path, Kb) of {error, nil} -> Kb; {ok, Inferred} -> graded@internal@effects:with_inferred(Kb, Inferred) end end end ). -file("src/graded.gleam", 587). ?DOC( " The names of `function`'s parameters whose inferred type (positional in\n" " `argument_types`) is itself a `Fn`.\n" ). -spec fn_typed_names(glance:function_(), list(girard@types:type())) -> gleam@set:set(binary()). fn_typed_names(Function, Argument_types) -> gleam@bool:guard( erlang:length(erlang:element(5, Function)) /= erlang:length( Argument_types ), gleam@set:new(), fun() -> _pipe = gleam@list:zip(erlang:element(5, Function), Argument_types), _pipe@1 = gleam@list:filter_map( _pipe, fun(Pair) -> {Parameter, Argument_type} = Pair, case {Argument_type, erlang:element(3, Parameter)} of {{fn, _, _}, {named, Parameter_name}} -> {ok, Parameter_name}; {_, _} -> {error, nil} end end ), gleam@set:from_list(_pipe@1) end ). -file("src/graded.gleam", 567). ?DOC( " From girard's inferred top-level signatures, the set of function-typed\n" " parameter names for each function — including parameters with no syntactic\n" " `fn(...)` annotation, which the glance-only detection misses. A parameter is\n" " function-typed when its inferred type (positional in the function's `Fn`\n" " type) is itself a `Fn`.\n" ). -spec fn_typed_params_from_schemes(girard:module_result(), glance:module_()) -> gleam@dict:dict(binary(), gleam@set:set(binary())). fn_typed_params_from_schemes(Module_result, Module) -> Function_map = gleam@list:fold( erlang:element(6, Module), maps:new(), fun(Acc, Definition) -> gleam@dict:insert( Acc, erlang:element(3, erlang:element(3, Definition)), erlang:element(3, Definition) ) end ), gleam@list:fold( erlang:element(2, erlang:element(2, Module_result)), maps:new(), fun(Acc@1, Entry) -> {Name, Scheme} = Entry, case {erlang:element(3, Scheme), gleam_stdlib:map_get(Function_map, Name)} of {{fn, Argument_types, _}, {ok, Function}} -> gleam@dict:insert( Acc@1, Name, fn_typed_names(Function, Argument_types) ); {_, _} -> Acc@1 end end ). -file("src/graded.gleam", 613). ?DOC( " A girard `Resolver` that resolves graded's own project modules from `index`\n" " first (so non-`src` layouts like `test/fixtures` work), then falls through\n" " to girard's stock disk resolver for dependencies and stdlib under\n" " `build/packages`.\n" ). -spec build_girard_resolver( gleam@dict:dict(binary(), {binary(), glance:module_()}) ) -> fun((binary()) -> {ok, binary()} | {error, nil}). build_girard_resolver(Index) -> Disk = girard:disk_resolver(), fun(Module_path) -> case gleam_stdlib:map_get(Index, Module_path) of {ok, {Gleam_path, _}} -> _pipe = simplifile:read(Gleam_path), gleam@result:replace_error(_pipe, nil); {error, nil} -> Disk(Module_path) end end. -file("src/graded.gleam", 520). ?DOC( " Run girard's whole-package type inference once over every project module\n" " and fold the result into a `TypeInfo` (module path -> span start -> type).\n" " girard is best-effort: a function it can't type contributes no expressions,\n" " so the checker silently falls back to syntax-level resolution for it.\n" ). -spec build_type_index(gleam@dict:dict(binary(), {binary(), glance:module_()})) -> graded@internal@typeinfo:type_info(). build_type_index(Index) -> Options = begin _pipe = girard:default_options(), girard:with_resolver(_pipe, build_girard_resolver(Index)) end, Entries = begin _pipe@1 = maps:to_list(Index), gleam@list:map( _pipe@1, fun(Pair) -> {Module_path, {_, Module}} = Pair, {Module_path, Module} end ) end, Results = begin _pipe@2 = girard:annotate_package(Entries, Options), maps:to_list(_pipe@2) end, Span_types = gleam@list:map( Results, fun(Pair@1) -> {Module_path@1, Module_result} = Pair@1, Types = gleam@list:fold( erlang:element(4, erlang:element(2, Module_result)), maps:new(), fun(Acc, Annotation) -> gleam@dict:insert( Acc, {erlang:element(2, erlang:element(2, Annotation)), erlang:element(3, erlang:element(2, Annotation))}, erlang:element(3, Annotation) ) end ), {Module_path@1, Types} end ), Fn_typed = gleam@list:filter_map( Results, fun(Pair@2) -> {Module_path@2, Module_result@1} = Pair@2, case gleam_stdlib:map_get(Index, Module_path@2) of {ok, {_, Module@1}} -> {ok, {Module_path@2, fn_typed_params_from_schemes( Module_result@1, Module@1 )}}; {error, nil} -> {error, nil} end end ), graded@internal@typeinfo:from_modules(Span_types, Fn_typed). -file("src/graded.gleam", 329). ?DOC( " Build a signature registry covering every project module. Used by\n" " the checker's call-site substitution to resolve effect variables\n" " when the caller passes positional (unlabeled) arguments.\n" ). -spec build_project_registry( gleam@dict:dict(binary(), {binary(), glance:module_()}) ) -> graded@internal@signatures:signature_registry(). build_project_registry(Index) -> gleam@dict:fold( Index, graded@internal@signatures:empty(), fun(Acc, Module_path, Entry) -> {_, Module} = Entry, graded@internal@signatures:merge( Acc, graded@internal@signatures:from_glance_module( Module_path, Module ) ) end ). -file("src/graded.gleam", 629). ?DOC( " Build an index from dotted module name (`app/router`) to the parsed file.\n" " This is the set of *project* modules — every module name in this dict is\n" " a candidate dependency-graph node.\n" ). -spec build_module_index(list({binary(), glance:module_()}), binary()) -> gleam@dict:dict(binary(), {binary(), glance:module_()}). build_module_index(Parsed, Directory) -> gleam@list:fold( Parsed, maps:new(), fun(Acc, Entry) -> {Gleam_path, Module} = Entry, Module_path = graded@internal@config:module_path_for_source( Gleam_path, Directory ), gleam@dict:insert(Acc, Module_path, {Gleam_path, Module}) end ). -file("src/graded.gleam", 317). ?DOC( " Parse every project source file once, returning `(path, parsed module)`\n" " pairs. Used by `run_infer` so the topo sort can read each module's\n" " imports without re-parsing on the inference pass.\n" ). -spec parse_all_files(list(binary())) -> {ok, list({binary(), glance:module_()})} | {error, graded_error()}. parse_all_files(Gleam_files) -> gleam@list:try_map( Gleam_files, fun(Gleam_path) -> gleam@result:'try'( read_and_parse_gleam(Gleam_path), fun(Module) -> {ok, {Gleam_path, Module}} end ) end ). -file("src/graded.gleam", 1125). -spec find_gleam_files(binary()) -> {ok, list(binary())} | {error, graded_error()}. find_gleam_files(Directory) -> _pipe = simplifile:get_files(Directory), _pipe@1 = gleam@result:map_error( _pipe, fun(_capture) -> {directory_read_error, Directory, _capture} end ), gleam@result:map( _pipe@1, fun(_capture@1) -> gleam@list:filter( _capture@1, fun(Path) -> gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) end ) end ). -file("src/graded.gleam", 864). ?DOC( " Group a parsed spec file's `check` annotations by their module path. Used\n" " during `run` to hand each source file only the checks that apply to it.\n" " The checker expects bare function names per module, so we strip the\n" " module qualifier from the grouped annotations.\n" ). -spec checks_grouped_by_module(graded@internal@types:graded_file()) -> gleam@dict:dict(binary(), list(graded@internal@types:effect_annotation())). checks_grouped_by_module(Spec) -> gleam@list:fold( graded@internal@annotation:extract_checks(Spec), maps:new(), fun(Acc, Ann) -> case graded@internal@annotation:split_qualified_name( erlang:element(3, Ann) ) of {error, _} -> Acc; {ok, {Module, Function}} -> Bare = {effect_annotation, erlang:element(2, Ann), Function, erlang:element(4, Ann), erlang:element(5, Ann)}, Existing = case gleam_stdlib:map_get(Acc, Module) of {ok, List} -> List; {error, _} -> [] end, gleam@dict:insert(Acc, Module, [Bare | Existing]) end end ). -file("src/graded.gleam", 953). -spec read_spec(binary()) -> graded@internal@types:graded_file(). read_spec(Spec_path) -> case simplifile:read(Spec_path) of {error, _} -> {graded_file, []}; {ok, Content} -> case graded@internal@annotation:parse_file(Content) of {ok, File} -> File; {error, _} -> {graded_file, []} end end. -file("src/graded.gleam", 936). ?DOC( " Join a path against a root, but leave it untouched if it's already\n" " absolute (starts with `/`) or if the root is `.` (so production paths\n" " stay short and unprefixed).\n" ). -spec resolve_path(binary(), binary()) -> binary(). resolve_path(Root, Path) -> gleam@bool:guard( gleam_stdlib:string_starts_with(Path, <<"/"/utf8>>) orelse (Root =:= <<"."/utf8>>), Path, fun() -> filepath:join(Root, Path) end ). -file("src/graded.gleam", 944). -spec default_package_name(binary()) -> binary(). default_package_name(Directory) -> case filepath:base_name(Directory) of <<""/utf8>> -> <<"graded"/utf8>>; <<"/"/utf8>> -> <<"graded"/utf8>>; Name -> Name end. -file("src/graded.gleam", 916). ?DOC( " Read the project's `[tools.graded]` config and return spec/cache paths\n" " already resolved relative to the project root. The \"project root\" is\n" " the directory containing `gleam.toml`:\n" "\n" " - When `directory == \"src\"` (the production case), project root is `.`\n" " and gleam.toml lives at `./gleam.toml`.\n" " - Otherwise (tests against ad-hoc directories), the source directory\n" " itself acts as the project root and gleam.toml is looked up there.\n" "\n" " Resolved paths are returned in the same `GradedConfig` shape so callers\n" " can use them as-is for I/O without further joining.\n" ). -spec read_config(binary()) -> graded@internal@config:graded_config(). read_config(Directory) -> Project_root = case Directory of <<"src"/utf8>> -> <<"."/utf8>>; _ -> Directory end, Toml_path = filepath:join(Project_root, <<"gleam.toml"/utf8>>), Raw = case graded@internal@config:read(Toml_path) of {ok, Cfg} -> Cfg; {error, _} -> graded@internal@config:defaults_for(default_package_name(Directory)) end, {graded_config, erlang:element(2, Raw), resolve_path(Project_root, erlang:element(3, Raw)), resolve_path(Project_root, erlang:element(4, Raw))}. -file("src/graded.gleam", 125). ?DOC( " Run the checker on all .gleam files in a directory.\n" "\n" " Reads the project's single spec file (default `.graded`)\n" " to find inferred public-API effects, `check` invariants, `external`\n" " hints, and `type` field annotations, then reports violations per source\n" " file.\n" ). -spec run(binary()) -> {ok, list(graded@internal@types:check_result())} | {error, graded_error()}. run(Directory) -> Cfg = read_config(Directory), Spec = read_spec(erlang:element(3, Cfg)), Checks_by_module = checks_grouped_by_module(Spec), gleam@result:'try'( find_gleam_files(Directory), fun(Gleam_files) -> gleam@result:'try'( parse_all_files(Gleam_files), fun(Parsed) -> Index = build_module_index(Parsed, Directory), Dep_registry = graded@internal@signatures:load_from_packages_dir( <<"build/packages"/utf8>> ), Registry = graded@internal@signatures:merge( Dep_registry, build_project_registry(Index) ), Type_info = build_type_index(Index), Kb_base = begin _pipe = graded@internal@effects:load_knowledge_base( <<"build/packages"/utf8>> ), _pipe@1 = enrich_with_path_deps(_pipe), _pipe@2 = graded@internal@effects:with_inferred( _pipe@1, graded@internal@effects:load_spec_effects_from_file( Spec ) ), _pipe@3 = graded@internal@effects:with_inferred_returned_operators( _pipe@2, graded@internal@effects:load_spec_returns_from_file( Spec ) ), _pipe@4 = graded@internal@effects:with_externals( _pipe@3, graded@internal@annotation:extract_externals(Spec) ), infer_project_in_memory( _pipe@4, Index, Registry, Type_info ) end, Knowledge_base = begin _pipe@5 = Kb_base, _pipe@6 = graded@internal@effects:with_inferred_type_fields( _pipe@5, build_constructor_field_index(Index, Kb_base) ), _pipe@7 = graded@internal@effects:with_type_fields( _pipe@6, graded@internal@annotation:extract_type_fields(Spec) ), graded@internal@effects:with_factories( _pipe@7, qualify_by_module( Index, fun graded@internal@extract:factory_map/1 ) ) end, Results = gleam@list:map( Parsed, fun(Entry) -> {Gleam_path, Module} = Entry, Module_path = graded@internal@config:module_path_for_source( Gleam_path, Directory ), Module_checks = case gleam_stdlib:map_get( Checks_by_module, Module_path ) of {ok, List} -> List; {error, _} -> [] end, check_one_file( Gleam_path, Module, Module_checks, Knowledge_base, Registry, graded@internal@typeinfo:for_module( Type_info, Module_path ), graded@internal@typeinfo:fn_typed_for_module( Type_info, Module_path ) ) end ), {ok, Results} end ) end ). -file("src/graded.gleam", 1090). -spec run_check(binary()) -> nil. run_check(Directory) -> case run(Directory) of {ok, Results} -> Violations = gleam@list:flat_map( Results, fun(Check_result) -> erlang:element(3, Check_result) end ), Warnings = gleam@list:flat_map( Results, fun(Check_result@1) -> erlang:element(4, Check_result@1) end ), gleam@list:each(Results, fun print_warnings/1), case Warnings of [] -> nil; _ -> gleam_stdlib:println( <<<<"graded: "/utf8, (erlang:integer_to_binary( erlang:length(Warnings) ))/binary>>/binary, " warning(s)"/utf8>> ) end, case Violations of [] -> gleam_stdlib:println(<<"graded: all checks passed"/utf8>>); _ -> gleam@list:each(Results, fun print_violations/1), gleam_stdlib:println( <<<<"\ngraded: "/utf8, (erlang:integer_to_binary( erlang:length(Violations) ))/binary>>/binary, " violation(s) found"/utf8>> ), erlang:halt(1) end; {error, Error} -> gleam_stdlib:println_error( <<"graded: error: "/utf8, (format_error(Error))/binary>> ), erlang:halt(1) end. -file("src/graded.gleam", 301). -spec format_one_spec(binary()) -> {ok, binary()} | {error, graded_error()}. format_one_spec(Spec_path) -> gleam@result:'try'( begin _pipe = simplifile:read(Spec_path), gleam@result:map_error( _pipe, fun(_capture) -> {file_read_error, Spec_path, _capture} end ) end, fun(Content) -> gleam@result:'try'( begin _pipe@1 = graded@internal@annotation:parse_file(Content), gleam@result:map_error( _pipe@1, fun(_capture@1) -> {graded_parse_error, Spec_path, _capture@1} end ) end, fun(File) -> {ok, graded@internal@annotation:format_sorted(File)} end ) end ). -file("src/graded.gleam", 273). ?DOC( " Format the project's spec file in place. The spec file is the single\n" " source of truth for hand-written `check`/`external`/`type` lines and\n" " the inferred public-API effects.\n" ). -spec run_format(binary()) -> {ok, nil} | {error, graded_error()}. run_format(Directory) -> Cfg = read_config(Directory), case format_one_spec(erlang:element(3, Cfg)) of {error, _} -> {ok, nil}; {ok, Formatted} -> _pipe = simplifile:write(erlang:element(3, Cfg), Formatted), gleam@result:map_error( _pipe, fun(_capture) -> {file_write_error, erlang:element(3, Cfg), _capture} end ) end. -file("src/graded.gleam", 285). ?DOC( " Check that the project's spec file is already formatted. Returns error\n" " with the file path if it isn't. Used by CI as `format --check`.\n" ). -spec run_format_check(binary()) -> {ok, nil} | {error, graded_error()}. run_format_check(Directory) -> Cfg = read_config(Directory), case format_one_spec(erlang:element(3, Cfg)) of {error, _} -> {ok, nil}; {ok, Formatted} -> case simplifile:read(erlang:element(3, Cfg)) of {error, _} -> {ok, nil}; {ok, Content} -> case Content =:= Formatted of true -> {ok, nil}; false -> {error, {format_check_failed, [erlang:element(3, Cfg)]}} end end end. -file("src/graded.gleam", 1142). -spec write_graded_file(binary(), graded@internal@types:graded_file()) -> {ok, nil} | {error, graded_error()}. write_graded_file(Path, Graded_file) -> _pipe = simplifile:write( Path, graded@internal@annotation:format_file(Graded_file) ), gleam@result:map_error( _pipe, fun(_capture) -> {file_write_error, Path, _capture} end ). -file("src/graded.gleam", 839). ?DOC( " Write the project's spec file. Reads the existing spec (if any),\n" " preserves all `check`/`external`/`type` lines plus comments and blank\n" " lines, replaces the inferred `effects` lines with the freshly inferred\n" " public-function annotations, and writes the result back.\n" ). -spec write_spec_file( binary(), graded@internal@types:graded_file(), list(graded@internal@types:effect_annotation()), list(graded@internal@types:returns_annotation()) ) -> {ok, nil} | {error, graded_error()}. write_spec_file(Spec_path, Existing, Inferred, Inferred_returns) -> Merged = graded@internal@annotation:merge_inferred( Existing, Inferred, Inferred_returns ), Parent = filepath:directory_name(Spec_path), gleam@result:'try'( case (Parent =:= <<""/utf8>>) orelse (Parent =:= <<"."/utf8>>) of true -> {ok, nil}; false -> _pipe = simplifile:create_directory_all(Parent), gleam@result:map_error( _pipe, fun(_capture) -> {directory_create_error, Parent, _capture} end ) end, fun(_use0) -> nil = _use0, write_graded_file(Spec_path, Merged) end ). -file("src/graded.gleam", 826). ?DOC(" Build a set of public function names from a parsed Gleam module.\n"). -spec public_function_names(glance:module_()) -> gleam@set:set(binary()). public_function_names(Module) -> gleam@list:fold( erlang:element(6, Module), gleam@set:new(), fun(Acc, Def) -> case erlang:element(4, erlang:element(3, Def)) of public -> gleam@set:insert( Acc, erlang:element(3, erlang:element(3, Def)) ); private -> Acc end end ). -file("src/graded.gleam", 661). ?DOC( " Infer effects for a single module, write its cache file (with bare\n" " names), and return the new knowledge base + the module's *public*\n" " inferred annotations qualified with the module path. The caller\n" " accumulates the public annotations for the eventual spec file write.\n" ). -spec infer_one_module( glance:module_(), binary(), binary(), graded@internal@effects:knowledge_base(), graded@internal@signatures:signature_registry(), gleam@dict:dict({integer(), integer()}, girard@types:type()), gleam@dict:dict(binary(), gleam@set:set(binary())) ) -> {ok, {graded@internal@effects:knowledge_base(), list(graded@internal@types:effect_annotation()), list(graded@internal@types:returns_annotation())}} | {error, graded_error()}. infer_one_module( Module, Module_path, Cache_dir, Knowledge_base, Registry, Module_types, Girard_fn_typed ) -> {Inferred, Returned_operators} = graded@internal@checker:infer_with_returns( Module, Knowledge_base, [], Registry, Module_types, Girard_fn_typed ), Cache_path = filepath:join( Cache_dir, <> ), gleam@result:'try'(case Inferred of [] -> {ok, nil}; _ -> Parent_directory = filepath:directory_name(Cache_path), gleam@result:'try'( begin _pipe = simplifile:create_directory_all( Parent_directory ), gleam@result:map_error( _pipe, fun(_capture) -> {directory_create_error, Parent_directory, _capture} end ) end, fun(_use0) -> nil = _use0, Cache_file = {graded_file, gleam@list:map( Inferred, fun(Field@0) -> {annotation_line, Field@0} end )}, write_graded_file(Cache_path, Cache_file) end ) end, fun(_use0@1) -> nil = _use0@1, Inferred_dict = gleam@list:fold( Inferred, maps:new(), fun(Acc, Ann) -> gleam@dict:insert( Acc, {qualified_name, Module_path, erlang:element(3, Ann)}, erlang:element(5, Ann) ) end ), Params_dict = gleam@list:fold( Inferred, maps:new(), fun(Acc@1, Ann@1) -> case erlang:element(4, Ann@1) of [] -> Acc@1; _ -> gleam@dict:insert( Acc@1, {qualified_name, Module_path, erlang:element(3, Ann@1)}, erlang:element(4, Ann@1) ) end end ), Returned_dict = gleam@dict:fold( Returned_operators, maps:new(), fun(Acc@2, Function, Operator) -> gleam@dict:insert( Acc@2, {qualified_name, Module_path, Function}, Operator ) end ), New_kb = begin _pipe@1 = Knowledge_base, _pipe@2 = graded@internal@effects:with_inferred( _pipe@1, Inferred_dict ), _pipe@3 = graded@internal@effects:with_inferred_params( _pipe@2, Params_dict ), graded@internal@effects:with_inferred_returned_operators( _pipe@3, Returned_dict ) end, Public_names = public_function_names(Module), Public_annotations = begin _pipe@4 = Inferred, _pipe@5 = gleam@list:filter( _pipe@4, fun(Ann@2) -> gleam@set:contains( Public_names, erlang:element(3, Ann@2) ) end ), gleam@list:map( _pipe@5, fun(Ann@3) -> {effect_annotation, erlang:element(2, Ann@3), <<<>/binary, (erlang:element(3, Ann@3))/binary>>, erlang:element(4, Ann@3), erlang:element(5, Ann@3)} end ) end, Public_returns = begin _pipe@6 = Returned_operators, _pipe@7 = maps:to_list(_pipe@6), _pipe@8 = gleam@list:filter( _pipe@7, fun(Pair) -> gleam@set:contains( Public_names, erlang:element(1, Pair) ) end ), gleam@list:map( _pipe@8, fun(Pair@1) -> {returns_annotation, <<<>/binary, (erlang:element(1, Pair@1))/binary>>, erlang:element(2, Pair@1)} end ) end, {ok, {New_kb, Public_annotations, Public_returns}} end). -file("src/graded.gleam", 196). ?DOC( " Infer effects for all `.gleam` files in `directory`. Writes two outputs:\n" "\n" " 1. **Per-module cache files** under `/.graded`,\n" " containing the inferred effects of every function in the module\n" " (public + private). Regenerated freely; not shipped.\n" "\n" " 2. **One spec file** at `` containing the inferred effects of\n" " every *public* function across all modules, plus any hand-written\n" " `check`, `external effects`, or `type` annotations the user already\n" " had in the spec file (those lines are preserved verbatim).\n" "\n" " Walks the project's import graph in topological order so each module is\n" " analysed after every other project module it imports — a single pass\n" " resolves transitive chains of any depth.\n" ). -spec run_infer(binary()) -> {ok, nil} | {error, graded_error()}. run_infer(Directory) -> Cfg = read_config(Directory), Spec = read_spec(erlang:element(3, Cfg)), gleam@result:'try'( find_gleam_files(Directory), fun(Gleam_files) -> gleam@result:'try'( parse_all_files(Gleam_files), fun(Parsed) -> Index = build_module_index(Parsed, Directory), Kb_base = begin _pipe = graded@internal@effects:load_knowledge_base( <<"build/packages"/utf8>> ), _pipe@1 = enrich_with_path_deps(_pipe), graded@internal@effects:with_externals( _pipe@1, graded@internal@annotation:extract_externals(Spec) ) end, Construction_kb = graded@internal@effects:with_inferred( Kb_base, graded@internal@effects:load_spec_effects_from_file( Spec ) ), Base_kb = begin _pipe@2 = Kb_base, _pipe@3 = graded@internal@effects:with_inferred_type_fields( _pipe@2, build_constructor_field_index( Index, Construction_kb ) ), _pipe@4 = graded@internal@effects:with_type_fields( _pipe@3, graded@internal@annotation:extract_type_fields(Spec) ), graded@internal@effects:with_factories( _pipe@4, qualify_by_module( Index, fun graded@internal@extract:factory_map/1 ) ) end, Graph = build_dependency_graph(Index), gleam@result:'try'( begin _pipe@5 = graded@internal@topo:sort(Graph), gleam@result:map_error( _pipe@5, fun(Error) -> {cycle, Nodes} = Error, {cyclic_imports, Nodes} end ) end, fun(Sorted) -> Dep_registry = graded@internal@signatures:load_from_packages_dir( <<"build/packages"/utf8>> ), Registry = graded@internal@signatures:merge( Dep_registry, build_project_registry(Index) ), Type_info = build_type_index(Index), gleam@result:'try'( gleam@list:try_fold( Sorted, {Base_kb, [], []}, fun(State, Module_path) -> {Kb, Acc, Returns_acc} = State, case gleam_stdlib:map_get( Index, Module_path ) of {error, _} -> {ok, State}; {ok, {_, Module}} -> gleam@result:'try'( infer_one_module( Module, Module_path, erlang:element(4, Cfg), Kb, Registry, graded@internal@typeinfo:for_module( Type_info, Module_path ), graded@internal@typeinfo:fn_typed_for_module( Type_info, Module_path ) ), fun(_use0) -> {New_kb, New_public, New_returns} = _use0, {ok, {New_kb, lists:append( New_public, Acc ), lists:append( New_returns, Returns_acc )}} end ) end end ), fun(_use0@1) -> {_, Public_annotations, Public_returns} = _use0@1, write_spec_file( erlang:element(3, Cfg), Spec, Public_annotations, Public_returns ) end ) end ) end ) end ). -file("src/graded.gleam", 77). -spec main() -> nil. main() -> Arguments = erlang:element(4, argv:load()), case Arguments of [<<"infer"/utf8>> | Rest] -> case run_infer(target_directory(Rest)) of {ok, nil} -> gleam_stdlib:println( <<"graded: inferred effects written"/utf8>> ); {error, Error} -> gleam_stdlib:println_error( <<"graded: error: "/utf8, (format_error(Error))/binary>> ), erlang:halt(1) end; [<<"format"/utf8>>, <<"--stdin"/utf8>>] -> Input = begin _pipe = stdin:read_lines(), _pipe@1 = gleam@yielder:to_list(_pipe), gleam@string:join(_pipe@1, <<""/utf8>>) end, case graded@internal@annotation:parse_file(Input) of {ok, File} -> gleam_stdlib:print( graded@internal@annotation:format_sorted(File) ); {error, _} -> gleam_stdlib:println_error( <<"graded: error: could not parse stdin"/utf8>> ), erlang:halt(1) end; [<<"format"/utf8>>, <<"--check"/utf8>> | Rest@1] -> case run_format_check(target_directory(Rest@1)) of {ok, nil} -> nil; {error, Error@1} -> gleam_stdlib:println_error( <<"graded: error: "/utf8, (format_error(Error@1))/binary>> ), erlang:halt(1) end; [<<"format"/utf8>> | Rest@2] -> case run_format(target_directory(Rest@2)) of {ok, nil} -> nil; {error, Error@2} -> gleam_stdlib:println_error( <<"graded: error: "/utf8, (format_error(Error@2))/binary>> ), erlang:halt(1) end; [<<"check"/utf8>> | Rest@3] -> run_check(target_directory(Rest@3)); _ -> run_check(target_directory(Arguments)) end.