Analyzer/FE integration for writes to read-only variables.
Previously, when the user attempted to write to a read-only variable,
the front end creates a SyntheticExpressionJudgment to represent the
write. This CL changes it to a new class,
InvalidVariableWriteJudgment, whose `infer` method sends the
appropriate resolution information to the analyzer. Also, it ensures
that inference is performed on the RHS of the assignment, so that it
gets resolved too.
Partially addresses #33694.
Change-Id: I56b12e5082434ca8569d15df1b01e5aaad8f0a7d
Reviewed-on: https://dart-review.googlesource.com/63160
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/test/generated/compile_time_error_code_kernel_test.dart b/pkg/analyzer/test/generated/compile_time_error_code_kernel_test.dart
index a025360..6c1ab27 100644
--- a/pkg/analyzer/test/generated/compile_time_error_code_kernel_test.dart
+++ b/pkg/analyzer/test/generated/compile_time_error_code_kernel_test.dart
@@ -2333,34 +2333,6 @@
@override
@failingTest
- test_prefix_assignment_compound_in_method() async {
- // Bad state: No reference information for p at 46
- await super.test_prefix_assignment_compound_in_method();
- }
-
- @override
- @failingTest
- test_prefix_assignment_compound_not_in_method() async {
- // Bad state: No reference information for p at 32
- await super.test_prefix_assignment_compound_not_in_method();
- }
-
- @override
- @failingTest
- test_prefix_assignment_in_method() async {
- // Bad state: No reference information for p at 46
- await super.test_prefix_assignment_in_method();
- }
-
- @override
- @failingTest
- test_prefix_assignment_not_in_method() async {
- // Bad state: No reference information for p at 32
- await super.test_prefix_assignment_not_in_method();
- }
-
- @override
- @failingTest
test_prefix_conditionalPropertyAccess_call() async {
// Bad state: Expected element reference for analyzer offset 32; got one for kernel offset 35
await super.test_prefix_conditionalPropertyAccess_call();
@@ -2445,13 +2417,6 @@
@override
@failingTest
- test_prefixNotFollowedByDot_compoundAssignment() async {
- // Bad state: No reference information for p at 32
- await super.test_prefixNotFollowedByDot_compoundAssignment();
- }
-
- @override
- @failingTest
test_prefixNotFollowedByDot_conditionalMethodInvocation() async {
// Bad state: Expected element reference for analyzer offset 32; got one for kernel offset 35
await super.test_prefixNotFollowedByDot_conditionalMethodInvocation();
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart
index f7f2ce2..883c655 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_resolution_test.dart
@@ -637,6 +637,27 @@
}
}
+ test_assignment_to_final_variable_local() async {
+ var content = '''
+main() {
+ final x = 1;
+ x += 2;
+}
+''';
+ addTestFile(content);
+ AnalysisResult result = await driver.getResult(testFile);
+ expect(result.errors, isNotEmpty);
+
+ var xDeclaration = new NodeLocator(content.indexOf('x ='))
+ .searchWithin(result.unit) as SimpleIdentifier;
+ var xElement = xDeclaration.staticElement;
+ expect(xElement, isNotNull);
+ var xReference = new NodeLocator(content.indexOf('x +='))
+ .searchWithin(result.unit) as SimpleIdentifier;
+ expect(xReference.staticElement, same(xElement));
+ expect(xReference.staticType.toString(), 'int');
+ }
+
test_assignmentExpression_compound_indexExpression() async {
String content = r'''
main() {
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
index 24590c7..175791d 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_generated.dart
@@ -5612,6 +5612,7 @@
const Code<Message Function(String name)> codeSetterNotFound =
const Code<Message Function(String name)>(
"SetterNotFound", templateSetterNotFound,
+ analyzerCode: "UNDEFINED_SETTER",
severity: Severity.errorLegacyWarning);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index 40571ac..394dfb6 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -297,8 +297,8 @@
} else if (node is Expression) {
return node;
} else if (node is SuperInitializer) {
- return buildCompileTimeError(
- fasta.messageSuperAsExpression, node.fileOffset, noLength);
+ return new SyntheticExpressionJudgment(buildCompileTimeError(
+ fasta.messageSuperAsExpression, node.fileOffset, noLength));
} else if (node is ProblemBuilder) {
return buildProblemExpression(node, -1, noLength);
} else {
@@ -376,9 +376,9 @@
int offset = variable.fileOffset;
Message message = template.withArguments(name);
if (variable.initializer == null) {
- variable.initializer =
- buildCompileTimeError(message, offset, name.length, context: context)
- ..parent = variable;
+ variable.initializer = new SyntheticExpressionJudgment(
+ buildCompileTimeError(message, offset, name.length, context: context))
+ ..parent = variable;
} else {
variable.initializer = wrapInLocatedCompileTimeError(
variable.initializer, message.withLocation(uri, offset, name.length),
@@ -1094,8 +1094,8 @@
pop();
token = token.next;
Message message = fasta.templateExpectedIdentifier.withArguments(token);
- push(buildCompileTimeError(
- message, offsetForToken(token), lengthForToken(token)));
+ push(new SyntheticExpressionJudgment(buildCompileTimeError(
+ message, offsetForToken(token), lengthForToken(token))));
}
}
@@ -1108,8 +1108,8 @@
pop();
token = token.next;
Message message = fasta.templateExpectedIdentifier.withArguments(token);
- push(buildCompileTimeError(
- message, offsetForToken(token), lengthForToken(token)));
+ push(new SyntheticExpressionJudgment(buildCompileTimeError(
+ message, offsetForToken(token), lengthForToken(token))));
}
}
@@ -1183,7 +1183,7 @@
isSetter: isSetter,
isStatic: isStatic,
isTopLevel: !isStatic && !isSuper);
- return new SyntheticExpressionJudgment(new Throw(error));
+ return new Throw(error);
}
}
@@ -1803,8 +1803,10 @@
Expression value = popForValue();
Object generator = pop();
if (generator is! Generator) {
- push(buildCompileTimeError(fasta.messageNotAnLvalue,
- offsetForToken(token), lengthForToken(token)));
+ push(new SyntheticExpressionJudgment(buildCompileTimeError(
+ fasta.messageNotAnLvalue,
+ offsetForToken(token),
+ lengthForToken(token))));
} else {
push(new DelayedAssignment(
this, token, generator, value, token.stringValue));
@@ -2610,13 +2612,13 @@
LocatedMessage argMessage = checkArgumentsForFunction(
target.function, arguments, charOffset, typeParameters);
if (argMessage != null) {
- return throwNoSuchMethodError(
+ return new SyntheticExpressionJudgment(throwNoSuchMethodError(
forest.literalNull(null)..fileOffset = charOffset,
target.name.name,
arguments,
charOffset,
candidate: target,
- argMessage: argMessage);
+ argMessage: argMessage));
}
if (target is Constructor) {
isConst =
@@ -2788,8 +2790,11 @@
push(type.invokeConstructor(
typeArguments, name, arguments, nameToken, constness));
} else {
- push(throwNoSuchMethodError(forest.literalNull(null)..fileOffset = offset,
- debugName(getNodeName(type), name), arguments, nameToken.charOffset));
+ push(new SyntheticExpressionJudgment(throwNoSuchMethodError(
+ forest.literalNull(null)..fileOffset = offset,
+ debugName(getNodeName(type), name),
+ arguments,
+ nameToken.charOffset)));
}
constantContext = savedConstantContext;
}
@@ -2894,11 +2899,11 @@
nameToken = nameToken.next.next;
}
- return throwNoSuchMethodError(
+ return new SyntheticExpressionJudgment(throwNoSuchMethodError(
forest.literalNull(null)..fileOffset = charOffset,
errorName,
arguments,
- nameToken.charOffset);
+ nameToken.charOffset));
}
@override
@@ -3206,8 +3211,9 @@
? fasta.messageForInLoopExactlyOneVariable
: fasta.messageForInLoopNotAssignable;
Token token = forToken.next.next;
- variable = new VariableDeclaration.forValue(buildCompileTimeError(
- message, offsetForToken(token), lengthForToken(token)));
+ variable = new VariableDeclaration.forValue(
+ new SyntheticExpressionJudgment(buildCompileTimeError(
+ message, offsetForToken(token), lengthForToken(token))));
}
Statement result = new ForInJudgment(
awaitToken,
@@ -3752,8 +3758,8 @@
@override
Expression deprecated_buildCompileTimeError(String error,
[int charOffset = -1]) {
- return buildCompileTimeError(
- fasta.templateUnspecified.withArguments(error), charOffset, noLength);
+ return new SyntheticExpressionJudgment(buildCompileTimeError(
+ fasta.templateUnspecified.withArguments(error), charOffset, noLength));
}
@override
@@ -3761,9 +3767,8 @@
{List<LocatedMessage> context}) {
library.addCompileTimeError(message, charOffset, length, uri,
wasHandled: true, context: context);
- return new SyntheticExpressionJudgment(library.loader
- .throwCompileConstantError(library.loader
- .buildCompileTimeError(message, charOffset, length, uri)));
+ return library.loader.throwCompileConstantError(
+ library.loader.buildCompileTimeError(message, charOffset, length, uri));
}
Expression wrapInCompileTimeError(Expression expression, Message message,
@@ -3780,9 +3785,10 @@
// TODO(askesc): Produce explicit error expression wrapping the original.
// See [issue 29717](https://github.com/dart-lang/sdk/issues/29717)
return new SyntheticExpressionJudgment(new Let(
- new VariableDeclaration.forValue(buildCompileTimeError(
- message.messageObject, message.charOffset, message.length,
- context: context))
+ new VariableDeclaration.forValue(new SyntheticExpressionJudgment(
+ buildCompileTimeError(
+ message.messageObject, message.charOffset, message.length,
+ context: context)))
..fileOffset = forest.readOffset(expression),
new Let(
new VariableDeclaration.forValue(expression)
@@ -3835,7 +3841,9 @@
Statement buildCompileTimeErrorStatement(Message message, int charOffset,
{List<LocatedMessage> context}) {
return new ExpressionStatementJudgment(
- buildCompileTimeError(message, charOffset, noLength, context: context),
+ new SyntheticExpressionJudgment(buildCompileTimeError(
+ message, charOffset, noLength,
+ context: context)),
null);
}
@@ -3978,7 +3986,8 @@
@override
Expression buildProblemExpression(
ProblemBuilder builder, int charOffset, int length) {
- return buildCompileTimeError(builder.message, charOffset, length);
+ return new SyntheticExpressionJudgment(
+ buildCompileTimeError(builder.message, charOffset, length));
}
@override
diff --git a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
index 6fb53a5..4a700dc 100644
--- a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
@@ -68,6 +68,7 @@
Member,
Name,
Procedure,
+ SyntheticExpressionJudgment,
TypeParameterType,
VariableDeclaration;
@@ -181,8 +182,8 @@
Initializer buildFieldInitializer(Map<String, int> initializedFields) {
int offset = offsetForToken(token);
return helper.buildInvalidInitializer(
- helper.buildCompileTimeError(
- messageInvalidInitializer, offset, lengthForToken(token)),
+ new SyntheticExpressionJudgment(helper.buildCompileTimeError(
+ messageInvalidInitializer, offset, lengthForToken(token))),
offset);
}
@@ -226,11 +227,11 @@
assert(forest.argumentsTypeArguments(arguments).isEmpty);
forest.argumentsSetTypeArguments(arguments, typeArguments);
}
- return helper.throwNoSuchMethodError(
+ return new SyntheticExpressionJudgment(helper.throwNoSuchMethodError(
forest.literalNull(token),
name == "" ? plainNameForRead : "${plainNameForRead}.$name",
arguments,
- nameToken.charOffset);
+ nameToken.charOffset));
}
bool get isThisPropertyAccess => false;
@@ -640,10 +641,10 @@
String get debugName => "LargeIntAccessGenerator";
Expression buildError() {
- return helper.buildCompileTimeError(
+ return new SyntheticExpressionJudgment(helper.buildCompileTimeError(
templateIntegerLiteralIsOutOfRange.withArguments(token),
offsetForToken(token),
- lengthForToken(token));
+ lengthForToken(token)));
}
@override
@@ -781,13 +782,13 @@
Expression buildError(Arguments arguments,
{bool isGetter: false, bool isSetter: false, int offset}) {
offset ??= offsetForToken(this.token);
- return helper.throwNoSuchMethodError(
+ return new SyntheticExpressionJudgment(helper.throwNoSuchMethodError(
forest.literalNull(null)..fileOffset = offset,
plainNameForRead,
arguments,
offset,
isGetter: isGetter,
- isSetter: isSetter);
+ isSetter: isSetter));
}
@override
@@ -1075,8 +1076,10 @@
@override
Expression makeInvalidRead() {
- return helper.buildCompileTimeError(messageCantUsePrefixAsExpression,
- offsetForToken(token), lengthForToken(token));
+ return new SyntheticExpressionJudgment(helper.buildCompileTimeError(
+ messageCantUsePrefixAsExpression,
+ offsetForToken(token),
+ lengthForToken(token)));
}
@override
@@ -1115,11 +1118,11 @@
@override
Expression doInvocation(int offset, Arguments arguments) {
- return helper.throwNoSuchMethodError(
+ return new SyntheticExpressionJudgment(helper.throwNoSuchMethodError(
forest.literalNull(null)..fileOffset = offset,
plainNameForRead,
arguments,
- offsetForToken(token));
+ offsetForToken(token)));
}
@override
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator.dart
index 1798ae5..084bc9f 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator.dart
@@ -107,6 +107,8 @@
PrefixBuilder,
TypeDeclarationBuilder;
+import 'kernel_shadow_ast.dart' show InvalidVariableWriteJudgment;
+
part 'kernel_expression_generator_impl.dart';
abstract class KernelExpressionGenerator implements ExpressionGenerator {
@@ -214,22 +216,26 @@
@override
Expression makeInvalidRead() {
- return helper.throwNoSuchMethodError(
+ return new SyntheticExpressionJudgment(helper.throwNoSuchMethodError(
forest.literalNull(token),
plainNameForRead,
forest.argumentsEmpty(noLocation),
offsetForToken(token),
- isGetter: true);
+ isGetter: true));
}
@override
Expression makeInvalidWrite(Expression value) {
- return helper.throwNoSuchMethodError(
+ return buildInvalidWriteJudgment(helper.throwNoSuchMethodError(
forest.literalNull(token),
plainNameForRead,
forest.arguments(<Expression>[value], noLocation),
offsetForToken(token),
- isSetter: true);
+ isSetter: true));
+ }
+
+ Expression buildInvalidWriteJudgment(Expression desugared) {
+ return new SyntheticExpressionJudgment(desugared);
}
Expression _makeSimpleRead() => _makeRead(null);
@@ -1231,13 +1237,13 @@
@override
Expression makeInvalidWrite(Expression value) {
- return helper.throwNoSuchMethodError(
+ return new SyntheticExpressionJudgment(helper.throwNoSuchMethodError(
forest.literalNull(token),
plainNameForRead,
forest.arguments(<Expression>[value], null)
..fileOffset = value.fileOffset,
offsetForToken(token),
- isSetter: true);
+ isSetter: true));
}
@override
@@ -1324,6 +1330,18 @@
super._finish(makeLet(value, body), complexAssignment);
@override
+ Expression buildInvalidWriteJudgment(Expression desugared) {
+ var expression = this.expression;
+ if (expression is VariableGet) {
+ return new InvalidVariableWriteJudgment(desugared, expression.variable)
+ ..fileOffset = token.charOffset;
+ } else {
+ // TODO(paulberry): handle other cases
+ return super.buildInvalidWriteJudgment(desugared);
+ }
+ }
+
+ @override
Expression doInvocation(int offset, Arguments arguments) {
return helper.buildMethodInvocation(buildSimpleRead(), callName, arguments,
adjustForImplicitCall(plainNameForRead, offset),
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator_impl.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator_impl.dart
index d777162..7f5a3dc 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator_impl.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_expression_generator_impl.dart
@@ -36,8 +36,10 @@
if (!isSuper) {
return forest.thisExpression(token);
} else {
- return helper.buildCompileTimeError(messageSuperAsExpression,
- offsetForToken(token), lengthForToken(token));
+ return new SyntheticExpressionJudgment(helper.buildCompileTimeError(
+ messageSuperAsExpression,
+ offsetForToken(token),
+ lengthForToken(token)));
}
}
@@ -91,8 +93,8 @@
if (isInitializer) {
return buildConstructorInitializer(offset, new Name(""), arguments);
} else if (isSuper) {
- return helper.buildCompileTimeError(
- messageSuperAsExpression, offset, noLength);
+ return new SyntheticExpressionJudgment(helper.buildCompileTimeError(
+ messageSuperAsExpression, offset, noLength));
} else {
return helper.buildMethodInvocation(
forest.thisExpression(null), callName, arguments, offset,
@@ -109,13 +111,14 @@
constructor.function, arguments, offset, <TypeParameter>[]);
}
if (constructor == null || argMessage != null) {
- return helper.buildInvalidInitializer(helper.throwNoSuchMethodError(
- forest.literalNull(null)..fileOffset = offset,
- name.name,
- arguments,
- offset,
- isSuper: isSuper,
- argMessage: argMessage));
+ return helper.buildInvalidInitializer(new SyntheticExpressionJudgment(
+ helper.throwNoSuchMethodError(
+ forest.literalNull(null)..fileOffset = offset,
+ name.name,
+ arguments,
+ offset,
+ isSuper: isSuper,
+ argMessage: argMessage)));
} else if (isSuper) {
return helper.buildSuperInitializer(
false, constructor, arguments, offset);
@@ -208,7 +211,8 @@
offset = offsetForToken(token);
length = lengthForToken(token);
}
- return helper.buildCompileTimeError(message, offset, length);
+ return new SyntheticExpressionJudgment(
+ helper.buildCompileTimeError(message, offset, length));
}
@override
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_shadow_ast.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_shadow_ast.dart
index afac249..4f8ff56 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_shadow_ast.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_shadow_ast.dart
@@ -1637,7 +1637,9 @@
/// Concrete shadow object representing an assignment to a target for which
/// assignment is not allowed.
class IllegalAssignmentJudgment extends ComplexAssignmentJudgment {
- IllegalAssignmentJudgment(ExpressionJudgment rhs) : super(rhs);
+ IllegalAssignmentJudgment(ExpressionJudgment rhs) : super(rhs) {
+ rhs.parent = this;
+ }
@override
DartType _getWriteType(ShadowTypeInferrer inferrer) {
@@ -1652,6 +1654,7 @@
if (write != null) {
inferrer.inferExpression(factory, write, const UnknownType(), false);
}
+ inferrer.inferExpression(factory, rhs, const UnknownType(), false);
_replaceWithDesugared();
inferredType = const DynamicType();
return null;
@@ -2906,6 +2909,26 @@
}
}
+/// Synthetic judgment class representing an attempt to write to a read-only
+/// local variable.
+class InvalidVariableWriteJudgment extends SyntheticExpressionJudgment {
+ /// Note: private to avoid colliding with Let.variable.
+ final VariableDeclaration _variable;
+
+ InvalidVariableWriteJudgment(kernel.Expression desugared, this._variable)
+ : super(desugared);
+
+ @override
+ Expression infer<Expression, Statement, Initializer, Type>(
+ ShadowTypeInferrer inferrer,
+ Factory<Expression, Statement, Initializer, Type> factory,
+ DartType typeContext) {
+ inferrer.listener.variableAssign(this, fileOffset, _variable.type,
+ _variable.fileOffset, null, _variable.type);
+ return super.infer(inferrer, factory, typeContext);
+ }
+}
+
/// Shadow object for expressions that are introduced by the front end as part
/// of desugaring or the handling of error conditions.
///
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
index acb161a..2a7dc9e 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
@@ -76,12 +76,13 @@
import '../kernel/kernel_shadow_ast.dart'
show
ArgumentsJudgment,
+ ConstructorInvocationJudgment,
ExpressionJudgment,
NullJudgment,
ShadowClass,
- ConstructorInvocationJudgment,
ShadowField,
ShadowMember,
+ SyntheticExpressionJudgment,
VariableDeclarationJudgment,
getExplicitTypeArguments;
@@ -657,10 +658,10 @@
new Let(
new VariableDeclaration.forValue(receiver)
..fileOffset = receiver.fileOffset,
- helper.buildCompileTimeError(
+ new SyntheticExpressionJudgment(helper.buildCompileTimeError(
errorTemplate.withArguments(name.name, receiverType),
fileOffset,
- noLength))
+ noLength)))
..fileOffset = fileOffset);
}
return interfaceMember;
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 7dd456c..772abe1 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -331,7 +331,7 @@
SdkSpecificationNotFound/example: Fail
SdkSummaryNotFound/analyzerCode: Fail
SdkSummaryNotFound/example: Fail
-SetterNotFound/analyzerCode: Fail
+SetterNotFound/dart2jsCode: Fail
SetterNotFound/example: Fail
SetterNotSync/example: Fail
SetterWithWrongNumberOfFormals/example: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index dbbf02a..4a022c8 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -1136,6 +1136,7 @@
SetterNotFound:
template: "Setter not found: '#name'."
+ analyzerCode: UNDEFINED_SETTER
severity: ERROR_LEGACY_WARNING
MethodNotFound: