| // 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 '../constants/values.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/jumps.dart'; |
| import '../elements/types.dart'; |
| import '../js_backend/backend.dart'; |
| import '../js_model/locals.dart' show JumpVisitor; |
| import '../kernel/element_map.dart'; |
| import '../native/behavior.dart'; |
| import '../options.dart'; |
| import '../types/abstract_value_domain.dart'; |
| import '../types/types.dart'; |
| import '../universe/selector.dart'; |
| import '../universe/side_effects.dart'; |
| import '../world.dart'; |
| import 'inferrer_engine.dart'; |
| import 'kernel_inferrer_engine.dart'; |
| import 'locals_handler.dart'; |
| import 'type_graph_nodes.dart'; |
| import 'type_system.dart'; |
| |
| /// Whether the static type of property gets and method invocations is used |
| /// to narrow the inferred type in strong mode. |
| bool useStaticResultTypes = false; |
| |
| /// [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 JClosedWorld _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<ir.Node> _locals; |
| final SideEffectsBuilder _sideEffectsBuilder; |
| final Map<JumpTarget, List<LocalsHandler<ir.Node>>> _breaksFor = |
| <JumpTarget, List<LocalsHandler<ir.Node>>>{}; |
| final Map<JumpTarget, List<LocalsHandler<ir.Node>>> _continuesFor = |
| <JumpTarget, List<LocalsHandler<ir.Node>>>{}; |
| TypeInformation _returnType; |
| final Set<Local> _capturedVariables = new Set<Local>(); |
| |
| /// 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), |
| // TODO(johnniwinther): Should side effects also be tracked for field |
| // initializers? |
| this._sideEffectsBuilder = _analyzedMember is FunctionEntity |
| ? _inferrer.inferredDataBuilder |
| .getSideEffectsBuilder(_analyzedMember) |
| : new SideEffectsBuilder.free(_analyzedMember), |
| this._inGenerativeConstructor = _analyzedNode is ir.Constructor { |
| if (_locals != null) return; |
| |
| FieldInitializationScope<ir.Node> fieldScope = |
| _inGenerativeConstructor ? new FieldInitializationScope(_types) : null; |
| _locals = new LocalsHandler<ir.Node>( |
| _inferrer, _types, _options, _analyzedNode, fieldScope); |
| } |
| |
| int _loopLevel = 0; |
| |
| bool get inLoop => _loopLevel > 0; |
| |
| bool get _isThisExposed { |
| return _inGenerativeConstructor ? _locals.fieldScope.isThisExposed : true; |
| } |
| |
| void _markThisAsExposed() { |
| if (_inGenerativeConstructor) { |
| _locals.fieldScope.isThisExposed = true; |
| } |
| } |
| |
| /// Returns `true` if [member] is defined in a subclass of the current this |
| /// type. |
| bool _isInClassOrSubclass(MemberEntity member) { |
| ClassEntity cls = _elementMap.getMemberThisType(_analyzedMember)?.element; |
| if (cls == null) return false; |
| return _closedWorld.isSubclassOf(member.enclosingClass, cls); |
| } |
| |
| /// Checks whether the access or update of [selector] on [mask] potentially |
| /// exposes `this`. |
| /// |
| /// If all matching members are instance fields on the current `this` |
| /// class or subclasses, `this` is not considered to be exposed. |
| /// |
| /// If an instance field matched with a [selector] that is _not_ a setter, the |
| /// field is considered to have been read before initialization and the field |
| /// is assumed to be potentially `null`. |
| void _checkIfExposesThis(Selector selector, AbstractValue mask) { |
| if (_isThisExposed) { |
| // We already consider `this` to have been exposed. |
| return; |
| } |
| if (_inferrer.closedWorld.includesClosureCall(selector, mask)) { |
| // TODO(ngeoffray): We could do better here if we knew what we |
| // are calling does not expose this. |
| _markThisAsExposed(); |
| } else { |
| _inferrer.forEachElementMatching(selector, mask, (MemberEntity element) { |
| if (element != null && element.isField) { |
| FieldEntity field = element; |
| if (!selector.isSetter && |
| _isInClassOrSubclass(field) && |
| field.isAssignable && |
| _locals.fieldScope.readField(field) == null && |
| getFieldInitializer(_elementMap, field) == null) { |
| // If the field is being used before this constructor |
| // actually had a chance to initialize it, say it can be |
| // null. |
| _inferrer.recordTypeOfField(field, _types.nullType); |
| } |
| // Accessing a field does not expose `this`. |
| return true; |
| } |
| // TODO(ngeoffray): We could do better here if we knew what we |
| // are calling does not expose this. |
| _markThisAsExposed(); |
| return false; |
| }); |
| } |
| } |
| |
| 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]. |
| ScopeInfo scopeInfo = _closureDataLookup.getScopeInfo(_analyzedMember); |
| scopeInfo.forEachBoxedVariable((variable, field) { |
| _locals.setCapturedAndBoxed(variable, field); |
| }); |
| |
| return _analyzedNode.accept(this); |
| } |
| |
| bool isIncompatibleInvoke(FunctionEntity function, ArgumentsTypes arguments) { |
| ParameterStructure parameterStructure = function.parameterStructure; |
| |
| return arguments.positional.length < |
| parameterStructure.requiredParameters || |
| arguments.positional.length > parameterStructure.positionalParameters || |
| arguments.named.keys |
| .any((name) => !parameterStructure.namedParameters.contains(name)); |
| } |
| |
| 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; |
| if (node.initializer != null) { |
| type = visit(node.initializer); |
| } else { |
| type = _types.nullType; |
| } |
| _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.forEachLocalClassMember(cls, |
| (MemberEntity member) { |
| 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); |
| } |
| } |
| }); |
| } |
| _inferrer.recordExposesThis(_analyzedMember, _isThisExposed); |
| |
| 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); |
| } |
| assert(_breaksFor.isEmpty); |
| assert(_continuesFor.isEmpty); |
| 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); |
| } |
| |
| @override |
| visitSuperInitializer(ir.SuperInitializer node) { |
| ConstructorEntity constructor = _elementMap.getConstructor(node.target); |
| ArgumentsTypes arguments = analyzeArguments(node.arguments); |
| Selector selector = new Selector(SelectorKind.CALL, constructor.memberName, |
| _elementMap.getCallStructure(node.arguments)); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| handleConstructorInvoke( |
| node, node.arguments, selector, mask, constructor, arguments); |
| |
| _inferrer.analyze(constructor); |
| if (_inferrer.checkIfExposesThis(constructor)) { |
| _markThisAsExposed(); |
| } |
| } |
| |
| @override |
| visitRedirectingInitializer(ir.RedirectingInitializer node) { |
| ConstructorEntity constructor = _elementMap.getConstructor(node.target); |
| ArgumentsTypes arguments = analyzeArguments(node.arguments); |
| Selector selector = new Selector(SelectorKind.CALL, constructor.memberName, |
| _elementMap.getCallStructure(node.arguments)); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| handleConstructorInvoke( |
| node, node.arguments, selector, mask, constructor, arguments); |
| |
| _inferrer.analyze(constructor); |
| if (_inferrer.checkIfExposesThis(constructor)) { |
| _markThisAsExposed(); |
| } |
| } |
| |
| @override |
| visitLocalInitializer(ir.LocalInitializer node) { |
| visit(node.variable); |
| } |
| |
| 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) { |
| handleParameters(node); |
| |
| if (_closedWorld.nativeData.isNativeMember(_analyzedMember)) { |
| // Native methods do not have a body, and we currently just say |
| // they return dynamic. |
| return _types.dynamicType; |
| } |
| |
| 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; |
| } |
| assert(_breaksFor.isEmpty); |
| assert(_continuesFor.isEmpty); |
| return _returnType; |
| } |
| |
| @override |
| TypeInformation visitInstantiation(ir.Instantiation node) { |
| // TODO(sra): Add a TypeInformation for Instantiations. Instantiated |
| // generic methods will need to be traced separately, and have the |
| // information gathered in tracing reflected back to the generic method. For |
| // now, pass along the uninstantiated method. |
| return visit(node.expression); |
| } |
| |
| @override |
| TypeInformation defaultExpression(ir.Expression node) { |
| throw new UnimplementedError( |
| 'Unhandled expression: ${node} (${node.runtimeType})'); |
| } |
| |
| @override |
| defaultStatement(ir.Statement node) { |
| throw new UnimplementedError( |
| 'Unhandled statement: ${node} (${node.runtimeType})'); |
| } |
| |
| @override |
| TypeInformation visitNullLiteral(ir.NullLiteral literal) { |
| return _types.nullType; |
| } |
| |
| @override |
| visitBlock(ir.Block block) { |
| for (ir.Statement statement in block.statements) { |
| statement.accept(this); |
| if (_locals.aborts) break; |
| } |
| } |
| |
| @override |
| visitExpressionStatement(ir.ExpressionStatement node) { |
| visit(node.expression); |
| } |
| |
| @override |
| visitEmptyStatement(ir.EmptyStatement node) { |
| // Nothing to do. |
| } |
| |
| @override |
| visitAssertStatement(ir.AssertStatement node) { |
| // Avoid pollution from assert statement unless enabled. |
| if (!_options.enableUserAssertions) { |
| return null; |
| } |
| // TODO(johnniwinther): Should assert be used with --trust-type-annotations? |
| // TODO(johnniwinther): Track reachable for assertions known to fail. |
| 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); |
| |
| LocalsHandler thenLocals = _locals; |
| _locals = new LocalsHandler.from(saved, node); |
| if (simpleCondition) _updateIsChecks(negativeTests, positiveTests); |
| visit(node.message); |
| _locals.seenReturnOrThrow = true; |
| saved.mergeDiamondFlow(thenLocals, _locals); |
| _locals = saved; |
| } |
| |
| @override |
| visitBreakStatement(ir.BreakStatement node) { |
| JumpTarget target = _localsMap.getJumpTargetForBreak(node); |
| _locals.seenBreakOrContinue = true; |
| // Do a deep-copy of the locals, because the code following the |
| // break will change them. |
| if (_localsMap.generateContinueForBreak(node)) { |
| _continuesFor[target].add(new LocalsHandler.deepCopyOf(_locals)); |
| } else { |
| _breaksFor[target].add(new LocalsHandler.deepCopyOf(_locals)); |
| } |
| } |
| |
| @override |
| visitLabeledStatement(ir.LabeledStatement node) { |
| ir.Statement body = node.body; |
| if (JumpVisitor.canBeBreakTarget(body)) { |
| // Loops and switches handle their own labels. |
| visit(body); |
| } else { |
| JumpTarget jumpTarget = _localsMap.getJumpTargetForLabel(node); |
| _setupBreaksAndContinues(jumpTarget); |
| visit(body); |
| _locals.mergeAfterBreaks(_getBreaks(jumpTarget)); |
| _clearBreaksAndContinues(jumpTarget); |
| } |
| } |
| |
| @override |
| visitSwitchStatement(ir.SwitchStatement node) { |
| visit(node.expression); |
| |
| JumpTarget jumpTarget = _localsMap.getJumpTargetForSwitch(node); |
| _setupBreaksAndContinues(jumpTarget); |
| |
| List<JumpTarget> continueTargets = <JumpTarget>[]; |
| for (ir.SwitchCase switchCase in node.cases) { |
| JumpTarget continueTarget = |
| _localsMap.getJumpTargetForSwitchCase(switchCase); |
| if (continueTarget != null) { |
| continueTargets.add(continueTarget); |
| } |
| } |
| if (continueTargets.isNotEmpty) { |
| continueTargets.forEach(_setupBreaksAndContinues); |
| |
| // If the switch statement has a continue, we conservatively |
| // visit all cases and update [locals] until we have reached a |
| // fixed point. |
| bool changed; |
| _locals.startLoop(node); |
| do { |
| changed = false; |
| for (ir.SwitchCase switchCase in node.cases) { |
| LocalsHandler saved = _locals; |
| _locals = new LocalsHandler.from(_locals, switchCase); |
| visit(switchCase); |
| changed = saved.mergeAll([_locals]) || changed; |
| _locals = saved; |
| } |
| } while (changed); |
| _locals.endLoop(node); |
| |
| continueTargets.forEach(_clearBreaksAndContinues); |
| } else { |
| LocalsHandler saved = _locals; |
| List<LocalsHandler<ir.Node>> localsToMerge = <LocalsHandler<ir.Node>>[]; |
| bool hasDefaultCase = false; |
| |
| for (ir.SwitchCase switchCase in node.cases) { |
| if (switchCase.isDefault) { |
| hasDefaultCase = true; |
| } |
| _locals = new LocalsHandler.from(saved, switchCase); |
| visit(switchCase); |
| localsToMerge.add(_locals); |
| } |
| saved.mergeAfterBreaks(localsToMerge, keepOwnLocals: !hasDefaultCase); |
| _locals = saved; |
| } |
| _clearBreaksAndContinues(jumpTarget); |
| } |
| |
| @override |
| visitSwitchCase(ir.SwitchCase node) { |
| visit(node.body); |
| } |
| |
| @override |
| visitContinueSwitchStatement(ir.ContinueSwitchStatement node) { |
| JumpTarget target = _localsMap.getJumpTargetForContinueSwitch(node); |
| _locals.seenBreakOrContinue = true; |
| // Do a deep-copy of the locals, because the code following the |
| // break will change them. |
| _continuesFor[target].add(new LocalsHandler.deepCopyOf(_locals)); |
| } |
| |
| @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 = <TypeInformation>[]; |
| List valueTypes = <TypeInformation>[]; |
| |
| 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(_closedWorld.abstractValueDomain |
| .computeAbstractValueForConstant( |
| constantSystem.createIntFromInt(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(_closedWorld.abstractValueDomain |
| .computeAbstractValueForConstant( |
| constantSystem.createDouble(node.value))); |
| } |
| |
| @override |
| TypeInformation visitStringLiteral(ir.StringLiteral node) { |
| return _types.stringLiteralType(node.value); |
| } |
| |
| @override |
| TypeInformation visitStringConcatenation(ir.StringConcatenation node) { |
| // Interpolation could have any effects since it could call any toString() |
| // method. |
| // TODO(sra): This could be modelled by a call to toString() but with a |
| // guaranteed String return type. Interpolation of known types would get |
| // specialized effects. This would not currently be effective since the JS |
| // code in the toString methods for intercepted primitive types is assumed |
| // to have all effects. Effect annotations on JS code would be needed to |
| // get the benefit. |
| _sideEffectsBuilder.setAllSideEffects(); |
| |
| node.visitChildren(this); |
| return _types.stringType; |
| } |
| |
| @override |
| TypeInformation visitSymbolLiteral(ir.SymbolLiteral node) { |
| return _types |
| .nonNullSubtype(_closedWorld.commonElements.symbolImplementationClass); |
| } |
| |
| @override |
| TypeInformation visitTypeLiteral(ir.TypeLiteral node) { |
| return _types.typeType; |
| } |
| |
| @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); |
| } |
| if (node.initializer is ir.ThisExpression) { |
| _markThisAsExposed(); |
| } |
| return null; |
| } |
| |
| @override |
| TypeInformation visitVariableGet(ir.VariableGet node) { |
| Local local = _localsMap.getLocalVariable(node.variable); |
| TypeInformation type = _locals.use(local); |
| assert(type != null, "Missing type information for $local."); |
| return type; |
| } |
| |
| @override |
| TypeInformation visitVariableSet(ir.VariableSet node) { |
| TypeInformation rhsType = visit(node.value); |
| if (node.value is ir.ThisExpression) { |
| _markThisAsExposed(); |
| } |
| Local local = _localsMap.getLocalVariable(node.variable); |
| DartType type = _localsMap.getLocalType(_elementMap, local); |
| _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) { |
| // TODO(ngeoffray): We could do better here if we knew what we |
| // are calling does not expose this. |
| if (argument is ir.ThisExpression) { |
| _markThisAsExposed(); |
| } |
| positional.add(argument.accept(this)); |
| } |
| for (ir.NamedExpression argument in arguments.named) { |
| named ??= <String, TypeInformation>{}; |
| ir.Expression value = argument.value; |
| // TODO(ngeoffray): We could do better here if we knew what we |
| // are calling does not expose this. |
| if (value is ir.ThisExpression) { |
| _markThisAsExposed(); |
| } |
| named[argument.name] = value.accept(this); |
| } |
| |
| return new ArgumentsTypes(positional, named); |
| } |
| |
| @override |
| TypeInformation visitMethodInvocation(ir.MethodInvocation node) { |
| Selector selector = _elementMap.getSelector(node); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| |
| ir.TreeNode receiver = node.receiver; |
| if (receiver is ir.VariableGet && |
| receiver.variable.parent is ir.FunctionDeclaration) { |
| // This is an invocation of a named local function. |
| ArgumentsTypes arguments = analyzeArguments(node.arguments); |
| ClosureRepresentationInfo info = |
| _closureDataLookup.getClosureInfo(receiver.variable.parent); |
| if (isIncompatibleInvoke(info.callMethod, arguments)) { |
| return _types.dynamicType; |
| } |
| |
| TypeInformation type = |
| handleStaticInvoke(node, selector, mask, info.callMethod, arguments); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| |
| TypeInformation receiverType = visit(receiver); |
| 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; |
| } |
| } |
| if (node.receiver is ir.ThisExpression) { |
| _checkIfExposesThis( |
| selector, _types.newTypedSelector(receiverType, mask)); |
| } |
| TypeInformation type = handleDynamicInvoke( |
| CallType.access, node, selector, mask, receiverType, arguments); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| |
| TypeInformation _handleDynamic( |
| CallType callType, |
| ir.Node node, |
| Selector selector, |
| AbstractValue 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); |
| } |
| |
| ir.VariableDeclaration variable; |
| if (node is ir.MethodInvocation && node.receiver is ir.VariableGet) { |
| ir.VariableGet get = node.receiver; |
| variable = get.variable; |
| } else if (node is ir.PropertyGet && node.receiver is ir.VariableGet) { |
| ir.VariableGet get = node.receiver; |
| variable = get.variable; |
| } else if (node is ir.PropertySet && node.receiver is ir.VariableGet) { |
| ir.VariableGet get = node.receiver; |
| variable = get.variable; |
| } |
| |
| if (variable != null) { |
| Local local = _localsMap.getLocalVariable(variable); |
| if (!_capturedVariables.contains(local)) { |
| TypeInformation refinedType = _types |
| .refineReceiver(selector, mask, receiverType, isConditional: false); |
| DartType type = _localsMap.getLocalType(_elementMap, local); |
| _locals.update(local, refinedType, node, type); |
| List<Refinement> refinements = _localRefinementMap[variable]; |
| if (refinements != null) { |
| refinements.add(new Refinement(selector, mask)); |
| } |
| } |
| } |
| |
| return _inferrer.registerCalledSelector(callType, node, selector, mask, |
| receiverType, _analyzedMember, arguments, _sideEffectsBuilder, |
| inLoop: inLoop, isConditional: false); |
| } |
| |
| TypeInformation handleDynamicGet(ir.Node node, Selector selector, |
| AbstractValue mask, TypeInformation receiverType) { |
| return _handleDynamic( |
| CallType.access, node, selector, mask, receiverType, null); |
| } |
| |
| TypeInformation handleDynamicSet( |
| ir.Node node, |
| Selector selector, |
| AbstractValue 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, |
| AbstractValue mask, |
| TypeInformation receiverType, |
| ArgumentsTypes arguments) { |
| return _handleDynamic( |
| callType, node, selector, mask, receiverType, arguments); |
| } |
| |
| /// Map from synthesized variables created for non-null operations to observed |
| /// refinements. This is used to refine locals in cases like: |
| /// |
| /// local?.method() |
| /// |
| /// which in kernel is encoded as |
| /// |
| /// let #t1 = local in #t1 == null ? null : #1.method() |
| /// |
| Map<ir.VariableDeclaration, List<Refinement>> _localRefinementMap = |
| <ir.VariableDeclaration, List<Refinement>>{}; |
| |
| @override |
| TypeInformation visitLet(ir.Let node) { |
| ir.VariableDeclaration alias; |
| ir.Expression body = node.body; |
| if (node.variable.name == null && |
| node.variable.isFinal && |
| node.variable.initializer is ir.VariableGet && |
| body is ir.ConditionalExpression && |
| body.condition is ir.MethodInvocation && |
| body.then is ir.NullLiteral) { |
| ir.VariableGet get = node.variable.initializer; |
| ir.MethodInvocation invocation = body.condition; |
| ir.Expression receiver = invocation.receiver; |
| if (invocation.name.name == '==' && |
| receiver is ir.VariableGet && |
| receiver.variable == node.variable && |
| invocation.arguments.positional.single is ir.NullLiteral) { |
| // We have |
| // let #t1 = local in #t1 == null ? null : e |
| alias = get.variable; |
| _localRefinementMap[node.variable] = <Refinement>[]; |
| } |
| } |
| visit(node.variable); |
| TypeInformation type = visit(body); |
| if (alias != null) { |
| List<Refinement> refinements = _localRefinementMap.remove(node.variable); |
| if (refinements.isNotEmpty) { |
| Local local = _localsMap.getLocalVariable(alias); |
| DartType type = _localsMap.getLocalType(_elementMap, local); |
| TypeInformation localType = _locals.use(local); |
| for (Refinement refinement in refinements) { |
| localType = _types.refineReceiver( |
| refinement.selector, refinement.mask, localType, |
| isConditional: true); |
| _locals.update(local, localType, node, type); |
| } |
| } |
| } |
| return type; |
| } |
| |
| @override |
| TypeInformation visitForInStatement(ir.ForInStatement node) { |
| if (node.iterable is ir.ThisExpression) { |
| // Any reasonable implementation of an iterator would expose |
| // this, so we play it safe and assume it will. |
| _markThisAsExposed(); |
| } |
| |
| AbstractValue currentMask; |
| AbstractValue moveNextMask; |
| TypeInformation iteratorType; |
| if (node.isAsync) { |
| TypeInformation expressionType = visit(node.iterable); |
| |
| currentMask = _memberData.typeOfIteratorCurrent(node); |
| moveNextMask = _memberData.typeOfIteratorMoveNext(node); |
| |
| ConstructorEntity constructor = |
| _closedWorld.commonElements.streamIteratorConstructor; |
| |
| /// Synthesize a call to the [StreamIterator] constructor. |
| iteratorType = handleStaticInvoke(node, null, null, constructor, |
| new ArgumentsTypes([expressionType], null)); |
| } else { |
| TypeInformation expressionType = visit(node.iterable); |
| Selector iteratorSelector = Selectors.iterator; |
| AbstractValue iteratorMask = _memberData.typeOfIterator(node); |
| currentMask = _memberData.typeOfIteratorCurrent(node); |
| moveNextMask = _memberData.typeOfIteratorMoveNext(node); |
| |
| iteratorType = handleDynamicInvoke(CallType.forIn, node, iteratorSelector, |
| iteratorMask, expressionType, new ArgumentsTypes.empty()); |
| } |
| |
| handleDynamicInvoke(CallType.forIn, node, Selectors.moveNext, moveNextMask, |
| iteratorType, new ArgumentsTypes.empty()); |
| TypeInformation currentType = handleDynamicInvoke( |
| CallType.forIn, |
| node, |
| Selectors.current, |
| 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<ir.Node>>[]; |
| } |
| if (target.isBreakTarget) { |
| _breaksFor[target] = <LocalsHandler<ir.Node>>[]; |
| } |
| } |
| |
| void _clearBreaksAndContinues(JumpTarget element) { |
| _continuesFor.remove(element); |
| _breaksFor.remove(element); |
| } |
| |
| List<LocalsHandler<ir.Node>> _getBreaks(JumpTarget target) { |
| List<LocalsHandler<ir.Node>> list = <LocalsHandler<ir.Node>>[_locals]; |
| if (target == null) return list; |
| if (!target.isBreakTarget) return list; |
| return list..addAll(_breaksFor[target]); |
| } |
| |
| List<LocalsHandler<ir.Node>> _getLoopBackEdges(JumpTarget target) { |
| List<LocalsHandler<ir.Node>> list = <LocalsHandler<ir.Node>>[_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); |
| Selector selector = _elementMap.getSelector(node); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| return handleConstructorInvoke( |
| node, node.arguments, selector, mask, constructor, arguments); |
| } |
| |
| /// Try to find the length given to a fixed array constructor call. |
| int _findLength(ir.Arguments arguments) { |
| ir.Expression firstArgument = arguments.positional.first; |
| if (firstArgument is ir.IntLiteral) { |
| return firstArgument.value; |
| } else if (firstArgument is ir.StaticGet) { |
| MemberEntity member = _elementMap.getMember(firstArgument.target); |
| if (member.isField && |
| (member.isStatic || member.isTopLevel) && |
| _closedWorld.fieldNeverChanges(member)) { |
| ConstantValue value = _elementMap.getFieldConstantValue(member); |
| if (value != null && value.isInt) { |
| IntConstantValue intValue = value; |
| return intValue.intValue.toInt(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /// Returns `true` if |
| bool _isConstructorOfTypedArraySubclass(ConstructorEntity constructor) { |
| ClassEntity cls = constructor.enclosingClass; |
| return cls.library.canonicalUri == Uris.dart__native_typed_data && |
| _closedWorld.nativeData.isNativeClass(cls) && |
| _closedWorld.isSubtypeOf( |
| cls, _closedWorld.commonElements.typedDataClass) && |
| _closedWorld.isSubtypeOf(cls, _closedWorld.commonElements.listClass) && |
| constructor.name == ''; |
| } |
| |
| TypeInformation handleConstructorInvoke( |
| ir.Node node, |
| ir.Arguments arguments, |
| Selector selector, |
| AbstractValue mask, |
| ConstructorEntity constructor, |
| ArgumentsTypes argumentsTypes) { |
| TypeInformation returnType = |
| handleStaticInvoke(node, selector, mask, constructor, argumentsTypes); |
| if (_elementMap.commonElements.isUnnamedListConstructor(constructor)) { |
| // We have `new List(...)`. |
| if (arguments.positional.isEmpty && arguments.named.isEmpty) { |
| // We have `new List()`. |
| return _inferrer.concreteTypes.putIfAbsent( |
| node, |
| () => _types.allocateList(_types.growableListType, node, |
| _analyzedMember, _types.nonNullEmpty(), 0)); |
| } else { |
| // We have `new List(len)`. |
| int length = _findLength(arguments); |
| return _inferrer.concreteTypes.putIfAbsent( |
| node, |
| () => _types.allocateList(_types.fixedListType, node, |
| _analyzedMember, _types.nullType, length)); |
| } |
| } else if (_elementMap.commonElements |
| .isFilledListConstructor(constructor)) { |
| // We have `new Uint32List(len, fill)`. |
| int length = _findLength(arguments); |
| TypeInformation elementType = argumentsTypes.positional[1]; |
| |
| return _inferrer.concreteTypes.putIfAbsent( |
| node, |
| () => _types.allocateList(_types.fixedListType, node, _analyzedMember, |
| elementType, length)); |
| } else if (_isConstructorOfTypedArraySubclass(constructor)) { |
| // We have something like `new List.filled(len, fill)`. |
| int length = _findLength(arguments); |
| MemberEntity member = _elementMap.elementEnvironment |
| .lookupClassMember(constructor.enclosingClass, '[]'); |
| TypeInformation elementType = _inferrer.returnTypeOfMember(member); |
| return _inferrer.concreteTypes.putIfAbsent( |
| node, |
| () => _types.allocateList( |
| _types.nonNullExact(constructor.enclosingClass), |
| node, |
| _analyzedMember, |
| elementType, |
| length)); |
| } else { |
| return returnType; |
| } |
| } |
| |
| TypeInformation handleStaticInvoke(ir.Node node, Selector selector, |
| AbstractValue mask, MemberEntity element, ArgumentsTypes arguments) { |
| return _inferrer.registerCalledMember(node, selector, mask, _analyzedMember, |
| element, arguments, _sideEffectsBuilder, inLoop); |
| } |
| |
| TypeInformation handleClosureCall(ir.Node node, Selector selector, |
| AbstractValue mask, MemberEntity member, ArgumentsTypes arguments) { |
| return _inferrer.registerCalledClosure( |
| node, |
| selector, |
| mask, |
| _inferrer.typeOfMember(member), |
| _analyzedMember, |
| arguments, |
| _sideEffectsBuilder, |
| inLoop: inLoop); |
| } |
| |
| TypeInformation handleForeignInvoke( |
| ir.StaticInvocation node, |
| FunctionEntity function, |
| ArgumentsTypes arguments, |
| Selector selector, |
| AbstractValue mask) { |
| String name = function.name; |
| handleStaticInvoke(node, selector, mask, function, arguments); |
| if (name == JavaScriptBackend.JS) { |
| NativeBehavior nativeBehavior = |
| _elementMap.getNativeBehaviorForJsCall(node); |
| _sideEffectsBuilder.add(nativeBehavior.sideEffects); |
| return _inferrer.typeOfNativeBehavior(nativeBehavior); |
| } else if (name == JavaScriptBackend.JS_EMBEDDED_GLOBAL) { |
| NativeBehavior nativeBehavior = |
| _elementMap.getNativeBehaviorForJsEmbeddedGlobalCall(node); |
| _sideEffectsBuilder.add(nativeBehavior.sideEffects); |
| return _inferrer.typeOfNativeBehavior(nativeBehavior); |
| } else if (name == JavaScriptBackend.JS_BUILTIN) { |
| NativeBehavior nativeBehavior = |
| _elementMap.getNativeBehaviorForJsBuiltinCall(node); |
| _sideEffectsBuilder.add(nativeBehavior.sideEffects); |
| return _inferrer.typeOfNativeBehavior(nativeBehavior); |
| } else if (name == JavaScriptBackend.JS_STRING_CONCAT) { |
| return _types.stringType; |
| } else { |
| _sideEffectsBuilder.setAllSideEffects(); |
| return _types.dynamicType; |
| } |
| } |
| |
| @override |
| TypeInformation visitStaticInvocation(ir.StaticInvocation node) { |
| MemberEntity member = _elementMap.getMember(node.target); |
| ArgumentsTypes arguments = analyzeArguments(node.arguments); |
| Selector selector = _elementMap.getSelector(node); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| if (_closedWorld.commonElements.isForeign(member)) { |
| return handleForeignInvoke(node, member, arguments, selector, mask); |
| } else if (member.isConstructor) { |
| return handleConstructorInvoke( |
| node, node.arguments, selector, mask, member, arguments); |
| } else if (member.isFunction) { |
| TypeInformation type = |
| handleStaticInvoke(node, selector, mask, member, arguments); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } else { |
| TypeInformation type = |
| handleClosureCall(node, selector, mask, member, arguments); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| } |
| |
| @override |
| TypeInformation visitLoadLibrary(ir.LoadLibrary node) { |
| // TODO(johnniwinther): Improve this by returning a Future type instead. |
| return _types.dynamicType; |
| } |
| |
| @override |
| TypeInformation visitStaticGet(ir.StaticGet node) { |
| MemberEntity member = _elementMap.getMember(node.target); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| TypeInformation type = handleStaticInvoke( |
| node, new Selector.getter(member.memberName), mask, member, null); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| |
| @override |
| TypeInformation visitStaticSet(ir.StaticSet node) { |
| TypeInformation rhsType = visit(node.value); |
| if (node.value is ir.ThisExpression) { |
| _markThisAsExposed(); |
| } |
| MemberEntity member = _elementMap.getMember(node.target); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| handleStaticInvoke(node, new Selector.setter(member.memberName), mask, |
| member, new ArgumentsTypes([rhsType], null)); |
| return rhsType; |
| } |
| |
| @override |
| TypeInformation visitPropertyGet(ir.PropertyGet node) { |
| TypeInformation receiverType = visit(node.receiver); |
| Selector selector = _elementMap.getSelector(node); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| // TODO(johnniwinther): Use `node.interfaceTarget` to narrow the receiver |
| // type for --trust-type-annotations/strong-mode. |
| if (node.receiver is ir.ThisExpression) { |
| _checkIfExposesThis( |
| selector, _types.newTypedSelector(receiverType, mask)); |
| } |
| TypeInformation type = handleDynamicGet(node, selector, mask, receiverType); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| |
| @override |
| TypeInformation visitDirectPropertyGet(ir.DirectPropertyGet node) { |
| TypeInformation receiverType = thisType; |
| MemberEntity member = _elementMap.getMember(node.target); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| // TODO(johnniwinther): Use `node.target` to narrow the receiver type. |
| Selector selector = new Selector.getter(member.memberName); |
| _checkIfExposesThis(selector, _types.newTypedSelector(receiverType, mask)); |
| TypeInformation type = handleDynamicGet(node, selector, mask, receiverType); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| |
| @override |
| TypeInformation visitPropertySet(ir.PropertySet node) { |
| TypeInformation receiverType = visit(node.receiver); |
| Selector selector = _elementMap.getSelector(node); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| |
| TypeInformation rhsType = visit(node.value); |
| if (node.value is ir.ThisExpression) { |
| _markThisAsExposed(); |
| } |
| |
| if (_inGenerativeConstructor && node.receiver is ir.ThisExpression) { |
| AbstractValue typedMask = _types.newTypedSelector(receiverType, mask); |
| if (!_closedWorld.includesClosureCall(selector, typedMask)) { |
| Iterable<MemberEntity> targets = |
| _closedWorld.locateMembers(selector, typedMask); |
| // 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); |
| } |
| } |
| } |
| } |
| if (node.receiver is ir.ThisExpression) { |
| _checkIfExposesThis( |
| selector, _types.newTypedSelector(receiverType, mask)); |
| } |
| 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; |
| } |
| |
| @override |
| TypeInformation visitConditionalExpression(ir.ConditionalExpression 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); |
| TypeInformation firstType = visit(node.then); |
| LocalsHandler thenLocals = _locals; |
| _locals = new LocalsHandler.from(saved, node); |
| if (simpleCondition) _updateIsChecks(negativeTests, positiveTests); |
| TypeInformation secondType = visit(node.otherwise); |
| saved.mergeDiamondFlow(thenLocals, _locals); |
| _locals = saved; |
| return _types.allocateDiamondPhi(firstType, secondType); |
| } |
| |
| TypeInformation handleLocalFunction( |
| ir.TreeNode node, ir.FunctionNode functionNode, |
| [ir.VariableDeclaration variable]) { |
| // We loose track of [this] in closures (see issue 20840). To be on |
| // the safe side, we mark [this] as exposed here. We could do better by |
| // analyzing the closure. |
| // TODO(herhut): Analyze whether closure exposes this. Possibly using |
| // whether the created closure as a `thisLocal`. |
| _markThisAsExposed(); |
| |
| ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node); |
| |
| // Record the types of captured non-boxed variables. Types of |
| // these variables may already be there, because of an analysis of |
| // a previous closure. |
| info.forEachFreeVariable((variable, field) { |
| if (!info.isVariableBoxed(variable)) { |
| if (variable == info.thisLocal) { |
| _inferrer.recordTypeOfField(field, thisType); |
| } |
| // The type is null for type parameters. |
| if (_locals.locals[variable] == null) return; |
| _inferrer.recordTypeOfField(field, _locals.locals[variable]); |
| } |
| _capturedVariables.add(variable); |
| }); |
| |
| TypeInformation localFunctionType = |
| _inferrer.concreteTypes.putIfAbsent(node, () { |
| return _types.allocateClosure(info.callMethod); |
| }); |
| if (variable != null) { |
| Local local = _localsMap.getLocalVariable(variable); |
| DartType type = _localsMap.getLocalType(_elementMap, local); |
| _locals.update(local, localFunctionType, node, type); |
| } |
| |
| // We don't put the closure in the work queue of the |
| // inferrer, because it will share information with its enclosing |
| // method, like for example the types of local variables. |
| LocalsHandler<ir.Node> closureLocals = |
| new LocalsHandler.from(_locals, node, useOtherTryBlock: false); |
| KernelTypeGraphBuilder visitor = new KernelTypeGraphBuilder( |
| _options, |
| _closedWorld, |
| _closureDataLookup, |
| _inferrer, |
| info.callMethod, |
| functionNode, |
| _elementMap, |
| _localsMap, |
| closureLocals); |
| visitor.run(); |
| _inferrer.recordReturnType(info.callMethod, visitor._returnType); |
| |
| return localFunctionType; |
| } |
| |
| @override |
| TypeInformation visitFunctionDeclaration(ir.FunctionDeclaration node) { |
| return handleLocalFunction(node, node.function, node.variable); |
| } |
| |
| @override |
| TypeInformation visitFunctionExpression(ir.FunctionExpression node) { |
| return handleLocalFunction(node, node.function); |
| } |
| |
| @override |
| visitWhileStatement(ir.WhileStatement node) { |
| return handleLoop(node, _localsMap.getJumpTargetForWhile(node), () { |
| List<IsCheck> positiveTests = <IsCheck>[]; |
| List<IsCheck> negativeTests = <IsCheck>[]; |
| handleCondition(node.condition, positiveTests, negativeTests); |
| _updateIsChecks(positiveTests, negativeTests); |
| visit(node.body); |
| }); |
| } |
| |
| @override |
| visitDoStatement(ir.DoStatement node) { |
| return handleLoop(node, _localsMap.getJumpTargetForDo(node), () { |
| visit(node.body); |
| List<IsCheck> positiveTests = <IsCheck>[]; |
| List<IsCheck> negativeTests = <IsCheck>[]; |
| handleCondition(node.condition, positiveTests, negativeTests); |
| // TODO(29309): This condition appears to strengthen both the back-edge |
| // and exit-edge. For now, avoid strengthening on the condition until the |
| // proper fix is found. |
| // |
| // updateIsChecks(positiveTests, negativeTests); |
| }); |
| } |
| |
| @override |
| visitForStatement(ir.ForStatement node) { |
| for (ir.VariableDeclaration variable in node.variables) { |
| visit(variable); |
| } |
| return handleLoop(node, _localsMap.getJumpTargetForFor(node), () { |
| List<IsCheck> positiveTests = <IsCheck>[]; |
| List<IsCheck> negativeTests = <IsCheck>[]; |
| handleCondition(node.condition, positiveTests, negativeTests); |
| _updateIsChecks(positiveTests, negativeTests); |
| visit(node.body); |
| for (ir.Expression update in node.updates) { |
| visit(update); |
| } |
| }); |
| } |
| |
| @override |
| visitTryCatch(ir.TryCatch node) { |
| LocalsHandler saved = _locals; |
| _locals = new LocalsHandler.from(_locals, node, |
| isTry: true, useOtherTryBlock: false); |
| initializationIsIndefinite(); |
| visit(node.body); |
| saved.mergeDiamondFlow(_locals, null); |
| _locals = saved; |
| for (ir.Catch catchBlock in node.catches) { |
| saved = _locals; |
| _locals = new LocalsHandler.from(_locals, catchBlock); |
| visit(catchBlock); |
| saved.mergeDiamondFlow(_locals, null); |
| _locals = saved; |
| } |
| } |
| |
| @override |
| visitTryFinally(ir.TryFinally node) { |
| LocalsHandler saved = _locals; |
| _locals = new LocalsHandler.from(_locals, node, |
| isTry: true, useOtherTryBlock: false); |
| initializationIsIndefinite(); |
| visit(node.body); |
| saved.mergeDiamondFlow(_locals, null); |
| _locals = saved; |
| visit(node.finalizer); |
| } |
| |
| @override |
| visitCatch(ir.Catch node) { |
| ir.VariableDeclaration exception = node.exception; |
| if (exception != null) { |
| TypeInformation mask; |
| DartType type = node.guard != null |
| ? _elementMap.getDartType(node.guard) |
| : const DynamicType(); |
| if (type.isInterfaceType) { |
| InterfaceType interfaceType = type; |
| mask = _types.nonNullSubtype(interfaceType.element); |
| } else { |
| mask = _types.dynamicType; |
| } |
| Local local = _localsMap.getLocalVariable(exception); |
| _locals.update(local, mask, node, const DynamicType()); |
| } |
| ir.VariableDeclaration stackTrace = node.stackTrace; |
| if (stackTrace != null) { |
| Local local = _localsMap.getLocalVariable(stackTrace); |
| // TODO(johnniwinther): Use a mask based on [StackTrace]. |
| _locals.update(local, _types.dynamicType, node, const DynamicType()); |
| } |
| visit(node.body); |
| } |
| |
| @override |
| TypeInformation visitThrow(ir.Throw node) { |
| visit(node.expression); |
| _locals.seenReturnOrThrow = true; |
| return _types.nonNullEmpty(); |
| } |
| |
| @override |
| TypeInformation visitRethrow(ir.Rethrow node) { |
| _locals.seenReturnOrThrow = true; |
| return _types.nonNullEmpty(); |
| } |
| |
| TypeInformation handleSuperNoSuchMethod(ir.Node node, Selector selector, |
| AbstractValue mask, ArgumentsTypes arguments) { |
| // Ensure we create a node, to make explicit the call to the |
| // `noSuchMethod` handler. |
| FunctionEntity noSuchMethod = |
| _elementMap.getSuperNoSuchMethod(_analyzedMember.enclosingClass); |
| return handleStaticInvoke(node, selector, mask, noSuchMethod, arguments); |
| } |
| |
| @override |
| TypeInformation visitSuperPropertyGet(ir.SuperPropertyGet node) { |
| // TODO(herhut): We could do better here if we knew what we |
| // are calling does not expose this. |
| _markThisAsExposed(); |
| |
| MemberEntity member = _elementMap.getSuperMember( |
| _analyzedMember, node.name, node.interfaceTarget); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| Selector selector = new Selector.getter(_elementMap.getName(node.name)); |
| if (member == null) { |
| return handleSuperNoSuchMethod(node, selector, mask, null); |
| } else { |
| TypeInformation type = |
| handleStaticInvoke(node, selector, mask, member, null); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| } |
| |
| @override |
| TypeInformation visitSuperPropertySet(ir.SuperPropertySet node) { |
| // TODO(herhut): We could do better here if we knew what we |
| // are calling does not expose this. |
| _markThisAsExposed(); |
| |
| TypeInformation rhsType = visit(node.value); |
| MemberEntity member = _elementMap.getSuperMember( |
| _analyzedMember, node.name, node.interfaceTarget, |
| setter: true); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| Selector selector = new Selector.setter(_elementMap.getName(node.name)); |
| ArgumentsTypes arguments = new ArgumentsTypes([rhsType], null); |
| if (member == null) { |
| return handleSuperNoSuchMethod(node, selector, mask, arguments); |
| } else { |
| handleStaticInvoke(node, selector, mask, member, arguments); |
| return rhsType; |
| } |
| } |
| |
| @override |
| TypeInformation visitSuperMethodInvocation(ir.SuperMethodInvocation node) { |
| // TODO(herhut): We could do better here if we knew what we |
| // are calling does not expose this. |
| _markThisAsExposed(); |
| |
| MemberEntity member = _elementMap.getSuperMember( |
| _analyzedMember, node.name, node.interfaceTarget); |
| ArgumentsTypes arguments = analyzeArguments(node.arguments); |
| Selector selector = _elementMap.getSelector(node); |
| AbstractValue mask = _memberData.typeOfSend(node); |
| if (member == null) { |
| return handleSuperNoSuchMethod(node, selector, mask, arguments); |
| } else if (member.isFunction) { |
| if (isIncompatibleInvoke(member, arguments)) { |
| return handleSuperNoSuchMethod(node, selector, mask, arguments); |
| } else { |
| TypeInformation type = |
| handleStaticInvoke(node, selector, mask, member, arguments); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| } else { |
| TypeInformation type = |
| handleClosureCall(node, selector, mask, member, arguments); |
| if (_options.strongMode && useStaticResultTypes) { |
| type = _types.narrowType(type, _elementMap.getStaticType(node)); |
| } |
| return type; |
| } |
| } |
| |
| @override |
| TypeInformation visitAsExpression(ir.AsExpression node) { |
| TypeInformation operandType = visit(node.operand); |
| return _types.narrowType(operandType, _elementMap.getDartType(node.type)); |
| } |
| |
| @override |
| TypeInformation visitAwaitExpression(ir.AwaitExpression node) { |
| TypeInformation futureType = visit(node.operand); |
| return _inferrer.registerAwait(node, futureType); |
| } |
| |
| @override |
| TypeInformation visitYieldStatement(ir.YieldStatement node) { |
| TypeInformation operandType = visit(node.expression); |
| return _inferrer.registerYield(node, operandType); |
| } |
| |
| @override |
| TypeInformation visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded node) { |
| return _types.nonNullEmpty(); |
| } |
| |
| @override |
| TypeInformation visitInvalidExpression(ir.InvalidExpression node) { |
| // TODO(johnniwinther): Maybe this should be [empty] instead? |
| return _types.dynamicType; |
| } |
| } |
| |
| class IsCheck { |
| final ir.Expression node; |
| final Local local; |
| final DartType type; |
| |
| IsCheck(this.node, this.local, this.type); |
| |
| String toString() => 'IsCheck($local,$type)'; |
| } |
| |
| class Refinement { |
| final Selector selector; |
| final AbstractValue mask; |
| |
| Refinement(this.selector, this.mask); |
| } |