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

Make type_expr private #9994

Merged
merged 24 commits into from Dec 14, 2020
Merged

Make type_expr private #9994

merged 24 commits into from Dec 14, 2020

Conversation

garrigue
Copy link
Contributor

@garrigue garrigue commented Oct 30, 2020

This PR makes Types.type_expr a private record, so that one cannot directly create or modify it.
The public definition is in submodule Internal, with conversion (identity) functions.

To make this definition easier to use, Btype.mark_type_node is extended with two new optional arguments, guard and after, which are called with the repred type, respectively before marking it (returning whether to mark it or not) and after marking it.

We have plans for further abstraction, but this seems a good first step.

@t6s co-authored this PR.

@gasche
Copy link
Member

gasche commented Oct 30, 2020

This looks interesting, thanks! (It's also joint work with @t6s, one of the first such contribution: welcome!)

I have the impression that mark_type_node is almost always passed an after parameter. To me this suggests that the API could be

val mark_type_node: type_expr -> ?guard:(type_expr -> bool) -> (type_expr -> unit) -> unit

to be used as

mark_type_node ty @@ fun ty ->
...

(or with just ignore in the cases that currently use the default after value; but I think that they could all be rewritten to make meaningful use of their parameter.)

@garrigue
Copy link
Contributor Author

garrigue commented Nov 6, 2020

I have no strong opinion as to whether after should be optional or not, but do you really think that this @@ syntax is going to be more readable?
It's starting to look Haskellish.

@gasche
Copy link
Member

gasche commented Nov 6, 2020

Don't use @@ if you don't like it, but I think after should be mandatory.

@trefis
Copy link
Contributor

trefis commented Nov 6, 2020

On a not-exactly-related-but-still-somewhat-relevant note: is there some reason to keep using the types marking mechanism instead of a table indexed by the type expr id (as is done in Ctype.lower_contravariant for instance)?

@t6s
Copy link
Contributor

t6s commented Nov 6, 2020

Ctype.closed_class is the only place where mark_type_node is called without after, but its code is complicated by try .. with. Can we fit this logic into a use of after?

@gasche
Copy link
Member

gasche commented Nov 6, 2020

I would try

let closed_class params sign =
  let ty = object_fields (repr sign.csig_self) in
  let (fields, rest) = flatten_fields ty in
  List.iter mark_type params;
  mark_type rest;
  List.iter
    (fun (lab, _, ty) -> if lab = dummy_method then mark_type ty)
    fields;
  try
-    mark_type_node (repr sign.csig_self);
+    mark_type_node (repr sign.csig_self) @@ fun csig_self ->
    List.iter
      (fun (lab, kind, ty) ->
        if field_kind_repr kind = Fpresent then
        try closed_type ty with Non_closed (ty0, real) ->
          raise (CCFailure (CC_Method (ty0, real, lab, ty))))
      fields;
-    mark_type_params (repr sign.csig_self);
+    mark_type_params csig_self;
    List.iter unmark_type params;
    unmark_class_signature sign;
    None
  with CCFailure reason ->
    mark_type_params (repr sign.csig_self);
    List.iter unmark_type params;
    unmark_class_signature sign;
    Some reason

@gasche
Copy link
Member

gasche commented Nov 6, 2020

We could also factorize as

let closed_class params sign =
  let ty = object_fields (repr sign.csig_self) in
  let (fields, rest) = flatten_fields ty in
  List.iter mark_type params;
  mark_type rest;
  List.iter
    (fun (lab, _, ty) -> if lab = dummy_method then mark_type ty)
    fields;
  mark_type_node (repr sign.csig_self) @@ fun csig_self ->
  let reason =
    try
      List.iter
      (fun (lab, kind, ty) ->
        if field_kind_repr kind = Fpresent then
        try closed_type ty with Non_closed (ty0, real) ->
          raise (CCFailure (CC_Method (ty0, real, lab, ty))))
      fields;
      None
    with CCFailure reason ->
      Some reason in
  mark_type_params csig_self;
  List.iter unmark_type params;
  unmark_class_signature sign;
  reason

@gasche
Copy link
Member

gasche commented Nov 6, 2020

Note: we would not need an exception at all if we had my baby List.map_option from #9630.

@garrigue
Copy link
Contributor Author

@gasche It looks like you misunderstood the role of after.
It is only called when the node has to be marked (i.e. level >= lowest_level).
So your suggested changes for closed_class would be incorrect.

My temporary conclusion is that the current proposed API of using an optional argument is better, because it does not invite people to always add some post-processing, which could be wrongly scoped.
Same thing for @@, which changes the execution path without parentheses (have we learnt nothing from the if then debacle :-)

As for @trefis 's comment, the use of a hash table in lower_contravariant comes from the possibility of expansion, which would invalidate the invariants of marking. But I agree that mark_type goes against the goal of abstracting the type representation (and requires exclusive access to the type graph, which is hard to enforce statically), and may eventually have to be replaced by something else. Not in this PR, though.

@gasche
Copy link
Member

gasche commented Nov 10, 2020

Good catch; in this case mark_type_node (repr sign.csig_self) ignore would be fine?

@gasche
Copy link
Member

gasche commented Nov 10, 2020

My temporary conclusion is that the current proposed API of using an optional argument is better, because it does not invite people to always add some post-processing, which could be wrongly scoped.

After thinking about this, I think that either APIs are just as error-prone as each other, and I am still in favor of a mandatory and non-labelled after parameter. If the error in my newcomer suggestion above can be attributed to something else than carelessness, it would dig in the following direction: why is this function using mark_type_node in such a different way than all other callsites? Should it not be using another marking function?

@garrigue
Copy link
Contributor Author

So you want use to re-split it in mark_type_node (as before) and mark_type_node_with ?
This makes all the other calls longer, but if you think so...

Note that this call is not that different: fields being a list of subnodes of sign.csig_self, the List.iter just after could be inside after. But since we do not refer directly to csig_self in it, there is little incentive to do that.

I think the real choice is whether it is ok to use optional arguments to factorize APIs.
This is certainly the case in libraries, but I understand if there is some resistance inside the compiler.

@t6s
Copy link
Contributor

t6s commented Nov 24, 2020

The last commit makes after mandatory and separates the particular use-case of mark_type_node in closed_class into a new function mark_type_node_only. Do you think this makes the changes look better? @gasche

typing/btype.ml Outdated Show resolved Hide resolved
typing/btype.ml Outdated
function
{desc = Tlink t' as d'} ->
repr_link true t d' t'
| {desc = Tfield (_, k, _, t') as d'} when field_kind_repr k = Fabsent ->
repr_link true t d' t'
| t' ->
if compress then begin
log_change (Ccompress (t, t.desc, d)); t.desc <- d
log_change (Ccompress (t, t.desc, d)); (Internal.unlock t).desc <- d
Copy link
Contributor

Choose a reason for hiding this comment

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

There are a few lines like this. Maybe add an Internal.set_desc to make them easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea here is that one should not directly modify types, so providing a function that does it in types.mli seems a mixed message. Or change the name Internal to something more scary like Unsafe_access.

typing/btype.ml Outdated
let rec mark_type ty =
let ty = repr ty in
if ty.level >= lowest_level then begin
ty.level <- pivot_level - ty.level;
(* type nodes with negative levels are "marked" *)
(Internal.unlock ty).level <- mirror_level ty.level;
Copy link
Contributor

Choose a reason for hiding this comment

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

And an Internal.set_level to use here too.

@@ -182,7 +180,8 @@ let extension_descr ~current_unit path_ext ext =
cstr_uid = ext.ext_uid;
}

let none = {desc = Ttuple []; level = -1; scope = Btype.generic_level; id = -1}
let none = Internal.lock
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe move this into Btype with a name like dummy_type.

typing/btype.mli Outdated Show resolved Hide resolved
Copy link
Contributor

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

This is a good idea. I think the code could be cleaner though, so I've left some comments.

Copy link
Contributor

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

I've reviewed the latest changes. They look good.

typing/ctype.ml Show resolved Hide resolved
@t6s
Copy link
Contributor

t6s commented Dec 11, 2020

The last commit replaces Internal module by Private_type_expr module, which provides a more modular interface with setters.
The name is also changed since Internal was sounding too generic.

@t6s
Copy link
Contributor

t6s commented Dec 11, 2020

Btw, I am seeing github's complaints about conflicts while the branch builds successfully at my machine. Why?

@trefis
Copy link
Contributor

trefis commented Dec 11, 2020

The conflicts that github is talking about is those you'd encounter if you were to rebase on a recent trunk.
Which you will want to do before we can merge your PR.

Copy link
Contributor

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

Latest changes look good. I think this is good to merge now.

Copy link
Member

@gasche gasche left a comment

Choose a reason for hiding this comment

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

Looks nice, thanks!

typing/btype.ml Outdated Show resolved Hide resolved
@garrigue
Copy link
Contributor Author

Thanks for you reviews.
I removed the commented out dead code, and rebased.
Waiting for the CI.

@garrigue garrigue merged commit 9f128b2 into ocaml:trunk Dec 14, 2020
@@ -730,8 +727,10 @@ let duplicate_class_type ty =
*)
let rec generalize ty =
let ty = repr ty in
(* generalize the type iff ty.level <= !current_level *)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit late to the party, but I think that comment is incorrect (and also, vaguely useless?).

trefis added a commit to trefis/ocaml that referenced this pull request Feb 10, 2021
@trefis trefis mentioned this pull request Feb 10, 2021
if (ty.level > !current_level) && (ty.level <> generic_level) then begin
set_level ty generic_level;
(* recur into abbrev for the speed *)
Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I think this comment (apart from being obscure to me) is at odds with the comment above the function.
So, which is it?

garrigue pushed a commit to garrigue/ocaml that referenced this pull request Mar 3, 2021
dbuenzli pushed a commit to dbuenzli/ocaml that referenced this pull request Mar 25, 2021
smuenzel pushed a commit to smuenzel/ocaml that referenced this pull request Mar 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants