Current section
Files
Jump to
Current section
Files
src/spectra_util.erl
-module(spectra_util).
-export([
test_abs_code/1,
fold_until_error/3,
map_until_error/2,
type_replace_vars/3,
record_replace_vars/2
]).
-ignore_xref([test_abs_code/1]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-include_lib("spectra/include/spectra.hrl").
-include("../include/spectra_internal.hrl").
-ifdef(TEST).
not_handled_modules_test() ->
Modules = erlang:loaded(),
Errors =
lists:filtermap(
fun(Module) ->
case test_abs_code(Module) of
{ok, _Types} ->
false;
{error, [#sp_error{} | _]} ->
false;
{error, {error, {beam_lib_error, _ModuleName, _Details}, _Stack}} ->
false;
{error, {error, {module_types_not_found, _ModuleName, _State}, _Stack}} ->
false;
{error, Reason} ->
{true, {Module, Reason}}
end
end,
Modules
),
?assertEqual([], Errors).
-endif.
-spec test_abs_code(module()) ->
{ok, spectra:type_info()} | {error, {atom(), term(), erlang:stacktrace()}}.
test_abs_code(Module) ->
try
{ok, spectra_abstract_code:types_in_module(Module)}
catch
Class:Reason:Stacktrace ->
{error, {Class, Reason, Stacktrace}}
end.
-spec fold_until_error(
Fun ::
fun(
(Elem :: dynamic(), Acc :: dynamic()) ->
{error, Err :: dynamic()} | {ok, Acc :: dynamic()}
),
Acc :: dynamic(),
List :: [Elem :: dynamic()]
) ->
{ok, Acc :: dynamic()} | {error, Err :: dynamic()}.
fold_until_error(Fun, Acc, [H | T]) ->
case Fun(H, Acc) of
{error, _} = Error ->
Error;
{ok, NewAcc} ->
fold_until_error(Fun, NewAcc, T)
end;
fold_until_error(Fun, Acc, []) when is_function(Fun, 2) ->
{ok, Acc}.
-spec map_until_error(
fun((Elem :: dynamic()) -> {error, Err :: dynamic()} | {ok, ResElem :: dynamic()}),
[Elem :: dynamic()]
) ->
{ok, [ResElem :: dynamic()]} | {error, Err :: dynamic()}.
map_until_error(Fun, List) when is_function(Fun, 1) ->
map_until_error(Fun, List, []).
map_until_error(Fun, [], Acc) when is_function(Fun, 1) ->
{ok, lists:reverse(Acc)};
map_until_error(Fun, [H | T], Acc) ->
case Fun(H) of
{error, _} = Error ->
Error;
{ok, Value} ->
map_until_error(Fun, T, [Value | Acc])
end.
-spec record_replace_vars(
RecordInfo :: [#sp_rec_field{}],
TypeArgs :: [spectra:record_field_arg()]
) -> [#sp_rec_field{}].
record_replace_vars(RecordInfo, TypeArgs) ->
lists:foldl(
fun({FieldName, Type}, Fields) ->
lists:map(
fun
(#sp_rec_field{name = Name} = Field) when Name =:= FieldName ->
Field#sp_rec_field{type = Type};
(Field) ->
Field
end,
Fields
)
end,
RecordInfo,
TypeArgs
).
-spec type_replace_vars(
TypeInfo :: spectra:type_info(),
Type :: spectra:sp_type(),
NamedTypes :: #{atom() => spectra:sp_type()}
) ->
spectra:sp_type().
type_replace_vars(_TypeInfo, #sp_var{name = Name}, NamedTypes) ->
case maps:find(Name, NamedTypes) of
{ok, Type} ->
Type;
error ->
erlang:error({type_variable_not_found, Name})
end;
type_replace_vars(TypeInfo, #sp_type_with_variables{type = Type}, NamedTypes) ->
case Type of
#sp_union{types = UnionTypes} ->
#sp_union{
types =
lists:map(
fun(UnionType) ->
type_replace_vars(TypeInfo, UnionType, NamedTypes)
end,
UnionTypes
)
};
#sp_map{fields = Fields} = Map ->
Map#sp_map{
fields =
lists:map(
fun
(#literal_map_field{val_type = FieldType} = Field) ->
Field#literal_map_field{
val_type = type_replace_vars(TypeInfo, FieldType, NamedTypes)
};
(#typed_map_field{key_type = KeyType, val_type = ValueType} = Field) ->
Field#typed_map_field{
key_type = type_replace_vars(TypeInfo, KeyType, NamedTypes),
val_type = type_replace_vars(TypeInfo, ValueType, NamedTypes)
}
end,
Fields
)
};
#sp_rec_ref{record_name = RecordName, field_types = RefFieldTypes} ->
{ok, #sp_rec{fields = Fields} = Rec} = spectra_type_info:find_record(
TypeInfo, RecordName
),
NewRec = Rec#sp_rec{fields = record_replace_vars(Fields, RefFieldTypes)},
type_replace_vars(TypeInfo, NewRec, NamedTypes);
_ ->
type_replace_vars(TypeInfo, Type, NamedTypes)
end;
type_replace_vars(TypeInfo, #sp_rec{fields = Fields} = Rec, NamedTypes) ->
Rec#sp_rec{
fields =
lists:map(
fun(#sp_rec_field{type = NType} = Field) ->
Field#sp_rec_field{
type = type_replace_vars(TypeInfo, NType, NamedTypes)
}
end,
Fields
)
};
type_replace_vars(TypeInfo, #sp_list{type = ItemType}, NamedTypes) ->
#sp_list{type = type_replace_vars(TypeInfo, ItemType, NamedTypes)};
type_replace_vars(TypeInfo, #sp_nonempty_list{type = ItemType}, NamedTypes) ->
#sp_nonempty_list{type = type_replace_vars(TypeInfo, ItemType, NamedTypes)};
type_replace_vars(TypeInfo, #sp_union{types = Types}, NamedTypes) ->
#sp_union{
types =
lists:map(
fun(T) -> type_replace_vars(TypeInfo, T, NamedTypes) end,
Types
)
};
type_replace_vars(
TypeInfo, #sp_remote_type{mfargs = {Module, TypeName, Args}}, NamedTypes
) ->
% Replace variables in Args with their actual types from NamedTypes
ResolvedArgs = lists:map(
fun(Arg) -> type_replace_vars(TypeInfo, Arg, NamedTypes) end,
Args
),
% Return the remote type with resolved args, preserving module context
#sp_remote_type{mfargs = {Module, TypeName, ResolvedArgs}};
type_replace_vars(_TypeInfo, Type, _NamedTypes) ->
Type.