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

Implement partial support for Ruby 2.7 pattern matching #2186

Merged
merged 28 commits into from
Dec 18, 2020

Conversation

tomstuart
Copy link
Contributor

@tomstuart tomstuart commented Dec 11, 2020

TruffleRuby needs support for case … in pattern matching in order to be fully compatible with Ruby 2.7 (#2004). This PR includes an initial implementation of parsing and matching simple patterns. Specifically we now support:

  • parsing case … in syntax
  • matching Arrays, Hashes and Structs against array and hash patterns (#deconstruct and #deconstruct_keys)
  • matching array literals against nested array patterns
  • assigning existing local variables in top-level patterns and inside nested arrays

Before these changes, 2% (2/89) of the relevant ruby/spec examples (spec/ruby/core/**/deconstruct* + spec/ruby/language/pattern_matching_spec.rb) were passing; now, 37% (35/94) are passing. Further parser work is required to pass all of the remaining examples.

Shopify#1

@tomstuart tomstuart force-pushed the partial-pattern-matching branch 2 times, most recently from 104c107 to bcd1b0b Compare December 11, 2020 16:54
Copy link
Member

@eregon eregon left a comment

Choose a reason for hiding this comment

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

Looks good so far.

This actually reminded me I implemented pattern matching in Mozart-Graal, maybe there is some similarity: https://github.com/eregon/mozart-graal/blob/ca762ad12d505647a8a872b4993ff76b92ea643a/vm/src/org/mozartoz/truffle/translator/Translator.java#L458

Do you intend the implement the rest of pattern matching in this PR, or would you rather merge this part, and the rest later?
If the latter, I think for users it would be safer to keep raising a SyntaxError (in visitCase3Node()) until it's complete. We could have a temporary option to enable pattern matching to let specs run and to help working on it (i.e., setting the option to true would not throw the SyntaxError).

spec/ruby/core/struct/deconstruct_keys_spec.rb Outdated Show resolved Hide resolved
src/main/java/org/truffleruby/parser/ast/InParseNode.java Outdated Show resolved Hide resolved
src/main/ruby/truffleruby/core/struct.rb Show resolved Hide resolved
src/main/ruby/truffleruby/core/struct.rb Outdated Show resolved Hide resolved
src/main/ruby/truffleruby/core/struct.rb Outdated Show resolved Hide resolved
src/main/java/org/truffleruby/parser/BodyTranslator.java Outdated Show resolved Hide resolved
src/main/java/org/truffleruby/parser/BodyTranslator.java Outdated Show resolved Hide resolved
@tomstuart tomstuart force-pushed the partial-pattern-matching branch 2 times, most recently from 7595b77 to 05d84e6 Compare December 11, 2020 17:31
@tomstuart
Copy link
Contributor Author

How do I get the linting check to pass? I’ve tried several times but can’t get it to stop flip-flopping.

@eregon
Copy link
Member

eregon commented Dec 11, 2020

@tomstuart Are you using jt format? That should work fine.

@eregon
Copy link
Member

eregon commented Dec 11, 2020

IMHO the best way to deal with formatting and lint checks is to enable this hook as pre-commit.
That in my experience saves far more time than it takes to run it.
We should probably document it in https://github.com/oracle/truffleruby/blob/master/doc/contributor/workflow.md

@tomstuart
Copy link
Contributor Author

Thanks, that worked. I was trying to eliminate the diff output by CI but that obviously wasn’t right because it kept changing its mind.

@chrisseaton
Copy link
Collaborator

Do you intend the implement the rest of pattern matching in this PR, or would you rather merge this part, and the rest later?

I think we'd like to merge this. It's the result of a team hack-week. We'll come back and do more pattern matching work later - probably starting with doing the parser properly.

@eregon
Copy link
Member

eregon commented Dec 14, 2020

OK, please add an expert experimental option for pattern matching then, as described in #2186 (review)

@tomstuart tomstuart force-pushed the partial-pattern-matching branch 5 times, most recently from 585eb17 to cf6c011 Compare December 15, 2020 14:44
@chrisseaton
Copy link
Collaborator

Option done

@chrisseaton
Copy link
Collaborator

Rebased to merge parser changes.

@chrisseaton
Copy link
Collaborator

We added

% ruby --experimental-options --pattern-matching spec/ruby/language/pattern_matching_spec.rb --excl-tag fails

to our test matrix.

@eregon
Copy link
Member

eregon commented Dec 16, 2020

I think we can automatically set the option when running specs, e.g., in jt's test_specs, so we just run them alongside other specs.
The command above won't test anything because of the fails tags.

Could you remove the last commit, and I'll find a way to run the specs for them automatically?

@tomstuart
Copy link
Contributor Author

Could you remove the last commit, and I'll find a way to run the specs for them automatically?

Done. Thank you — it’s better for us if these specs run in this repo’s CI, cos then they won’t get accidentally broken.

chrisseaton and others added 21 commits December 16, 2020 11:23
We don’t intend to implement Ruby 3.0’s `#deconstruct` cache yet, but
we’re including the example here for future reference.
This is how MRI behaves as of Ruby 2.7.2 & 3.0.0-preview2, so it’s
important that we continue to do the same when implementing the rest of
pattern matching.
… they are

Co-authored-by: Lillian Zhang <[email protected]>
Co-authored-by: Maple Ong <[email protected]>
We can't untag any existing tests because the simplest binding variable
spec is in an array, but this now works:

```
>> a = "FIRSTINSTANCE"
=> "FIRSTINSTANCE"

>> case 1
   in a
     p "s"
   in 1
     p "o"
   end
"s"
=> "s"

>> a
=> 1
```
This will make it easier to combine the two switch statements.
This isn’t always needed, so we shouldn’t do it unconditionally.
We’re switching twice on the node type, so we should just do it once
instead.
We can't untag any existing tests because we currently depend upon the
local variable having already been declared, but this now works:

```
>> a = "FIRSTINSTANCE"
=> "FIRSTINSTANCE"

>> case [[1,1,1],[6,6,6]]
   in [[a,1,1],[Integer,Integer,Integer,]]
>> end
=> nil

>> a
=> 1
```
This branch didn’t introduce these violations, but they’re preventing
its build from going green.
By adding it to `src/options.yml`, then running `ruby
tool/generate-options.rb` to regenerate `LanguageOptions.java` and
`OptionsCatalog.java`.
The message is “syntax error, unexpected keyword_in” because that’s what
TruffleRuby (without pattern matching support) currently raises.

# Conflicts:
#	src/main/java/org/truffleruby/parser/BodyTranslator.java
@eregon eregon self-assigned this Dec 16, 2020
@eregon eregon added the in-ci The PR is being tested in CI. Do not push new commits. label Dec 16, 2020
*
* @param expression of the case node (e.g. case foo)
* @param firstInNode first in (which could also be the else)
* @return a new case node */
Copy link
Contributor

Choose a reason for hiding this comment

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

For what it's worth, I don't think this is ugly at all. It keeps the grammar clean and all the logic in a single place.

The alternative would be to use the pattern used with ArrayParser in the grammar with left-recursion, so something roughly like this:

p_case_body     : keyword_in args then compstmt {
                    $$ = new SomeKindOfCaseBodyThing();
                    // or $$ = support.storedCaseInNode;
                    $$.add(support.newInNode($1, $2, $4));
                }
                | p_case_body keyword_in args then compstmt {
                    $$ = $1.add(support.newInNode($2, $3, $5));
                }
                | p_case_body keyword_else compstmt {
                    $$ = $1.set_else($3);
                }

Is this problematic with the grammar?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that this comment was copied from newCaseNode(), which was @enebo’s work in d7361b8.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay! Probably worth changing that later if we decide that's not how we feel about it.

graalvmbot pushed a commit that referenced this pull request Dec 18, 2020
@graalvmbot graalvmbot merged commit 4ca026c into oracle:master Dec 18, 2020
@tomstuart tomstuart deleted the partial-pattern-matching branch January 5, 2021 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in-ci The PR is being tested in CI. Do not push new commits. oca-signed shopify
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants