Fix error reporting and recovery for declared variable in pattern assignment.

If a declared variable occurs inside a pattern assignment (e.g. `[a,
var b] = c`), we now recover using the `DeclaredVariablePattern` AST
node.  Normally, a `DeclaredVariablePattern` wouldn't appear inside a
pattern assignment, but it's better to recover in this way because it
avoids dropping the keyword token (`var` or `final`) or type.

Fixes #50927.
Fixes #51529.

Bug: https://github.com/dart-lang/sdk/issues/50927, https://github.com/dart-lang/sdk/issues/51529
Change-Id: Ia65853baeae0e8eb999b7bd82fb2455e10b2b996
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291044
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2023-03-24 20:14:49 +00:00 committed by Commit Queue
parent f4bbc427c3
commit 2c16ff8827
10 changed files with 155 additions and 4 deletions

View file

@ -10921,8 +10921,8 @@ const Template<
const Code<Message Function(String name)>
codePatternAssignmentDeclaresVariable =
const Code<Message Function(String name)>(
"PatternAssignmentDeclaresVariable",
);
"PatternAssignmentDeclaresVariable",
index: 145);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsPatternAssignmentDeclaresVariable(String name) {

View file

@ -2384,6 +2384,8 @@ ParserErrorCode.NULL_AWARE_CASCADE_OUT_OF_ORDER:
status: needsEvaluation
ParserErrorCode.OUT_OF_ORDER_CLAUSES:
status: needsEvaluation
ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE:
status: needsEvaluation
ParserErrorCode.POSITIONAL_AFTER_NAMED_ARGUMENT:
status: needsEvaluation
ParserErrorCode.POSITIONAL_PARAMETER_OUTSIDE_GROUP:

View file

@ -159,6 +159,7 @@ final fastaAnalyzerErrorCodes = <ErrorCode?>[
ParserErrorCode.FINAL_MIXIN_CLASS,
ParserErrorCode.INTERFACE_MIXIN_CLASS,
ParserErrorCode.SEALED_MIXIN_CLASS,
ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE,
];
class ParserErrorCode extends ErrorCode {
@ -1506,6 +1507,15 @@ class ParserErrorCode extends ErrorCode {
correctionMessage: "Try moving the '{0}' clause before the '{1}' clause.",
);
static const ParserErrorCode PATTERN_ASSIGNMENT_DECLARES_VARIABLE =
ParserErrorCode(
'PATTERN_ASSIGNMENT_DECLARES_VARIABLE',
"Variable '{0}' can't be declared in a pattern assignment.",
correctionMessage:
"Try using a preexisting variable or changing the assignment to a "
"pattern variable declaration.",
);
static const ParserErrorCode POSITIONAL_AFTER_NAMED_ARGUMENT =
ParserErrorCode(
'POSITIONAL_AFTER_NAMED_ARGUMENT',

View file

@ -1070,6 +1070,17 @@ class ResolutionVisitor extends RecursiveAstVisitor<void> {
});
}
@override
void visitPatternAssignment(PatternAssignment node) {
// We need to call `casePatternStart` and `casePatternFinish` in case there
// are any declared variable patterns inside the pattern assignment (this
// could happen due to error recovery). But we don't need to keep the
// variables map that `casePatternFinish` returns.
_patternVariables.casePatternStart();
super.visitPatternAssignment(node);
_patternVariables.casePatternFinish();
}
@override
void visitPatternVariableDeclaration(
covariant PatternVariableDeclarationImpl node,

View file

@ -818,6 +818,7 @@ const List<ErrorCode> errorCodeValues = [
ParserErrorCode.NORMAL_BEFORE_OPTIONAL_PARAMETERS,
ParserErrorCode.NULL_AWARE_CASCADE_OUT_OF_ORDER,
ParserErrorCode.OUT_OF_ORDER_CLAUSES,
ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE,
ParserErrorCode.POSITIONAL_AFTER_NAMED_ARGUMENT,
ParserErrorCode.POSITIONAL_PARAMETER_OUTSIDE_GROUP,
ParserErrorCode.PREFIX_AFTER_COMBINATOR,

View file

@ -32,7 +32,8 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
templateExpectedIdentifier,
templateExperimentNotEnabled,
templateExtraneousModifier,
templateInternalProblemUnhandled;
templateInternalProblemUnhandled,
templatePatternAssignmentDeclaresVariable;
import 'package:_fe_analyzer_shared/src/parser/parser.dart'
show
Assert,
@ -5502,6 +5503,18 @@ class AstBuilder extends StackListener {
throw UnimplementedError('Patterns not enabled');
}
var type = pop() as TypeAnnotationImpl?;
if (inAssignmentPattern && (type != null || keyword != null)) {
// TODO(paulberry): Consider generating this error in the parser
// This error is also reported in the body builder
handleRecoverableError(
templatePatternAssignmentDeclaresVariable
.withArguments(variable.lexeme),
variable,
variable);
// To ensure that none of the tokens are dropped from the AST, don't build
// an `AssignedVariablePatternImpl`.
inAssignmentPattern = false;
}
if (variable.lexeme == '_') {
push(
WildcardPatternImpl(

View file

@ -2926,6 +2926,86 @@ NullCheckPattern
''');
}
test_declaredVariable_inPatternAssignment_usingFinal() {
_parse('''
void f() {
[a, final d] = y;
}
''', errors: [
error(ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE, 23, 1),
]);
var node = findNode.patternAssignment('=');
assertParsedNodeText(node, r'''
PatternAssignment
pattern: ListPattern
leftBracket: [
elements
AssignedVariablePattern
name: a
DeclaredVariablePattern
keyword: final
name: d
rightBracket: ]
equals: =
expression: SimpleIdentifier
token: y
''');
}
test_declaredVariable_inPatternAssignment_usingType() {
_parse('''
void f() {
[a, int d] = y;
}
''', errors: [
error(ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE, 21, 1),
]);
var node = findNode.patternAssignment('=');
assertParsedNodeText(node, r'''
PatternAssignment
pattern: ListPattern
leftBracket: [
elements
AssignedVariablePattern
name: a
DeclaredVariablePattern
type: NamedType
name: SimpleIdentifier
token: int
name: d
rightBracket: ]
equals: =
expression: SimpleIdentifier
token: y
''');
}
test_declaredVariable_inPatternAssignment_usingVar() {
_parse('''
void f() {
[a, var d] = y;
}
''', errors: [
error(ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE, 21, 1),
]);
var node = findNode.patternAssignment('=');
assertParsedNodeText(node, r'''
PatternAssignment
pattern: ListPattern
leftBracket: [
elements
AssignedVariablePattern
name: a
DeclaredVariablePattern
keyword: var
name: d
rightBracket: ]
equals: =
expression: SimpleIdentifier
token: y
''');
}
test_errorRecovery_afterQuestionSuffixInExpression() {
// Based on co19 test `Language/Expressions/Conditional/syntax_t06.dart`.
// Even though we now support suffix `?` in patterns, we need to make sure

View file

@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@ -339,6 +340,38 @@ PatternAssignment
''');
}
test_declaredVariable_inPatternAssignment_referenced() async {
// Note: the error is reporting during parsing but we test it here to make
// sure that error recovery produces an AST that can be analyzed without
// crashing.
await assertErrorsInCode(r'''
void f(a, y) {
[a, var d] = y;
d;
}
''', [
// The reference doesn't resolve so the errors include
// UNUSED_LOCAL_VARIABLE and UNDEFINED_IDENTIFIER.
error(ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE, 25, 1),
error(HintCode.UNUSED_LOCAL_VARIABLE, 25, 1),
error(CompileTimeErrorCode.UNDEFINED_IDENTIFIER, 35, 1),
]);
}
test_declaredVariable_inPatternAssignment_unreferenced() async {
// Note: the error is reporting during parsing but we test it here to make
// sure that error recovery produces an AST that can be analyzed without
// crashing.
await assertErrorsInCode(r'''
void f(a, y) {
[a, var d] = y;
}
''', [
error(ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE, 25, 1),
error(HintCode.UNUSED_LOCAL_VARIABLE, 25, 1),
]);
}
test_final_becomesDefinitelyAssigned() async {
await assertErrorsInCode(r'''
void f() {

View file

@ -847,7 +847,6 @@ PatchInjectionFailed/analyzerCode: Fail
PatchInjectionFailed/example: Fail
PatchNonExternal/analyzerCode: Fail
PatchNonExternal/example: Fail
PatternAssignmentDeclaresVariable/analyzerCode: Fail
PatternMatchingError/analyzerCode: Fail
PatternMatchingError/example: Fail
PlatformPrivateLibraryAccess/example: Fail

View file

@ -6662,6 +6662,8 @@ InvalidConstantPatternBinary:
PatternAssignmentDeclaresVariable:
problemMessage: "Variable '#name' can't be declared in a pattern assignment."
correctionMessage: "Try using a preexisting variable or changing the assignment to a pattern variable declaration."
analyzerCode: ParserErrorCode.PATTERN_ASSIGNMENT_DECLARES_VARIABLE
index: 145
experiments: patterns
script: |
method(x) {