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

Record pattern compilation #1816

Merged
merged 14 commits into from
Feb 15, 2024
Merged

Record pattern compilation #1816

merged 14 commits into from
Feb 15, 2024

Conversation

yannham
Copy link
Member

@yannham yannham commented Feb 13, 2024

Following the introduction of ADTs, and the generalization of match expression to patterns, this PR implements pattern compilation for record patterns. Compilation means elaborating a basic Nickel expression, which doesn't make use of patterns anymore, to which patterns and match expressions can be compiled (or desugared) to.

Enums are left for follow-up work. In this specific PR, the code isn't actually used anywhere, but I've pushed the branch tmp/matchv2 which introduces a separate matchv2 keyword which accepts generic patterns instead of just enum tags, and used that branch to do tests and experiment. Reviewers are invited to try it out, if they wish to.

Content

  • Add a new submodule compile to the term::pattern module
  • Add traits Compile and CompilePart to be implemented by various pattern AST nodes
  • Add MatchData data type which will hold the future Match node
  • Add new required primops to implement pattern compilation: field_is_defined, pattern_branch,
  • Implement compile for record patterns

Technical choices

Compiling a full match statement

As described in the compile module-level documentation, the current approach for a full-fledged match statement is the naive approach: we compile each pattern independently, and try them one by one. We don't expect this to have any impact on performance today. If it does, there are classic references to optimize the generated decision tree.

Pass bindings to branches

When a branch is selected, we must bring the variable captured by the pattern in scope. The current solution is to build a record along the way - when checking if a pattern match a value. If it does match, the final bindings are added to the environment of the branch by a new special primop introduced here. This is a simple approach that reuses existing Nickel datastructure. We can imagine do something more performant than building the record step by step in the Nickel world: after all, we know in advance the number of pattern variables, and we could pre-allocate memory and fill it imperatively using special primops

Manual compilation

The current implementation generates all the Nickel expression programmatically. It's a bit clunky, and we could offload the bigger blocks to, say, the internals module of the stdlib, as we do for built-in contracts. However, I believe the current approach is more amenable to more aggressive optimizations, as the compiling code has access to all the bits. It's already probably more performant as well - the blocks offloaded to the internals module would need to be functions, and we would need to apply them, while the current implementation morally inlines everything.

Follow-up

  • Implement compilation for enums
  • Switch to a match statement that is able to handle the full patterns thanks to compilation
  • Implement static typing of match statement with generic patterns

This PR implements core functions to compile patterns and match
expressions to simpler Nickel code that doesn't use patterns at all.
This is a step toward allowing general pattern in match expressions.

For now, this new implementation isn't actually used yet as it lacks a
couple new primitive operations to work properly. The implementation
also ignores contracts annotation in the pattern.
To safely check if a pattern matches a value (safely meaning without
throwing an uncatchable error), we need a primitive operation to check
if we can safely access the field of a record. To do so, we need the
field to exists _and_ to have a definition. This is what the added
`field_is_defined` checks.
As match expressions are going to handle more general patterns, the
existing NonExhaustiveMatch error - which was in fact specialized for
enums - is renamed to NonExhaustiveMatchEnum, and the previous name is
reused for a more general errors which doesn't assume that the pattern
or the matched value are enums.
The partial implementation of record pattern compilation didn't handle
the record pattern tail yet: for example, a closed pattern `{foo, bar}`
would match a larger record `{foo=1, bar=2, baz=3}`.

This commit additionally keeps the current rest of the record being
matched, that is the original record minus all the matched fields, to
either check that it is empty at the end (closed pattern), or to bind
this rest to an identifier if the pattern captures the rest with `{foo,
bar, ..rest}`.
After doing some experimentation, fix a number of bugs observed in the
compilation of record patterns: wrong order of arguments, wrong
identifiers used, clash of the special rest field between patterns and
sub-patterns, and so on.
Rename the `with_env` primop to `pattern_branch` in order to avoid
confusion about its scope and usage: this shouldn't be used as a general
environment augmentation operation, although it kinda is, because of
existing footguns.
@yannham yannham requested review from jneem and vkleen February 13, 2024 17:11
@github-actions github-actions bot temporarily deployed to pull request February 13, 2024 17:15 Inactive
@github-actions github-actions bot temporarily deployed to pull request February 13, 2024 17:42 Inactive
This was referenced Feb 14, 2024
core/src/error/mod.rs Outdated Show resolved Hide resolved
core/src/typecheck/operation.rs Outdated Show resolved Hide resolved
core/src/term/pattern/compile.rs Outdated Show resolved Hide resolved
//
// let value_id = value in
//
// <for (pattern, body) in branches.rev()
Copy link
Member

Choose a reason for hiding this comment

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

fold?

@github-actions github-actions bot temporarily deployed to pull request February 15, 2024 18:46 Inactive
Co-authored-by: jneem <joeneeman@gmail.com>
@github-actions github-actions bot temporarily deployed to pull request February 15, 2024 19:08 Inactive
@yannham yannham added this pull request to the merge queue Feb 15, 2024
Merged via the queue into master with commit f7b50d3 Feb 15, 2024
5 checks passed
@yannham yannham deleted the feat/pattern-elaboration branch February 15, 2024 19:31
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.

2 participants