Current section

Files

Jump to
gradualizer src gradualizer_int.erl
Raw

src/gradualizer_int.erl

%% @private
%% Integer types represented as ranges with infinities
-module(gradualizer_int).
%% Integer types
-export([is_int_type/1,
is_int_subtype/2,
int_type_glb/2,
int_type_diff/2,
negate_int_type/1,
merge_int_types/1]).
%% Ranges <--> integer types
-export([int_type_to_range/1,
int_range_to_type/1,
int_range_to_types/1]).
%% Ranges
-export([int_range_diff/2]).
%% Types
-export_type([int_range/0]).
-type int() :: integer() | neg_inf | pos_inf.
-type int_range() :: {int(), int()}.
-type type() :: gradualizer_type:abstract_type().
-type extended_int_type() :: {type, erl_anno:anno(), range, [{integer, erl_anno:anno(), int()}]}
| {'integer', erl_anno:anno(), integer()}.
%% `extended_int_type' is needed to describe type representations
%% which are not part of `gradualizer_type:abstract_type()'.
%% We get rid of it before returning from this module.
%% TODO: Actually, we don't, though we should :/ https://github.com/josefs/Gradualizer/issues/406
-include("gradualizer.hrl").
%% +----------------------------------------+
%% | Functions operating on integer types |
%% +----------------------------------------+
%% Checks if a type is an integer type. This is a pre-condition for most of
%% the functions in this module.
%%
%% A macro with the same name is defined in typelib.hrl, which can be used in
%% guards.
-spec is_int_type(type()) -> boolean().
is_int_type({type, _, T, _})
when T == pos_integer; T == non_neg_integer; T == neg_integer;
T == integer; T == range -> true;
is_int_type({integer, _, _}) -> true;
is_int_type({char, _, _}) -> true;
is_int_type(_) -> false.
%% Checks if an integer type is a subtype of another integer type. Both
%% arguments must be integer types.
-spec is_int_subtype(type(), type()) -> boolean().
is_int_subtype(Ty1, Ty2) ->
R1 = int_type_to_range(Ty1),
R2 = int_type_to_range(Ty2),
lower_bound_less_or_eq(R2, R1) andalso
upper_bound_more_or_eq(R2, R1).
%% Greatest lower bound of two integer types.
int_type_glb(Ty1, Ty2) ->
{Lo1, Hi1} = int_type_to_range(Ty1),
{Lo2, Hi2} = int_type_to_range(Ty2),
int_range_to_type({int_max(Lo1, Lo2), int_min(Hi1, Hi2)}).
%% Range difference, like set difference. The result may be zero, one or two
%% types.
%% TODO: https://github.com/josefs/Gradualizer/issues/406
-spec int_type_diff(type(), type()) -> type().
int_type_diff(Ty1, Ty2) ->
IntRanges = int_range_diff(int_type_to_range(Ty1),
int_type_to_range(Ty2)),
%% Make sure the result is a standard erlang type.
%% Perhaps we can include generalized ranges such as 10..pos_inf
ExpandedRanges = lists:map(fun int_range_expand_to_valid/1, IntRanges),
%% TODO: This fails self-gradualisation, see https://github.com/josefs/Gradualizer/issues/406
int_ranges_to_type(ExpandedRanges).
%% Merges integer types by sorting on the lower bound and then merging adjacent
%% ranges. Returns a list of mutually exclusive integer types.
%%
%% This is an adoption of the standard algorithm for merging intervals.
-spec merge_int_types([type()]) -> [type()].
merge_int_types([]) ->
[];
merge_int_types(IntTypes) ->
Ranges = lists:map(fun int_type_to_range/1, IntTypes),
int_ranges_to_types(Ranges).
%% Negates an integer type, e.g. `neg_integer() -> pos_integer()', `1..5 ->
%% -1..-5', etc.
negate_int_type(RangeTy) ->
{L, U} = int_type_to_range(RangeTy),
L2 = int_negate(U),
U2 = int_negate(L),
int_range_to_type({L2, U2}).
%% +---------------------------------------+
%% | Conversion between types and ranges |
%% +---------------------------------------+
%% Integer type to range.
-spec int_type_to_range(type()) -> int_range().
int_type_to_range({type, _, integer, []}) -> {neg_inf, pos_inf};
int_type_to_range({type, _, neg_integer, []}) -> {neg_inf, -1};
int_type_to_range({type, _, non_neg_integer, []}) -> {0, pos_inf};
int_type_to_range({type, _, pos_integer, []}) -> {1, pos_inf};
int_type_to_range({type, _, range, [{Tag1, _, I1}, {Tag2, _, I2}]})
when Tag1 =:= integer orelse Tag1 =:= char,
Tag2 =:= integer orelse Tag2 =:= char -> {I1, I2};
int_type_to_range({char, _, I}) -> {I, I};
int_type_to_range({integer, _, I}) -> {I, I}.
%% Converts a range back to a type.
-spec int_range_to_type(int_range()) -> type() | extended_int_type().
int_range_to_type(Range) ->
union(int_range_to_types(Range)).
%% +----------------+
%% | Type helpers |
%% +----------------+
%% Converts a range to a list of types. Creates two types in some cases and zero
%% types if lower bound is greater than upper bound.
-spec int_range_to_types(int_range()) -> [type() | extended_int_type()].
int_range_to_types({neg_inf, pos_inf}) ->
[typechecker:type(integer)];
int_range_to_types({neg_inf, -1}) ->
[typechecker:type(neg_integer)];
int_range_to_types({neg_inf, 0}) ->
[typechecker:type(neg_integer), {integer, erl_anno:new(0), 0}];
int_range_to_types({neg_inf, I}) when I > 0 ->
%% I > 0 - see the guard above - so we can safely assert that
I = ?assert_type(I, pos_integer()),
[typechecker:type(neg_integer),
{type, erl_anno:new(0), range, [{integer, erl_anno:new(0), 0}
,{integer, erl_anno:new(0), I}]}];
int_range_to_types({neg_inf, I}) when I < -1 ->
%% Non-standard - see extended_int_type()
%% I < -1 - see the guard - so we can safely assert that
I = ?assert_type(I, neg_integer()),
[{type, erl_anno:new(0), range, [{integer, erl_anno:new(0), neg_inf}
,{integer, erl_anno:new(0), I}]}];
int_range_to_types({I, pos_inf}) when I < -1 ->
[{type, erl_anno:new(0), range, [{integer, erl_anno:new(0), I}
,{integer, erl_anno:new(0), -1}]},
typechecker:type(non_neg_integer)];
int_range_to_types({-1, pos_inf}) ->
[{integer, erl_anno:new(0), -1}, typechecker:type(non_neg_integer)];
int_range_to_types({0, pos_inf}) ->
[typechecker:type(non_neg_integer)];
int_range_to_types({1, pos_inf}) ->
[typechecker:type(pos_integer)];
int_range_to_types({I, pos_inf}) when I > 1 ->
%% Non-standard
[{type, erl_anno:new(0), range, [{integer, erl_anno:new(0), I}
,{integer, erl_anno:new(0), pos_inf}]}];
int_range_to_types({I, I}) ->
[{integer, erl_anno:new(0), I}];
int_range_to_types({pos_inf, _}) -> [];
int_range_to_types({_, neg_inf}) -> [];
int_range_to_types({I, J}) when is_integer(I) andalso is_integer(J) ->
if
I < J ->
[{type, erl_anno:new(0), range, [{integer, erl_anno:new(0), I}
,{integer, erl_anno:new(0), J}]}];
I > J ->
[]
end.
%% Merges ranges and returns a single type (possibly a union).
-spec int_ranges_to_type([int_range()]) -> type().
int_ranges_to_type(Ranges) ->
union(int_ranges_to_types(Ranges)).
%% Merges overlapping ranges and converts them to types.
-spec int_ranges_to_types([int_range()]) -> [type()].
int_ranges_to_types(Ranges) ->
MergedRanges = merge_int_ranges(Ranges),
lists:flatmap(fun int_range_to_types/1, MergedRanges).
-spec union(list()) -> type().
union([]) -> typechecker:type(none);
union([T]) -> T;
union(Ts) -> typechecker:type(union, Ts).
%% +---------------------------------+
%% | Functions operating on ranges |
%% +---------------------------------+
%% Merges overlapping ranges and returns a sorted list of disjoint ranges.
-spec merge_int_ranges([int_range()]) -> [int_range()].
merge_int_ranges([]) ->
[];
merge_int_ranges(Ranges) ->
[T | Ts] = lists:sort(fun lower_bound_less_or_eq/2, Ranges),
merge_int_ranges_help(Ts, [T]).
merge_int_ranges_help([{R1, R2} = R | Rs], [{S1, S2} | StackTail] = Stack) ->
NewStack = if
R1 == neg_inf; S2 == pos_inf; R1 =< S2 + 1 ->
%% Overlapping or adjacent ranges. Merge them.
[{S1, int_max(R2, S2)} | StackTail];
true ->
%% Not mergeable ranges. Push R to stack.
[R | Stack]
end,
merge_int_ranges_help(Rs, NewStack);
merge_int_ranges_help([], Stack) ->
lists:reverse(Stack).
%% Compares the lower bound of two ranges. Used as callback for sorting ranges.
-spec lower_bound_less_or_eq(int_range(), int_range()) -> boolean().
lower_bound_less_or_eq({A, _}, {B, _}) ->
if
A == neg_inf -> true;
B == neg_inf -> false;
true -> A =< B
end.
%% Compares the upper bound of two ranges.
-spec upper_bound_more_or_eq(int_range(), int_range()) -> boolean().
upper_bound_more_or_eq({_, A}, {_, B}) ->
if
A == pos_inf -> true;
B == pos_inf -> false;
true -> A >= B
end.
%% Computes the difference between two integer intervals and returns the result
%% as a list of zero, one or two intervals.
-spec int_range_diff(int_range(), int_range()) -> [int_range()].
int_range_diff({Lo1, Hi1}, {Lo2, Hi2}) ->
%% R1: xxxxxxxxxxxxxxxxxxxxx
%% R2: xxxxxxxxxxx
%% diff: xxxxxx xxxx
Lo2x = int_decr(Lo2),
Hi2x = int_incr(Hi2),
BeforeR2 = [{Lo1, int_min(Hi1, Lo2x)} || int_less_than(Lo1, Lo2)],
AfterR2 = [{int_max(Lo1, Hi2x), Hi1} || int_greater_than(Hi1, Hi2)],
BeforeR2 ++ AfterR2.
%% Makes sure a range can be represented as a syntactically valid Erlang type,
%% by expanding it if necessary.
-spec int_range_expand_to_valid(int_range()) -> int_range().
int_range_expand_to_valid({neg_inf, N}) when is_integer(N),
N < -1 ->
{neg_inf, -1}; % neg_integer()
int_range_expand_to_valid({N, pos_inf}) when is_integer(N),
N > 1 ->
{1, pos_inf}; % pos_integer()
int_range_expand_to_valid(Range) ->
Range.
%% +-----------------------------------------+
%% | Functions operating on a single int() |
%% +-----------------------------------------+
int_min(A, B) when A == neg_inf; B == neg_inf -> neg_inf;
int_min(pos_inf, B) -> B;
int_min(A, pos_inf) -> A;
int_min(A, B) when is_integer(A), is_integer(B) -> min(A, B).
int_max(A, B) when A == pos_inf; B == pos_inf -> pos_inf;
int_max(neg_inf, B) -> B;
int_max(A, neg_inf) -> A;
int_max(A, B) when is_integer(A), is_integer(B) -> max(A, B).
int_less_than(A, A) -> false;
int_less_than(A, B) -> A =:= int_min(A, B).
int_greater_than(A, A) -> false;
int_greater_than(A, B) -> A =:= int_max(A, B).
int_incr(N) when is_integer(N) -> N + 1;
int_incr(Inf) -> Inf.
int_decr(N) when is_integer(N) -> N - 1;
int_decr(Inf) -> Inf.
int_negate(pos_inf) ->
neg_inf;
int_negate(neg_inf) ->
pos_inf;
int_negate(I) when is_integer(I) ->
-I.