Current section
Files
Jump to
Current section
Files
src/libero@walker.erl
-module(libero@walker).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/libero/walker.gleam").
-export([to_snake_case/1, qualified_atom_name/2, walk/2]).
-export_type([discovered_type/0, discovered_variant/0, type_ref_resolver/0, walker_state/0, alias_def/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.
-type discovered_type() :: {discovered_type,
binary(),
binary(),
list(binary()),
list(discovered_variant())}.
-type discovered_variant() :: {discovered_variant,
binary(),
binary(),
binary(),
list(integer()),
list(gleam@option:option(binary())),
list(libero@field_type:field_type())}.
-type type_ref_resolver() :: {type_ref_resolver,
gleam@dict:dict(binary(), binary()),
gleam@dict:dict(binary(), binary()),
gleam@dict:dict(binary(), binary())}.
-type walker_state() :: {walker_state,
list({binary(), binary()}),
gleam@set:set({binary(), binary()}),
list(discovered_type()),
gleam@dict:dict(binary(), binary()),
gleam@dict:dict(binary(), glance:module_()),
list(libero@gen_error:gen_error())}.
-type alias_def() :: {alias_def, list(binary()), glance:type()}.
-file("src/libero/walker.gleam", 84).
?DOC(" True if a module path should not be walked by the type graph walker.\n").
-spec is_skipped_module(binary()) -> boolean().
is_skipped_module(Module_path) ->
gleam@list:any(
[<<"libero/"/utf8>>, <<"gleam/"/utf8>>],
fun(Prefix) -> gleam_stdlib:string_starts_with(Module_path, Prefix) end
).
-file("src/libero/walker.gleam", 91).
?DOC(" True if a type name is a primitive/builtin that needs no registration.\n").
-spec is_primitive_type(binary()) -> boolean().
is_primitive_type(Name) ->
libero@field_type:is_builtin(Name).
-file("src/libero/walker.gleam", 135).
?DOC(
" True when a module path is a Gleam stdlib (or stdlib-extension) module,\n"
" e.g. `gleam`, `gleam/option`, `gleam/dict`. Used to decide whether an\n"
" unqualified primitive type name imported from that module is still the\n"
" stdlib type (e.g. `import gleam/option.{type Option}`) versus a user\n"
" type that happens to share a name with a stdlib primitive.\n"
).
-spec is_stdlib_module_path(binary()) -> boolean().
is_stdlib_module_path(Path) ->
(Path =:= <<"gleam"/utf8>>) orelse gleam_stdlib:string_starts_with(
Path,
<<"gleam/"/utf8>>
).
-file("src/libero/walker.gleam", 103).
?DOC(
" True when a NamedType reference points to a Gleam stdlib type, not a\n"
" user-defined type that happens to share the name. Without this check,\n"
" `pub type Result` defined in shared/ would be silently dropped from\n"
" codegen because its name matches `registry_primitives`.\n"
"\n"
" Distinguishes by module qualifier and by whether the user imported a\n"
" same-named type. A bare `Result` with no shadowing import is stdlib;\n"
" `import shared/myresult.{type Result}` makes bare `Result` user-defined.\n"
).
-spec is_stdlib_reference(
binary(),
gleam@option:option(binary()),
type_ref_resolver()
) -> boolean().
is_stdlib_reference(Name, Module, Resolver) ->
case {is_primitive_type(Name), Module} of
{false, _} ->
false;
{true, {some, <<"gleam"/utf8>>}} ->
true;
{true, {some, <<"option"/utf8>>}} ->
Name =:= <<"Option"/utf8>>;
{true, {some, <<"result"/utf8>>}} ->
Name =:= <<"Result"/utf8>>;
{true, {some, <<"dict"/utf8>>}} ->
Name =:= <<"Dict"/utf8>>;
{true, {some, <<"list"/utf8>>}} ->
Name =:= <<"List"/utf8>>;
{true, {some, <<"bool"/utf8>>}} ->
Name =:= <<"Bool"/utf8>>;
{true, {some, <<"bit_array"/utf8>>}} ->
Name =:= <<"BitArray"/utf8>>;
{true, {some, _}} ->
false;
{true, none} ->
case gleam_stdlib:map_get(erlang:element(2, Resolver), Name) of
{error, nil} ->
true;
{ok, Module_path} ->
is_stdlib_module_path(Module_path)
end
end.
-file("src/libero/walker.gleam", 671).
?DOC(" Extract the type from a variant field, whether labelled or unlabelled.\n").
-spec variant_field_type(glance:variant_field()) -> glance:type().
variant_field_type(Field) ->
case Field of
{labelled_variant_field, Item, _} ->
Item;
{unlabelled_variant_field, Item@1} ->
Item@1
end.
-file("src/libero/walker.gleam", 430).
?DOC(
" Resolve a type name (with optional module qualifier) to its full\n"
" module path. Falls back to current_module when the name is unqualified\n"
" and not in the resolver - meaning it's defined in the current module.\n"
).
-spec resolve_type_module(
binary(),
gleam@option:option(binary()),
type_ref_resolver(),
binary()
) -> {ok, binary()} | {error, nil}.
resolve_type_module(Name, Module, Resolver, Current_module) ->
case Module of
{some, Alias} ->
gleam_stdlib:map_get(erlang:element(3, Resolver), Alias);
none ->
case gleam_stdlib:map_get(erlang:element(2, Resolver), Name) of
{ok, Mp} ->
{ok, Mp};
{error, nil} ->
{ok, Current_module}
end
end.
-file("src/libero/walker.gleam", 451).
?DOC(
" Walk a glance.Type and return (module_path, type_name) refs for any\n"
" named custom types found. Uses resolver to map alias/unqualified names\n"
" to their full module paths. `current_module` is the module path of the\n"
" file being walked - used to resolve unqualified names that are defined\n"
" in the same file (not in any import).\n"
).
-spec collect_type_refs(glance:type(), type_ref_resolver(), binary()) -> list({binary(),
binary()}).
collect_type_refs(T, Resolver, Current_module) ->
case T of
{named_type, _, Name, Module, Parameters} ->
Param_refs = gleam@list:flat_map(
Parameters,
fun(P) -> collect_type_refs(P, Resolver, Current_module) end
),
gleam@bool:guard(
is_stdlib_reference(Name, Module, Resolver),
Param_refs,
fun() ->
case resolve_type_module(
Name,
Module,
Resolver,
Current_module
) of
{error, nil} ->
Param_refs;
{ok, Mp} ->
gleam@bool:guard(
is_skipped_module(Mp),
Param_refs,
fun() ->
Original_name = gleam@result:unwrap(
gleam_stdlib:map_get(
erlang:element(4, Resolver),
Name
),
Name
),
lists:append(
[{Mp, Original_name}],
Param_refs
)
end
)
end
end
);
{tuple_type, _, Elements} ->
gleam@list:flat_map(
Elements,
fun(E) -> collect_type_refs(E, Resolver, Current_module) end
);
{function_type, _, _, _} ->
[];
{variable_type, _, _} ->
[];
{hole_type, _, _} ->
[]
end.
-file("src/libero/walker.gleam", 407).
?DOC(
" Collect (module_path, type_name) refs from a variant's fields,\n"
" filtering out visited, skipped, and primitive refs.\n"
).
-spec collect_variant_field_refs(
glance:variant(),
type_ref_resolver(),
binary(),
gleam@set:set({binary(), binary()})
) -> list({binary(), binary()}).
collect_variant_field_refs(Variant, Resolver, Current_module, Visited) ->
Field_refs = gleam@list:flat_map(
erlang:element(3, Variant),
fun(Field) ->
collect_type_refs(
variant_field_type(Field),
Resolver,
Current_module
)
end
),
gleam@list:filter(
Field_refs,
fun(Ref) ->
{Ref_module, _} = Ref,
not gleam@set:contains(Visited, Ref) andalso not is_skipped_module(
Ref_module
)
end
).
-file("src/libero/walker.gleam", 666).
-spec is_upper_grapheme(binary()) -> boolean().
is_upper_grapheme(G) ->
G /= string:lowercase(G).
-file("src/libero/walker.gleam", 652).
-spec build_triples(list(binary()), binary()) -> list({binary(),
binary(),
binary()}).
build_triples(Remaining, Prev) ->
case Remaining of
[] ->
[];
[G] ->
[{Prev, G, <<""/utf8>>}];
[G@1, Next | Rest] ->
[{Prev, G@1, Next} | build_triples([Next | Rest], G@1)]
end.
-file("src/libero/walker.gleam", 626).
?DOC(
" Convert a PascalCase variant name to snake_case for the wire atom.\n"
" \"AdminData\" → \"admin_data\", \"One\" → \"one\", \"TwoOrMore\" → \"two_or_more\".\n"
" Handles consecutive uppercase: \"XMLParser\" → \"xml_parser\".\n"
" Must stay aligned with `snakeCase()` in etf/wire_ffi.mjs.\n"
).
-spec to_snake_case(binary()) -> binary().
to_snake_case(Name) ->
Graphemes = gleam@string:to_graphemes(Name),
Triples = build_triples(Graphemes, <<""/utf8>>),
gleam@list:index_fold(
Triples,
<<""/utf8>>,
fun(Acc, Triple, I) ->
{Prev, G, Next} = Triple,
case {I =:= 0, is_upper_grapheme(G)} of
{true, _} ->
<<Acc/binary, (string:lowercase(G))/binary>>;
{false, true} ->
Prev_upper = is_upper_grapheme(Prev),
Next_lower = (Next /= <<""/utf8>>) andalso not is_upper_grapheme(
Next
),
case {Prev_upper, Next_lower} of
{true, true} ->
<<<<Acc/binary, "_"/utf8>>/binary,
(string:lowercase(G))/binary>>;
{true, false} ->
<<Acc/binary, (string:lowercase(G))/binary>>;
{_, _} ->
<<<<Acc/binary, "_"/utf8>>/binary,
(string:lowercase(G))/binary>>
end;
{false, false} ->
<<Acc/binary, G/binary>>
end
end
).
-file("src/libero/walker.gleam", 615).
?DOC(
" Build a module-qualified atom name from a module path and variant name.\n"
" \"shared/discount\" + \"Discount\" → \"shared_discount__discount\".\n"
" Two modules with the same variant name produce distinct atoms, so the\n"
" atom→decoder reverse mapping cannot collide.\n"
).
-spec qualified_atom_name(binary(), binary()) -> binary().
qualified_atom_name(Module_path, Variant_name) ->
<<<<(gleam@string:replace(Module_path, <<"/"/utf8>>, <<"_"/utf8>>))/binary,
"__"/utf8>>/binary,
(to_snake_case(Variant_name))/binary>>.
-file("src/libero/walker.gleam", 554).
-spec substitute_type_vars(
glance:type(),
gleam@dict:dict(binary(), glance:type())
) -> glance:type().
substitute_type_vars(T, Bindings) ->
case T of
{variable_type, _, Name} ->
_pipe = gleam_stdlib:map_get(Bindings, Name),
gleam@result:unwrap(_pipe, T);
{named_type, Location, Name@1, Module, Parameters} ->
{named_type,
Location,
Name@1,
Module,
gleam@list:map(
Parameters,
fun(_capture) ->
substitute_type_vars(_capture, Bindings)
end
)};
{tuple_type, Location@1, Elements} ->
{tuple_type,
Location@1,
gleam@list:map(
Elements,
fun(_capture@1) ->
substitute_type_vars(_capture@1, Bindings)
end
)};
{function_type, Location@2, Parameters@1, Return} ->
{function_type,
Location@2,
gleam@list:map(
Parameters@1,
fun(_capture@2) ->
substitute_type_vars(_capture@2, Bindings)
end
),
substitute_type_vars(Return, Bindings)};
{hole_type, _, _} ->
T
end.
-file("src/libero/walker.gleam", 503).
?DOC(
" Convert a glance.Type into a FieldType, resolving named types via the\n"
" shared resolver. Local type aliases are resolved transparently before\n"
" delegating to the shared resolver.\n"
).
-spec field_type_of(
glance:type(),
libero@glance_type_resolver:type_resolver(),
gleam@dict:dict(binary(), alias_def()),
binary()
) -> libero@field_type:field_type().
field_type_of(T, Shared_resolver, Aliases, Current_module) ->
case T of
{named_type, _, Name, none, Parameters} ->
case gleam_stdlib:map_get(Aliases, Name) of
{ok, {alias_def, Alias_params, Aliased_type}} ->
Bindings = gleam@list:fold(
gleam@list:zip(Alias_params, Parameters),
maps:new(),
fun(Acc, Pair) ->
{Param, Replacement} = Pair,
gleam@dict:insert(Acc, Param, Replacement)
end
),
Aliased_type@1 = substitute_type_vars(
Aliased_type,
Bindings
),
field_type_of(
Aliased_type@1,
Shared_resolver,
Aliases,
Current_module
);
{error, nil} ->
Ft@1 = case libero@glance_type_resolver:type_to_field_type(
T,
Shared_resolver,
Current_module,
preserve_unsupported
) of
{ok, Ft} -> Ft;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"libero/walker"/utf8>>,
function => <<"field_type_of"/utf8>>,
line => 531,
value => _assert_fail,
start => 18085,
'end' => 18310,
pattern_start => 18096,
pattern_end => 18102})
end,
Ft@1
end;
_ ->
Ft@3 = case libero@glance_type_resolver:type_to_field_type(
T,
Shared_resolver,
Current_module,
preserve_unsupported
) of
{ok, Ft@2} -> Ft@2;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"libero/walker"/utf8>>,
function => <<"field_type_of"/utf8>>,
line => 542,
value => _assert_fail@1,
start => 18359,
'end' => 18560,
pattern_start => 18370,
pattern_end => 18376})
end,
Ft@3
end.
-file("src/libero/walker.gleam", 680).
?DOC(
" Extract the label from a variant field. Returns `Some(label)` for labelled\n"
" fields and `None` for unlabelled fields.\n"
).
-spec variant_field_label(glance:variant_field()) -> gleam@option:option(binary()).
variant_field_label(Field) ->
case Field of
{labelled_variant_field, _, Label} ->
{some, Label};
{unlabelled_variant_field, _} ->
none
end.
-file("src/libero/walker.gleam", 397).
?DOC(" Check if a glance type is `Float` (unqualified or gleam-qualified).\n").
-spec is_float_type(glance:type()) -> boolean().
is_float_type(T) ->
case T of
{named_type, _, <<"Float"/utf8>>, none, _} ->
true;
{named_type, _, <<"Float"/utf8>>, {some, <<"gleam"/utf8>>}, _} ->
true;
_ ->
false
end.
-file("src/libero/walker.gleam", 386).
?DOC(
" Return 0-based indices of fields whose outermost type is Float.\n"
" Used by the JS ETF encoder to distinguish Int from Float\n"
" (JS erases this distinction at runtime, but ETF and BEAM need it).\n"
).
-spec detect_float_fields(list(glance:variant_field())) -> list(integer()).
detect_float_fields(Fields) ->
_pipe = gleam@list:index_fold(
Fields,
[],
fun(Acc, Field, Index) ->
case is_float_type(variant_field_type(Field)) of
true ->
[Index | Acc];
false ->
Acc
end
end
),
lists:reverse(_pipe).
-file("src/libero/walker.gleam", 586).
?DOC(
" Build a map from type alias names to their underlying glance.Type.\n"
" Used to resolve aliases transparently in field_type_of.\n"
).
-spec build_alias_map(list(glance:definition(glance:type_alias()))) -> gleam@dict:dict(binary(), alias_def()).
build_alias_map(Type_aliases) ->
gleam@list:fold(
Type_aliases,
maps:new(),
fun(Acc, Def) ->
gleam@dict:insert(
Acc,
erlang:element(3, erlang:element(3, Def)),
{alias_def,
erlang:element(5, erlang:element(3, Def)),
erlang:element(6, erlang:element(3, Def))}
)
end
).
-file("src/libero/walker.gleam", 601).
-spec build_type_resolver(list(glance:definition(glance:import()))) -> type_ref_resolver().
build_type_resolver(Imports) ->
{type_ref_resolver,
libero@source:build_type_import_map(Imports),
libero@source:build_alias_resolution_map(Imports),
libero@source:build_type_alias_originals(Imports)}.
-file("src/libero/walker.gleam", 369).
?DOC(" Parse a module, returning the cached version if available.\n").
-spec load_ast(binary(), binary(), gleam@dict:dict(binary(), glance:module_())) -> {ok,
{glance:module_(), gleam@dict:dict(binary(), glance:module_())}} |
{error, libero@gen_error:gen_error()}.
load_ast(Module_path, File_path, Parsed_cache) ->
case gleam_stdlib:map_get(Parsed_cache, Module_path) of
{ok, Ast} ->
{ok, {Ast, Parsed_cache}};
{error, nil} ->
gleam@result:map(
libero@source:parse_module(File_path),
fun(Ast@1) ->
{Ast@1, gleam@dict:insert(Parsed_cache, Module_path, Ast@1)}
end
)
end.
-file("src/libero/walker.gleam", 307).
-spec walk_custom_type_variants(
walker_state(),
glance:custom_type(),
binary(),
binary(),
type_ref_resolver(),
libero@glance_type_resolver:type_resolver(),
gleam@dict:dict(binary(), alias_def())
) -> {ok, list(discovered_type())} | {error, list(libero@gen_error:gen_error())}.
walk_custom_type_variants(
State,
Custom_type,
Module_path,
Type_name,
Resolver,
Shared_resolver,
Aliases
) ->
{Variants_rev, New_queue_items_rev} = gleam@list:fold(
erlang:element(7, Custom_type),
{[], []},
fun(Acc, Variant) ->
{Disc_acc, Queue_acc} = Acc,
Float_indices = detect_float_fields(erlang:element(3, Variant)),
Field_labels = gleam@list:map(
erlang:element(3, Variant),
fun variant_field_label/1
),
Fields = gleam@list:map(
erlang:element(3, Variant),
fun(Field) ->
field_type_of(
variant_field_type(Field),
Shared_resolver,
Aliases,
Module_path
)
end
),
Disc_item = {discovered_variant,
Module_path,
erlang:element(2, Variant),
qualified_atom_name(Module_path, erlang:element(2, Variant)),
Float_indices,
Field_labels,
Fields},
Field_refs = collect_variant_field_refs(
Variant,
Resolver,
Module_path,
erlang:element(3, State)
),
{[Disc_item | Disc_acc], lists:append(Field_refs, Queue_acc)}
end
),
Discovered_type = {discovered_type,
Module_path,
Type_name,
erlang:element(6, Custom_type),
lists:reverse(Variants_rev)},
New_queue_items = lists:reverse(New_queue_items_rev),
do_walk(
{walker_state,
lists:append(New_queue_items, erlang:element(2, State)),
erlang:element(3, State),
[Discovered_type | erlang:element(4, State)],
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State)}
).
-file("src/libero/walker.gleam", 262).
-spec process_type_ast_custom(
binary(),
binary(),
glance:module_(),
walker_state()
) -> {ok, list(discovered_type())} | {error, list(libero@gen_error:gen_error())}.
process_type_ast_custom(Module_path, Type_name, Ast, State) ->
case gleam@list:find(
erlang:element(3, Ast),
fun(D) -> erlang:element(3, erlang:element(3, D)) =:= Type_name end
) of
{error, nil} ->
do_walk(
{walker_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
[{type_not_found, Module_path, Type_name} |
erlang:element(7, State)]}
);
{ok, Ct_def} ->
Custom_type = erlang:element(3, Ct_def),
Resolver = build_type_resolver(erlang:element(2, Ast)),
Aliases = build_alias_map(erlang:element(4, Ast)),
case libero@glance_type_resolver:resolver_from_imports(
erlang:element(2, Ast)
) of
{error, _} ->
do_walk(
{walker_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
[{type_resolution_failed,
Module_path,
<<"ambiguous or conflicting imports in module "/utf8,
Module_path/binary>>} |
erlang:element(7, State)]}
);
{ok, Shared_resolver} ->
walk_custom_type_variants(
State,
Custom_type,
Module_path,
Type_name,
Resolver,
Shared_resolver,
Aliases
)
end
end.
-file("src/libero/walker.gleam", 229).
-spec process_type_ast(binary(), binary(), glance:module_(), walker_state()) -> {ok,
list(discovered_type())} |
{error, list(libero@gen_error:gen_error())}.
process_type_ast(Module_path, Type_name, Ast, State) ->
case gleam@list:find(
erlang:element(4, Ast),
fun(D) -> erlang:element(3, erlang:element(3, D)) =:= Type_name end
) of
{ok, Alias_def} ->
Resolver = build_type_resolver(erlang:element(2, Ast)),
Target_refs = collect_type_refs(
erlang:element(6, erlang:element(3, Alias_def)),
Resolver,
Module_path
),
New_refs = gleam@list:filter(
Target_refs,
fun(Ref) ->
{Ref_module, Ref_type} = Ref,
(not gleam@set:contains(erlang:element(3, State), Ref)
andalso not is_skipped_module(Ref_module))
andalso not is_primitive_type(Ref_type)
end
),
do_walk(
{walker_state,
lists:append(New_refs, erlang:element(2, State)),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State)}
);
{error, nil} ->
process_type_ast_custom(Module_path, Type_name, Ast, State)
end.
-file("src/libero/walker.gleam", 210).
-spec process_type_file(binary(), binary(), binary(), walker_state()) -> {ok,
list(discovered_type())} |
{error, list(libero@gen_error:gen_error())}.
process_type_file(Module_path, Type_name, File_path, State) ->
case load_ast(Module_path, File_path, erlang:element(6, State)) of
{error, E} ->
do_walk(
{walker_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
[E | erlang:element(7, State)]}
);
{ok, {Ast, New_cache}} ->
process_type_ast(
Module_path,
Type_name,
Ast,
{walker_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
New_cache,
erlang:element(7, State)}
)
end.
-file("src/libero/walker.gleam", 191).
-spec process_type(binary(), binary(), walker_state()) -> {ok,
list(discovered_type())} |
{error, list(libero@gen_error:gen_error())}.
process_type(Module_path, Type_name, State) ->
case gleam_stdlib:map_get(erlang:element(5, State), Module_path) of
{error, nil} ->
do_walk(
{walker_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
[{unresolved_type_module, Module_path, Type_name} |
erlang:element(7, State)]}
);
{ok, File_path} ->
process_type_file(Module_path, Type_name, File_path, State)
end.
-file("src/libero/walker.gleam", 166).
-spec do_walk(walker_state()) -> {ok, list(discovered_type())} |
{error, list(libero@gen_error:gen_error())}.
do_walk(State) ->
case erlang:element(2, State) of
[] ->
case erlang:element(7, State) of
[] ->
{ok, lists:reverse(erlang:element(4, State))};
_ ->
{error, lists:reverse(erlang:element(7, State))}
end;
[{Module_path, Type_name} | Rest_queue] ->
Key = {Module_path, Type_name},
gleam@bool:lazy_guard(
gleam@set:contains(erlang:element(3, State), Key),
fun() ->
do_walk(
{walker_state,
Rest_queue,
erlang:element(3, State),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State)}
)
end,
fun() ->
State@1 = {walker_state,
Rest_queue,
gleam@set:insert(erlang:element(3, State), Key),
erlang:element(4, State),
erlang:element(5, State),
erlang:element(6, State),
erlang:element(7, State)},
process_type(Module_path, Type_name, State@1)
end
)
end.
-file("src/libero/walker.gleam", 144).
?DOC(
" Walk the type graph starting from seeds, discovering all reachable custom types.\n"
" Seeds come from framework-owned contract discovery.\n"
" file_paths are the .gleam files to search for type definitions.\n"
" BFS traversal order affects discovery order but not correctness:\n"
" all reachable types are found regardless of queue order.\n"
).
-spec walk(list({binary(), binary()}), list(binary())) -> {ok,
list(discovered_type())} |
{error, list(libero@gen_error:gen_error())}.
walk(Seeds, File_paths) ->
Module_files = gleam@list:fold(
File_paths,
maps:new(),
fun(Acc, File_path) ->
Module_path = libero@source:derive_module_path(File_path),
gleam@dict:insert(Acc, Module_path, File_path)
end
),
do_walk(
{walker_state, Seeds, gleam@set:new(), [], Module_files, maps:new(), []}
).