mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 20:01:19 +00:00
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:
parent
585794ab75
commit
f4dc001729
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() =>
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue