diff --git a/changes/object-field-reads.polish.md b/changes/object-field-reads.polish.md new file mode 100644 index 0000000000..2470c2484d --- /dev/null +++ b/changes/object-field-reads.polish.md @@ -0,0 +1 @@ +- Speed up reading many fields of a large object, and shrink the `.cmt` files of such modules. diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 2ae56d4446..de8553ec37 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -2566,6 +2566,65 @@ let object_valid_fields env ty = Some (List.fold_left collect_fields [] fields) | _ -> None +(* Whether [ty] is generic and has no variables, so that an instance of it + would be an identical copy. Generic types don't change, so the answer + for the last type asked about is kept: field reads of one object usually + come one after another, and each check traverses the whole type. *) +let last_closed_scheme_key = Request_local.new_key (fun () -> ref None) + +let is_closed_scheme ty = + let ty = repr ty in + let last = Domain.DLS.get last_closed_scheme_key in + match !last with + | Some (ty', closed) when ty' == ty -> closed + | _ -> + let closed = ty.level = generic_level && Ctype.free_variables ty = [] in + last := Some (ty, closed); + closed + +(* The identifier [lid] of [sexp], before it is unified with the expected + type. [share]: when the identifier's type is generic and has no variables, + the expression gets that type itself rather than an identical copy, for a + caller that won't unify it. *) +let type_ident ~context ~deprecated_context ~recarg ~share env sexp lid = + let path, desc = + try + Typetexp.find_value + ?deprecated_context: + (match deprecated_context with + | None -> Some Reference + | v -> v) + env lid.loc lid.txt + with + | Typetexp.Error (_, _, Unbound_value (Ldot (module_lid, "make"))) + when context = Some JsxComponent + -> + (* [