blob: 4062371dafb477b8acab59defc9d94f5180f5d15 [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'package:kernel/ast.dart' as ir;
import '../closure.dart';
import '../common.dart';
import '../common/names.dart';
import '../constants/constant_system.dart';
import '../elements/entities.dart';
import '../elements/jumps.dart';
import '../elements/types.dart';
import '../kernel/element_map.dart';
import '../options.dart';
import '../types/constants.dart';
import '../types/types.dart';
import '../universe/selector.dart';
import '../universe/side_effects.dart';
import '../world.dart';
import 'inferrer_engine.dart';
import 'locals_handler.dart';
import 'type_graph_nodes.dart';
import 'type_system.dart';
/// [KernelTypeGraphBuilder] constructs a type-inference graph for a particular
/// element.
///
/// Calling [run] will start the work of visiting the body of the code to
/// construct a set of inference-nodes that abstractly represent what the code
/// is doing.
class KernelTypeGraphBuilder extends ir.Visitor<TypeInformation> {
final CompilerOptions _options;
final ClosedWorld _closedWorld;
final ClosureDataLookup<ir.Node> _closureDataLookup;
final InferrerEngine<ir.Node> _inferrer;
final TypeSystem<ir.Node> _types;
final MemberEntity _analyzedMember;
final ir.Node _analyzedNode;
final KernelToElementMapForBuilding _elementMap;
final KernelToLocalsMap _localsMap;
final GlobalTypeInferenceElementData<ir.Node> _memberData;
LocalsHandler _locals;
SideEffects _sideEffects = new SideEffects.empty();
final Map<JumpTarget, List<LocalsHandler>> _breaksFor =
<JumpTarget, List<LocalsHandler>>{};
final Map<JumpTarget, List<LocalsHandler>> _continuesFor =
<JumpTarget, List<LocalsHandler>>{};
TypeInformation _returnType;
KernelTypeGraphBuilder(
this._options,
this._closedWorld,
this._closureDataLookup,
this._inferrer,
this._analyzedMember,
this._analyzedNode,
this._elementMap,
this._localsMap,
[this._locals])
: this._types = _inferrer.types,
this._memberData = _inferrer.dataOfMember(_analyzedMember) {
if (_locals != null) return;
FieldInitializationScope<ir.Node> fieldScope =
_analyzedNode is ir.Constructor
? new FieldInitializationScope(_types)
: null;
_locals = new LocalsHandler(
_inferrer, _types, _options, _analyzedNode, fieldScope);
}
int _loopLevel = 0;
bool get inLoop => _loopLevel > 0;
TypeInformation run() {
if (_analyzedMember.isField) {
if (_analyzedNode == null || _analyzedNode is ir.NullLiteral) {
// Eagerly bailout, because computing the closure data only
// works for functions and field assignments.
return _types.nullType;
}
}
// Update the locals that are boxed in [locals]. These locals will
// be handled specially, in that we are computing their LUB at
// each update, and reading them yields the type that was found in a
// previous analysis of [outermostElement].
ClosureRepresentationInfo closureData =
_closureDataLookup.getClosureInfoForMember(_analyzedMember);
closureData.forEachCapturedVariable((variable, field) {
_locals.setCaptured(variable, field);
});
closureData.forEachBoxedVariable((variable, field) {
_locals.setCapturedAndBoxed(variable, field);
});
return _analyzedNode.accept(this);
}
void recordReturnType(TypeInformation type) {
FunctionEntity analyzedMethod = _analyzedMember;
_returnType =
_inferrer.addReturnTypeForMethod(analyzedMethod, _returnType, type);
}
void initializationIsIndefinite() {
MemberEntity member = _analyzedMember;
if (member is ConstructorEntity && member.isGenerativeConstructor) {
_locals.fieldScope.isIndefinite = true;
}
}
TypeInformation visit(ir.Node node) {
return node == null ? null : node.accept(this);
}
void visitList(List<ir.Node> nodes) {
if (nodes == null) return;
nodes.forEach(visit);
}
void handleParameter(ir.VariableDeclaration node, {bool isOptional}) {
Local local = _localsMap.getLocalVariable(node);
DartType type = _localsMap.getLocalType(_elementMap, local);
_locals.update(local, _inferrer.typeOfParameter(local), node, type);
if (isOptional) {
TypeInformation type = visit(node.initializer);
_inferrer.setDefaultTypeOfParameter(local, type,
isInstanceMember: _analyzedMember.isInstanceMember);
}
}
@override
TypeInformation visitFunctionNode(ir.FunctionNode node) {
// TODO(redemption): Handle native methods.
int position = 0;
for (ir.VariableDeclaration parameter in node.positionalParameters) {
handleParameter(parameter,
isOptional: position >= node.requiredParameterCount);
position++;
}
for (ir.VariableDeclaration parameter in node.namedParameters) {
handleParameter(parameter, isOptional: true);
}
visit(node.body);
MemberEntity analyzedMember = _analyzedMember;
if (analyzedMember is ConstructorEntity &&
analyzedMember.isGenerativeConstructor) {
// TODO(redemption): Handle initializers.
ClassEntity cls = analyzedMember.enclosingClass;
if (cls.isAbstract) {
if (_closedWorld.isInstantiated(cls)) {
_returnType = _types.nonNullSubclass(cls);
} else {
// TODO(johnniwinther): Avoid analyzing [_analyzedMember] in this
// case; it's never called.
_returnType = _types.nonNullEmpty();
}
} else {
_returnType = _types.nonNullExact(cls);
}
} else {
switch (node.asyncMarker) {
case ir.AsyncMarker.Sync:
if (_returnType == null) {
// No return in the body.
_returnType = _locals.seenReturnOrThrow
? _types.nonNullEmpty() // Body always throws.
: _types.nullType;
} else if (!_locals.seenReturnOrThrow) {
// We haven'TypeInformation seen returns on all branches. So the
// method may also return null.
recordReturnType(_types.nullType);
}
break;
case ir.AsyncMarker.SyncStar:
// TODO(asgerf): Maybe make a ContainerTypeMask for these? The type
// contained is the method body's return type.
recordReturnType(_types.syncStarIterableType);
break;
case ir.AsyncMarker.Async:
recordReturnType(_types.asyncFutureType);
break;
case ir.AsyncMarker.AsyncStar:
recordReturnType(_types.asyncStarStreamType);
break;
case ir.AsyncMarker.SyncYielding:
failedAt(
_analyzedMember, "Unexpected async marker: ${node.asyncMarker}");
break;
}
}
return _returnType;
}
@override
TypeInformation defaultExpression(ir.Expression node) {
// TODO(johnniwinther): Make this throw to assert that all expressions are
// handled.
return _types.dynamicType;
}
@override
TypeInformation defaultStatement(ir.Statement node) {
// TODO(johnniwinther): Make this throw to assert that all statements are
// handled.
node.visitChildren(this);
return null;
}
@override
TypeInformation visitNullLiteral(ir.NullLiteral literal) {
return _types.nullType;
}
@override
TypeInformation visitBlock(ir.Block block) {
for (ir.Statement statement in block.statements) {
statement.accept(this);
if (_locals.aborts) break;
}
return null;
}
@override
TypeInformation visitListLiteral(ir.ListLiteral listLiteral) {
// We only set the type once. We don't need to re-visit the children
// when re-analyzing the node.
return _inferrer.concreteTypes.putIfAbsent(listLiteral, () {
TypeInformation elementType;
int length = 0;
for (ir.Expression element in listLiteral.expressions) {
TypeInformation type = element.accept(this);
elementType = elementType == null
? _types.allocatePhi(null, null, type, isTry: false)
: _types.addPhiInput(null, elementType, type);
length++;
}
elementType = elementType == null
? _types.nonNullEmpty()
: _types.simplifyPhi(null, null, elementType);
TypeInformation containerType =
listLiteral.isConst ? _types.constListType : _types.growableListType;
return _types.allocateList(
containerType, listLiteral, _analyzedMember, elementType, length);
});
}
@override
TypeInformation visitReturnStatement(ir.ReturnStatement node) {
ir.Node expression = node.expression;
recordReturnType(
expression == null ? _types.nullType : expression.accept(this));
_locals.seenReturnOrThrow = true;
initializationIsIndefinite();
return null;
}
@override
TypeInformation visitIntLiteral(ir.IntLiteral node) {
ConstantSystem constantSystem = _closedWorld.constantSystem;
// The JavaScript backend may turn this literal into a double at
// runtime.
return _types.getConcreteTypeFor(
computeTypeMask(_closedWorld, constantSystem.createInt(node.value)));
}
@override
TypeInformation visitDoubleLiteral(ir.DoubleLiteral node) {
ConstantSystem constantSystem = _closedWorld.constantSystem;
// The JavaScript backend may turn this literal into an integer at
// runtime.
return _types.getConcreteTypeFor(
computeTypeMask(_closedWorld, constantSystem.createDouble(node.value)));
}
@override
TypeInformation visitVariableDeclaration(ir.VariableDeclaration node) {
assert(
node.parent is! ir.FunctionNode, "Unexpected parameter declaration.");
Local local = _localsMap.getLocalVariable(node);
DartType type = _localsMap.getLocalType(_elementMap, local);
if (node.initializer == null) {
_locals.update(local, _types.nullType, node, type);
} else {
_locals.update(local, visit(node.initializer), node, type);
}
return null;
}
@override
TypeInformation visitVariableGet(ir.VariableGet node) {
return _locals.use(_localsMap.getLocalVariable(node.variable));
}
@override
TypeInformation visitVariableSet(ir.VariableSet node) {
Local local = _localsMap.getLocalVariable(node.variable);
DartType type = _localsMap.getLocalType(_elementMap, local);
TypeInformation rhsType = visit(node.value);
_locals.update(local, rhsType, node, type);
return rhsType;
}
ArgumentsTypes analyzeArguments(ir.Arguments arguments) {
List<TypeInformation> positional = <TypeInformation>[];
Map<String, TypeInformation> named;
for (ir.Expression argument in arguments.positional) {
positional.add(argument.accept(this));
}
for (ir.NamedExpression argument in arguments.named) {
named ??= <String, TypeInformation>{};
named[argument.name] = argument.value.accept(this);
}
/// TODO(johnniwinther): Track `isThisExposed`.
return new ArgumentsTypes(positional, named);
}
@override
TypeInformation visitMethodInvocation(ir.MethodInvocation node) {
TypeInformation receiverType = visit(node.receiver);
Selector selector = _elementMap.getSelector(node);
TypeMask mask = _memberData.typeOfSend(node);
ArgumentsTypes arguments = analyzeArguments(node.arguments);
if (selector.name == '==' || selector.name == '!=') {
if (_types.isNull(receiverType)) {
// TODO(johnniwinther): Add null check.
return _types.boolType;
} else if (_types.isNull(arguments.positional[0])) {
// TODO(johnniwinther): Add null check.
return _types.boolType;
}
}
return handleDynamicInvoke(
CallType.access, node, selector, mask, receiverType, arguments);
}
TypeInformation handleDynamicInvoke(
CallType callType,
ir.Node node,
Selector selector,
TypeMask mask,
TypeInformation receiverType,
ArgumentsTypes arguments) {
assert(receiverType != null);
if (_types.selectorNeedsUpdate(receiverType, mask)) {
mask = receiverType == _types.dynamicType
? null
: _types.newTypedSelector(receiverType, mask);
_inferrer.updateSelectorInMember(
_analyzedMember, callType, node, selector, mask);
}
// TODO(johnniwinther): Refine receiver on non-captured locals.
return _inferrer.registerCalledSelector(callType, node, selector, mask,
receiverType, _analyzedMember, arguments, _sideEffects,
inLoop: inLoop, isConditional: false);
}
@override
TypeInformation visitLet(ir.Let node) {
visit(node.variable);
return visit(node.body);
}
@override
TypeInformation visitForInStatement(ir.ForInStatement node) {
TypeInformation expressionType = visit(node.iterable);
Selector iteratorSelector = Selectors.iterator;
TypeMask iteratorMask = _memberData.typeOfIterator(node);
Selector currentSelector = Selectors.current;
TypeMask currentMask = _memberData.typeOfIteratorCurrent(node);
Selector moveNextSelector = Selectors.moveNext;
TypeMask moveNextMask = _memberData.typeOfIteratorMoveNext(node);
TypeInformation iteratorType = handleDynamicInvoke(
CallType.forIn,
node,
iteratorSelector,
iteratorMask,
expressionType,
new ArgumentsTypes.empty());
handleDynamicInvoke(CallType.forIn, node, moveNextSelector, moveNextMask,
iteratorType, new ArgumentsTypes.empty());
TypeInformation currentType = handleDynamicInvoke(CallType.forIn, node,
currentSelector, currentMask, iteratorType, new ArgumentsTypes.empty());
Local variable = _localsMap.getLocalVariable(node.variable);
DartType variableType = _localsMap.getLocalType(_elementMap, variable);
_locals.update(variable, currentType, node.variable, variableType);
JumpTarget target = _localsMap.getJumpTargetForForIn(node);
return handleLoop(node, target, () {
visit(node.body);
});
}
void _setupBreaksAndContinues(JumpTarget target) {
if (target == null) return;
if (target.isContinueTarget) _continuesFor[target] = <LocalsHandler>[];
if (target.isBreakTarget) _breaksFor[target] = <LocalsHandler>[];
}
void _clearBreaksAndContinues(JumpTarget element) {
_continuesFor.remove(element);
_breaksFor.remove(element);
}
List<LocalsHandler> _getBreaks(JumpTarget target) {
List<LocalsHandler> list = <LocalsHandler>[_locals];
if (target == null) return list;
if (!target.isBreakTarget) return list;
return list..addAll(_breaksFor[target]);
}
List<LocalsHandler> _getLoopBackEdges(JumpTarget target) {
List<LocalsHandler> list = <LocalsHandler>[_locals];
if (target == null) return list;
if (!target.isContinueTarget) return list;
return list..addAll(_continuesFor[target]);
}
TypeInformation handleLoop(ir.Node node, JumpTarget target, void logic()) {
_loopLevel++;
bool changed = false;
LocalsHandler saved = _locals;
saved.startLoop(node);
do {
// Setup (and clear in case of multiple iterations of the loop)
// the lists of breaks and continues seen in the loop.
_setupBreaksAndContinues(target);
_locals = new LocalsHandler.from(saved, node);
logic();
changed = saved.mergeAll(_getLoopBackEdges(target));
} while (changed);
_loopLevel--;
saved.endLoop(node);
bool keepOwnLocals = node is! ir.DoStatement;
saved.mergeAfterBreaks(_getBreaks(target), keepOwnLocals: keepOwnLocals);
_locals = saved;
_clearBreaksAndContinues(target);
return null;
}
@override
TypeInformation visitConstructorInvocation(ir.ConstructorInvocation node) {
ConstructorEntity constructor = _elementMap.getConstructor(node.target);
ArgumentsTypes arguments = analyzeArguments(node.arguments);
// TODO(redemption): Handle initializers.
// TODO(redemption): Handle foreign constructors.
Selector selector = _elementMap.getSelector(node);
TypeMask mask = _memberData.typeOfSend(node);
return handleConstructorInvoke(
node, selector, mask, constructor, arguments);
}
TypeInformation handleConstructorInvoke(ir.Node node, Selector selector,
TypeMask mask, ConstructorEntity constructor, ArgumentsTypes arguments) {
TypeInformation returnType =
handleStaticInvoke(node, selector, mask, constructor, arguments);
// TODO(redemption): Special-case `List` constructors.
return returnType;
}
TypeInformation handleStaticInvoke(ir.Node node, Selector selector,
TypeMask mask, MemberEntity element, ArgumentsTypes arguments) {
return _inferrer.registerCalledMember(node, selector, mask, _analyzedMember,
element, arguments, _sideEffects, inLoop);
}
@override
TypeInformation visitStaticInvocation(ir.StaticInvocation node) {
MemberEntity member = _elementMap.getMember(node.target);
ArgumentsTypes arguments = analyzeArguments(node.arguments);
// TODO(redemption): Handle foreign functions.
Selector selector = _elementMap.getSelector(node);
TypeMask mask = _memberData.typeOfSend(node);
if (member.isConstructor) {
return handleConstructorInvoke(node, selector, mask, member, arguments);
} else if (member.isFunction) {
return handleStaticInvoke(node, selector, mask, member, arguments);
} else {
handleStaticInvoke(node, selector, mask, member, arguments);
return _inferrer.registerCalledClosure(
node,
selector,
mask,
_inferrer.typeOfMember(member),
_analyzedMember,
arguments,
_sideEffects,
inLoop);
}
}
}