| // Copyright (c) 2016, 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 'graph_builder.dart'; |
| import 'nodes.dart'; |
| import '../closure.dart'; |
| import '../common.dart'; |
| import '../types/types.dart'; |
| import '../elements/elements.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/resolution_types.dart'; |
| import '../io/source_information.dart'; |
| import '../universe/use.dart' show TypeUse; |
| |
| /// Functions to insert type checking, coercion, and instruction insertion |
| /// depending on the environment for dart code. |
| class TypeBuilder { |
| final GraphBuilder builder; |
| TypeBuilder(this.builder); |
| |
| /// Create an instruction to simply trust the provided type. |
| HInstruction _trustType(HInstruction original, ResolutionDartType type) { |
| assert(builder.compiler.options.trustTypeAnnotations); |
| assert(type != null); |
| type = builder.localsHandler.substInContext(type); |
| type = type.unaliased; |
| if (type.isDynamic) return original; |
| if (!type.isInterfaceType) return original; |
| if (type.isObject) return original; |
| // The type element is either a class or the void element. |
| ClassElement element = type.element; |
| TypeMask mask = new TypeMask.subtype(element, builder.closedWorld); |
| return new HTypeKnown.pinned(mask, original); |
| } |
| |
| /// Produces code that checks the runtime type is actually the type specified |
| /// by attempting a type conversion. |
| HInstruction _checkType( |
| HInstruction original, ResolutionDartType type, int kind) { |
| assert(builder.compiler.options.enableTypeAssertions); |
| assert(type != null); |
| type = builder.localsHandler.substInContext(type); |
| HInstruction other = buildTypeConversion(original, type, kind); |
| // TODO(johnniwinther): This operation on `registry` may be inconsistent. |
| // If it is needed then it seems likely that similar invocations of |
| // `buildTypeConversion` in `SsaBuilder.visitAs` should also be followed by |
| // a similar operation on `registry`; otherwise, this one might not be |
| // needed. |
| builder.registry?.registerTypeUse(new TypeUse.isCheck(type)); |
| return other; |
| } |
| |
| /// Depending on the context and the mode, wrap the given type in an |
| /// instruction that checks the type is what we expect or automatically |
| /// trusts the written type. |
| HInstruction potentiallyCheckOrTrustType( |
| HInstruction original, ResolutionDartType type, |
| {int kind: HTypeConversion.CHECKED_MODE_CHECK}) { |
| if (type == null) return original; |
| HInstruction checkedOrTrusted = original; |
| if (builder.compiler.options.trustTypeAnnotations) { |
| checkedOrTrusted = _trustType(original, type); |
| } else if (builder.compiler.options.enableTypeAssertions) { |
| checkedOrTrusted = _checkType(original, type, kind); |
| } |
| if (checkedOrTrusted == original) return original; |
| builder.add(checkedOrTrusted); |
| return checkedOrTrusted; |
| } |
| |
| /// Helper to create an instruction that gets the value of a type variable. |
| HInstruction addTypeVariableReference( |
| ResolutionTypeVariableType type, Element member, |
| {SourceInformation sourceInformation}) { |
| assert(assertTypeInContext(type)); |
| if (type is MethodTypeVariableType) { |
| return builder.graph.addConstantNull(builder.closedWorld); |
| } |
| bool isClosure = member.enclosingElement.isClosure; |
| if (isClosure) { |
| ClosureClassElement closureClass = member.enclosingElement; |
| member = closureClass.methodElement; |
| member = member.outermostEnclosingMemberOrTopLevel; |
| } |
| bool isInConstructorContext = |
| member.isConstructor || member.isGenerativeConstructorBody; |
| Local typeVariableLocal = |
| builder.localsHandler.getTypeVariableAsLocal(type); |
| if (isClosure) { |
| if (member.isFactoryConstructor || |
| (isInConstructorContext && |
| builder.hasDirectLocal(typeVariableLocal))) { |
| // The type variable is used from a closure in a factory constructor. |
| // The value of the type argument is stored as a local on the closure |
| // itself. |
| return builder.localsHandler |
| .readLocal(typeVariableLocal, sourceInformation: sourceInformation); |
| } else if (member.isFunction || |
| member.isGetter || |
| member.isSetter || |
| isInConstructorContext) { |
| // The type variable is stored on the "enclosing object" and needs to be |
| // accessed using the this-reference in the closure. |
| return readTypeVariable(type, member, |
| sourceInformation: sourceInformation); |
| } else { |
| assert(member.isField); |
| // The type variable is stored in a parameter of the method. |
| return builder.localsHandler.readLocal(typeVariableLocal); |
| } |
| } else if (isInConstructorContext || |
| // When [member] is a field, we can be either |
| // generating a checked setter or inlining its |
| // initializer in a constructor. An initializer is |
| // never built standalone, so in that case [target] is not |
| // the [member] itself. |
| (member.isField && member != builder.targetElement)) { |
| // The type variable is stored in a parameter of the method. |
| return builder.localsHandler |
| .readLocal(typeVariableLocal, sourceInformation: sourceInformation); |
| } else if (member.isInstanceMember) { |
| // The type variable is stored on the object. |
| return readTypeVariable(type, member, |
| sourceInformation: sourceInformation); |
| } else { |
| builder.compiler.reporter.internalError( |
| type.element, 'Unexpected type variable in static context.'); |
| return null; |
| } |
| } |
| |
| /// Generate code to extract the type argument from the object. |
| HInstruction readTypeVariable( |
| ResolutionTypeVariableType variable, Element member, |
| {SourceInformation sourceInformation}) { |
| assert(member.isInstanceMember); |
| assert(variable is! MethodTypeVariableType); |
| HInstruction target = builder.localsHandler.readThis(); |
| builder.push(new HTypeInfoReadVariable( |
| variable, target, builder.commonMasks.dynamicType) |
| ..sourceInformation = sourceInformation); |
| return builder.pop(); |
| } |
| |
| HInstruction buildTypeArgumentRepresentations( |
| ResolutionDartType type, Element sourceElement) { |
| assert(!type.isTypeVariable); |
| // Compute the representation of the type arguments, including access |
| // to the runtime type information for type variables as instructions. |
| assert(type.element.isClass); |
| ResolutionInterfaceType interface = type; |
| List<HInstruction> inputs = <HInstruction>[]; |
| for (ResolutionDartType argument in interface.typeArguments) { |
| inputs.add(analyzeTypeArgument(argument, sourceElement)); |
| } |
| HInstruction representation = new HTypeInfoExpression( |
| TypeInfoExpressionKind.INSTANCE, |
| interface.element.thisType, |
| inputs, |
| builder.commonMasks.dynamicType); |
| return representation; |
| } |
| |
| /// Check that [type] is valid in the context of `localsHandler.contextClass`. |
| /// This should only be called in assertions. |
| bool assertTypeInContext(ResolutionDartType type, [Spannable spannable]) { |
| return invariant(spannable == null ? CURRENT_ELEMENT_SPANNABLE : spannable, |
| () { |
| ClassElement contextClass = Types.getClassContext(type); |
| return contextClass == null || |
| contextClass == builder.localsHandler.contextClass; |
| }, |
| message: "Type '$type' is not valid context of " |
| "${builder.localsHandler.contextClass}."); |
| } |
| |
| HInstruction analyzeTypeArgument( |
| ResolutionDartType argument, Element sourceElement, |
| {SourceInformation sourceInformation}) { |
| assert(assertTypeInContext(argument)); |
| argument = argument.unaliased; |
| if (argument.treatAsDynamic) { |
| // Represent [dynamic] as [null]. |
| return builder.graph.addConstantNull(builder.closedWorld); |
| } |
| |
| if (argument.isTypeVariable) { |
| return addTypeVariableReference(argument, sourceElement, |
| sourceInformation: sourceInformation); |
| } |
| |
| List<HInstruction> inputs = <HInstruction>[]; |
| argument.forEachTypeVariable((variable) { |
| if (variable is! MethodTypeVariableType) { |
| inputs.add(analyzeTypeArgument(variable, sourceElement)); |
| } |
| }); |
| HInstruction result = new HTypeInfoExpression( |
| TypeInfoExpressionKind.COMPLETE, |
| argument, |
| inputs, |
| builder.commonMasks.dynamicType)..sourceInformation = sourceInformation; |
| builder.add(result); |
| return result; |
| } |
| |
| /// In checked mode, generate type tests for the parameters of the inlined |
| /// function. |
| void potentiallyCheckInlinedParameterTypes(FunctionElement function) { |
| if (!checkOrTrustTypes) return; |
| |
| FunctionSignature signature = function.functionSignature; |
| signature.orderedForEachParameter((ParameterElement parameter) { |
| HInstruction argument = builder.localsHandler.readLocal(parameter); |
| potentiallyCheckOrTrustType(argument, parameter.type); |
| }); |
| } |
| |
| bool get checkOrTrustTypes => |
| builder.compiler.options.enableTypeAssertions || |
| builder.compiler.options.trustTypeAnnotations; |
| |
| /// Build a [HTypeConversion] for converting [original] to type [type]. |
| /// |
| /// Invariant: [type] must be valid in the context. |
| /// See [LocalsHandler.substInContext]. |
| HInstruction buildTypeConversion( |
| HInstruction original, ResolutionDartType type, int kind) { |
| if (type == null) return original; |
| // GENERIC_METHODS: The following statement was added for parsing and |
| // ignoring method type variables; must be generalized for full support of |
| // generic methods. |
| type = type.dynamifyMethodTypeVariableType; |
| type = type.unaliased; |
| assert(assertTypeInContext(type, original)); |
| if (type.isInterfaceType && !type.treatAsRaw) { |
| ResolutionInterfaceType interfaceType = type; |
| TypeMask subtype = |
| new TypeMask.subtype(interfaceType.element, builder.closedWorld); |
| HInstruction representations = |
| buildTypeArgumentRepresentations(type, builder.sourceElement); |
| builder.add(representations); |
| return new HTypeConversion.withTypeRepresentation( |
| type, kind, subtype, original, representations); |
| } else if (type.isTypeVariable) { |
| TypeMask subtype = original.instructionType; |
| HInstruction typeVariable = |
| addTypeVariableReference(type, builder.sourceElement); |
| return new HTypeConversion.withTypeRepresentation( |
| type, kind, subtype, original, typeVariable); |
| } else if (type.isFunctionType) { |
| HInstruction reifiedType = |
| analyzeTypeArgument(type, builder.sourceElement); |
| // TypeMasks don't encode function types. |
| TypeMask refinedMask = original.instructionType; |
| return new HTypeConversion.withTypeRepresentation( |
| type, kind, refinedMask, original, reifiedType); |
| } else { |
| return original.convertType(builder.closedWorld, type, kind); |
| } |
| } |
| } |