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>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_operator_call_error.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_operator_call_error.dart
new file mode 100644
index 0000000..c619de5
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/why_not_promoted/data/nullable_operator_call_error.dart
@@ -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;
+}
diff --git a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
index 20a67a7..69a9aa7 100644
--- a/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
@@ -666,7 +666,8 @@
read,
readType,
node.binaryName,
- node.rhs);
+ node.rhs,
+ null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@@ -2906,7 +2907,8 @@
read,
readType,
node.binaryName,
- node.rhs);
+ node.rhs,
+ null);
DartType binaryType = binaryResult.inferredType;
Expression binary =
@@ -3999,7 +4001,8 @@
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 @@
}
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 @@
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 @@
///
/// [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 @@
}
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 @@
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 @@
left,
readType,
node.binaryName,
- node.rhs);
+ node.rhs,
+ null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@@ -5263,7 +5279,8 @@
left,
readType,
node.binaryName,
- node.rhs);
+ node.rhs,
+ null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@@ -5417,7 +5434,8 @@
left,
readType,
node.binaryName,
- node.rhs);
+ node.rhs,
+ null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@@ -5596,7 +5614,8 @@
left,
readType,
node.binaryName,
- node.rhs);
+ node.rhs,
+ null);
Expression binary = binaryResult.expression;
DartType binaryType = binaryResult.inferredType;
@@ -6922,13 +6941,16 @@
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 @@
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(
diff --git a/tests/language/why_not_promoted/nullable_operator_call_error.dart b/tests/language/why_not_promoted/nullable_operator_call_error.dart
new file mode 100644
index 0000000..a145f34
--- /dev/null
+++ b/tests/language/why_not_promoted/nullable_operator_call_error.dart
@@ -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.
+}