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>
This commit is contained in:
Mike Fairhurst 2019-05-07 16:34:17 +00:00 committed by commit-bot@chromium.org
parent 78e50a5f9b
commit a8b6e18b62
4 changed files with 129 additions and 13 deletions

View file

@ -71,7 +71,10 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
*/
TypePromotionManager _promoteManager;
bool nonNullableEnabled;
/**
* Whether NNBD is enabled for this compilation unit.
*/
bool _nonNullableEnabled;
/**
* Initialize a newly created type analyzer.
@ -86,7 +89,7 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
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 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
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 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
@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 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
// 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 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
{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 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
returnType = type.returnType;
}
if (isNullableInvoke && nonNullableEnabled) {
if (isNullableInvoke && _nonNullableEnabled) {
returnType = returnType?.withNullability(NullabilitySuffix.question);
}

View file

@ -2082,6 +2082,11 @@ abstract class TypeSystem implements public.TypeSystem {
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].

View file

@ -55,6 +55,56 @@ m() {
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() {

View file

@ -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