Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions erts/doc/guides/absform.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ represented in the same way as the corresponding expression.
- If T is an atom, a character, or an integer literal L, then Rep(T) = Rep(L).
- If T is a bitstring type `<<_:M,_:_*N>>`, where `M` and `N` are singleton
integer types, then Rep(T) = `{type,ANNO,binary,[Rep(M),Rep(N)]}`.
- If T is a singleton binary type `<<"bytes">>`, then Rep(T) =
`{bin_type,ANNO,Bin}`, where `Bin` is the binary value.
- If T is the empty list type `[]`, then Rep(T) = `{type,ANNO,nil,[]}`, that is,
the empty list type `[]` cannot be distinguished from the predefined type
`t:nil/0`.
Expand Down
75 changes: 74 additions & 1 deletion lib/dialyzer/src/erl_types.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
t_atoms/1,
t_atom_vals/1,
t_binary/0,
t_binary_val/1,
t_bitstr/0,
t_bitstr/2,
t_bitstr_base/1,
Expand Down Expand Up @@ -290,7 +291,8 @@
-define(unknown_qual, unknown).

-type qual() :: ?float_qual | ?integer_qual | ?nonempty_qual | ?pid_qual
| ?port_qual | ?reference_qual | ?unknown_qual | {_, _}.
| ?port_qual | ?reference_qual | ?unknown_qual | {_, _}
| [binary()].

%%-----------------------------------------------------------------------------
%% The type representation
Expand Down Expand Up @@ -330,6 +332,9 @@

-define(atom(Set), #c{tag=?atom_tag, elements=Set}).
-define(bitstr(Unit, Base), #c{tag=?binary_tag, elements={Unit,Base}}).
-define(bitstr_vals(Unit, Base, Vals),
#c{tag=?binary_tag, elements={Unit,Base},
qualifier=Vals}).
-define(float, ?number(?any, ?float_qual)).
-define(function(Domain, Range), #c{tag=?function_tag,
elements={Domain,Range}}).
Expand Down Expand Up @@ -664,6 +669,11 @@ t_bitstr(U, B) ->
end,
?bitstr(U, NewB).

-spec t_binary_val(binary()) -> erl_type().

t_binary_val(Bin) ->
?bitstr_vals(0, bit_size(Bin), set_singleton(Bin)).

-spec t_bitstr_unit(erl_type()) -> non_neg_integer().

t_bitstr_unit(?bitstr(U, _)) -> U.
Expand Down Expand Up @@ -1971,6 +1981,7 @@ t_collect_vars_list([], Acc) -> Acc.
t_from_term([H|T]) -> t_cons(t_from_term(H), t_from_term(T));
t_from_term([]) -> t_nil();
t_from_term(T) when is_atom(T) -> t_atom(T);
t_from_term(T) when erlang:is_binary(T) -> t_binary_val(T);
t_from_term(T) when is_bitstring(T) -> t_bitstr(0, erlang:bit_size(T));
t_from_term(T) when is_float(T) -> t_float();
t_from_term(T) when is_function(T) ->
Expand Down Expand Up @@ -2150,6 +2161,14 @@ t_sup_aux(?var(_), _) -> ?any;
t_sup_aux(_, ?var(_)) -> ?any;
t_sup_aux(?atom(Set1), ?atom(Set2)) ->
?atom(set_union(Set1, Set2));
t_sup_aux(?bitstr_vals(U1, B1, V1), ?bitstr_vals(U2, B2, V2))
when is_list(V1), is_list(V2) ->
SupU = gcd(gcd(U1, U2), abs(B1 - B2)),
SupB = lists:min([B1, B2]),
case set_union(V1, V2) of
?any -> t_bitstr(SupU, SupB);
NewVals -> ?bitstr_vals(SupU, SupB, NewVals)
end;
t_sup_aux(?bitstr(U1, B1), ?bitstr(U2, B2)) ->
t_bitstr(gcd(gcd(U1, U2), abs(B1-B2)), lists:min([B1, B2]));
t_sup_aux(?function(Domain1, Range1), ?function(Domain2, Range2)) ->
Expand Down Expand Up @@ -2603,6 +2622,8 @@ t_elements(?nil = T) -> [T];
t_elements(?atom(?any) = T) -> [T];
t_elements(?atom(Atoms)) ->
[t_atom(A) || A <- Atoms];
t_elements(#c{tag=?binary_tag, qualifier=Vals}) when is_list(Vals) ->
[t_binary_val(V) || V <- Vals];
t_elements(?bitstr(_, _) = T) -> [T];
t_elements(?function(_, _) = T) -> [T];
t_elements(?identifier(?any) = T) -> [T];
Expand Down Expand Up @@ -2686,6 +2707,22 @@ t_inf_aux(?atom(Set1), ?atom(Set2)) ->
?none -> ?none;
NewSet -> ?atom(NewSet)
end;
t_inf_aux(?bitstr_vals(_U1, _B1, V1), ?bitstr_vals(_U2, _B2, V2))
when is_list(V1), is_list(V2) ->
case ordsets:intersection(V1, V2) of
[] -> ?none;
NewVals -> make_bitstr_vals(NewVals)
end;
t_inf_aux(?bitstr_vals(_U1, _B1, Vals), ?bitstr(U2, B2))
when is_list(Vals) ->
Filtered = [V || V <- Vals, bitstr_val_matches(V, U2, B2)],
case Filtered of
[] -> ?none;
_ -> make_bitstr_vals(Filtered)
end;
t_inf_aux(?bitstr(U1, B1), ?bitstr_vals(U2, B2, Vals))
when is_list(Vals) ->
t_inf_aux(?bitstr_vals(U2, B2, Vals), ?bitstr(U1, B1));
t_inf_aux(?bitstr(U1, B1), ?bitstr(0, B2)) ->
if B2 >= B1 andalso (B2-B1) rem U1 =:= 0 -> t_bitstr(0, B2);
true -> ?none
Expand Down Expand Up @@ -3149,6 +3186,19 @@ findfirst(N1, N2, U1, B1, U2, B2) ->
findfirst(N1_1, N2, U1, B1, U2, B2)
end.

bitstr_val_matches(Bin, Unit, Base) ->
Sz = bit_size(Bin),
case Unit of
0 -> Sz =:= Base;
_ -> Sz >= Base andalso (Sz - Base) rem Unit =:= 0
end.

make_bitstr_vals(Vals) ->
Sizes = [bit_size(V) || V <- Vals],
Base = lists:min(Sizes),
Unit = lists:foldl(fun(S, U) -> gcd(abs(S - Base), U) end, 0, Sizes),
?bitstr_vals(Unit, Base, Vals).

%%-----------------------------------------------------------------------------
%% Substitution of variables
%%
Expand Down Expand Up @@ -3403,6 +3453,12 @@ t_subtract_aux(?atom(Set1), ?atom(Set2)) ->
?none -> ?none;
Set -> ?atom(Set)
end;
t_subtract_aux(?bitstr_vals(U1, B1, V1), ?bitstr_vals(_U2, _B2, V2))
when is_list(V1), is_list(V2) ->
case ordsets:subtract(V1, V2) of
[] -> ?none;
NewVals -> ?bitstr_vals(U1, B1, NewVals)
end;
t_subtract_aux(?bitstr(U1, B1), ?bitstr(U2, B2)) ->
subtract_bin(t_bitstr(U1, B1), t_inf(t_bitstr(U1, B1), t_bitstr(U2, B2)));
t_subtract_aux(?function(_, _) = T1, ?function(_, _) = T2) ->
Expand Down Expand Up @@ -3973,6 +4029,9 @@ t_to_string(?atom(Set), _RecDict) ->
_ ->
set_to_string(Set)
end;
t_to_string(#c{tag=?binary_tag, qualifier=Vals}, _RecDict)
when is_list(Vals) ->
flat_join([bin_val_to_string(V) || V <- Vals], " | ");
t_to_string(?bitstr(0, 0), _RecDict) ->
"<<>>";
t_to_string(?bitstr(8, 0), _RecDict) ->
Expand Down Expand Up @@ -4391,6 +4450,8 @@ from_form({paren_type, _Anno, [Type]}, S, D, L, C) ->
from_form({remote_type, Anno, [{atom, _, Module}, {atom, _, Type}, Args]},
S, D, L, C) ->
remote_from_form(Anno, Module, Type, Args, S, D, L, C);
from_form({bin_type, _Anno, Bin}, _S, _D, L, C) ->
{t_binary_val(Bin), L, C};
from_form({atom, _Anno, Atom}, _S, _D, L, C) ->
{t_atom(Atom), L, C};
from_form({integer, _Anno, Int}, _S, _D, L, C) ->
Expand Down Expand Up @@ -4875,6 +4936,8 @@ separate_key(?atom(Atoms)) when Atoms =/= ?any ->
[t_atom(A) || A <- Atoms];
separate_key(?number(_, _) = T) ->
t_elements(T);
separate_key(#c{tag=?binary_tag, qualifier=Vals}) when is_list(Vals) ->
[t_binary_val(V) || V <- Vals];
separate_key(?union(List)) ->
lists:append([separate_key(K) || K <- List, not t_is_none(K)]);
separate_key(Key) ->
Expand Down Expand Up @@ -4972,6 +5035,7 @@ check_record_fields({remote_type, _Anno, [{atom, _, _}, {atom, _, _}, Args]},
check_record_fields({atom, _Anno, _}, _S, C) -> C;
check_record_fields({integer, _Anno, _}, _S, C) -> C;
check_record_fields({char, _Anno, _}, _S, C) -> C;
check_record_fields({bin_type, _Anno, _}, _S, C) -> C;
check_record_fields({op, _Anno, _Op, _Arg}, _S, C) -> C;
check_record_fields({op, _Anno, _Op, _Arg1, _Arg2}, _S, C) -> C;
check_record_fields({type, _Anno, tuple, any}, _S, C) -> C;
Expand Down Expand Up @@ -5115,6 +5179,8 @@ t_form_to_string({var, _Anno, Name}) -> atom_to_list(Name);
t_form_to_string({atom, _Anno, Atom}) ->
io_lib:write_string(atom_to_list(Atom), $'); % To quote or not to quote... '
t_form_to_string({integer, _Anno, Int}) -> integer_to_list(Int);
t_form_to_string({bin_type, _Anno, Bin}) ->
"<<" ++ io_lib:write_string(binary_to_list(Bin), $") ++ ">>";
t_form_to_string({char, _Anno, Char}) -> integer_to_list(Char);
t_form_to_string({op, _Anno, _Op, _Arg} = Op) ->
case erl_eval:partial_eval(Op) of
Expand Down Expand Up @@ -5397,6 +5463,8 @@ is_singleton_type(?int_range(V, V)) ->
true; % cannot happen
is_singleton_type(?int_set([_])) ->
true;
is_singleton_type(#c{tag=?binary_tag, qualifier=[_]}) ->
true;
is_singleton_type(_) ->
false.

Expand Down Expand Up @@ -5470,6 +5538,9 @@ set_max(Set) ->
flat_format(F, S) ->
lists:flatten(io_lib:format(F, S)).

bin_val_to_string(Bin) ->
flat_format("<<~ts>>", [io_lib:write_string(binary_to_list(Bin), $")]).

flat_join(List, Sep) ->
lists:flatten(lists:join(Sep, List)).

Expand Down Expand Up @@ -5614,6 +5685,8 @@ get_modules_mentioned({integer, _L, _Int}, _D, L, Acc) ->
{L, Acc};
get_modules_mentioned({char, _L, _Char}, _D, L, Acc) ->
{L, Acc};
get_modules_mentioned({bin_type, _L, _Bin}, _D, L, Acc) ->
{L, Acc};
get_modules_mentioned({op, _L, _Op, _Arg}, _D, L, Acc) ->
{L, Acc};
get_modules_mentioned({op, _L, _Op, _Arg1, _Arg2}, _D, L, Acc) ->
Expand Down
90 changes: 90 additions & 0 deletions lib/dialyzer/test/erl_types_SUITE.erl
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
%% -*- erlang-indent-level: 4 -*-
%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2025-2026. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
Expand All @@ -12,10 +18,13 @@
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(erl_types_SUITE).

-export([all/0,groups/0,init_per_group/2,end_per_group/2,
consistency_and_to_string/1,misc/1,map_multiple_representations/1,
bin_type_modules_mentioned/1,bin_type_operations/1,
absorption/1,associativity/1,commutativity/1,idempotence/1,
identity/1,limit/1
]).
Expand All @@ -29,6 +38,8 @@ all() ->
[consistency_and_to_string,
misc,
map_multiple_representations,
bin_type_modules_mentioned,
bin_type_operations,
{group,property_tests}
].

Expand Down Expand Up @@ -357,6 +368,85 @@ map_multiple_representations(_Config) ->
end(),
ok.

bin_type_modules_mentioned(_Config) ->
%% A standalone bin_type form has no module dependencies.
[] = ?M:type_form_to_remote_modules({bin_type, 0, <<"foo">>}),

%% A union of bin_type forms has no module dependencies.
[] = ?M:type_form_to_remote_modules(
{type, 0, union, [{bin_type, 0, <<"a">>},
{bin_type, 0, <<"b">>}]}),

%% A union of bin_type and remote_type extracts the remote module.
[some_mod] = ?M:type_form_to_remote_modules(
{type, 0, union,
[{bin_type, 0, <<"foo">>},
{remote_type, 0, [{atom, 0, some_mod},
{atom, 0, some_type},
[]]}]}),

%% bin_type inside a list type has no module dependencies.
[] = ?M:type_form_to_remote_modules(
{type, 0, list, [{bin_type, 0, <<"x">>}]}),

ok.

bin_type_operations(_Config) ->
%% Construction and t_to_string.
Foo = ?M:t_binary_val(<<"foo">>),
Bar = ?M:t_binary_val(<<"bar">>),
Baz = ?M:t_binary_val(<<"baz">>),
Empty = ?M:t_binary_val(<<"">>),
"<<\"foo\">>" = ?M:t_to_string(Foo),
"<<\"bar\">>" = ?M:t_to_string(Bar),
"<<\"\">>" = ?M:t_to_string(Empty),

%% t_is_binary / t_is_bitstr.
true = ?M:t_is_binary(Foo),
true = ?M:t_is_bitstr(Foo),

%% t_from_term for binaries.
Foo = ?M:t_from_term(<<"foo">>),
Empty = ?M:t_from_term(<<"">>),

%% t_sup — same-size vals merge into a multi-val type.
FooBar = ?M:t_sup(Foo, Bar),
"<<\"bar\">> | <<\"foo\">>" = ?M:t_to_string(FooBar),
%% t_sup — different-size vals fall back to generic binary.
Hi = ?M:t_binary_val(<<"hi">>),
FooHi = ?M:t_sup(Foo, Hi),
true = ?M:t_is_binary(FooHi),

%% t_inf — val∩val.
None = ?M:t_none(),
Foo = ?M:t_inf(Foo, Foo),
None = ?M:t_inf(Foo, Bar),
%% t_inf — val∩binary().
Foo = ?M:t_inf(Foo, ?M:t_binary()),
%% t_inf — val∩incompatible.
None = ?M:t_inf(Foo, ?M:t_atom()),

%% t_subtract.
FooBarBaz = ?M:t_sup([Foo, Bar, Baz]),
FooBaz = ?M:t_subtract(FooBarBaz, Bar),
"<<\"baz\">> | <<\"foo\">>" = ?M:t_to_string(FooBaz),

%% t_elements — decompose into individual vals.
[Foo] = ?M:t_elements(Foo),
2 = length(?M:t_elements(FooBar)),

%% is_singleton_type — single vs multiple vals.
%% (is_singleton_type is not exported, test indirectly via t_elements)
[_] = ?M:t_elements(Foo),
[_, _] = ?M:t_elements(FooBar),

%% UTF-8 encoded binary literal types produce the same type as raw bytes.
%% <<"é"/utf8>> encodes to <<195,169>> — same as the raw binary.
Utf8Bin = ?M:t_binary_val(<<195,169>>),
Utf8Bin = ?M:t_binary_val(unicode:characters_to_binary([233], unicode, utf8)),

ok.

absorption(Config) ->
%% manual test: proper:quickcheck(erl_types_prop:absorption()).
true = ct_property_test:quickcheck(erl_types_prop:absorption(), Config).
Expand Down
Loading
Loading