Skip to content

Commit

Permalink
Patterns parsing: track when variable patterns are in an assignment c…
Browse files Browse the repository at this point in the history
…ontext.

Variable patterns behave so differently inside a patternAssignment
that we may want to represent them using different AST nodes inside
the analyzer/CFE.  This change adds a boolean flag allowing the
implementation to know what kind of variable pattern it's looking at
when parsing occurs.

Bug: #50035
Change-Id: I60adf2865bbe24f85b72a79b1360833bf823bd67
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/273829
Reviewed-by: Konstantin Shcheglov <[email protected]>
Commit-Queue: Paul Berry <[email protected]>
Reviewed-by: Jens Johansen <[email protected]>
  • Loading branch information
stereotype441 authored and Commit Queue committed Dec 7, 2022
1 parent aea639b commit 38cab10
Show file tree
Hide file tree
Showing 517 changed files with 2,038 additions and 2,017 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1778,8 +1778,10 @@ class ForwardingListener implements Listener {
}

@override
void handleVariablePattern(Token? keyword, Token variable) {
listener?.handleVariablePattern(keyword, variable);
void handleVariablePattern(Token? keyword, Token variable,
{required bool inAssignmentPattern}) {
listener?.handleVariablePattern(keyword, variable,
inAssignmentPattern: inAssignmentPattern);
}

@override
Expand Down
7 changes: 6 additions & 1 deletion pkg/_fe_analyzer_shared/lib/src/parser/listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1433,7 +1433,12 @@ class Listener implements UnescapeErrorListener {
/// Called after the parser has consumed a variable pattern, consisting of an
/// optional `var` or `final` keyword, an optional type annotation, and a
/// variable name identifier.
void handleVariablePattern(Token? keyword, Token variable) {
///
/// The flag [inAssignmentPattern] indicates whether this variable pattern is
/// part of a `patternAssignment` (and hence should refer to a previously
/// declared variable rather than declaring a fresh one).
void handleVariablePattern(Token? keyword, Token variable,
{required bool inAssignmentPattern}) {
logEvent('VariablePattern');
}

Expand Down
107 changes: 58 additions & 49 deletions pkg/_fe_analyzer_shared/lib/src/parser/parser_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6298,7 +6298,7 @@ class Parser {
Token next = token.next!;
if (allowPatterns && optional('case', next)) {
Token case_ = token = next;
token = parsePattern(token, isRefutableContext: true);
token = parsePattern(token, PatternContext.matching);
next = token.next!;
Token? when;
if (optional('when', next)) {
Expand Down Expand Up @@ -7459,7 +7459,7 @@ class Parser {
}
if (forPartsContext != null) {
forPartsContext.patternKeyword = varFinalOrConst;
return parsePattern(beforeType, isRefutableContext: false);
return parsePattern(beforeType, PatternContext.declaration);
} else {
return parsePatternVariableDeclarationStatement(
beforeType, start, varFinalOrConst);
Expand Down Expand Up @@ -8371,7 +8371,7 @@ class Parser {
}
listener.beginCaseExpression(caseKeyword);
if (allowPatterns) {
token = parsePattern(caseKeyword, isRefutableContext: true);
token = parsePattern(caseKeyword, PatternContext.matching);
} else {
token = parseExpression(caseKeyword);
}
Expand Down Expand Up @@ -9234,20 +9234,13 @@ class Parser {
/// nullAssertPattern ::= primaryPattern '!'
/// nullCheckPattern ::= primaryPattern '?'
///
/// [isRefutableContext] should be `true` if the pattern occurs in a
/// `guardedPattern` or any of its sub-patterns (i.e. in a
/// `switchStatementCase`, `switchExpressionCase`, or `ifCondition`); these
/// are contexts where a pattern match might be expected to fail, and bare
/// identifiers are treated as constant patterns. It should be `false` if the
/// pattern occurs in a `localVariableDeclaration`, `forLoopParts`, or
/// `patternAssignment`; these are contexts where a pattern match failure is
/// either prohibited statically or causes a runtime exception, and bare
/// identifiers are treated as variable patterns.
Token parsePattern(Token token,
{int precedence = 1, required bool isRefutableContext}) {
/// [patternContext] indicates whether the pattern is refutable or
/// irrefutable, and whether it occurs as part of a patternAssignment.
Token parsePattern(Token token, PatternContext patternContext,
{int precedence = 1}) {
assert(precedence >= 1);
assert(precedence <= SELECTOR_PRECEDENCE);
token = parsePrimaryPattern(token, isRefutableContext: isRefutableContext);
token = parsePrimaryPattern(token, patternContext);
while (true) {
Token next = token.next!;
int tokenLevel = _computePrecedence(next, forPattern: true);
Expand Down Expand Up @@ -9278,9 +9271,8 @@ class Parser {
case '||':
listener.beginBinaryPattern(next);
// Left associative so we parse the RHS one precedence level higher
token = parsePattern(next,
precedence: tokenLevel + 1,
isRefutableContext: isRefutableContext);
token =
parsePattern(next, patternContext, precedence: tokenLevel + 1);
listener.endBinaryPattern(next);
break;
default:
Expand Down Expand Up @@ -9317,7 +9309,7 @@ class Parser {
/// | 'const' typeArguments? '{' elements? '}'
/// | 'const' '(' expression ')'
/// objectPattern ::= typeName typeArguments? '(' patternFields? ')'
Token parsePrimaryPattern(Token token, {required bool isRefutableContext}) {
Token parsePrimaryPattern(Token token, PatternContext patternContext) {
Token start = token;
TypeParamOrArgInfo typeArg =
computeTypeParamOrArg(token, /* inDeclaration = */ true);
Expand All @@ -9327,8 +9319,7 @@ class Parser {
case '[':
// listPattern ::= typeArguments? '[' patterns? ']'
token = typeArg.parseArguments(token, this);
token = parseListPatternSuffix(token,
isRefutableContext: isRefutableContext);
token = parseListPatternSuffix(token, patternContext);
// A list pattern is a valid form of outerPattern, so verify that
// skipOuterPattern would have skipped this pattern properly.
assert(
Expand All @@ -9339,8 +9330,7 @@ class Parser {
// mapPatternEntries ::= mapPatternEntry ( ',' mapPatternEntry )* ','?
// mapPatternEntry ::= expression ':' pattern
token = typeArg.parseArguments(token, this);
token = parseMapPatternSuffix(token,
isRefutableContext: isRefutableContext);
token = parseMapPatternSuffix(token, patternContext);
// A map pattern is a valid form of outerPattern, so verify that
// skipOuterPattern would have skipped this pattern properly.
assert(
Expand All @@ -9355,7 +9345,7 @@ class Parser {
case 'var':
case 'final':
// variablePattern ::= ( 'var' | 'final' | 'final'? type )? identifier
return parseVariablePattern(token);
return parseVariablePattern(token, patternContext);
case '(':
// parenthesizedPattern ::= '(' pattern ')'
// recordPattern ::= '(' patternFields? ')'
Expand All @@ -9366,8 +9356,8 @@ class Parser {
listener.handleRecordPattern(next, /* count = */ 0);
token = nextNext;
} else {
token = parseParenthesizedPatternOrRecordPattern(token,
isRefutableContext: isRefutableContext);
token =
parseParenthesizedPatternOrRecordPattern(token, patternContext);
}
// A record or parenthesized pattern is a valid form of outerPattern, so
// verify that skipOuterPattern would have skipped this pattern
Expand Down Expand Up @@ -9412,7 +9402,7 @@ class Parser {
}
TypeInfo typeInfo = computeVariablePatternType(token);
if (typeInfo != noType) {
return parseVariablePattern(token, typeInfo: typeInfo);
return parseVariablePattern(token, patternContext, typeInfo: typeInfo);
}
// objectPattern ::= typeName typeArguments? '(' patternFields? ')'
// TODO(paulberry): Make sure OTHER_IDENTIFIER is handled
Expand Down Expand Up @@ -9441,8 +9431,7 @@ class Parser {
if (optional('(', afterToken) && !potentialTypeArg.recovered) {
TypeParamOrArgInfo typeArg = potentialTypeArg;
token = typeArg.parseArguments(token, this);
token = parseObjectPatternRest(token,
isRefutableContext: isRefutableContext);
token = parseObjectPatternRest(token, patternContext);
listener.handleObjectPattern(firstIdentifier, dot, secondIdentifier);
// An object pattern is a valid form of outerPattern, so verify that
// skipOuterPattern would have skipped this pattern properly.
Expand All @@ -9452,10 +9441,10 @@ class Parser {
} else if (dot == null) {
// It's a single identifier. If it's a wildcard pattern or we're in an
// irrefutable context, parse it as a variable pattern.
if (!isRefutableContext || firstIdentifier.lexeme == '_') {
if (!patternContext.isRefutable || firstIdentifier.lexeme == '_') {
// It's a wildcard pattern with no preceding type, so parse it as a
// variable pattern.
return parseVariablePattern(beforeFirstIdentifier,
return parseVariablePattern(beforeFirstIdentifier, patternContext,
typeInfo: typeInfo);
}
}
Expand All @@ -9474,7 +9463,8 @@ class Parser {
/// about the type appearing after [token], if any.
///
/// variablePattern ::= ( 'var' | 'final' | 'final'? type )? identifier
Token parseVariablePattern(Token token, {TypeInfo typeInfo = noType}) {
Token parseVariablePattern(Token token, PatternContext patternContext,
{TypeInfo typeInfo = noType}) {
Token? keyword;
if (typeInfo != noType) {
token = typeInfo.parseType(token, this);
Expand All @@ -9500,16 +9490,16 @@ class Parser {
token = insertSyntheticIdentifier(
token, IdentifierContext.localVariableDeclaration);
}
listener.handleVariablePattern(keyword, token);
listener.handleVariablePattern(keyword, token,
inAssignmentPattern: patternContext == PatternContext.assignment);
return token;
}

/// This method parses the portion of a list pattern starting with the left
/// bracket.
///
/// listPattern ::= typeArguments? '[' patterns? ']'
Token parseListPatternSuffix(Token token,
{required bool isRefutableContext}) {
Token parseListPatternSuffix(Token token, PatternContext patternContext) {
Token beforeToken = token;
Token beginToken = token = token.next!;
assert(optional('[', token) || optional('[]', token));
Expand Down Expand Up @@ -9537,11 +9527,11 @@ class Parser {
next = token.next!;
bool hasSubPattern = looksLikePatternStart(next);
if (hasSubPattern) {
token = parsePattern(token, isRefutableContext: isRefutableContext);
token = parsePattern(token, patternContext);
}
listener.handleRestPattern(dots, hasSubPattern: hasSubPattern);
} else {
token = parsePattern(token, isRefutableContext: isRefutableContext);
token = parsePattern(token, patternContext);
}
next = token.next!;
++count;
Expand Down Expand Up @@ -9585,7 +9575,7 @@ class Parser {
/// mapPattern ::= typeArguments? '{' mapPatternEntries? '}'
/// mapPatternEntries ::= mapPatternEntry ( ',' mapPatternEntry )* ','?
/// mapPatternEntry ::= expression ':' pattern
Token parseMapPatternSuffix(Token token, {required bool isRefutableContext}) {
Token parseMapPatternSuffix(Token token, PatternContext patternContext) {
Token leftBrace = token = token.next!;
assert(optional('{', leftBrace));
Token next = token.next!;
Expand All @@ -9604,7 +9594,7 @@ class Parser {
next = token.next!;
bool hasSubPattern = looksLikePatternStart(next);
if (hasSubPattern) {
token = parsePattern(token, isRefutableContext: isRefutableContext);
token = parsePattern(token, patternContext);
}
listener.handleRestPattern(dots, hasSubPattern: hasSubPattern);
} else {
Expand All @@ -9618,7 +9608,7 @@ class Parser {
codes.templateExpectedButGot.withArguments(':'),
new SyntheticToken(TokenType.PERIOD, next.charOffset));
}
token = parsePattern(colon, isRefutableContext: isRefutableContext);
token = parsePattern(colon, patternContext);
listener.handleMapPatternEntry(colon, token.next!);
}
++count;
Expand Down Expand Up @@ -9664,8 +9654,8 @@ class Parser {
/// recordPattern ::= '(' patternFields? ')'
/// patternFields ::= patternField ( ',' patternField )* ','?
/// patternField ::= ( identifier? ':' )? pattern
Token parseParenthesizedPatternOrRecordPattern(Token token,
{required bool isRefutableContext}) {
Token parseParenthesizedPatternOrRecordPattern(
Token token, PatternContext patternContext) {
Token begin = token.next!;
assert(optional('(', begin));
bool old = mayParseFunctionExpressions;
Expand Down Expand Up @@ -9698,7 +9688,7 @@ class Parser {
colon = token;
wasValidRecord = true;
}
token = parsePattern(token, isRefutableContext: isRefutableContext);
token = parsePattern(token, patternContext);
next = token.next!;
if (wasRecord || colon != null) {
listener.handlePatternField(colon);
Expand Down Expand Up @@ -9741,8 +9731,7 @@ class Parser {
/// `(`.
///
/// objectPattern ::= typeName typeArguments? '(' patternFields? ')'
Token parseObjectPatternRest(Token token,
{required bool isRefutableContext}) {
Token parseObjectPatternRest(Token token, PatternContext patternContext) {
Token begin = token = token.next!;
assert(optional('(', begin));
int argumentCount = 0;
Expand All @@ -9764,7 +9753,7 @@ class Parser {
.next!;
colon = token;
}
token = parsePattern(token, isRefutableContext: isRefutableContext);
token = parsePattern(token, patternContext);
next = token.next!;
listener.handlePatternField(colon);
++argumentCount;
Expand Down Expand Up @@ -9869,7 +9858,7 @@ class Parser {
/// expression
Token parsePatternVariableDeclarationStatement(
Token keyword, Token start, Token varOrFinal) {
Token token = parsePattern(keyword, isRefutableContext: false);
Token token = parsePattern(keyword, PatternContext.declaration);
Token equals = token.next!;
// Caller should have assured that the pattern was followed by an `=`.
assert(optional('=', equals));
Expand All @@ -9882,7 +9871,7 @@ class Parser {

/// patternAssignment ::= outerPattern '=' expression
Token parsePatternAssignment(Token token) {
token = parsePattern(token, isRefutableContext: false);
token = parsePattern(token, PatternContext.assignment);
Token equals = token.next!;
// Caller should have assured that the pattern was followed by an `=`.
assert(optional('=', equals));
Expand Down Expand Up @@ -9911,7 +9900,7 @@ class Parser {
mayParseFunctionExpressions = false;
while (true) {
listener.beginSwitchExpressionCase();
token = parsePattern(token, isRefutableContext: true);
token = parsePattern(token, PatternContext.matching);
Token? when;
next = token.next!;
if (optional('when', next)) {
Expand Down Expand Up @@ -9980,3 +9969,23 @@ class ForPartsContext {
@override
String toString() => 'ForPartsContext($patternKeyword)';
}

/// Enum describing the different contexts in which a pattern can occur.
enum PatternContext {
/// The pattern is part of a localVariableDeclaration or forLoopParts, meaning
/// bare identifiers refer to freshly declared variables.
declaration(isRefutable: false),

/// The pattern is part of a guardedPattern inside an if-case, switch
/// expression, or switch statement, meaning bare identifiers refer to
/// constants.
matching(isRefutable: true),

/// The pattern is part of a pattern assignment, meaning bare identifiers
/// refer to previously declared variables.
assignment(isRefutable: false);

final bool isRefutable;

const PatternContext({required this.isRefutable});
}
4 changes: 3 additions & 1 deletion pkg/analyzer/lib/src/fasta/ast_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5274,7 +5274,9 @@ class AstBuilder extends StackListener {
}

@override
void handleVariablePattern(Token? keyword, Token variable) {
void handleVariablePattern(Token? keyword, Token variable,
{required bool inAssignmentPattern}) {
// TODO(paulberry, scheglov): use inAssignmentPattern
debugEvent('VariablePattern');
if (!_featureSet.isEnabled(Feature.patterns)) {
// TODO(paulberry): report the appropriate error
Expand Down
4 changes: 3 additions & 1 deletion pkg/front_end/lib/src/fasta/kernel/body_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8428,7 +8428,8 @@ class BodyBuilder extends StackListenerImpl
}

@override
void handleVariablePattern(Token? keyword, Token variable) {
void handleVariablePattern(Token? keyword, Token variable,
{required bool inAssignmentPattern}) {
debugEvent('VariablePattern');
assert(checkState(keyword ?? variable, [
ValueKinds.TypeBuilderOrNull,
Expand All @@ -8443,6 +8444,7 @@ class BodyBuilder extends StackListenerImpl
if (variable.lexeme == "_") {
pattern = new WildcardPattern(patternType, variable.charOffset);
} else {
// TODO(paulberry): use inAssignmentPattern.
pattern = new VariablePattern(
patternType,
variable.lexeme,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2032,7 +2032,8 @@ class _MacroListener implements Listener {
}

@override
void handleVariablePattern(Token? keyword, Token variable) {
void handleVariablePattern(Token? keyword, Token variable,
{required bool inAssignmentPattern}) {
_unsupported();
}

Expand Down
Loading

0 comments on commit 38cab10

Please sign in to comment.