Flow analysis: implement "why not promoted" for binary/unary operator target.

Bug: https://github.com/dart-lang/sdk/issues/44898
Change-Id: If464a54bdb63fc661db312df5dc10f108049286a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/196240
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2021-04-22 12:21:46 +00:00 committed by commit-bot@chromium.org
parent 6645753681
commit e3443b9f53
3 changed files with 106 additions and 13 deletions

View file

@ -0,0 +1,32 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// 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.
// This test contains a test case for each condition that can lead to the front
// end's `NullableOperatorCallError` error, for which we wish to report "why not
// promoted" context information.
class C1 {
int? bad;
}
userDefinableBinaryOpLhs(C1 c) {
if (c.bad == null) return;
c.bad
/*cfe.invoke: notPromoted(propertyNotPromoted(target: member:C1.bad, type: int?))*/
/*analyzer.notPromoted(propertyNotPromoted(target: member:C1.bad, type: int?))*/
+
1;
}
class C2 {
int? bad;
}
userDefinableUnaryOp(C2 c) {
if (c.bad == null) return;
/*cfe.invoke: notPromoted(propertyNotPromoted(target: member:C2.bad, type: int?))*/
-c.
/*analyzer.notPromoted(propertyNotPromoted(target: member:C2.bad, type: int?))*/
bad;
}

View file

@ -666,7 +666,8 @@ class InferenceVisitor
read,
readType,
node.binaryName,
node.rhs);
node.rhs,
null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@ -2906,7 +2907,8 @@ class InferenceVisitor
read,
readType,
node.binaryName,
node.rhs);
node.rhs,
null);
DartType binaryType = binaryResult.inferredType;
Expression binary =
@ -3999,7 +4001,8 @@ class InferenceVisitor
Expression left,
DartType leftType,
Name binaryName,
Expression right) {
Expression right,
Map<DartType, NonPromotionReason> Function() whyNotPromoted) {
assert(binaryName != equalsName);
ObjectAccessTarget binaryTarget = inferrer.findInterfaceMember(
@ -4203,6 +4206,10 @@ class InferenceVisitor
}
if (!inferrer.isTopLevel && binaryTarget.isNullable) {
List<LocatedMessage> context = inferrer.getWhyNotPromotedContext(
whyNotPromoted?.call(),
binary,
(type) => !type.isPotentiallyNullable);
return new ExpressionInferenceResult(
binaryType,
inferrer.helper.wrapInProblem(
@ -4210,7 +4217,8 @@ class InferenceVisitor
templateNullableOperatorCallError.withArguments(
binaryName.text, leftType, inferrer.isNonNullableByDefault),
binary.fileOffset,
binaryName.text.length));
binaryName.text.length,
context: context));
}
return new ExpressionInferenceResult(binaryType, binary);
}
@ -4220,8 +4228,12 @@ class InferenceVisitor
///
/// [fileOffset] is used as the file offset for created nodes.
/// [expressionType] is the already inferred type of the [expression].
ExpressionInferenceResult _computeUnaryExpression(int fileOffset,
Expression expression, DartType expressionType, Name unaryName) {
ExpressionInferenceResult _computeUnaryExpression(
int fileOffset,
Expression expression,
DartType expressionType,
Name unaryName,
Map<DartType, NonPromotionReason> Function() whyNotPromoted) {
ObjectAccessTarget unaryTarget = inferrer.findInterfaceMember(
expressionType, unaryName, fileOffset,
includeExtensionMethods: true);
@ -4352,6 +4364,8 @@ class InferenceVisitor
}
if (!inferrer.isTopLevel && unaryTarget.isNullable) {
List<LocatedMessage> context = inferrer.getWhyNotPromotedContext(
whyNotPromoted?.call(), unary, (type) => !type.isPotentiallyNullable);
// TODO(johnniwinther): Special case 'unary-' in messages. It should
// probably be referred to as "Unary operator '-' ...".
return new ExpressionInferenceResult(
@ -4361,7 +4375,8 @@ class InferenceVisitor
templateNullableOperatorCallError.withArguments(unaryName.text,
expressionType, inferrer.isNonNullableByDefault),
unary.fileOffset,
unaryName == unaryMinusName ? 1 : unaryName.text.length));
unaryName == unaryMinusName ? 1 : unaryName.text.length,
context: context));
}
return new ExpressionInferenceResult(unaryType, unary);
}
@ -5121,7 +5136,8 @@ class InferenceVisitor
left,
readType,
node.binaryName,
node.rhs);
node.rhs,
null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@ -5263,7 +5279,8 @@ class InferenceVisitor
left,
readType,
node.binaryName,
node.rhs);
node.rhs,
null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@ -5417,7 +5434,8 @@ class InferenceVisitor
left,
readType,
node.binaryName,
node.rhs);
node.rhs,
null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@ -5596,7 +5614,8 @@ class InferenceVisitor
left,
readType,
node.binaryName,
node.rhs);
node.rhs,
null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@ -6922,13 +6941,16 @@ class InferenceVisitor
BinaryExpression node, DartType typeContext) {
ExpressionInferenceResult leftResult =
inferrer.inferExpression(node.left, const UnknownType(), true);
Map<DartType, NonPromotionReason> Function() whyNotPromoted =
inferrer.flowAnalysis?.whyNotPromoted(leftResult.expression);
return _computeBinaryExpression(
node.fileOffset,
typeContext,
leftResult.expression,
leftResult.inferredType,
node.binaryName,
node.right);
node.right,
whyNotPromoted);
}
ExpressionInferenceResult visitUnary(
@ -7001,8 +7023,10 @@ class InferenceVisitor
expressionResult =
inferrer.inferExpression(node.expression, const UnknownType(), true);
}
Map<DartType, NonPromotionReason> Function() whyNotPromoted =
inferrer.flowAnalysis?.whyNotPromoted(expressionResult.expression);
return _computeUnaryExpression(node.fileOffset, expressionResult.expression,
expressionResult.inferredType, node.unaryName);
expressionResult.inferredType, node.unaryName, whyNotPromoted);
}
ExpressionInferenceResult visitParenthesized(

View file

@ -0,0 +1,37 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// 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.
// This test contains a test case for each condition that can lead to the front
// end's `NullableOperatorCallError` error, for which we wish to report "why not
// promoted" context information.
class C1 {
int? bad;
// ^^^
// [context 2] 'bad' refers to a property so it couldn't be promoted. See http://dart.dev/go/non-promo-property
// [context 3] 'bad' refers to a property so it couldn't be promoted.
}
userDefinableBinaryOpLhs(C1 c) {
if (c.bad == null) return;
c.bad + 1;
// ^
// [analyzer 2] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 3] Operator '+' cannot be called on 'int?' because it is potentially null.
}
class C2 {
int? bad;
// ^^^
// [context 1] 'bad' refers to a property so it couldn't be promoted. See http://dart.dev/go/non-promo-property
// [context 4] 'bad' refers to a property so it couldn't be promoted.
}
userDefinableUnaryOp(C2 c) {
if (c.bad == null) return;
-c.bad;
//^
// [analyzer 1] COMPILE_TIME_ERROR.UNCHECKED_USE_OF_NULLABLE_VALUE
// [cfe 4] Operator 'unary-' cannot be called on 'int?' because it is potentially null.
}