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

Ruby: pattern matching #7154

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Ruby: pattern matching #7154

wants to merge 7 commits into from

Conversation

@aibaars
Copy link
Contributor

@aibaars aibaars commented Nov 17, 2021

This pull request updates the Ruby grammar to tree-sitter/tree-sitter-ruby#193

@github-actions github-actions bot added the Ruby label Nov 17, 2021
@aibaars aibaars force-pushed the ruby-pattern-matching branch 2 times, most recently from 2e9b57a to b602549 Nov 17, 2021
@aibaars aibaars changed the title Ruby pattern matching Ruby: pattern matching Nov 17, 2021
@aibaars aibaars force-pushed the ruby-pattern-matching branch 6 times, most recently from 93ce829 to 157877e Nov 22, 2021
@aibaars aibaars marked this pull request as ready for review Nov 23, 2021
@aibaars aibaars requested a review from as a code owner Nov 23, 2021
@aibaars aibaars force-pushed the ruby-pattern-matching branch 5 times, most recently from 5bd7bde to 5ca8fe7 Nov 23, 2021
@aibaars aibaars force-pushed the ruby-pattern-matching branch from 5ca8fe7 to cec34a5 Nov 23, 2021
@aibaars aibaars force-pushed the ruby-pattern-matching branch from cec34a5 to c3112c4 Nov 24, 2021
Copy link
Contributor

@nickrolfe nickrolfe left a comment

I haven't reviewed the tests yet, but looks very sensible so far. I found an issue in the upgrade script, plus some comment suggestions.

Loading

final override string getAPrimaryQlClass() { result = "CaseMatch" }

/**
* Gets the expression being compared. For example, `foo` in the following example.
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Would 'matched' be a clearer way of phrasing it?

Suggested change
* Gets the expression being compared. For example, `foo` in the following example.
* Gets the expression being matched. For example, `foo` in the following example.

Loading

*/
final Expr getABranch() { result = this.getBranch(_) }

/** Gets a `in` clause of this case expression. */
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Suggested change
/** Gets a `in` clause of this case expression. */
/** Gets the `n`th `in` clause of this case expression. */

Loading

/** Gets a `in` clause of this case expression. */
final InClause getInClause(int n) { result = this.getBranch(n) }

/** Gets the `n`th `in` clause of this case expression. */
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Suggested change
/** Gets the `n`th `in` clause of this case expression. */
/** Gets an `in` clause of this case expression. */

Loading


private Ruby::KeywordPattern keyValuePair(int n) { result = g.getChild(n) }

/** Get the key of the `n`th pair. */
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Suggested change
/** Get the key of the `n`th pair. */
/** Gets the key of the `n`th pair. */

Loading

/** Get the key of the `n`th pair. */
StringlikeLiteral getKey(int n) { toGenerated(result) = keyValuePair(n).getKey() }

/** Get the value of the `n`th pair. */
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Suggested change
/** Get the value of the `n`th pair. */
/** Gets the value of the `n`th pair. */

Loading

/** Get the value of the `n`th pair. */
CasePattern getValue(int n) { toGenerated(result) = keyValuePair(n).getValue() }

/** Get the value for a given key name. */
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Suggested change
/** Get the value for a given key name. */
/** Gets the value for a given key name. */

Loading

exists(int i | key = this.getKey(i).getValueText() and result = this.getValue(i))
}

/** Get the variable of the keyword rest token, if any, i.e. `name` in `**name`. */
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Suggested change
/** Get the variable of the keyword rest token, if any, i.e. `name` in `**name`. */
/** Gets the variable of the keyword rest token, if any, i.e. `name` in `**name`. */

Loading

}

/**
* A pattern matching with a rename into specified local variable, for example `Integer => a`
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

This sentence is a little hard to understand. Is my suggestion correct, and do you think it's any clearer?

Suggested change
* A pattern matching with a rename into specified local variable, for example `Integer => a`
* A pattern match that binds to the specified local variable, for example `Integer => a`

Loading


AsPattern() { this = TAsPattern(g) }

/** Get the underlying pattern. */
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

Suggested change
/** Get the underlying pattern. */
/** Gets the underlying pattern. */

Loading


bindingset[old]
private int newKind(int old) {
old in [0 .. 7] and result = old
Copy link
Contributor

@nickrolfe nickrolfe Nov 24, 2021

Choose a reason for hiding this comment

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

.. is inclusive.

Suggested change
old in [0 .. 7] and result = old
old in [0 .. 6] and result = old
``

Loading

Copy link
Contributor

@hvitved hvitved left a comment

Looks very solid to me!

As for the CFG inconsistencies: that is expected, so for now we should simply commit library-tests/ast/CONSISTENCY/CfgConsistency.expected and library-tests/ast/control/CONSISTENCY/CfgConsistency.ql. Then once we actually implement the CFG logic, those inconsistencies should disappear.

Loading

or
pred = "getValue" and result = this.getValue()
or
pred = "getBranch" and result = this.getBranch(_)
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

should we include getInClause and getElseBranch here as well (they should be concatenated in the PrintAST query)?

Loading

* following example, the pattern is `Point{ x:, y: }`.
* ```rb
* case foo
* in Point{ x:, y: }
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

are we missing a then keyword here?

Loading

* end
* ```
*/
final PatternGuard getPatternGuard() { toGenerated(result) = g.getGuard() }
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

Should we call it getCondition instead?

Loading

*/
final PatternGuard getPatternGuard() { toGenerated(result) = g.getGuard() }

final override string toString() { result = "in ..." }
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

perhaps in ... then ... if the then keyword is required.

Loading

or
pred = "getPattern" and result = this.getPattern()
or
pred = "getGuard" and result = this.getPatternGuard()
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

pred = "getPatternGuard" (or getCondition, if you agree with my suggestion above).

Loading

TVariableReferencePattern;

private class TPattern =
TPatternNode or TLiteral or TLambda or TConstantAccess or TLocalVariableAccess or
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

Why is TLambda included?

Loading

Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

I see that you have a test case in -> x { x == 10 }, so I guess that is why. But there are no sub classes of CasePattern that correspond to this case; should there be?

Loading

@@ -223,6 +223,40 @@ private class FalseLiteral extends BooleanLiteral, TFalseLiteral {
final override predicate isFalse() { any() }
}

/**
* The `__ENCODING__` literal.
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

An? And same for the two others below.

Loading

or
casePattern(g)
or
exists(Ruby::ArrayPattern p | g = p.getClass())
or
exists(Ruby::FindPattern p | g = p.getClass())
or
exists(Ruby::HashPattern p | g = p.getClass())
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

This logic is duplicated further down, so perhaps put into separate predicate.

Loading

@@ -422,6 +422,189 @@ class WhenExpr extends Expr, TWhenExpr {
}
}

/**
* A `case` statement used for pattern matching.
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

Include an example.

Loading

/**
* A `case` statement used for pattern matching.
*/
class CaseMatch extends ControlExpr, TCaseMatch {
Copy link
Contributor

@hvitved hvitved Nov 24, 2021

Choose a reason for hiding this comment

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

I was wondering whether CaseMatch and CaseExpr should be combined, but it looks like you can't actually combine when matching and pattern matching, e.g.

case foo
in 2
  puts "2"
when 42
  puts "42"
end

Still, I would expect when matching to be just a simple form of pattern matching.

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

3 participants