-
Notifications
You must be signed in to change notification settings - Fork 35
Unify type_check_expr and type_check_expr_in #574
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
96df82f
a23106e
2e0dd23
ceb2655
02df3f0
ba3a0b9
ca9094d
96c2b9b
8ca1591
e9fd335
e4145b6
ee3f7ec
ed50fec
32b6a2c
6102c5b
89c3f4e
093af82
96f838b
b283f1a
37fa124
f097860
f1b6e9c
86a6c31
68a4b8f
fa10bee
390cdea
d43dcbc
64c834c
6210517
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,8 +28,9 @@ | |
minimal_substitution/2, | ||
type_vars_variances/1]). | ||
|
||
-compile([warn_missing_spec, warn_missing_spec_all, | ||
warnings_as_errors]). | ||
-compile([warn_missing_spec, warn_missing_spec_all]). | ||
%, | ||
%warnings_as_errors]). | ||
|
||
-include("typelib.hrl"). | ||
|
||
|
@@ -1861,12 +1862,15 @@ do_type_check_expr(Env, {match, _, Pat, Expr}) -> | |
_Other -> UBoundNorm end, | ||
{UBound, Env2}; | ||
do_type_check_expr(Env, {'if', _, Clauses}) -> | ||
infer_clauses(Env, Clauses); | ||
{Ty, Env1} = infer_clauses(Env, Clauses), | ||
_Env2 = check_clauses(Env, [], Ty, Clauses, capture_vars), | ||
{Ty, Env1}; | ||
do_type_check_expr(Env, {'case', _, Expr, Clauses}) -> | ||
{_ExprTy, Env1} = type_check_expr(Env, Expr), | ||
{ExprTy, Env1} = type_check_expr(Env, Expr), | ||
Env2 = add_var_binds(Env, Env1, Env), | ||
{Ty, VB} = infer_clauses(Env2, Clauses), | ||
{Ty, union_var_binds(Env1, VB, Env)}; | ||
{Ty, Env3} = infer_clauses(Env2, Clauses), | ||
_Env4 = check_clauses(Env2, [ExprTy], Ty, Clauses, capture_vars), | ||
{Ty, union_var_binds(Env1, Env3, Env)}; | ||
do_type_check_expr(Env, {tuple, _, TS}) -> | ||
{Tys, VarBindsList} = lists:unzip([ type_check_expr(Env, Expr) || Expr <- TS ]), | ||
InferredTy = type(tuple, Tys), | ||
|
@@ -2634,11 +2638,28 @@ type_check_comprehension(Env, Compr, Expr, [Guard | Quals]) -> | |
ResTy :: type(), | ||
Expr :: expr(). | ||
type_check_expr_in(Env, ResTy, Expr) -> | ||
{InferredTy, NewEnv} = type_check_expr(Env, Expr), | ||
?verbose(Env, "~sChecking that ~ts :: ~ts~n", | ||
[gradualizer_fmt:format_location(Expr, brief), erl_prettypr:format(Expr), typelib:pp_type(ResTy)]), | ||
NormResTy = normalize(ResTy, Env), | ||
R = ?throw_orig_type(do_type_check_expr_in(Env, NormResTy, Expr), ResTy, NormResTy), | ||
?assert_type(R, env()). | ||
[gradualizer_fmt:format_location(Expr, brief), | ||
erl_prettypr:format(Expr), | ||
typelib:pp_type(ResTy)]), | ||
case subtype(InferredTy, ResTy, NewEnv) of | ||
true -> | ||
NewEnv; | ||
false -> | ||
throw(type_error(Expr, InferredTy, ResTy)) | ||
end. | ||
Comment on lines
+2641
to
+2651
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The state of the art in writing typecheckers is to do it the other way around, compared to what you've done here. Always pass in the type that's expected, though if we don't know the type just pass a variable. You'll likely get better propagation of types that way. See e.g. slide 43 in this presentation by Simon Peyton Jones: https://haskell.foundation/assets/other/Simon%20Peyton%20Jones%20-%202023-06-08%20-%20Type%20inference%20in%20GHC%20Jun%2023.pdf |
||
|
||
%-spec type_check_expr_in(Env, ResTy, Expr) -> Env when | ||
% Env :: env(), | ||
% ResTy :: type(), | ||
% Expr :: expr(). | ||
%type_check_expr_in(Env, ResTy, Expr) -> | ||
% ?verbose(Env, "~sChecking that ~ts :: ~ts~n", | ||
% [gradualizer_fmt:format_location(Expr, brief), erl_prettypr:format(Expr), typelib:pp_type(ResTy)]), | ||
% NormResTy = normalize(ResTy, Env), | ||
% R = ?throw_orig_type(do_type_check_expr_in(Env, NormResTy, Expr), ResTy, NormResTy), | ||
% ?assert_type(R, env()). | ||
|
||
-spec do_type_check_expr_in(Env, ResTy, Expr) -> Env when | ||
Env :: env(), | ||
|
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-module(rel_op_should_fail). | ||
-export([fail/1]). | ||
|
||
-spec fail(term()) -> tuple(). | ||
fail(X) -> X > X. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
-module(return_fun_should_fail). | ||
|
||
-export([return_fun_no_spec/0]). | ||
|
||
-spec return_fun_no_spec() -> integer(). | ||
return_fun_no_spec() -> fun no_spec/0. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be solved by this commit xxdavid@13d6893 |
||
|
||
no_spec() -> ok. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
-module(rigid_type_variables_should_fail). | ||
|
||
-export([bad_curry/1]). | ||
|
||
-spec bad_curry(fun(({A, B}) -> C)) -> fun((A, B) -> C). | ||
bad_curry(F) -> fun (X, _Y) -> F({X, X}) end. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
-module(operator_subtypes_should_pass). | ||
|
||
-export([arith_op3/1, | ||
arith_op4/1, | ||
float_op2/2, | ||
pos_op1/2, | ||
nonneg_op1/2, | ||
neg_op1/2, | ||
word_op1/1, | ||
word_op2/2, | ||
word_op3/3, | ||
list_op4/2, | ||
list_op6/2, | ||
list_op7/2, | ||
list_op9/0, | ||
unary_op3/1]). | ||
|
||
%% Arithmetic operations | ||
|
||
-spec arith_op3(non_neg_integer()) -> pos_integer(). | ||
arith_op3(N) -> | ||
id(N + 1). | ||
|
||
-spec arith_op4(non_neg_integer()) -> pos_integer(). | ||
arith_op4(N) -> | ||
id(1 + N). | ||
|
||
-spec id(pos_integer()) -> pos_integer(). | ||
id(P) -> P. | ||
|
||
-spec float_op2(integer(), float()) -> float(). | ||
float_op2(X, Y) -> X / Y. | ||
|
||
-spec pos_op1(pos_integer(), 1..10) -> pos_integer(). | ||
pos_op1(X, Y) -> X * Y + 777 bor Y. | ||
|
||
-spec nonneg_op1(0..100, non_neg_integer()) -> non_neg_integer() | error. | ||
nonneg_op1(X, Y) -> X + Y * X div Y rem X band Y bor X bxor Y bsl X bsr Y. | ||
|
||
-spec neg_op1(neg_integer(), -10..-5 | -2..-1) -> neg_integer() | 0..10. | ||
neg_op1(X, Y) -> X + Y + (-10). | ||
|
||
-type word16() :: 0..65535. | ||
|
||
-spec word_op1(non_neg_integer()) -> word16(). | ||
word_op1(N) -> N rem 65536. | ||
|
||
-spec word_op2(word16(), word16()) -> word16(). | ||
word_op2(X, Y) -> X band Y bor 32768 bxor Y. | ||
|
||
-spec word_op3(word16(), non_neg_integer(), pos_integer()) -> word16(). | ||
word_op3(X, Y, Z) -> (X bsr Y) div Z. | ||
|
||
%% List operations | ||
|
||
-spec list_op4([], [tuple()]) -> []. | ||
list_op4(Xs, Ys) -> Xs -- Ys. | ||
|
||
-spec list_op6([integer()], maybe_improper_list(integer(), tl)) -> maybe_improper_list(integer(), tl | 5). | ||
list_op6(Xs, Ys) -> Xs ++ Ys. | ||
|
||
-spec list_op7([integer(), ...], nonempty_improper_list(integer(), tl)) -> nonempty_improper_list(integer(), tl). | ||
list_op7(Xs, Ys) -> Xs ++ Ys. | ||
|
||
-spec list_op9() -> iolist(). | ||
list_op9() -> | ||
[$a | list_to_binary("b")]. | ||
|
||
%% Unary operators | ||
|
||
-spec unary_op3(0..10) -> 1..11. | ||
unary_op3(X) -> - (bnot X). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
-module(qlc_test). | ||
-module(qlc_test_should_pass). | ||
|
||
-export([test/0]). | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
-module(record_refinement_should_pass). | ||
|
||
-export([refined_field/1, | ||
refined_field_safe/1, | ||
%refined_field_unsafe/1, | ||
not_all_fields_refined/0]). | ||
|
||
-record(refined_field, {f :: integer() | undefined}). | ||
|
||
-spec refined_field(#refined_field{}) -> #refined_field{f :: integer()}. | ||
refined_field(#refined_field{f = undefined}) -> #refined_field{f = 0}; | ||
refined_field(R) -> R. | ||
|
||
-spec refined_field_safe(#refined_field{f :: integer()}) -> #refined_field{f :: integer()}. | ||
refined_field_safe(#refined_field{f = I}) -> #refined_field{f = I + 1}. | ||
|
||
%% This doesn't fail, but calls refined_field_safe/1 which does, | ||
%% so we can't have it in tests/should_pass. | ||
-spec refined_field_unsafe(#refined_field{}) -> #refined_field{}. | ||
refined_field_unsafe(R = #refined_field{f = undefined}) -> R; | ||
refined_field_unsafe(R) -> refined_field_safe(R). | ||
|
||
-record(not_all_fields_refined_r, {a, b}). | ||
-type not_all_fields_refined_t() :: #not_all_fields_refined_r{b :: b | c}. | ||
|
||
-spec not_all_fields_refined() -> not_all_fields_refined_t(). | ||
not_all_fields_refined() -> #not_all_fields_refined_r{b = c}. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
-module(refine_bound_var_should_pass). | ||
|
||
%% Note: Here we're refining an already bound variable | ||
|
||
-export([refined_var_not_matching_itself/1]). | ||
|
||
-spec refined_var_not_matching_itself(x | y | z) -> ok. | ||
refined_var_not_matching_itself(Var) -> | ||
case Var of | ||
x -> ok; | ||
Var -> ok | ||
end. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
-module(return_fun_should_pass). | ||
|
||
-export([return_fun_intersection/0, | ||
return_fun_union_intersection/0]). | ||
|
||
-spec return_fun_intersection() -> fun((any()) -> integer()). | ||
return_fun_intersection() -> fun number/1. | ||
|
||
-spec return_fun_union_intersection() | ||
-> fun((atom()) -> atom()) | | ||
fun((1..3) -> integer()). | ||
return_fun_union_intersection() -> fun number/1. | ||
|
||
-spec number(integer()) -> integer(); | ||
(float()) -> float(). | ||
number(N) -> N. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hehe, I've always been annoyed by build tools that don't show they are doing, i.e. don't print the commands that run (like CMake or Rebar, they hide everything by default). In make you at least see the commands by default. When the commands are printed, you can understand what happens and you can easily repeat the same commands manually.
I'm not going to argue, but I am going to unassign myself as a reviewer of this PR. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I agree, I don't like opaque complexity either. With this number of repetitions, though, I could tell by heart what it's doing ;) I don't really need to see again and again the parts that don't change, I'm more interested in the output which does change. Anyway, this bit is a marginal detail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the files don't change, they targets shouldn't need to be rebuilt. Some dependency declaration missing here seems to be the cause of repetition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that the Makefile can probably be tweaked so that tests are built only when they actually change. I would consider that a better option than just silencing the commands.