Skip to content

Commit

Permalink
[cfe] Add inference and type checks for if-elements in lists and sets
Browse files Browse the repository at this point in the history
Change-Id: Iad4e6a6ed86e17f97f6bb7ad16f2bff20ab84fae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97925
Reviewed-by: Kevin Millikin <kmillikin@google.com>
  • Loading branch information
Dmitry Stefantsov authored and commit-bot@chromium.org committed Mar 26, 2019
1 parent 73d5c6b commit 49a7fa1
Show file tree
Hide file tree
Showing 11 changed files with 2,345 additions and 116 deletions.
183 changes: 83 additions & 100 deletions pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -690,17 +690,25 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
return getSpreadElementType(spreadType, element.isNullAware) ??
const DynamicType();
} else if (element is IfElement) {
// TODO(kmillikin): Implement inference rules for if elements.
inferrer.inferExpression(element.condition,
inferrer.coreTypes.boolClass.rawType, typeChecksNeeded,
isVoidAllowed: false);
inferElement(element.then, index, inferredTypeArgument, spreadTypes,
inferenceNeeded, typeChecksNeeded);
DartType thenType = inferElement(element.then, index,
inferredTypeArgument, spreadTypes, inferenceNeeded, typeChecksNeeded);
DartType otherwiseType;
if (element.otherwise != null) {
inferElement(element.otherwise, index, inferredTypeArgument,
spreadTypes, inferenceNeeded, typeChecksNeeded);
otherwiseType = inferElement(
element.otherwise,
index,
inferredTypeArgument,
spreadTypes,
inferenceNeeded,
typeChecksNeeded);
}
return const DynamicType();
return otherwiseType == null
? thenType
: inferrer.typeSchemaEnvironment
.getStandardUpperBound(thenType, otherwiseType);
} else if (element is ForElement) {
// TODO(kmillikin): Implement inference rules for for elements.
for (VariableDeclaration declaration in element.variables) {
Expand Down Expand Up @@ -751,6 +759,71 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
}
}

void checkElement(Expression item, Expression parent, DartType typeArgument,
DartType actualType, DartType spreadType) {
if (item is SpreadElement) {
DartType spreadElementType =
getSpreadElementType(spreadType, item.isNullAware);
if (spreadElementType == null) {
if (spreadType is InterfaceType &&
spreadType.classNode == inferrer.coreTypes.nullClass &&
!item.isNullAware) {
parent.replaceChild(
item,
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(messageNonNullAwareSpreadIsNull,
item.expression.fileOffset, 1)));
} else {
parent.replaceChild(
item,
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(
templateSpreadTypeMismatch.withArguments(spreadType),
item.expression.fileOffset,
1)));
}
} else if (spreadType is DynamicType) {
inferrer.ensureAssignable(inferrer.coreTypes.iterableClass.rawType,
spreadType, item.expression, item.expression.fileOffset);
} else if (spreadType is InterfaceType) {
if (!inferrer.isAssignable(typeArgument, spreadElementType)) {
parent.replaceChild(
item,
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(
templateSpreadElementTypeMismatch.withArguments(
spreadElementType, typeArgument),
item.expression.fileOffset,
1)));
}
}
} else if (item is IfElement) {
if (!inferrer.isAssignable(typeArgument, actualType)) {
int offset =
item.otherwise == null ? item.then.fileOffset : item.fileOffset;
parent.replaceChild(
item,
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(
templateInvalidAssignment.withArguments(
actualType, typeArgument),
offset,
1)));
} else {
checkElement(item.then, item, typeArgument, actualType, spreadType);
if (item.otherwise != null) {
checkElement(
item.otherwise, item, typeArgument, actualType, spreadType);
}
}
} else if (item is ForElement || item is ForInElement) {
// TODO(kmillikin): Insert type checks on loop elements.
} else {
inferrer.ensureAssignable(typeArgument, actualType, item, item.fileOffset,
isVoidAllowed: typeArgument is VoidType);
}
}

void visitListLiteralJudgment(
ListLiteralJudgment node, DartType typeContext) {
var listClass = inferrer.coreTypes.listClass;
Expand Down Expand Up @@ -809,53 +882,8 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
}
if (typeChecksNeeded) {
for (int i = 0; i < node.expressions.length; i++) {
Expression item = node.expressions[i];
if (item is SpreadElement) {
DartType spreadType = spreadTypes[i];
DartType spreadElementType =
getSpreadElementType(spreadType, item.isNullAware);
if (spreadElementType == null) {
if (spreadType is InterfaceType &&
spreadType.classNode == inferrer.coreTypes.nullClass &&
!item.isNullAware) {
node.replaceChild(
node.expressions[i],
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(messageNonNullAwareSpreadIsNull,
item.expression.fileOffset, 1)));
} else {
node.replaceChild(
node.expressions[i],
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(
templateSpreadTypeMismatch.withArguments(spreadType),
item.expression.fileOffset,
1)));
}
} else if (spreadType is DynamicType) {
inferrer.ensureAssignable(inferrer.coreTypes.iterableClass.rawType,
spreadType, item.expression, item.expression.fileOffset);
} else if (spreadType is InterfaceType) {
if (!inferrer.isAssignable(node.typeArgument, spreadElementType)) {
node.replaceChild(
node.expressions[i],
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(
templateSpreadElementTypeMismatch.withArguments(
spreadElementType, node.typeArgument),
item.expression.fileOffset,
1)));
}
}
} else if (item is IfElement) {
// TODO(kmillikin): Insert type checks on if elements.
} else if (item is ForElement || item is ForInElement) {
// TODO(kmillikin): Insert type checks on loop elements.
} else {
inferrer.ensureAssignable(
node.typeArgument, actualTypes[i], item, item.fileOffset,
isVoidAllowed: node.typeArgument is VoidType);
}
checkElement(node.expressions[i], node, node.typeArgument,
actualTypes[i], spreadTypes[i]);
}
}
node.inferredType = new InterfaceType(listClass, [inferredTypeArgument]);
Expand Down Expand Up @@ -1595,53 +1623,8 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
}
if (typeChecksNeeded) {
for (int i = 0; i < node.expressions.length; i++) {
Expression item = node.expressions[i];
if (item is SpreadElement) {
DartType spreadType = spreadTypes[i];
DartType spreadElementType =
getSpreadElementType(spreadType, item.isNullAware);
if (spreadElementType == null) {
if (spreadType is InterfaceType &&
spreadType.classNode == inferrer.coreTypes.nullClass &&
!item.isNullAware) {
node.replaceChild(
node.expressions[i],
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(messageNonNullAwareSpreadIsNull,
item.expression.fileOffset, 1)));
} else {
node.replaceChild(
node.expressions[i],
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(
templateSpreadTypeMismatch.withArguments(spreadType),
item.expression.fileOffset,
1)));
}
} else if (spreadType is DynamicType) {
inferrer.ensureAssignable(inferrer.coreTypes.iterableClass.rawType,
spreadType, item.expression, item.expression.fileOffset);
} else if (spreadType is InterfaceType) {
if (!inferrer.isAssignable(node.typeArgument, spreadElementType)) {
node.replaceChild(
node.expressions[i],
inferrer.helper.desugarSyntheticExpression(inferrer.helper
.buildProblem(
templateSpreadElementTypeMismatch.withArguments(
spreadElementType, node.typeArgument),
item.expression.fileOffset,
1)));
}
}
} else if (item is IfElement) {
// TODO(kmillikin): Insert type checks on if elements.
} else if (item is ForElement || item is ForInElement) {
// TODO(kmillikin): Insert type checks on loop elements.
} else {
inferrer.ensureAssignable(node.typeArgument, actualTypes[i],
node.expressions[i], node.expressions[i].fileOffset,
isVoidAllowed: node.typeArgument is VoidType);
}
checkElement(node.expressions[i], node, node.typeArgument,
actualTypes[i], spreadTypes[i]);
}
}
node.inferredType = new InterfaceType(setClass, [inferredTypeArgument]);
Expand Down
1 change: 1 addition & 0 deletions pkg/front_end/lib/src/fasta/kernel/kernel_shadow_ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import '../fasta_codes.dart'
templateForInLoopElementTypeNotAssignable,
templateForInLoopTypeNotIterable,
templateIntegerLiteralIsOutOfRange,
templateInvalidAssignment,
templateSpreadElementTypeMismatch,
templateSpreadMapEntryElementKeyTypeMismatch,
templateSpreadMapEntryElementValueTypeMismatch,
Expand Down
76 changes: 76 additions & 0 deletions pkg/front_end/testcases/control_flow_collection_inference.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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.

// Oracle is generic to test the inference in conditions of if-elements.
oracle<T>([T t]) => true;

testIfElement(dynamic dynVar, List<int> listInt, List<double> listDouble) {
var list10 = [if (oracle("foo")) 42];
var set10 = {if (oracle("foo")) 42, null};
var list11 = [if (oracle("foo")) dynVar];
var set11 = {if (oracle("foo")) dynVar, null};
var list12 = [if (oracle("foo")) [42]];
var set12 = {if (oracle("foo")) [42], null};
var list20 = [if (oracle("foo")) ...[42]];
var set20 = {if (oracle("foo")) ...[42], null};
var list21 = [if (oracle("foo")) ...[dynVar]];
var set21 = {if (oracle("foo")) ...[dynVar], null};
var list22 = [if (oracle("foo")) ...[[42]]];
var set22 = {if (oracle("foo")) ...[[42]], null};
var list30 = [if (oracle("foo")) if (oracle()) ...[42]];
var set30 = {if (oracle("foo")) if (oracle()) ...[42], null};
var list31 = [if (oracle("foo")) if (oracle()) ...[dynVar]];
var set31 = {if (oracle("foo")) if (oracle()) ...[dynVar], null};
var list33 = [if (oracle("foo")) if (oracle()) ...[[42]]];
var set33 = {if (oracle("foo")) if (oracle()) ...[[42]], null};
List<List<int>> list40 = [if (oracle("foo")) ...[[]]];
Set<List<int>> set40 = {if (oracle("foo")) ...[[]], null};
List<List<int>> list41 = [if (oracle("foo")) ...{[]}];
Set<List<int>> set41 = {if (oracle("foo")) ...{[]}, null};
List<List<int>> list42 = [if (oracle("foo")) if (oracle()) ...[[]]];
Set<List<int>> set42 = {if (oracle("foo")) if (oracle()) ...[[]], null};
List<int> list50 = [if (oracle("foo")) ...[]];
Set<int> set50 = {if (oracle("foo")) ...[], null};
List<int> list51 = [if (oracle("foo")) ...{}];
Set<int> set51 = {if (oracle("foo")) ...{}, null};
List<int> list52 = [if (oracle("foo")) if (oracle()) ...[]];
Set<int> set52 = {if (oracle("foo")) if (oracle()) ...[], null};
List<List<int>> list60 = [if (oracle("foo")) ...[[]]];
Set<List<int>> set60 = {if (oracle("foo")) ...[[]], null};
List<List<int>> list61 = [if (oracle("foo")) if (oracle()) ...[[]]];
Set<List<int>> set61 = {if (oracle("foo")) if (oracle()) ...[[]], null};
List<List<int>> list70 = [if (oracle("foo")) []];
Set<List<int>> set70 = {if (oracle("foo")) [], null};
List<List<int>> list71 = [if (oracle("foo")) if (oracle()) []];
Set<List<int>> set71 = {if (oracle("foo")) if (oracle()) [], null};
var list80 = [if (oracle("foo")) 42 else 3.14];
var set80 = {if (oracle("foo")) 42 else 3.14, null};
var list81 = [if (oracle("foo")) ...listInt else ...listDouble];
var set81 = {if (oracle("foo")) ...listInt else ...listDouble, null};
var list82 = [if (oracle("foo")) ...listInt else ...dynVar];
var set82 = {if (oracle("foo")) ...listInt else ...dynVar, null};
var list83 = [if (oracle("foo")) 42 else ...listDouble];
var set83 = {if (oracle("foo")) ...listInt else 3.14, null};
List<int> list90 = [if (oracle("foo")) dynVar];
Set<int> set90 = {if (oracle("foo")) dynVar, null};
List<int> list91 = [if (oracle("foo")) ...dynVar];
Set<int> set91 = {if (oracle("foo")) ...dynVar, null};
}

testIfElementErrors(Map<int, int> map) {
<int>[if (oracle("foo")) "bar"];
<int>{if (oracle("foo")) "bar", null};
<int>[if (oracle("foo")) ...["bar"]];
<int>{if (oracle("foo")) ...["bar"], null};
<int>[if (oracle("foo")) ...map];
<int>{if (oracle("foo")) ...map, null};
<String>[if (oracle("foo")) 42 else 3.14];
<String>{if (oracle("foo")) 42 else 3.14, null};
<int>[if (oracle("foo")) ...map else 42];
<int>{if (oracle("foo")) ...map else 42, null};
<int>[if (oracle("foo")) 42 else ...map];
<int>{if (oracle("foo")) ...map else 42, null};
}

main() {}
Loading

0 comments on commit 49a7fa1

Please sign in to comment.