-
Notifications
You must be signed in to change notification settings - Fork 87
Support modules of non-legacy modes #3759
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: minor-fix-modes
Are you sure you want to change the base?
Conversation
typing/includecore.mli
Outdated
*) | ||
|
||
(** Gives the modes suitable for the inclusion check of a child item. *) | ||
val child_modes: string -> | ||
?modalities:(Mode.Modality.Value.t * Mode.Modality.Value.t) -> |
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.
Comment on what modalities
means?
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.
It could still be clearer what modalities
is saying: why are there two of them and what do they mean? (Too bad we don't have named tuples here)
eefebc5
to
2f89dc6
Compare
module M' = (M @ nonportable) | ||
(* CR zqian: The following should fail but doesn't, due to A limitation | ||
described in "inferred modalities" in [mode.ml] *) | ||
let _ = portable_use M'.x (* [M] is nonportable and [x] doesn't have modality *) |
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.
This seems pretty bad. Is this a soundness bug?
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.
It's not a soundness bug in the sense that we are just forgetting the explicit weakening.
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.
This PR should also include documentation for the new syntax. (Just making this comment here so that GitHub sees it as a Discussion that won't get lost.)
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.
Probably not in this PR as it's not still stable, with various issues.
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.
As discussed, we will add documentation in another PR.
3 | (module M : S) | ||
^ | ||
Error: Signature mismatch: | ||
Got "regional" but expected "global". |
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 doubt users will understand what regional
means, and local
seems fine here even though it's a bit imprecise?
is not included in | ||
val foo : 'a -> 'a @@ portable | ||
The second is portable and the first is nonportable. | ||
Got "nonportable" but expected "portable". |
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.
This is a bit sad. We don't need to drill down all the way to foo
anymore, but the user should still find out that the module M'
is expected to be portable
but is nonportable
.
@@ -123,7 +123,6 @@ module type S = sig | |||
with [t] to tie the knot on recursive operations. *) | |||
|
|||
module Result : sig | |||
@@ portable |
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.
This seems strange for 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.
This is not needed, since there is @@ portable
at the begining of this file.
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.
Sure. I don't care enough to put my foot down here, but I think we should avoid this kind of unrelated cleanup on otherwise unaffected files.
typing/includecore.mli
Outdated
*) | ||
|
||
(** Gives the modes suitable for the inclusion check of a child item. *) | ||
val child_modes: string -> | ||
?modalities:(Mode.Modality.Value.t * Mode.Modality.Value.t) -> |
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.
It could still be clearer what modalities
is saying: why are there two of them and what do they mean? (Too bad we don't have named tuples here)
| Sig_module (id, pres, md, rec_, vis) -> | ||
let md_modalities = | ||
md.md_modalities | ||
|> Mode.Modality.Value.to_const_exn |
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.
Why do we expect to_const_exn
to succeed here? I suppose a modality variable in a signature wouldn't make much sense? (There should be a comment here in any case)
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.
include S @@ modalities
, here S
must be a constant modality (as opposed to inferred modalities). See maybe_infer_modalities
in this file.
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.
Can we rename the function apply_modalities_included_signature
then to express this constraint, in addition to adding the comment here?
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.
maybe apply_constant_modalities_sg
match Mode.Modality.Value.Const.is_id modalities with | ||
| true -> mty | ||
| false -> apply_modalities_module_type env modalities mty | ||
let transl_modalities ~sig_modalities modalities = |
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.
This surprises me as a thing we want to do? This suggests we're treating sig_modalities
as defaults rather than something onto which we want to compose other modalities. Oh, I guess they're the default modalities from the start of the signature? I guess then the overriding makes sense, but I think we should find a better name than sig_modalities
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.
You are right that this is very bad. Ideally we want per-axis overriding: I.e., if default modality is portable
, then later if we write nonportable
, that will override. If later we write global
, that will compose into global portable
.
This should be fixed ASAP.
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.
Oh heh good point. Still, calling the argument ~default_modalities
seems like an improvement
(* the module is added only for its types, so we give it the | ||
strongest mode to avoid false mode errors. *) |
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.
As discussed, this could be a bit clearer. I would emphasize that we're in a signature here, so this newenv
will only be used to typecheck signature items
(* CR zqian: currently modules are checked bottom-up, which gives worse error | ||
messages. The following example should point to [foo] instead. *) | ||
module M @ many = struct | ||
let (foo @ once) () = () | ||
end | ||
[%%expect{| | ||
Lines 1-3, characters 18-3: | ||
1 | ..................struct | ||
2 | let (foo @ once) () = () | ||
3 | end | ||
Error: This is "once", but expected to be "many". |
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.
just to highlight this issue.
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.
Took a quick look at the tests, just to understand the design better. Looks good to me, just a few comments as I was scrolling through.
1 | module M' = (M @ portable) | ||
^^^^^^^^ | ||
Error: Mode annotations on modules are not supported yet. | ||
module M' = M |
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 would expect a portable modality to be printed here, I think
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.
Even though the module is portable, its "exposed mode" could be nonportable, and since printtyp.ml
zaps to legacy, it would be nonportable.
(Yes, we should let printtyp.ml
zap to floor, in align with module type of
and mli
-less files).
1 | module M @ portable = struct end | ||
^^^^^^^^ | ||
Error: Mode annotations on modules are not supported yet. | ||
module M : sig end |
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 think I would expect to see a portable modality printed here. Maybe mode-crossing eliminates it?
^^^^^^^^ | ||
Error: Mode annotations on modules are not supported yet. | ||
File "_none_", line 1: | ||
Error: Recursive modules require an explicit module type. |
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 think this test would be more informative if we put on the type signatures to fix this error, so that the rest of the syntax gets checked.
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.
Wait is the syntax check interrupted by type error? Oh wow.
In the long run this file should just be about syntax... the type errors are already checked by other files...
(* packing is allocation, and we can't construct a global module using local values *) | ||
let bar (local_ m) = | ||
let module M = (val m : S) in | ||
(module M : S) |
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 think this restriction (that an expression for packing cannot contain any local values) should get a brief mention in the reference page for stack allocation. (Why document this small point? Because I think the time cost of writing the doc -- 5-10 minutes -- is less than my estimate of the expected value of the amount of time lost when someone tries to do this and fails, even taking into account my estimate of the percentage of people who actually read the docs.)
@@ -160,7 +148,7 @@ module type S = sig | |||
end | |||
|
|||
module M : S = struct | |||
let foo = fun x -> x | |||
let foo @ nonportable = fun x -> x |
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.
Why is this change necessary?
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.
Urgh. I've figured it out. This is necessary to make M
nonportable so that a test 70 lines later still tests the right thing. Ok, I suppose. But it would be much better if we included the portability of modules in printing, and then this would be more obvious. Actually I'm a little surprised that printing doesn't zap the modes. Maybe at least leave a comment on this line saying what the motivation for the annotation is? As written, it looks like perhaps the nonportable
is necessary in order for M
to type-check against S
.
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.
Yes, the printing still zaps to id. Now with #3922 , I think the default should be zapping to floor.
@@ -123,7 +123,6 @@ module type S = sig | |||
with [t] to tie the knot on recursive operations. *) | |||
|
|||
module Result : sig | |||
@@ portable |
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.
This is not needed, since there is @@ portable
at the begining of this file.
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.
As discussed, we will add documentation in another PR.
| Sig_module (id, pres, md, rec_, vis) -> | ||
let md_modalities = | ||
md.md_modalities | ||
|> Mode.Modality.Value.to_const_exn | ||
|> (fun then_ -> Mode.Modality.Value.Const.concat ~then_ modalities) | ||
|> Mode.Modality.Value.of_const | ||
in | ||
let md = {md with md_modalities} in |
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.
As mentioned in #3943 , now include S @@ modalities
is properly implemented, we can mention it in syntax.md
.
| Sig_module (id, pres, md, rec_, vis) -> | ||
let md_modalities = | ||
md.md_modalities | ||
|> Mode.Modality.Value.to_const_exn |
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.
include S @@ modalities
, here S
must be a constant modality (as opposed to inferred modalities). See maybe_infer_modalities
in this file.
@@ -160,7 +148,7 @@ module type S = sig | |||
end | |||
|
|||
module M : S = struct | |||
let foo = fun x -> x | |||
let foo @ nonportable = fun x -> x |
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.
Yes, the printing still zaps to id. Now with #3922 , I think the default should be zapping to floor.
module M' = (M @ nonportable) | ||
let () = portable_use M'.length |
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.
Well, M
has an explicit signature that doesn't have @@ portable
.
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.
Still not entirely reviewed but just have env.ml
and env.mli
left.
@@ -2977,6 +3038,8 @@ and type_open_decl_aux ?used_slot ?toplevel funct_body names env od = | |||
in | |||
let md = { mod_desc = Tmod_ident (path, lid); | |||
mod_type = Mty_alias path; | |||
mod_mode = Value.(disallow_right max), None; | |||
(* CR zqian: maybe put in the correct mode and locks. *) |
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.
What's up here?
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'm being lazy and not putting in the correct mode. This is fine because the type checking only cares about newenv
, and this open_descr
is only used in typedtree.
However, it's probably also not much work to do it properly.
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'd rather the typedtree have the correct info if it's not much work—at some point, odoc's going to want to know these things from the typedtree as well. (But that won't be for a bit so if this turns out to be hard I won't lose any sleep over it)
@@ -799,7 +857,6 @@ and signature_components : | |||
let id, item, shape_map, present_at_runtime = | |||
match sigi1, sigi2 with | |||
| Sig_value(id1, valdecl1, _) ,Sig_value(_id2, valdecl2, _) -> | |||
let mmodes = append_ldot (Ident.name id1) mmodes in |
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.
Not clear to me why we don't need this anymore?
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.
This is now handled in child_modes
.
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 don't see a call to child_modes
though?
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.
This is handled in Includecore.value_descriptions
.
Based on #3757 and #3758 and #3824
This PR adds the support for modules of non-legacy modes. Functor parameter and body are still fixed to be legacy.
TODO:
The room for improvement shouldn't block preliminary review and discussion. In particular, changes to the existing tests seem correct, and can serve as a good starting point.