-module(graded). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/graded.gleam"). -export([run_format/1, run_format_check/1, infer_path_dep/2, run/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", 238). -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", 266). ?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@extract:module_path_for_source( Gleam_path, Directory ), gleam@dict:insert(Acc, Module_path, {Gleam_path, Module}) end ). -file("src/graded.gleam", 281). ?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", 345). ?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", 382). ?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", 446). ?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", 454). -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", 426). ?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", 210). ?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", 222). ?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", 463). -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", 565). -spec infer_path_dep_module( {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), 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_set()), 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), 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", 588). -spec target_directory(list(binary())) -> binary(). target_directory(Arguments) -> case Arguments of [Directory | _] -> Directory; [] -> <<"src"/utf8>> end. -file("src/graded.gleam", 630). -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", 636). -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", 254). ?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", 402). ?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(), list(graded@internal@types:effect_annotation()), graded@internal@effects:knowledge_base() ) -> {ok, graded@internal@types:check_result()} | {error, nil}. check_one_file(Gleam_path, Module_checks, Knowledge_base) -> gleam@result:'try'( begin _pipe = read_and_parse_gleam(Gleam_path), gleam@result:replace_error(_pipe, nil) end, fun(Module) -> {Violations, Warnings} = graded@internal@checker:check( Module, Module_checks, Knowledge_base ), {ok, {check_result, Gleam_path, Violations, Warnings}} end ). -file("src/graded.gleam", 518). ?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_set())} | {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@extract: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", 486). ?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_file = case graded@internal@config:read( filepath:join(Dep_path, <<"gleam.toml"/utf8>>) ) of {ok, Cfg} -> erlang:element(3, Cfg); {error, _} -> graded@internal@config:default_spec_file(Name) end, Spec_path = filepath:join(Dep_path, Spec_file), 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", 119). ?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)), Knowledge_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_externals( _pipe@2, graded@internal@annotation:extract_externals(Spec) ), graded@internal@effects:with_type_fields( _pipe@3, graded@internal@annotation:extract_type_fields(Spec) ) end, Checks_by_module = checks_grouped_by_module(Spec), gleam@result:'try'( find_gleam_files(Directory), fun(Gleam_files) -> Results = begin _pipe@4 = gleam@list:map( Gleam_files, fun(Gleam_path) -> Module_path = graded@internal@extract: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_checks, Knowledge_base ) end ), gleam@list:filter_map(_pipe@4, fun(Result) -> Result end) end, {ok, Results} end ). -file("src/graded.gleam", 647). -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", 298). ?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() ) -> {ok, {graded@internal@effects:knowledge_base(), list(graded@internal@types:effect_annotation())}} | {error, graded_error()}. infer_one_module(Module, Module_path, Cache_dir, Knowledge_base) -> Inferred = graded@internal@checker:infer(Module, Knowledge_base, []), 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 ), New_kb = graded@internal@effects:with_inferred( Knowledge_base, Inferred_dict ), Public_names = public_function_names(Module), Public_annotations = begin _pipe@1 = Inferred, _pipe@2 = gleam@list:filter( _pipe@1, fun(Ann@1) -> gleam@set:contains( Public_names, erlang:element(3, Ann@1) ) end ), gleam@list:map( _pipe@2, fun(Ann@2) -> {effect_annotation, erlang:element(2, Ann@2), <<<>/binary, (erlang:element(3, Ann@2))/binary>>, erlang:element(4, Ann@2), erlang:element(5, Ann@2)} end ) end, {ok, {New_kb, Public_annotations}} end). -file("src/graded.gleam", 358). ?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()) ) -> {ok, nil} | {error, graded_error()}. write_spec_file(Spec_path, Existing, Inferred) -> Merged = graded@internal@annotation:merge_inferred(Existing, Inferred), 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", 162). ?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)), Base_kb = begin _pipe = graded@internal@effects:load_knowledge_base( <<"build/packages"/utf8>> ), _pipe@1 = enrich_with_path_deps(_pipe), _pipe@2 = graded@internal@effects:with_externals( _pipe@1, graded@internal@annotation:extract_externals(Spec) ), graded@internal@effects:with_type_fields( _pipe@2, graded@internal@annotation:extract_type_fields(Spec) ) end, 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), Graph = build_dependency_graph(Index), gleam@result:'try'( begin _pipe@3 = graded@internal@topo:sort(Graph), gleam@result:map_error( _pipe@3, fun(Error) -> {cycle, Nodes} = Error, {cyclic_imports, Nodes} end ) end, fun(Sorted) -> gleam@result:'try'( gleam@list:try_fold( Sorted, {Base_kb, []}, fun(State, Module_path) -> {Kb, 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 ), fun(_use0) -> {New_kb, New_public} = _use0, {ok, {New_kb, lists:append( New_public, Acc )}} end ) end end ), fun(_use0@1) -> {_, Public_annotations} = _use0@1, write_spec_file( erlang:element(3, Cfg), Spec, Public_annotations ) end ) end ) end ) end ). -file("src/graded.gleam", 655). -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", 678). -spec print_violation(binary(), graded@internal@types:violation()) -> nil. print_violation(File, Violation) -> gleam_stdlib:println( <<<<<<<<<<<<<<<<<<<>/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>> ). -file("src/graded.gleam", 672). -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", 700). -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", 694). -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", 595). -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", 71). -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.