Current section

Files

Jump to
libero src libero@walker.erl
Raw

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, variant_field_type/1, walk_message_registry_types/2]).
-export_type([field_type/0, discovered_type/0, discovered_variant/0, type_resolver/0, walker_state/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 field_type() :: {user_type, binary(), binary(), list(field_type())} |
{list_of, field_type()} |
{option_of, field_type()} |
{result_of, field_type(), field_type()} |
{dict_of, field_type(), field_type()} |
{tuple_of, list(field_type())} |
int_field |
float_field |
string_field |
bool_field |
bit_array_field |
nil_field |
{type_var, binary()}.
-type discovered_type() :: {discovered_type,
binary(),
binary(),
list(binary()),
list(discovered_variant())}.
-type discovered_variant() :: {discovered_variant,
binary(),
binary(),
binary(),
list(integer()),
list(field_type())}.
-type type_resolver() :: {type_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())}.
-file("src/libero/walker.gleam", 324).
?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:'try'(
begin
_pipe = simplifile:read(File_path),
gleam@result:map_error(
_pipe,
fun(Cause) -> {cannot_read_file, File_path, Cause} end
)
end,
fun(Source) ->
gleam@result:map(
begin
_pipe@1 = glance:module(Source),
gleam@result:map_error(
_pipe@1,
fun(Cause@1) ->
{parse_failed, File_path, Cause@1}
end
)
end,
fun(Ast@1) ->
{Ast@1,
gleam@dict:insert(
Parsed_cache,
Module_path,
Ast@1
)}
end
)
end
)
end.
-file("src/libero/walker.gleam", 361).
?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", 396).
?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_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", 413).
?DOC(" Unwrap a Result, returning the default on Error or continuing with the Ok value.\n").
-spec result_guard({ok, VMF} | {error, nil}, VMI, fun((VMF) -> VMI)) -> VMI.
result_guard(R, Default, Next) ->
case R of
{ok, Value} ->
Next(Value);
{error, nil} ->
Default
end.
-file("src/libero/walker.gleam", 574).
?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(), glance:type()).
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)),
erlang:element(6, erlang:element(3, Def))
)
end
).
-file("src/libero/walker.gleam", 628).
-spec default_module_alias(binary()) -> binary().
default_module_alias(Module_path) ->
_pipe = gleam@string:split(Module_path, <<"/"/utf8>>),
_pipe@1 = gleam@list:last(_pipe),
gleam@result:unwrap(_pipe@1, Module_path).
-file("src/libero/walker.gleam", 582).
-spec build_type_resolver(list(glance:definition(glance:import()))) -> type_resolver().
build_type_resolver(Imports) ->
Empty_unq = maps:new(),
Empty_al = maps:new(),
Empty_orig = maps:new(),
Init = {type_resolver, Empty_unq, Empty_al, Empty_orig},
gleam@list:fold(
Imports,
Init,
fun(Acc, Def) ->
Imp = erlang:element(3, Def),
Module_path = erlang:element(3, Imp),
Acc@2 = gleam@list:fold(
erlang:element(5, Imp),
Acc,
fun(Acc@1, Uq) ->
Name = case erlang:element(3, Uq) of
{some, A} ->
A;
none ->
erlang:element(2, Uq)
end,
New_originals = case erlang:element(3, Uq) of
{some, _} ->
gleam@dict:insert(
erlang:element(4, Acc@1),
Name,
erlang:element(2, Uq)
);
none ->
erlang:element(4, Acc@1)
end,
{type_resolver,
gleam@dict:insert(
erlang:element(2, Acc@1),
Name,
Module_path
),
erlang:element(3, Acc@1),
New_originals}
end
),
Alias_name = case erlang:element(4, Imp) of
{some, {named, Name@1}} ->
Name@1;
_ ->
default_module_alias(Module_path)
end,
{type_resolver,
erlang:element(2, Acc@2),
gleam@dict:insert(
erlang:element(3, Acc@2),
Alias_name,
Module_path
),
erlang:element(4, Acc@2)}
end
).
-file("src/libero/walker.gleam", 664).
-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", 678).
-spec is_upper_grapheme(binary()) -> boolean().
is_upper_grapheme(G) ->
G /= string:lowercase(G).
-file("src/libero/walker.gleam", 638).
?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 rpc_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", 683).
?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", 350).
?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", 101).
?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", 108).
?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) ->
gleam@list:contains(
[<<"Int"/utf8>>,
<<"Float"/utf8>>,
<<"String"/utf8>>,
<<"Bool"/utf8>>,
<<"Nil"/utf8>>,
<<"BitArray"/utf8>>,
<<"List"/utf8>>,
<<"Result"/utf8>>,
<<"Option"/utf8>>,
<<"Dict"/utf8>>],
Name
).
-file("src/libero/walker.gleam", 429).
?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_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_primitive_type(Name),
Param_refs,
fun() ->
Module_path = resolve_type_module(
Name,
Module,
Resolver,
Current_module
),
result_guard(
Module_path,
Param_refs,
fun(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", 371).
?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_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_type} = Ref,
(not gleam@set:contains(Visited, Ref) andalso not is_skipped_module(
Ref_module
))
andalso not is_primitive_type(Ref_type)
end
).
-file("src/libero/walker.gleam", 184).
-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", 203).
-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", 222).
-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", 255).
-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)),
{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)
),
Fields = gleam@list:map(
erlang:element(3, Variant),
fun(Field) ->
field_type_of(
variant_field_type(Field),
Resolver,
Aliases,
Module_path
)
end
),
Disc_item = {discovered_variant,
Module_path,
erlang:element(2, Variant),
to_snake_case(erlang:element(2, Variant)),
Float_indices,
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)}
)
end.
-file("src/libero/walker.gleam", 159).
-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", 119).
?DOC(
" Walk the type graph rooted at MsgFromClient/MsgFromServer message types.\n"
" Seeds the BFS walker from all variants of MsgFromClient and MsgFromServer custom\n"
" types in each message module, then walks their field types transitively.\n"
"\n"
" Both the MsgFromClient/MsgFromServer types themselves (and their constructors) and\n"
" all transitively reachable types are included in the discovered list,\n"
" since they all need codec registration.\n"
).
-spec walk_message_registry_types(
list(libero@scanner:message_module()),
gleam@dict:dict(binary(), binary())
) -> {ok, list(discovered_type())} | {error, list(libero@gen_error:gen_error())}.
walk_message_registry_types(Message_modules, Module_files) ->
Seed = begin
_pipe = gleam@list:fold(
Message_modules,
gleam@set:new(),
fun(Acc, Message_module) ->
gleam@bool:guard(
is_skipped_module(erlang:element(2, Message_module)),
Acc,
fun() ->
With_msg_from_client = case erlang:element(
4,
Message_module
) of
true ->
gleam@set:insert(
Acc,
{erlang:element(2, Message_module),
<<"MsgFromClient"/utf8>>}
);
false ->
Acc
end,
case erlang:element(5, Message_module) of
true ->
gleam@set:insert(
With_msg_from_client,
{erlang:element(2, Message_module),
<<"MsgFromServer"/utf8>>}
);
false ->
With_msg_from_client
end
end
)
end
),
gleam@set:to_list(_pipe)
end,
do_walk(
{walker_state, Seed, gleam@set:new(), [], Module_files, maps:new(), []}
).
-file("src/libero/walker.gleam", 545).
?DOC(
" Resolve a named type: if it's a local type alias, recurse on the aliased\n"
" type; otherwise produce a UserType.\n"
).
-spec resolve_field_type(
binary(),
gleam@option:option(binary()),
list(glance:type()),
type_resolver(),
gleam@dict:dict(binary(), glance:type()),
binary()
) -> field_type().
resolve_field_type(Name, Module, Parameters, Resolver, Aliases, Current_module) ->
case {Module, gleam_stdlib:map_get(Aliases, Name)} of
{none, {ok, Aliased_type}} ->
field_type_of(Aliased_type, Resolver, Aliases, Current_module);
{_, _} ->
Args = gleam@list:map(
Parameters,
fun(P) ->
field_type_of(P, Resolver, Aliases, Current_module)
end
),
Resolved_module = resolve_type_module(
Name,
Module,
Resolver,
Current_module
),
Mp = gleam@result:unwrap(Resolved_module, Current_module),
Original_name = gleam@result:unwrap(
gleam_stdlib:map_get(erlang:element(4, Resolver), Name),
Name
),
{user_type, Mp, Original_name, Args}
end.
-file("src/libero/walker.gleam", 472).
?DOC(
" Convert a glance.Type into a FieldType, resolving named types via the resolver.\n"
" Type aliases in `aliases` are resolved transparently to their underlying type.\n"
).
-spec field_type_of(
glance:type(),
type_resolver(),
gleam@dict:dict(binary(), glance:type()),
binary()
) -> field_type().
field_type_of(T, Resolver, Aliases, Current_module) ->
case T of
{variable_type, _, Name} ->
{type_var, Name};
{tuple_type, _, Elements} ->
{tuple_of,
gleam@list:map(
Elements,
fun(E) ->
field_type_of(E, Resolver, Aliases, Current_module)
end
)};
{function_type, _, _, _} ->
{type_var, <<"_fn"/utf8>>};
{hole_type, _, _} ->
{type_var, <<"_"/utf8>>};
{named_type, _, Name@1, Module, Parameters} ->
case {Module, Name@1, Parameters} of
{none, <<"Int"/utf8>>, []} ->
int_field;
{{some, <<"gleam"/utf8>>}, <<"Int"/utf8>>, []} ->
int_field;
{none, <<"Float"/utf8>>, []} ->
float_field;
{{some, <<"gleam"/utf8>>}, <<"Float"/utf8>>, []} ->
float_field;
{none, <<"String"/utf8>>, []} ->
string_field;
{{some, <<"gleam"/utf8>>}, <<"String"/utf8>>, []} ->
string_field;
{none, <<"Bool"/utf8>>, []} ->
bool_field;
{{some, <<"gleam"/utf8>>}, <<"Bool"/utf8>>, []} ->
bool_field;
{none, <<"BitArray"/utf8>>, []} ->
bit_array_field;
{{some, <<"gleam"/utf8>>}, <<"BitArray"/utf8>>, []} ->
bit_array_field;
{none, <<"Nil"/utf8>>, []} ->
nil_field;
{{some, <<"gleam"/utf8>>}, <<"Nil"/utf8>>, []} ->
nil_field;
{none, <<"List"/utf8>>, [Elem]} ->
{list_of,
field_type_of(Elem, Resolver, Aliases, Current_module)};
{{some, <<"gleam"/utf8>>}, <<"List"/utf8>>, [Elem]} ->
{list_of,
field_type_of(Elem, Resolver, Aliases, Current_module)};
{none, <<"Option"/utf8>>, [Inner]} ->
{option_of,
field_type_of(Inner, Resolver, Aliases, Current_module)};
{{some, <<"option"/utf8>>}, <<"Option"/utf8>>, [Inner]} ->
{option_of,
field_type_of(Inner, Resolver, Aliases, Current_module)};
{none, <<"Result"/utf8>>, [Ok, Err]} ->
{result_of,
field_type_of(Ok, Resolver, Aliases, Current_module),
field_type_of(Err, Resolver, Aliases, Current_module)};
{{some, <<"result"/utf8>>}, <<"Result"/utf8>>, [Ok, Err]} ->
{result_of,
field_type_of(Ok, Resolver, Aliases, Current_module),
field_type_of(Err, Resolver, Aliases, Current_module)};
{none, <<"Dict"/utf8>>, [Key, Value]} ->
{dict_of,
field_type_of(Key, Resolver, Aliases, Current_module),
field_type_of(Value, Resolver, Aliases, Current_module)};
{{some, <<"dict"/utf8>>}, <<"Dict"/utf8>>, [Key, Value]} ->
{dict_of,
field_type_of(Key, Resolver, Aliases, Current_module),
field_type_of(Value, Resolver, Aliases, Current_module)};
{_, _, _} ->
resolve_field_type(
Name@1,
Module,
Parameters,
Resolver,
Aliases,
Current_module
)
end
end.