Resolve x ?? y as LUB(X!, Y) instead of LUB(X, Y).
Change-Id: I081d8ec153916fb37e2a949d9fa85a6ea778438e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/92390
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Mike Fairhurst <mfairhurst@google.com>
Auto-Submit: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index ceca4ee..14f6958 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -71,7 +71,10 @@
*/
TypePromotionManager _promoteManager;
- bool nonNullableEnabled;
+ /**
+ * Whether NNBD is enabled for this compilation unit.
+ */
+ bool _nonNullableEnabled;
/**
* Initialize a newly created type analyzer.
@@ -86,7 +89,7 @@
AnalysisOptionsImpl analysisOptions =
_resolver.definingLibrary.context.analysisOptions;
_strictInference = analysisOptions.strictInference;
- nonNullableEnabled = featureSet.isEnabled(Feature.non_nullable);
+ _nonNullableEnabled = featureSet.isEnabled(Feature.non_nullable);
}
/**
@@ -288,10 +291,22 @@
DartType staticType = _getStaticType(rightHandSide);
_recordStaticType(node, staticType);
} else if (operator == TokenType.QUESTION_QUESTION_EQ) {
- // The static type of a compound assignment using ??= is the least upper
- // bound of the static types of the LHS and RHS.
- _analyzeLeastUpperBound(node, node.leftHandSide, node.rightHandSide,
- read: true);
+ if (_nonNullableEnabled) {
+ // The static type of a compound assignment using ??= with NNBD is the
+ // least upper bound of the static types of the LHS and RHS after
+ // promoting the LHS/ to non-null (as we know its value will not be used
+ // if null)
+ _analyzeLeastUpperBoundTypes(
+ node,
+ _typeSystem.promoteToNonNull(
+ _getExpressionType(node.leftHandSide, read: true)),
+ _getExpressionType(node.rightHandSide, read: true));
+ } else {
+ // The static type of a compound assignment using ??= before NNBD is the
+ // least upper bound of the static types of the LHS and RHS.
+ _analyzeLeastUpperBound(node, node.leftHandSide, node.rightHandSide,
+ read: true);
+ }
return;
} else if (operator == TokenType.AMPERSAND_AMPERSAND_EQ ||
operator == TokenType.BAR_BAR_EQ) {
@@ -370,11 +385,23 @@
@override
void visitBinaryExpression(BinaryExpression node) {
if (node.operator.type == TokenType.QUESTION_QUESTION) {
- // Evaluation of an if-null expression e of the form e1 ?? e2 is
- // equivalent to the evaluation of the expression
- // ((x) => x == null ? e2 : x)(e1). The static type of e is the least
- // upper bound of the static type of e1 and the static type of e2.
- _analyzeLeastUpperBound(node, node.leftOperand, node.rightOperand);
+ if (_nonNullableEnabled) {
+ // The static type of a compound assignment using ??= with NNBD is the
+ // least upper bound of the static types of the LHS and RHS after
+ // promoting the LHS/ to non-null (as we know its value will not be used
+ // if null)
+ _analyzeLeastUpperBoundTypes(
+ node,
+ _typeSystem.promoteToNonNull(
+ _getExpressionType(node.leftOperand, read: true)),
+ _getExpressionType(node.rightOperand, read: true));
+ } else {
+ // Without NNBD, evaluation of an if-null expression e of the form
+ // e1 ?? e2 is equivalent to the evaluation of the expression
+ // ((x) => x == null ? e2 : x)(e1). The static type of e is the least
+ // upper bound of the static type of e1 and the static type of e2.
+ _analyzeLeastUpperBound(node, node.leftOperand, node.rightOperand);
+ }
return;
}
DartType staticType = node.staticInvokeType?.returnType ?? _dynamicType;
@@ -874,7 +901,8 @@
// TODO(brianwilkerson) Report this internal error.
}
- if (node.operator.type == TokenType.QUESTION_PERIOD && nonNullableEnabled) {
+ if (node.operator.type == TokenType.QUESTION_PERIOD &&
+ _nonNullableEnabled) {
staticType =
(staticType as TypeImpl).withNullability(NullabilitySuffix.question);
}
@@ -1087,10 +1115,21 @@
{bool read: false}) {
DartType staticType1 = _getExpressionType(expr1, read: read);
DartType staticType2 = _getExpressionType(expr2, read: read);
+
+ _analyzeLeastUpperBoundTypes(node, staticType1, staticType2);
+ }
+
+ /**
+ * Set the static type of [node] to be the least upper bound of the static
+ * types [staticType1] and [staticType2].
+ */
+ void _analyzeLeastUpperBoundTypes(
+ Expression node, DartType staticType1, DartType staticType2) {
if (staticType1 == null) {
// TODO(brianwilkerson) Determine whether this can still happen.
staticType1 = _dynamicType;
}
+
if (staticType2 == null) {
// TODO(brianwilkerson) Determine whether this can still happen.
staticType2 = _dynamicType;
@@ -1186,7 +1225,7 @@
returnType = type.returnType;
}
- if (isNullableInvoke && nonNullableEnabled) {
+ if (isNullableInvoke && _nonNullableEnabled) {
returnType = returnType?.withNullability(NullabilitySuffix.question);
}
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index 64b747e..a774f85 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -2082,6 +2082,11 @@
return null;
}
+ DartType promoteToNonNull(TypeImpl type) {
+ // TODO(mfairhurst): handle type parameter types
+ return type.withNullability(NullabilitySuffix.none);
+ }
+
/**
* Attempts to make a better guess for the type of a binary with the given
* [operator], given that resolution has so far produced the [currentType].
diff --git a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
index 089d66d..0011f32 100644
--- a/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/non_nullable_test.dart
@@ -55,6 +55,56 @@
assertType(findNode.methodInvocation('c?.x()'), 'bool?');
}
+ test_local_nullCoalesce_nullableInt_int() async {
+ await addTestFile(r'''
+m() {
+ int? x;
+ int y;
+ x ?? y;
+}
+''');
+ await resolveTestFile();
+ assertNoTestErrors();
+ assertType(findNode.binary('x ?? y'), 'int');
+ }
+
+ test_local_nullCoalesce_nullableInt_nullableInt() async {
+ await addTestFile(r'''
+m() {
+ int? x;
+ x ?? x;
+}
+''');
+ await resolveTestFile();
+ assertNoTestErrors();
+ assertType(findNode.binary('x ?? x'), 'int?');
+ }
+
+ test_local_nullCoalesceAssign_nullableInt_int() async {
+ await addTestFile(r'''
+m() {
+ int? x;
+ int y;
+ x ??= y;
+}
+''');
+ await resolveTestFile();
+ assertNoTestErrors();
+ assertType(findNode.assignment('x ??= y'), 'int');
+ }
+
+ test_local_nullCoalesceAssign_nullableInt_nullableInt() async {
+ await addTestFile(r'''
+m() {
+ int? x;
+ x ??= x;
+}
+''');
+ await resolveTestFile();
+ assertNoTestErrors();
+ assertType(findNode.assignment('x ??= x'), 'int?');
+ }
+
test_local_parameter_interfaceType() async {
addTestFile('''
main() {
diff --git a/tests/language_2/nnbd/resolution/question_question_lub_test.dart b/tests/language_2/nnbd/resolution/question_question_lub_test.dart
new file mode 100644
index 0000000..1606441
--- /dev/null
+++ b/tests/language_2/nnbd/resolution/question_question_lub_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, 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.
+
+// SharedOptions=--enable-experiment=non-nullable
+
+// Test that `x ?? y` results in type LUB(x!, y)
+void main() {
+ f1(null, 2);
+}
+
+void f1(
+ int? nullableInt,
+ int nonNullInt,
+) {
+ (nullableInt ?? nonNullInt) + 1; //# 00: ok
+ (nullableInt ?? nullableInt) + 1; //# 01: compile-time error
+ (nonNullInt ?? nullableInt) + 1; //# 02: compile-time error
+ (nonNullInt ?? nonNullInt) + 1; //# 03: ok
+}
+
+// TODO(mfairhurst) add cases with type parameter types