Skip to content

Commit

Permalink
[cfe] Implement compilation of loops in lists and sets
Browse files Browse the repository at this point in the history
Implement the translation of for and for-in loops in non-const lists
and sets.  They are lowered to block expressions containing a loop.
It is impossible to target these loops with a break or continue
because they do not contain statements in their body.

Type inference is not yet implemented.

Change-Id: I35ff1650fde54cd376fe0b444891010435c74507
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97922
Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
  • Loading branch information
Kevin Millikin committed Mar 26, 2019
1 parent 0a3cd80 commit ac21076
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 115 deletions.
8 changes: 5 additions & 3 deletions pkg/front_end/lib/src/fasta/kernel/body_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4058,7 +4058,7 @@ abstract class BodyBuilder extends ScopeListener<JumpTarget>
var entry = pop();
Token inToken = pop();
Token forToken = pop();
pop(NullValue.AwaitToken);
Token awaitToken = pop(NullValue.AwaitToken);
Expression iterable = popForValue();
Object lvalue = pop(); // lvalue
exitLocalScope();
Expand All @@ -4083,10 +4083,12 @@ abstract class BodyBuilder extends ScopeListener<JumpTarget>
Statement prologue = buildForInBody(lvalue, variable, forToken, inToken);
if (entry is MapEntry) {
push(forest.forInMapEntry(
variable, iterable, prologue, entry, problem, forToken));
variable, iterable, prologue, entry, problem, forToken,
isAsync: awaitToken != null));
} else {
push(forest.forInElement(
variable, iterable, prologue, toValue(entry), problem, forToken));
variable, iterable, prologue, toValue(entry), problem, forToken,
isAsync: awaitToken != null));
}
}

Expand Down
12 changes: 8 additions & 4 deletions pkg/front_end/lib/src/fasta/kernel/fangorn.dart
Original file line number Diff line number Diff line change
Expand Up @@ -328,15 +328,19 @@ class Fangorn extends Forest {

@override
Expression forInElement(VariableDeclaration variable, Expression iterable,
Statement prologue, Expression body, Expression problem, Token token) {
return new ForInElement(variable, iterable, prologue, body, problem)
Statement prologue, Expression body, Expression problem, Token token,
{bool isAsync: false}) {
return new ForInElement(variable, iterable, prologue, body, problem,
isAsync: isAsync)
..fileOffset = offsetForToken(token);
}

@override
MapEntry forInMapEntry(VariableDeclaration variable, Expression iterable,
Statement prologue, MapEntry body, Expression problem, Token token) {
return new ForInMapEntry(variable, iterable, prologue, body, problem)
Statement prologue, MapEntry body, Expression problem, Token token,
{bool isAsync: false}) {
return new ForInMapEntry(variable, iterable, prologue, body, problem,
isAsync: isAsync)
..fileOffset = offsetForToken(token);
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/front_end/lib/src/fasta/kernel/forest.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,12 @@ abstract class Forest {
Token token);

Expression forInElement(VariableDeclaration variable, Expression iterable,
Statement prologue, Expression body, Expression problem, Token token);
Statement prologue, Expression body, Expression problem, Token token,
{bool isAsync: false});

MapEntry forInMapEntry(VariableDeclaration variable, Expression iterable,
Statement prologue, MapEntry body, Expression problem, Token token);
Statement prologue, MapEntry body, Expression problem, Token token,
{bool isAsync: false});

/// Return a representation of an assert that appears in a constructor's
/// initializer list.
Expand Down
132 changes: 86 additions & 46 deletions pkg/front_end/lib/src/fasta/kernel/inference_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {

@override
void defaultExpression(Expression node, DartType typeContext) {
if (node is ForElement) {
visitForElement(node, typeContext);
return;
}
if (node is ForInElement) {
visitForInElement(node, typeContext);
return;
}
unhandled("${node.runtimeType}", "InferenceVisitor", node.fileOffset,
inferrer.helper.uri);
}
Expand All @@ -29,18 +21,6 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
inferrer.helper.uri);
}

void visitForElement(ForElement node, DartType typeContext) {
node.parent.replaceChild(node,
new InvalidExpression('unhandled for element in collection literal'));
}

void visitForInElement(ForInElement node, DartType typeContext) {
node.parent.replaceChild(
node,
new InvalidExpression(
'unhandled for-in element in collection literal'));
}

@override
void visitInvalidExpression(InvalidExpression node, DartType typeContext) {}

Expand Down Expand Up @@ -264,57 +244,65 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
node.field.type, initializerType, node.value, node.fileOffset);
}

void handleForInStatementDeclaringVariable(ForInStatement node) {
void handleForInDeclaringVariable(
VariableDeclaration variable, Expression iterable, Statement body,
{bool isAsync: false}) {
DartType elementType;
bool typeNeeded = false;
bool typeChecksNeeded = !inferrer.isTopLevel;
final VariableDeclaration variable = node.variable;
if (VariableDeclarationJudgment.isImplicitlyTyped(variable)) {
typeNeeded = true;
elementType = const UnknownType();
} else {
elementType = variable.type;
}

DartType inferredType =
inferForInIterable(node, elementType, typeNeeded || typeChecksNeeded);
DartType inferredType = inferForInIterable(
iterable, elementType, typeNeeded || typeChecksNeeded,
isAsync: isAsync);
if (typeNeeded) {
inferrer.instrumentation?.record(inferrer.uri, variable.fileOffset,
'type', new InstrumentationValueForType(inferredType));
variable.type = inferredType;
}

inferrer.inferStatement(node.body);
if (body != null) inferrer.inferStatement(body);

VariableDeclaration tempVar =
new VariableDeclaration(null, type: inferredType, isFinal: true);
VariableGet variableGet = new VariableGet(tempVar)
..fileOffset = variable.fileOffset;
TreeNode parent = variable.parent;
Expression implicitDowncast = inferrer.ensureAssignable(
variable.type, inferredType, variableGet, node.fileOffset,
variable.type, inferredType, variableGet, parent.fileOffset,
template: templateForInLoopElementTypeNotAssignable);
if (implicitDowncast != null) {
node.variable = tempVar..parent = node;
parent.replaceChild(variable, tempVar);
variable.initializer = implicitDowncast..parent = variable;
node.body = combineStatements(variable, node.body)..parent = node;
if (body == null) {
ForInElement element = parent;
element.prologue = variable;
} else {
parent.replaceChild(body, combineStatements(variable, body));
}
}
}

DartType inferForInIterable(
ForInStatement node, DartType elementType, bool typeNeeded) {
Class iterableClass = node.isAsync
Expression iterable, DartType elementType, bool typeNeeded,
{bool isAsync: false}) {
Class iterableClass = isAsync
? inferrer.coreTypes.streamClass
: inferrer.coreTypes.iterableClass;
DartType context = inferrer.wrapType(elementType, iterableClass);
Expression iterable = node.iterable;
inferrer.inferExpression(iterable, context, typeNeeded);
DartType inferredExpressionType =
inferrer.resolveTypeParameter(getInferredType(iterable, inferrer));
inferrer.ensureAssignable(
inferrer.wrapType(const DynamicType(), iterableClass),
inferredExpressionType,
node.iterable,
node.iterable.fileOffset,
iterable,
iterable.fileOffset,
template: templateForInLoopTypeNotIterable);
DartType inferredType;
if (typeNeeded) {
Expand All @@ -330,15 +318,17 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
return inferredType;
}

void handleForInStatementWithoutVariable(ForInStatement node) {
void handleForInWithoutVariable(
VariableDeclaration variable, Expression iterable, Statement body,
{bool isAsync: false}) {
DartType elementType;
bool typeChecksNeeded = !inferrer.isTopLevel;
DartType syntheticWriteType;
Expression syntheticAssignment;
Block block = node.body;
ExpressionStatement statement = block.statements[0];
SyntheticExpressionJudgment judgment = statement.expression;
Expression rhs;
ExpressionStatement statement =
body is Block ? body.statements.first : body;
SyntheticExpressionJudgment judgment = statement.expression;
syntheticAssignment = judgment.desugared;
if (syntheticAssignment is VariableSet) {
syntheticWriteType = elementType = syntheticAssignment.variable.type;
Expand Down Expand Up @@ -368,18 +358,19 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
inferrer.helper.uri);
}

DartType inferredType =
inferForInIterable(node, elementType, typeChecksNeeded);
DartType inferredType = inferForInIterable(
iterable, elementType, typeChecksNeeded,
isAsync: isAsync);
if (typeChecksNeeded) {
node.variable.type = inferredType;
variable.type = inferredType;
}

inferrer.inferStatement(node.body);
inferrer.inferStatement(body);

if (syntheticWriteType != null) {
inferrer.ensureAssignable(
greatestClosure(inferrer.coreTypes, syntheticWriteType),
node.variable.type,
variable.type,
rhs,
rhs.fileOffset,
template: templateForInLoopElementTypeNotAssignable,
Expand All @@ -390,9 +381,11 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
@override
void visitForInStatement(ForInStatement node, _) {
if (node.variable.name == null) {
handleForInStatementWithoutVariable(node);
handleForInWithoutVariable(node.variable, node.iterable, node.body,
isAsync: node.isAsync);
} else {
handleForInStatementDeclaringVariable(node);
handleForInDeclaringVariable(node.variable, node.iterable, node.body,
isAsync: node.isAsync);
}
}

Expand Down Expand Up @@ -697,16 +690,59 @@ 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);
isVoidAllowed: false); // Should be void allowed + runtime check?
inferElement(element.then, index, inferredTypeArgument, spreadTypes,
inferenceNeeded, typeChecksNeeded);
if (element.otherwise != null) {
inferElement(element.otherwise, index, inferredTypeArgument,
spreadTypes, inferenceNeeded, typeChecksNeeded);
}
// TODO(kmillikin): Implement inference rules for if elements.
return const DynamicType();
} else if (element is ForElement) {
// TODO(kmillikin): Implement inference rules for for elements.
for (VariableDeclaration declaration in element.variables) {
if (declaration.initializer != null) {
inferrer.inferExpression(declaration.initializer, declaration.type,
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: true);
}
}
if (element.condition != null) {
inferrer.inferExpression(
element.condition,
inferrer.coreTypes.boolClass.rawType,
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: false); // Should be void allowed + runtime check.
}
for (Expression expression in element.updates) {
inferrer.inferExpression(expression, const UnknownType(),
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: true);
}
inferElement(element.body, index, inferredTypeArgument, spreadTypes,
inferenceNeeded, typeChecksNeeded);
return const DynamicType();
} else if (element is ForInElement) {
// TODO(kmillikin): Implement inference rules for for-in elements.
if (element.variable.name == null) {
handleForInWithoutVariable(
element.variable, element.iterable, element.prologue,
isAsync: element.isAsync);
} else {
handleForInDeclaringVariable(
element.variable, element.iterable, element.prologue,
isAsync: element.isAsync);
}
if (element.problem != null) {
inferrer.inferExpression(element.problem, const UnknownType(),
inferenceNeeded || typeChecksNeeded,
isVoidAllowed: true);
}
inferElement(element.body, index, inferredTypeArgument, spreadTypes,
inferenceNeeded, typeChecksNeeded);
return const DynamicType();
} else {
return inferrer.inferExpression(
Expand Down Expand Up @@ -813,6 +849,8 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
}
} 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,
Expand Down Expand Up @@ -1553,6 +1591,8 @@ class InferenceVisitor extends BodyVisitor1<void, DartType> {
}
} 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,
Expand Down
14 changes: 0 additions & 14 deletions pkg/front_end/lib/src/fasta/kernel/inferred_type_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,11 @@ class InferredTypeVisitor

@override
DartType defaultExpression(Expression node, TypeInferrerImpl inferrer) {
if (node is ForElement) {
return visitForElement(node, inferrer);
}
if (node is ForInElement) {
return visitForInElement(node, inferrer);
}
unhandled("${node.runtimeType}", "getInferredType", node.fileOffset,
inferrer.uri);
return const InvalidType();
}

DartType visitForElement(ForElement node, TypeInferrerImpl inferrer) {
return const BottomType();
}

DartType visitForInElement(ForInElement node, TypeInferrerImpl inferrer) {
return const BottomType();
}

@override
DartType visitIntLiteral(IntLiteral node, TypeInferrerImpl inferrer) {
return inferrer.coreTypes.intClass.rawType;
Expand Down
Loading

0 comments on commit ac21076

Please sign in to comment.