Skip to content
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

Fix #10907: Wrong type inferred from existential types #10959

Merged
merged 2 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,9 @@ OCaml 4.14.0
operation in `NO_SHARING` mode.
(Xavier Leroy, report by Anil Madhavapeddy, review by Alain Frisch)

- #10907, #10959: Wrong type inferred from existential types
(Jacques Garrigue and Gabriel Scherer, report by @dyzsr, review by Leo White)


OCaml 4.13 maintenance branch
-----------------------------
Expand Down
18 changes: 0 additions & 18 deletions testsuite/tests/typing-gadts/pr10189.ml
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,6 @@ Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
Some A
val g : 'a M.j t option -> unit = <fun>
|}, Principal{|
module M :
sig
type 'a d
type i = < m : 'c. 'c -> 'c d >
type 'a j = < m : 'c. 'c -> 'a >
end
type _ t = A : M.i t
File "_none_", line 1:
Warning 18 [not-principal]: typing this pattern requires considering $0 and 'c M.d as equal.
But the knowledge of these types is not principal.
Copy link
Member

@gasche gasche Feb 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this is an example of a principality warning that was raised during exhaustivity analysis, on a sub-case of an "exploded" wildcard pattern. The dummy location (File "none", line 1) comes from the dummy location used by parmatch when exploding wildcard partterns.

I believe that the change in this testsuite result does not come from the lowering of levels (the actual type-checking change in the PR), but simply from the fact that we now silence this warning during exhaustivity analysis. (We silenced them because our behavior change would raise those warnings very often in this situation.) It's interesting that it already occurred in the wild.

Copy link
Contributor Author

@garrigue garrigue Feb 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it purely comes from my turning off the warning on counter-examples.
Whether turning off the warning is correct might be debatable. Namely, the fact the inference is deemed not principal suggests that the exhaustiveness assessment might not be principal. However, at least the current warning was not satisfactory, as it warns on non-existing code. Moreover, the exhaustiveness check runs after everything else, so at that point all types should be already known, even if it is not in a principal way, so the surprise factor seems much lower.

Line 9, characters 2-20:
9 | let None = y in () ;;
^^^^^^^^^^^^^^^^^^
Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
Some A
val g : 'a M.j t option -> unit = <fun>
|}]

(* more examples by @lpw25 *)
Expand Down
53 changes: 53 additions & 0 deletions testsuite/tests/typing-gadts/pr10907.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
(* TEST
* expect
*)

(* from @dyzsr *)
type 'a t = T : ('a -> 'b) * ('b -> 'a) -> 'a t;;
[%%expect{|
type 'a t = T : ('a -> 'b) * ('b -> 'a) -> 'a t
|}]

let t = T ((fun x -> x), (fun x -> x));;
[%%expect{|
val t : 'a t = T (<fun>, <fun>)
|}]

let t1 = let T (g, h) = t in h (g 1);;
[%%expect{|
val t1 : int = 1
|}]

let f x = let T (g, h) = t in h (g x);;
[%%expect{|
val f : 'a -> 'a = <fun>
|}]

(* reformulation by @gasche *)

(* an isomorphism between 'a and 'b *)
type ('a, 'b) iso = ('a -> 'b) * ('b -> 'a)

(* exists 'b. ('a, 'b) iso *)
type 'a some_iso = Iso : ('a, 'b) iso -> 'a some_iso
[%%expect{|
type ('a, 'b) iso = ('a -> 'b) * ('b -> 'a)
type 'a some_iso = Iso : ('a, 'b) iso -> 'a some_iso
|}]

(* forall 'a. exists 'b. ('a, 'b) iso *)
let t : 'a . 'a some_iso =
Iso ((fun x -> x), (fun x -> x))
[%%expect{|
val t : 'a some_iso = Iso (<fun>, <fun>)
|}]

let unsound_cast : 'a 'b. 'a -> 'b = fun x ->
match t with Iso (g, h) -> h (g x)
[%%expect{|
Lines 1-2, characters 37-36:
1 | .....................................fun x ->
2 | match t with Iso (g, h) -> h (g x)
Error: This definition has type 'c. 'c -> 'c which is less general than
'a 'b. 'a -> 'b
|}]
4 changes: 4 additions & 0 deletions typing/ctype.ml
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,10 @@ let rec lower_contravariant env var_level visited contra ty =
iter_type_expr (lower_rec contra) ty
end

let lower_variables_only env level ty =
simple_abbrevs := Mnil;
lower_contravariant env level (Hashtbl.create 7) true ty

let lower_contravariant env ty =
simple_abbrevs := Mnil;
lower_contravariant env !nongen_level (Hashtbl.create 7) false ty
Expand Down
2 changes: 2 additions & 0 deletions typing/ctype.mli
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ val generalize: type_expr -> unit
val lower_contravariant: Env.t -> type_expr -> unit
(* Lower level of type variables inside contravariant branches;
to be used before generalize for expansive expressions *)
val lower_variables_only: Env.t -> int -> type_expr -> unit
(* Lower all variables to the given level *)
val generalize_structure: type_expr -> unit
(* Generalize the structure of a type, lowering variables
to !current_level *)
Expand Down
5 changes: 4 additions & 1 deletion typing/typecore.ml
Original file line number Diff line number Diff line change
Expand Up @@ -716,11 +716,14 @@ let solve_Ppat_construct ~refine env loc constr no_existentials
solve_constructor_annotation env name_list sty ty_args ty_ex in
ty_args, ty_res, equated_types, existential_ctyp
in
if constr.cstr_existentials <> [] then
lower_variables_only !env expansion_scope ty_res;
end_def ();
generalize_structure expected_ty;
generalize_structure ty_res;
List.iter generalize_structure ty_args;
if !Clflags.principal then begin
if !Clflags.principal && refine = None then begin
(* Do not warn for couter examples *)
let exception Warn_only_once in
try
TypePairs.iter
Expand Down