-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Use local scopes to detect univar escapes #10787
base: trunk
Are you sure you want to change the base?
Conversation
I haven't looked at the code yet¹, and there are still parts of the design that I'm unsure of (mainly the decision to have a separate field for « local scopes », which makes sense, but which could also be argued against).
I agree with this, except some parts of the typechecker call module M : sig
type t = < m: 'a. < m: 'b. 'a (* fresh at each cycle *) * 'x > option > as 'x
end = struct
type t = < m: 'a. (< m: 'b. 'a (* always pointing to the first binder *) * < m: 'c. 'x option > > as 'x) option >
end while it is rejected with the following error on 4.14:
tl;dr: local scopes are going to matter for checking equality / compatibility of types. Although I don't think we want to be calling 1: I will do so at some point, but it's probably going to wait a few weeks. (But given that nothing is going to get merged before multicore anyway, it shouldn't be a problem?) |
This looks very promising, and I should look into this in detail, but something surprises me in the presentation:
There is an invariant that variant types sharing the same row variable should be identical up to levels of internal nodes. |
@@ -648,7 +648,7 @@ val f : | |||
- : (< m : 'a. 'a * < p : 'b. 'b * 'd * 'c > as 'd > as 'c) -> | |||
('f * < p : 'b. 'b * 'e * 'c > as 'e) | |||
= <fun> | |||
- : < m : 'a. < p : 'a; .. > as 'b > -> 'b = <fun> | |||
- : < m : 'a. < p : 'a; .. > > -> < p : 'b; .. > = <fun> |
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.
Here the type annotations should probably be rejected as ill-formed: one cannot have a free row variable on a variant type containing univars. I don't think there is any way to build such a type other than such an ill-formed type annotation.
The invariant was already broken, see e.g. (tested in a 4.11.2 toplevel): # fun (x : <a: 'a. [> `A of 'a]>) (y : <a: 'b. [> `A of 'b]>) b -> if b then x else y;;
- : < a : 'a. [> `A of 'a ] as 'c > ->
< a : 'b. 'c > -> bool -> < a : 'a. 'c >
= <fun>
# let f (x : <a: 'a. [> `A of 'a]>) (y : <a: 'b. [> `A of 'b]>) b = if b then x else y;;
Error: Values do not match:
val f :
< a : 'a. [> `A of 'a ] as 'c > ->
< a : 'b. 'c > -> bool -> < a : 'a. 'c >
is not included in
val f :
< a : 'a. [> `A of 'a0 ] as 'c > ->
< a : 'b. 'c > -> bool -> < a : 'a0. 'c > This PR fixes that broken
Do you have a sense of what we should expect to be broken? I would like to try to get this fixed as part of the modular explicits work, so that we don't get strange errors with the equivalent in local modules e.g. module type S = sig
type t
val f : t -> [> `A of t]
end
let f (module M: S) x = M.f x
(* val f : (module M: S) -> M.t -> [> `A of M.t] *)
let g b = if b then f else f Handling these cases is obviously less pressing than the more general concept, so I'm happy to separate out these changes from this PR if they're too objectionable. |
Indeed, I could already see from your PR that there were examples in So the answer is that the printer is correct, I think that |
By the way, the relation of your explicit modular example with this problem is subtle. val f : forall 'a. (module M: S) -> M.t -> [> `A of M.t] as 'a However, the relaxed value restriction says that since val f : (module M: S) -> M.t -> forall 'a. [> `A of M.t] as 'a which is fine. Yet, the symmetrical case with val f : [< `A of t] -> t inside val f : (module M: S) -> forall 'a. ([> `A of M.t] as 'a) -> M.t It is not that surprising that explicit polymorphism would require a finer expression of polymorphism in other places. |
cc @goldfirere |
This PR implements a 'local scope' mechanism, used to track the number of binders (currently only
Tpoly
s) that types appear within, and used to detect scope escapes. This is pretty closely related to @trefis's extended levels proposal, with some key differences:Tunivar
s, rather than blindly lowering them to an inaccurate levelunivars_escape
checks previously used byenter_poly
have been removed, since they can be entirely replaced by the new univar escape checks inupdate_level
'a 'b. ('a, 'b) contains_polys
still maintains a correct local scope structure when new levels of nesting are introduced.This PR also contains some miscellaneous fixes enabled by this new approach:
let f (n : < m : 'a. [<
Foo of 'a & int |Bar] >) = (n : < m : 'b. [<
Foo of 'b & int |Bar] >)
row_more
s but containing distinct univars are now printed in their entirety, instead of as an opaque variable< m : 'a. [<
Foo of 'a |Bar] as 'd > -> < m : 'b. [<
Foo of 'b |Bar] as 'd > -> < m : 'c. [<
Foo of 'c |Bar] as 'd >
Technical notes:
type_expr
and friends now have a local scope (calledlscope
in the code for brevity), which is tracked and updated in a similar fashion to levelsmore_variables
has been added toTypetexp
, so that non-fixed rows and objects may be associated with univars as before, but have their local scope lowered if they aren't captured as suchTvar
in place of aTunivar
while initially constructing aTpoly
) are placed at local scope 0, ensuring that they can't capture univars and allow them to escape.Aside from getting the algorithms for
copy
andupdate_level
correct, most of the trickiness comes from choosing the 'correct' local scope, especially in interaction with row and object types. We can't blindly use acurrent_level
like we do for existing levels, so unfortunately this PR introduces a lot of explicit arguments for thelscope
into various functions.