Skip to content

Remove arity-interrupting elaboration of module patterns#146

Merged
goldfirere merged 20 commits intoocaml-flambda:mainfrom
ncik-roberts:typedtree-module-patterns
Mar 15, 2023
Merged

Remove arity-interrupting elaboration of module patterns#146
goldfirere merged 20 commits intoocaml-flambda:mainfrom
ncik-roberts:typedtree-module-patterns

Conversation

@ncik-roberts
Copy link
Copy Markdown
Contributor

@ncik-roberts ncik-roberts commented Mar 10, 2023

Remove the elaboration of module patterns, where typechecking would insert let-module expressions for each unpack. For example, this:

let f (module M : S) y = M.x + y

used to be elaborated to this during typechecking:

let f (m : (module S)) =
  let module M = (val m) in
  fun y -> M.x + y

The same went for match expressions (including when guards!) and let expressions (including the RHS when the let binding was recursive).

Now, we treat the existing Tpat_var construct in typedtree as unpacking a module pattern (as long as pat_extra = Tpat_unpack). To typecheck in the new scheme, we:

  • Treat module patterns as introducing a module identifier at an elevated scope
  • Take care to check that the module's types don't escape via the bound variable types or the overall expression types

The mechanism we use for this is similar to gadt_equations_level, in that we wrap pattern typechecking its own level and store the elevated scope in a global ref that pattern typechecking refers to. (In particular, we repurpose the allow_modules bool to an int option, where the int payload communicates the scope to enter the module identifier at.) Like gadt_equations_level, we only elevate the level if the pattern contains the relevant kind of subpattern (here, a module unpack).

We want to gain confidence not only that this PR avoids scope escape, but that future constructs involving patterns avoid scope escape. Previously, the way this was accomplished was via the type_unpacks function, which typechecked a body expression within the scope of a list of unpacked modules, and confirmed the modules' types didn't escape via the body's type. In this world, it was easy to see that scope escape was checked anywhere a module was used. Now, the responsibility is more diffuse. But we can still state the matter simply. The responsibility to check scope escape falls to whoever creates the scope such that pattern typechecking sets allow_modules = Modules_allowed { scope }. You can see this responsibility in action in both the type_cases and type_let functions. This is summarized in a comment by the module_patterns_restriction type.

type_unpacks now doesn't do much at all. I clean it up in a stacked PR (ncik-roberts#1).

I add a few tests that did not always exhibit the desired behavior over the course of my feature.

@ncik-roberts ncik-roberts requested a review from goldfirere March 10, 2023 23:25
@ncik-roberts ncik-roberts force-pushed the typedtree-module-patterns branch 3 times, most recently from e69579e to 5cd891e Compare March 13, 2023 16:16
Copy link
Copy Markdown
Contributor

@goldfirere goldfirere left a comment

Choose a reason for hiding this comment

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

This looks like it works well!

But I believe I found a few opportunities for simplification -- see line comments.

@ncik-roberts ncik-roberts force-pushed the typedtree-module-patterns branch from 3cc3156 to 717265a Compare March 14, 2023 16:23
@ncik-roberts ncik-roberts force-pushed the typedtree-module-patterns branch from 7f0563e to bd29267 Compare March 15, 2023 09:11
@goldfirere goldfirere merged commit 50d54db into ocaml-flambda:main Mar 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants