Skip to content

Commit 8144f60

Browse files
committed
Better support for arithmetic operations
When checking the type of an arithmetic operation, we know sometimes fall back to infering the type since otherwise the check is too conservative.
1 parent f017ddc commit 8144f60

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

src/typechecker.erl

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3232,18 +3232,57 @@ type_check_arith_op_in(Env, Kind, ResTy, Op, P, Arg1, Arg2) ->
32323232
try
32333233
arith_op_do_check(ArgTy21, ArgTy22, Arg1, Arg2, Env)
32343234
catch
3235-
throw:_ -> throw(E)
3235+
throw:_ ->
3236+
arith_op_infer_fallback(Env, Op, P, Arg1, Arg2, ResTy1, E)
32363237
end
32373238
end;
32383239
{ArgTy1, ArgTy2} ->
3239-
VarBinds1 = type_check_expr_in(Env, ArgTy1, Arg1),
3240-
VarBinds2 = type_check_expr_in(Env, ArgTy2, Arg2),
3241-
union_var_binds(VarBinds1, VarBinds2, Env);
3240+
try
3241+
VarBinds1 = type_check_expr_in(Env, ArgTy1, Arg1),
3242+
VarBinds2 = type_check_expr_in(Env, ArgTy2, Arg2),
3243+
union_var_binds(VarBinds1, VarBinds2, Env)
3244+
catch
3245+
throw:E when element(1, E) == type_error ->
3246+
arith_op_infer_fallback(Env, Op, P, Arg1, Arg2, ResTy1, E)
3247+
end;
32423248
false ->
3243-
throw(type_error(op_type_too_precise, Op, P, ResTy1))
3249+
arith_op_infer_fallback(Env, Op, P, Arg1, Arg2, ResTy1,
3250+
type_error(op_type_too_precise, Op, P, ResTy1))
32443251
end
32453252
end.
32463253

3254+
%% Fall back to inference mode for arithmetic operations. Infers the result
3255+
%% type and checks if it's compatible with the expected type. This handles
3256+
%% cases where arith_op_arg_types is too conservative (e.g. 0 - 0 :: non_neg_integer()).
3257+
-spec arith_op_infer_fallback(env(), atom(), _, expr(), expr(), type(), _) -> env().
3258+
arith_op_infer_fallback(Env, Op, P, Arg1, Arg2, ResTy1, OrigError) ->
3259+
try
3260+
{InferredTy, VB} = type_check_arith_op(Env, Op, P, Arg1, Arg2),
3261+
%% For constant expressions, try to evaluate to get a precise result type
3262+
InferredTy2 = try_const_eval_arith(Op, Arg1, Arg2, InferredTy),
3263+
case subtype(InferredTy2, ResTy1, Env) of
3264+
true -> VB;
3265+
false -> throw(OrigError)
3266+
end
3267+
catch
3268+
throw:E when element(1, E) == type_error ->
3269+
throw(OrigError)
3270+
end.
3271+
3272+
%% Try to evaluate a constant arithmetic expression for a more precise type.
3273+
%% If both arguments are integer literals, compute the result and return a
3274+
%% singleton integer type. Otherwise, return the inferred type as-is.
3275+
-spec try_const_eval_arith(atom(), expr(), expr(), type()) -> type().
3276+
try_const_eval_arith(Op, {integer, _, V1}, {integer, _, V2}, _InferredTy) ->
3277+
try
3278+
Result = erlang:Op(V1, V2),
3279+
{integer, erl_anno:new(0), Result}
3280+
catch
3281+
_:_ -> _InferredTy
3282+
end;
3283+
try_const_eval_arith(_Op, _Arg1, _Arg2, InferredTy) ->
3284+
InferredTy.
3285+
32473286
-spec arith_op_do_check(type(), type(), expr(), expr(), env()) -> env().
32483287
arith_op_do_check(ArgTy1, ArgTy2, Arg1, Arg2, Env) ->
32493288
VarBinds1 = type_check_expr_in(Env, ArgTy1, Arg1),
File renamed without changes.

0 commit comments

Comments
 (0)