Migration: begin adding support for LUB computations in conditional expressions.

A number of corner cases still need to be addressed, and I need to
generalize the logic to work for `??` expressions.

Change-Id: I6998058698c8b293f7d7b99f3c231a9173b9ff58
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107901
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Paul Berry 2019-07-02 16:12:07 +00:00 committed by commit-bot@chromium.org
parent 585794ab75
commit f4dc001729
4 changed files with 290 additions and 22 deletions

View file

@ -322,22 +322,10 @@ class EdgeBuilder extends GeneralizingAstVisitor<DecoratedType> {
_handleAssignment(node.condition, _notNullType);
// TODO(paulberry): guard anything inside the true and false branches
var thenType = node.thenExpression.accept(this);
assert(_isSimple(thenType)); // TODO(paulberry)
var elseType = node.elseExpression.accept(this);
assert(_isSimple(elseType)); // TODO(paulberry)
DartType staticType = node.staticType;
DecoratedType returnType;
if (staticType is FunctionType) {
DartType functReturnType = staticType.returnType;
returnType = functReturnType.isDynamic || functReturnType.isVoid
// TODO(danrubel): handle LUB for constituent types
? DecoratedType(functReturnType, _graph.always)
: _variables.decoratedElementType(functReturnType.element);
}
var overallType = DecoratedType(
staticType, NullabilityNode.forLUB(thenType.node, elseType.node),
returnType: returnType);
var overallType = _decorateUpperOrLowerBound(
node, node.staticType, thenType, elseType, true);
_variables.recordDecoratedExpressionType(node, overallType);
return overallType;
}
@ -800,7 +788,8 @@ $stackTrace''');
var staticElement = node.staticElement;
if (staticElement is ParameterElement ||
staticElement is LocalVariableElement ||
staticElement is FunctionElement) {
staticElement is FunctionElement ||
staticElement is MethodElement) {
return getOrComputeElementType(staticElement);
} else if (staticElement is PropertyAccessorElement) {
var elementType = getOrComputeElementType(staticElement);
@ -978,6 +967,92 @@ $stackTrace''');
assert(name != 'runtimeType');
}
DecoratedType _decorateUpperOrLowerBound(AstNode astNode, DartType type,
DecoratedType left, DecoratedType right, bool isLUB,
{NullabilityNode node}) {
if (type.isDynamic || type.isVoid) {
if (type.isDynamic) {
_unimplemented(astNode, 'LUB/GLB with dynamic');
}
return DecoratedType(type, _graph.always);
}
node ??= isLUB
? NullabilityNode.forLUB(left.node, right.node)
: _nullabilityNodeForGLB(astNode, left.node, right.node);
if (type is InterfaceType) {
if (type.typeArguments.isEmpty) {
return DecoratedType(type, node);
} else {
var leftType = left.type;
var rightType = right.type;
if (leftType is InterfaceType && rightType is InterfaceType) {
if (leftType.element != type.element ||
rightType.element != type.element) {
_unimplemented(astNode, 'LUB/GLB with substitution');
}
List<DecoratedType> newTypeArguments = [];
for (int i = 0; i < type.typeArguments.length; i++) {
newTypeArguments.add(_decorateUpperOrLowerBound(
astNode,
type.typeArguments[i],
left.typeArguments[i],
right.typeArguments[i],
isLUB));
}
return DecoratedType(type, node, typeArguments: newTypeArguments);
} else {
_unimplemented(
astNode,
'LUB/GLB with unexpected types: ${leftType.runtimeType}/'
'${rightType.runtimeType}');
}
}
} else if (type is FunctionType) {
var leftType = left.type;
var rightType = right.type;
if (leftType is FunctionType && rightType is FunctionType) {
var returnType = _decorateUpperOrLowerBound(
astNode, type.returnType, left.returnType, right.returnType, isLUB);
List<DecoratedType> positionalParameters = [];
Map<String, DecoratedType> namedParameters = {};
int positionalParameterCount = 0;
for (var parameter in type.parameters) {
DecoratedType leftParameterType;
DecoratedType rightParameterType;
if (parameter.isNamed) {
leftParameterType = left.namedParameters[parameter.name];
rightParameterType = right.namedParameters[parameter.name];
} else {
leftParameterType =
left.positionalParameters[positionalParameterCount];
rightParameterType =
right.positionalParameters[positionalParameterCount];
positionalParameterCount++;
}
var decoratedParameterType = _decorateUpperOrLowerBound(astNode,
parameter.type, leftParameterType, rightParameterType, !isLUB);
if (parameter.isNamed) {
namedParameters[parameter.name] = decoratedParameterType;
} else {
positionalParameters.add(decoratedParameterType);
}
}
return DecoratedType(type, node,
returnType: returnType,
positionalParameters: positionalParameters,
namedParameters: namedParameters);
} else {
_unimplemented(
astNode,
'LUB/GLB with unexpected types: ${leftType.runtimeType}/'
'${rightType.runtimeType}');
}
} else if (type is TypeParameterType) {
_unimplemented(astNode, 'LUB/GLB with type parameter types');
}
_unimplemented(astNode, '_decorateUpperOrLowerBound');
}
/// Creates the necessary constraint(s) for an assignment of the given
/// [expression] to a destination whose type is [destinationType].
DecoratedType _handleAssignment(
@ -1209,7 +1284,9 @@ $stackTrace''');
}
return calleeType.positionalParameters[0];
} else {
var expressionType = calleeType.returnType;
var expressionType = callee is PropertyAccessorElement
? calleeType.returnType
: calleeType;
if (isConditional) {
expressionType = expressionType.withNode(
NullabilityNode.forLUB(targetType.node, expressionType.node));
@ -1276,6 +1353,16 @@ $stackTrace''');
return false;
}
NullabilityNode _nullabilityNodeForGLB(
AstNode astNode, NullabilityNode leftNode, NullabilityNode rightNode) {
var node = NullabilityNode.forGLB();
var origin = GreatestLowerBoundOrigin(_source, astNode.offset);
_graph.connect(leftNode, node, origin, guards: [rightNode]);
_graph.connect(node, leftNode, origin);
_graph.connect(node, rightNode, origin);
return node;
}
@alwaysThrows
void _unimplemented(AstNode node, String message) {
CompilationUnit unit = node.root as CompilationUnit;

View file

@ -40,6 +40,19 @@ class FieldFormalParameterOrigin extends EdgeOriginWithLocation {
FieldFormalParameterOrigin(Source source, int offset) : super(source, offset);
}
/// Edge origin resulting from the use of greatest lower bound.
///
/// For example, in the following code snippet:
/// void Function(int) f(void Function(int) x, void Function(int) y)
/// => x ?? y;
///
/// the `int` in the return type is nullable if both the `int`s in the types of
/// `x` and `y` are nullable, due to the fact that the `int` in the return type
/// is the greatest lower bound of the two other `int`s.
class GreatestLowerBoundOrigin extends EdgeOriginWithLocation {
GreatestLowerBoundOrigin(Source source, int offset) : super(source, offset);
}
/// Edge origin resulting from the presence of a `??` operator.
class IfNullOrigin extends EdgeOriginWithLocation {
IfNullOrigin(Source source, int offset) : super(source, offset);

View file

@ -311,6 +311,13 @@ abstract class NullabilityNode {
/// List of edges that have this node as their destination.
final _upstreamEdges = <NullabilityEdge>[];
/// Creates a [NullabilityNode] representing the nullability of an expression
/// which is nullable iff two other nullability nodes are both nullable.
///
/// The caller is required to create the appropriate graph edges to ensure
/// that the appropriate relationship between the nodes' nullabilities holds.
factory NullabilityNode.forGLB() => _NullabilityNodeSimple('GLB');
/// Creates a [NullabilityNode] representing the nullability of a variable
/// whose type is determined by the `??` operator.
factory NullabilityNode.forIfNotNull() =>

View file

@ -32,7 +32,15 @@ class EdgeBuilderTest extends MigrationVisitorTestBase {
return unit;
}
void assertConditional(
void assertGLB(
NullabilityNode node, NullabilityNode left, NullabilityNode right) {
expect(node, isNot(TypeMatcher<NullabilityNodeForLUB>()));
assertEdge(left, node, hard: false, guards: [right]);
assertEdge(node, left, hard: false);
assertEdge(node, right, hard: false);
}
void assertLUB(
NullabilityNode node, NullabilityNode left, NullabilityNode right) {
var conditionalNode = node as NullabilityNodeForLUB;
expect(conditionalNode.left, same(left));
@ -642,6 +650,97 @@ int f(bool b, int i, int j) {
assertNullCheck(check_b, assertEdge(nullable_b, never, hard: true));
}
test_conditionalExpression_functionTyped_namedParameter() async {
await analyze('''
void f(bool b, void Function({int p}) x, void Function({int p}) y) {
(b ? x : y);
}
''');
var xType =
decoratedGenericFunctionTypeAnnotation('void Function({int p}) x');
var yType =
decoratedGenericFunctionTypeAnnotation('void Function({int p}) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.namedParameters['p'].node,
xType.namedParameters['p'].node, yType.namedParameters['p'].node);
}
test_conditionalExpression_functionTyped_normalParameter() async {
await analyze('''
void f(bool b, void Function(int) x, void Function(int) y) {
(b ? x : y);
}
''');
var xType = decoratedGenericFunctionTypeAnnotation('void Function(int) x');
var yType = decoratedGenericFunctionTypeAnnotation('void Function(int) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.positionalParameters[0].node,
xType.positionalParameters[0].node, yType.positionalParameters[0].node);
}
test_conditionalExpression_functionTyped_normalParameters() async {
await analyze('''
void f(bool b, void Function(int, int) x, void Function(int, int) y) {
(b ? x : y);
}
''');
var xType =
decoratedGenericFunctionTypeAnnotation('void Function(int, int) x');
var yType =
decoratedGenericFunctionTypeAnnotation('void Function(int, int) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.positionalParameters[0].node,
xType.positionalParameters[0].node, yType.positionalParameters[0].node);
assertGLB(resultType.positionalParameters[1].node,
xType.positionalParameters[1].node, yType.positionalParameters[1].node);
}
test_conditionalExpression_functionTyped_optionalParameter() async {
await analyze('''
void f(bool b, void Function([int]) x, void Function([int]) y) {
(b ? x : y);
}
''');
var xType =
decoratedGenericFunctionTypeAnnotation('void Function([int]) x');
var yType =
decoratedGenericFunctionTypeAnnotation('void Function([int]) y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertGLB(resultType.positionalParameters[0].node,
xType.positionalParameters[0].node, yType.positionalParameters[0].node);
}
test_conditionalExpression_functionTyped_returnType() async {
await analyze('''
void f(bool b, int Function() x, int Function() y) {
(b ? x : y);
}
''');
var xType = decoratedGenericFunctionTypeAnnotation('int Function() x');
var yType = decoratedGenericFunctionTypeAnnotation('int Function() y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertLUB(resultType.returnType.node, xType.returnType.node,
yType.returnType.node);
}
test_conditionalExpression_functionTyped_returnType_void() async {
await analyze('''
void f(bool b, void Function() x, void Function() y) {
(b ? x : y);
}
''');
var xType = decoratedGenericFunctionTypeAnnotation('void Function() x');
var yType = decoratedGenericFunctionTypeAnnotation('void Function() y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
expect(resultType.returnType.node, same(always));
}
test_conditionalExpression_general() async {
await analyze('''
int f(bool b, int i, int j) {
@ -652,12 +751,28 @@ int f(bool b, int i, int j) {
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_j = decoratedTypeAnnotation('int j').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertConditional(nullable_conditional, nullable_i, nullable_j);
assertLUB(nullable_conditional, nullable_i, nullable_j);
var nullable_return = decoratedTypeAnnotation('int f').node;
assertNullCheck(checkExpression('(b ? i : j)'),
assertEdge(nullable_conditional, nullable_return, hard: false));
}
test_conditionalExpression_generic() async {
await analyze('''
void f(bool b, Map<int, String> x, Map<int, String> y) {
(b ? x : y);
}
''');
var xType = decoratedTypeAnnotation('Map<int, String> x');
var yType = decoratedTypeAnnotation('Map<int, String> y');
var resultType = decoratedExpressionType('(b ?');
assertLUB(resultType.node, xType.node, yType.node);
assertLUB(resultType.typeArguments[0].node, xType.typeArguments[0].node,
yType.typeArguments[0].node);
assertLUB(resultType.typeArguments[1].node, xType.typeArguments[1].node,
yType.typeArguments[1].node);
}
test_conditionalExpression_left_non_null() async {
await analyze('''
int f(bool b, int i) {
@ -670,7 +785,7 @@ int f(bool b, int i) {
decoratedExpressionType('(b ?').node as NullabilityNodeForLUB;
var nullable_throw = nullable_conditional.left;
assertNoUpstreamNullability(nullable_throw);
assertConditional(nullable_conditional, nullable_throw, nullable_i);
assertLUB(nullable_conditional, nullable_throw, nullable_i);
}
test_conditionalExpression_left_null() async {
@ -682,7 +797,7 @@ int f(bool b, int i) {
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertConditional(nullable_conditional, always, nullable_i);
assertLUB(nullable_conditional, always, nullable_i);
}
test_conditionalExpression_right_non_null() async {
@ -697,7 +812,7 @@ int f(bool b, int i) {
decoratedExpressionType('(b ?').node as NullabilityNodeForLUB;
var nullable_throw = nullable_conditional.right;
assertNoUpstreamNullability(nullable_throw);
assertConditional(nullable_conditional, nullable_i, nullable_throw);
assertLUB(nullable_conditional, nullable_i, nullable_throw);
}
test_conditionalExpression_right_null() async {
@ -709,7 +824,7 @@ int f(bool b, int i) {
var nullable_i = decoratedTypeAnnotation('int i').node;
var nullable_conditional = decoratedExpressionType('(b ?').node;
assertConditional(nullable_conditional, nullable_i, always);
assertLUB(nullable_conditional, nullable_i, always);
}
test_constructor_named() async {
@ -1964,6 +2079,22 @@ void test(C c) {
assertEdge(decoratedTypeAnnotation('C c').node, never, hard: true);
}
test_prefixedIdentifier_tearoff() async {
await analyze('''
abstract class C {
int f(int i);
}
int Function(int) g(C c) => c.f;
''');
var fType = variables.decoratedElementType(findElement.method('f'));
var gReturnType =
variables.decoratedElementType(findElement.function('g')).returnType;
assertEdge(fType.returnType.node, gReturnType.returnType.node, hard: false);
assertEdge(gReturnType.positionalParameters[0].node,
fType.positionalParameters[0].node,
hard: false);
}
test_prefixExpression_bang() async {
await analyze('''
bool f(bool b) {
@ -2368,6 +2499,36 @@ main() {
hard: true);
}
test_simpleIdentifier_tearoff_function() async {
await analyze('''
int f(int i) => 0;
int Function(int) g() => f;
''');
var fType = variables.decoratedElementType(findElement.function('f'));
var gReturnType =
variables.decoratedElementType(findElement.function('g')).returnType;
assertEdge(fType.returnType.node, gReturnType.returnType.node, hard: false);
assertEdge(gReturnType.positionalParameters[0].node,
fType.positionalParameters[0].node,
hard: false);
}
test_simpleIdentifier_tearoff_method() async {
await analyze('''
abstract class C {
int f(int i);
int Function(int) g() => f;
}
''');
var fType = variables.decoratedElementType(findElement.method('f'));
var gReturnType =
variables.decoratedElementType(findElement.method('g')).returnType;
assertEdge(fType.returnType.node, gReturnType.returnType.node, hard: false);
assertEdge(gReturnType.positionalParameters[0].node,
fType.positionalParameters[0].node,
hard: false);
}
test_skipDirectives() async {
await analyze('''
import "dart:core" as one;