Conversation
|
#9765 is now a strict subset of this PR, so that it is no longer needed to fix #9759. Note that a review of the semantics of propagation is still needed. |
|
Naive questions. Why do we need the There are already | Ppat_array spl ->
let ty_elt = newgenvar() in
begin_def ();
let expected_ty = instance expected_ty in
end_def ();
generalize_structure expected_ty;
unify_pat_types ~refine
loc env (Predef.type_array ty_elt) expected_ty;
[...]and now the PR proposes | Pexp_tuple sexpl ->
begin_def ();
let subtypes = List.map (fun _ -> newvar ()) sexpl in
let to_unify = newty (Ttuple subtypes) in
with_explanation (fun () ->
unify_exp_types loc env to_unify (instance ty_expected));
end_def ();
List.iter generalize_structure subtypes;
[...]There are several differences here, for example:
Do these different approaches result in different behavior? My naive understanding is that the two approaches are essentially equivalent (in both cases the type parameter of the array constructor is lowered from generic to current level), so I'm not sure why we are writing things in fairly different ways. (The pattern approach look simpler, especially if we encapsulated taking-an-instance-at-a-local-level-then-lowering as a helper function). Note: there is no instance of |
|
I had thought these cases were wrong when @trefis and I were adding the levels stuff to |
As explained in the second message, this sentence is no longer true: this PR now truly extends #9765. |
|
Taking a fresh instance of In you example, let spl_ann = List.map (fun p -> (p,newgenvar ())) spl in
let ty = newgenty (Ttuple(List.map snd spl_ann)) in
begin_def ();
let expected_ty = instance expected_ty in
end_def ();
generalize_structure expected_ty;
unify_pat_types ~refine loc env ty expected_ty;It creates a generalized copy of the expected type, and unifies it with a fully general pattern type. Note also that the approach in ...
begin_def ();
init_def generic_level;
let expected_ty = instance expected_ty in
end_def ();
unify_pat_types ~refine loc env ty expected_ty;That is, by setting the level to Finally, is there any advantage in doing the unification on types at |
I don't think there is any particular advantage to it. If you want to change the code in type_pat to match the code in typ_exp, that would be fine by me. It would be great to one day ban unification on type schemes. |
|
Would it be possible to abstract away some of the machinery here in a helper function? This is easier to do with the pattern approach, because basically it suffices to abstract let generic_instance ty =
begin_def ();
init_def generic_level;
let inst = instance expected_ty in
end_def ();
inst
[...]
| Ppat_array spatl ->
let ty_elt = newgenvar () in
unify_pat_types ~refine loc env (Predef.type_array ty_elt) (generic_instance expected_ty);
[...]
| Pexp_array sargl ->
let ty_elt = newgenvar () in
with_explanation (fun () ->
unify_exp_types loc env (Predef.type_array ty_elt) (generic_instance expected_ty));
[...]But I understand that unifying schemes (that is, types with some parts remaining at the generic level) is frowned upon. The expression approach proposed in this PR is a bit harder to factor out. Maybe something like: let def_and_generalize f =
begin_def ();
let vars = Queue.create () in
let gen_var () =
let v = newvar () in
Queue.add vars v; v
let ty = f gen_var in
end_def ();
Queue.iter generalize_structure vars;
ty
| Ppat_array(spatl) ->
let elt_ty =
def_and_generalize @@ fun gen_var ->
let ty = gen_var () in
unify_pat_types ~refine loc env (Predef.type_array ty) (instance ty_expected);
ty
in
| Pexp_array(sargl) ->
let elt_ty =
def_and_generalize @@ fun gen_var ->
let ty = gen_var () in
with_explanation (fun () ->
unify_exp_types loc env (Predef.type_array ty) (instance ty_expected));
ty
in |
| Error: This expression has type t = bool | ||
| but an expression was expected of type bool | ||
| This instance of bool is ambiguous: | ||
| it would escape the scope of its equation |
There was a problem hiding this comment.
The comments above and below these test are now stale.
|
OK, up to now we have discussed implementation details. However, my main concern is not here, but rather the last commit, which makes ambiguity detection stricter, by removing the clutch that made |
|
One way to go would be to split the PR in two, quickly converge on the part that is consensual and fixes the bug, and then for the second commit with the last commit do opam-wide testing to have an idea of how much code is broken in the wild. |
|
FTR: I'm quite keen to see the last commit being merged! But @gasche suggestion also seems reasonable. |
|
I'm also okay with deciding to go ahead and break some programs that were not annotated enough. They are easy to fix in a backward-compatible way. |
ba43227 to
bcf80c2
Compare
|
I have done as announced:
Once we converge and merge this PR, I will make a separate PR removing propagation between branches. |
|
I'm looking at the code now, and it looks much nicer, thanks! Why is there no generic instance taken in the |
gasche
left a comment
There was a problem hiding this comment.
Note: generic_instance could also be used in the build_as_type definition.
I went through the file and I believe that the uses of unify_pat_types and unify_exp_types are consistent with my expectations: either the two types have inference variables at the current level (typically by calling instance), or the two types have variables at the generic level (typically by calling newgenvar () on one side and generic_instance on the other).
I would say that the patch as proposed is good to merge.
Seems I missed it. Or more precisely, it was missing in the original code.
Actually, no. It can only be used if the variables of the original type are not at |
|
This seems good to go when the CI is green. (You should probably add @trefis and myself as reviewers.) |
|
I haven't had time to give this a proper review yet. I'd be happy to do so, but it will probably have to wait until Monday. |
I'll wait for that. No hurry. |
| stdlib__scanf.cmx) echo ' -inline 9';; | ||
| *Labels.cm[ox]) echo ' -nolabels -no-alias-deps';; | ||
| stdlib__float.cm[ox]) echo ' -nolabels -no-alias-deps';; | ||
| stdlib__oo.cmi) echo ' -no-principal';; |
There was a problem hiding this comment.
I'm slightly surprised by this.
There was a problem hiding this comment.
If you remove it, the type of Oo.copy loses abbreviations (i.e. applying it to an object returns a non-abbreviated type).
I admit that this tuning is disturbing, but losing the abbreviation because of a change in the standard library seems a nuisance.
There was a problem hiding this comment.
It would be nice to leave a comment here so that we don't remove (or consider removing) the flag in the future because we forgot the reason for having it.
(Is there a different type signature that one could use to enforce sharing even in -principal mode?)
There was a problem hiding this comment.
Unfortunately, I see no way to enforce the sharing of an alias inside a signature in principal mode. The unsharing of aliases is part of the design.
There may be a way by using a different way to introduce sharing, but it is going to be subtle. Even something like
type 'a identity = 'a -> 'a
val copy : < .. > identitymysteriously doesn't seem to work.
* Fix ocaml#9759: Typing without -principal is broken in 4.11 and trunk * compile stdlib in -principal mode * never modify generic part of ty_expected_explained * use generic_instance where possible * add comment for -no-principal in stdlib__oo.cmi
This is an alternative fix to #9765 .
Rather than calling
correct_levelsin more branches, it does the right thing: ensure that the generic parts of the expected type are not modified by calls totype_expect.It is built on top of #9765 but doesn't really reuse it (except for the example).
The 3 new commits are actually orthogonal
-principalmode by adding (required!) type annotations in camlinternalFormat.mlThe PR is larger, and probably shouldn't go in 4.11.
Since the last commit changes the behaviour, it is worth discussing the merits of this approach.