blob: 28194d1dfa78e7efc0a4bab7463216f02b9f1a89 [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;
final bool _inGenerativeConstructor;
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;
/// Whether we currently collect [IsCheck]s.
bool _accumulateIsChecks = false;
bool _conditionIsSimple = false;
/// The [IsCheck]s that show us what types locals currently _are_.
List<IsCheck> _positiveIsChecks;
/// The [IsCheck]s that show us what types locals currently are _not_.
List<IsCheck> _negativeIsChecks;
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),
this._inGenerativeConstructor = _analyzedNode is ir.Constructor {
if (_locals != null) return;
FieldInitializationScope<ir.Node> fieldScope =
_inGenerativeConstructor ? 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() {
if (_inGenerativeConstructor) {
_locals.fieldScope.isIndefinite = true;
}
}
TypeInformation _thisType;
TypeInformation get thisType {
if (_thisType != null) return _thisType;
ClassEntity cls = _elementMap.getMemberThisType(_analyzedMember)?.element;
if (_closedWorld.isUsedAsMixin(cls)) {
return _thisType = _types.nonNullSubtype(cls);
} else {
return _thisType = _types.nonNullSubclass(cls);
}
}
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 visitConstructor(ir.Constructor node) {
handleParameters(node.function);
node.initializers.forEach(visit);
visit(node.function.body);
ClassEntity cls = _analyzedMember.enclosingClass;
if (!(node.initializers.isNotEmpty &&
node.initializers.first is ir.RedirectingInitializer)) {
// Iterate over all instance fields, and give a null type to
// fields that we haven't initialized for sure.
_elementMap.elementEnvironment.forEachClassMember(cls,
(ClassEntity declarer, MemberEntity member) {
if (declarer != cls) return;
if (member.isField && member.isInstanceMember && member.isAssignable) {
TypeInformation type = _locals.fieldScope.readField(member);
MemberDefinition definition = _elementMap.getMemberDefinition(member);
assert(definition.kind == MemberKind.regular);
ir.Field node = definition.node;
if (type == null &&
(node.initializer == null ||
node.initializer is ir.NullLiteral)) {
_inferrer.recordTypeOfField(member, _types.nullType);
}
}
});
}
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);
}
return _returnType;
}
@override
visitFieldInitializer(ir.FieldInitializer node) {
TypeInformation rhsType = visit(node.value);
FieldEntity field = _elementMap.getField(node.field);
_locals.updateField(field, rhsType);
_inferrer.recordTypeOfField(field, rhsType);
}
void handleParameters(ir.FunctionNode node) {
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);
}
}
@override
TypeInformation visitFunctionNode(ir.FunctionNode node) {
// TODO(redemption): Handle native methods.
handleParameters(node);
visit(node.body);
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 visitMapLiteral(ir.MapLiteral node) {
return _inferrer.concreteTypes.putIfAbsent(node, () {
List keyTypes = [];
List valueTypes = [];
for (ir.MapEntry entry in node.entries) {
keyTypes.add(visit(entry.key));
valueTypes.add(visit(entry.value));
}
TypeInformation type =
node.isConst ? _types.constMapType : _types.mapType;
return _types.allocateMap(
type, node, _analyzedMember, keyTypes, valueTypes);
});
}
@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 visitBoolLiteral(ir.BoolLiteral node) {
return _types.boolLiteralType(node.value);
}
@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 visitStringLiteral(ir.StringLiteral node) {
return _types.stringLiteralType(node.value);
}
@override
TypeInformation visitStringConcatenation(ir.StringConcatenation node) {
node.visitChildren(this);
return _types.stringType;
}
@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 == '==') {
if (_types.isNull(receiverType)) {
// null == o
_potentiallyAddNullCheck(node, node.arguments.positional.first);
return _types.boolType;
} else if (_types.isNull(arguments.positional[0])) {
// o == null
_potentiallyAddNullCheck(node, node.receiver);
return _types.boolType;
}
}
return handleDynamicInvoke(
CallType.access, node, selector, mask, receiverType, arguments);
}
TypeInformation _handleDynamic(
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);
}
TypeInformation handleDynamicGet(ir.Node node, Selector selector,
TypeMask mask, TypeInformation receiverType) {
return _handleDynamic(
CallType.access, node, selector, mask, receiverType, null);
}
TypeInformation handleDynamicSet(ir.Node node, Selector selector,
TypeMask mask, TypeInformation receiverType, TypeInformation rhsType) {
ArgumentsTypes arguments = new ArgumentsTypes([rhsType], null);
return _handleDynamic(
CallType.access, node, selector, mask, receiverType, arguments);
}
TypeInformation handleDynamicInvoke(
CallType callType,
ir.Node node,
Selector selector,
TypeMask mask,
TypeInformation receiverType,
ArgumentsTypes arguments) {
return _handleDynamic(
callType, node, selector, mask, receiverType, arguments);
}
@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);
}
}
@override
TypeInformation visitPropertyGet(ir.PropertyGet node) {
TypeInformation receiverType = visit(node.receiver);
Selector selector = _elementMap.getSelector(node);
TypeMask mask = _memberData.typeOfSend(node);
// TODO(redemption): Use `node.interfaceTarget` to narrow the receiver type
// for --trust-type-annotations/strong-mode.
return handleDynamicGet(node, selector, mask, receiverType);
}
@override
TypeInformation visitPropertySet(ir.PropertySet node) {
TypeInformation rhsType = visit(node.value);
TypeInformation receiverType = visit(node.receiver);
Selector selector = _elementMap.getSelector(node);
TypeMask mask = _memberData.typeOfSend(node);
if (_inGenerativeConstructor && node.receiver is ir.ThisExpression) {
Iterable<MemberEntity> targets = _closedWorld.locateMembers(
selector, _types.newTypedSelector(receiverType, mask));
// We just recognized a field initialization of the form:
// `this.foo = 42`. If there is only one target, we can update
// its type.
if (targets.length == 1) {
MemberEntity single = targets.first;
if (single.isField) {
FieldEntity field = single;
_locals.updateField(field, rhsType);
}
}
}
handleDynamicSet(node, selector, mask, receiverType, rhsType);
return rhsType;
}
@override
TypeInformation visitThisExpression(ir.ThisExpression node) {
return thisType;
}
bool handleCondition(
ir.Node node, List<IsCheck> positiveTests, List<IsCheck> negativeTests) {
bool oldConditionIsSimple = _conditionIsSimple;
bool oldAccumulateIsChecks = _accumulateIsChecks;
List<IsCheck> oldPositiveIsChecks = _positiveIsChecks;
List<IsCheck> oldNegativeIsChecks = _negativeIsChecks;
_accumulateIsChecks = true;
_conditionIsSimple = true;
_positiveIsChecks = positiveTests;
_negativeIsChecks = negativeTests;
visit(node);
bool simpleCondition = _conditionIsSimple;
_accumulateIsChecks = oldAccumulateIsChecks;
_positiveIsChecks = oldPositiveIsChecks;
_negativeIsChecks = oldNegativeIsChecks;
_conditionIsSimple = oldConditionIsSimple;
return simpleCondition;
}
void _potentiallyAddIsCheck(ir.IsExpression node) {
if (!_accumulateIsChecks) return;
ir.Expression operand = node.operand;
if (operand is ir.VariableGet) {
_positiveIsChecks.add(new IsCheck(
node,
_localsMap.getLocalVariable(operand.variable),
_elementMap.getDartType(node.type)));
}
}
void _potentiallyAddNullCheck(
ir.MethodInvocation node, ir.Expression receiver) {
if (!_accumulateIsChecks) return;
if (receiver is ir.VariableGet) {
_positiveIsChecks.add(new IsCheck(
node, _localsMap.getLocalVariable(receiver.variable), null));
}
}
void _updateIsChecks(
List<IsCheck> positiveTests, List<IsCheck> negativeTests) {
for (IsCheck check in positiveTests) {
if (check.type != null) {
_locals.narrow(check.local, check.type, check.node);
} else {
DartType localType = _localsMap.getLocalType(_elementMap, check.local);
_locals.update(check.local, _types.nullType, check.node, localType);
}
}
for (IsCheck check in negativeTests) {
if (check.type != null) {
// TODO(johnniwinther): Use negative type knowledge.
} else {
_locals.narrow(
check.local, _closedWorld.commonElements.objectType, check.node);
}
}
}
@override
TypeInformation visitIfStatement(ir.IfStatement node) {
List<IsCheck> positiveTests = <IsCheck>[];
List<IsCheck> negativeTests = <IsCheck>[];
bool simpleCondition =
handleCondition(node.condition, positiveTests, negativeTests);
LocalsHandler saved = _locals;
_locals = new LocalsHandler.from(_locals, node);
_updateIsChecks(positiveTests, negativeTests);
visit(node.then);
LocalsHandler thenLocals = _locals;
_locals = new LocalsHandler.from(saved, node);
if (simpleCondition) {
_updateIsChecks(negativeTests, positiveTests);
}
visit(node.otherwise);
saved.mergeDiamondFlow(thenLocals, _locals);
_locals = saved;
return null;
}
@override
TypeInformation visitIsExpression(ir.IsExpression node) {
_potentiallyAddIsCheck(node);
visit(node.operand);
return _types.boolType;
}
@override
TypeInformation visitNot(ir.Not node) {
List<IsCheck> temp = _positiveIsChecks;
_positiveIsChecks = _negativeIsChecks;
_negativeIsChecks = temp;
visit(node.operand);
temp = _positiveIsChecks;
_positiveIsChecks = _negativeIsChecks;
_negativeIsChecks = temp;
return _types.boolType;
}
@override
TypeInformation visitLogicalExpression(ir.LogicalExpression node) {
if (node.operator == '&&') {
_conditionIsSimple = false;
bool oldAccumulateIsChecks = _accumulateIsChecks;
List<IsCheck> oldPositiveIsChecks = _positiveIsChecks;
List<IsCheck> oldNegativeIsChecks = _negativeIsChecks;
if (!_accumulateIsChecks) {
_accumulateIsChecks = true;
_positiveIsChecks = <IsCheck>[];
_negativeIsChecks = <IsCheck>[];
}
visit(node.left);
LocalsHandler saved = _locals;
_locals = new LocalsHandler.from(_locals, node);
_updateIsChecks(_positiveIsChecks, _negativeIsChecks);
LocalsHandler narrowed;
if (oldAccumulateIsChecks) {
narrowed = new LocalsHandler.topLevelCopyOf(_locals);
} else {
_accumulateIsChecks = false;
_positiveIsChecks = oldPositiveIsChecks;
_negativeIsChecks = oldNegativeIsChecks;
}
visit(node.right);
if (oldAccumulateIsChecks) {
bool invalidatedInRightHandSide(IsCheck check) {
return narrowed.locals[check.local] != _locals.locals[check.local];
}
_positiveIsChecks.removeWhere(invalidatedInRightHandSide);
_negativeIsChecks.removeWhere(invalidatedInRightHandSide);
}
saved.mergeDiamondFlow(_locals, null);
_locals = saved;
return _types.boolType;
} else if (node.operator == '||') {
_conditionIsSimple = false;
List<IsCheck> positiveIsChecks = <IsCheck>[];
List<IsCheck> negativeIsChecks = <IsCheck>[];
bool isSimple =
handleCondition(node.left, positiveIsChecks, negativeIsChecks);
LocalsHandler saved = _locals;
_locals = new LocalsHandler.from(_locals, node);
if (isSimple) {
_updateIsChecks(negativeIsChecks, positiveIsChecks);
}
bool oldAccumulateIsChecks = _accumulateIsChecks;
_accumulateIsChecks = false;
visit(node.right);
_accumulateIsChecks = oldAccumulateIsChecks;
saved.mergeDiamondFlow(_locals, null);
_locals = saved;
return _types.boolType;
}
failedAt(CURRENT_ELEMENT_SPANNABLE,
"Unexpected logical operator '${node.operator}'.");
return null;
}
}
class IsCheck {
final ir.Expression node;
final Local local;
final DartType type;
IsCheck(this.node, this.local, this.type);
String toString() => 'IsCheck($local,$type)';
}