| // Copyright (c) 2012, 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. |
| |
| part of js_backend; |
| |
| /// For each class, stores the possible class subtype tests that could succeed. |
| abstract class TypeChecks { |
| /// Get the set of checks required for class [element]. |
| Iterable<ClassElement> operator[](ClassElement element); |
| /// Get the iterator for all classes that need type checks. |
| Iterator<ClassElement> get iterator; |
| } |
| |
| class RuntimeTypeInformation { |
| final Compiler compiler; |
| |
| RuntimeTypeInformation(this.compiler); |
| |
| /// Contains the classes of all arguments that have been used in |
| /// instantiations and checks. |
| Set<ClassElement> allArguments; |
| |
| bool isJsNative(Element element) { |
| return (element == compiler.intClass || |
| element == compiler.boolClass || |
| element == compiler.numClass || |
| element == compiler.doubleClass || |
| element == compiler.stringClass || |
| element == compiler.listClass); |
| } |
| |
| TypeChecks cachedRequiredChecks; |
| |
| TypeChecks getRequiredChecks() { |
| if (cachedRequiredChecks != null) return cachedRequiredChecks; |
| |
| // Get all types used in type arguments of instantiated types. |
| Set<ClassElement> instantiatedArguments = getInstantiatedArguments(); |
| |
| // Collect all type arguments used in is-checks. |
| Set<ClassElement> checkedArguments = getCheckedArguments(); |
| |
| // Precompute the set of all seen type arguments for use in the emitter. |
| allArguments = new Set<ClassElement>.from(instantiatedArguments) |
| ..addAll(checkedArguments); |
| |
| // Finally, run through the combination of instantiated and checked |
| // arguments and record all combination where the element of a checked |
| // argument is a superclass of the element of an instantiated type. |
| TypeCheckMapping requiredChecks = new TypeCheckMapping(); |
| for (ClassElement element in instantiatedArguments) { |
| if (element == compiler.dynamicClass) continue; |
| if (checkedArguments.contains(element)) { |
| requiredChecks.add(element, element); |
| } |
| // Find all supertypes of [element] in [checkedArguments] and add checks. |
| for (DartType supertype in element.allSupertypes) { |
| ClassElement superelement = supertype.element; |
| if (checkedArguments.contains(superelement)) { |
| requiredChecks.add(element, superelement); |
| } |
| } |
| } |
| |
| // TODO(karlklose): remove this temporary fix: we need to add the classes |
| // used in substitutions to addArguments. |
| allArguments.addAll([compiler.intClass, |
| compiler.boolClass, |
| compiler.numClass, |
| compiler.doubleClass, |
| compiler.stringClass, |
| compiler.listClass]); |
| |
| return cachedRequiredChecks = requiredChecks; |
| } |
| |
| /** |
| * Collects all types used in type arguments of instantiated types. |
| * |
| * This includes type arguments used in supertype relations, because we may |
| * have a type check against this supertype that includes a check against |
| * the type arguments. |
| */ |
| Set<ClassElement> getInstantiatedArguments() { |
| Set<ClassElement> instantiatedArguments = new Set<ClassElement>(); |
| for (DartType type in instantiatedTypes) { |
| addAllInterfaceTypeArguments(type, instantiatedArguments); |
| ClassElement cls = type.element; |
| for (DartType type in cls.allSupertypes) { |
| addAllInterfaceTypeArguments(type, instantiatedArguments); |
| } |
| } |
| for (ClassElement cls in instantiatedArguments.toList()) { |
| for (DartType type in cls.allSupertypes) { |
| addAllInterfaceTypeArguments(type, instantiatedArguments); |
| } |
| } |
| return instantiatedArguments; |
| } |
| |
| /// Collects all type arguments used in is-checks. |
| Set<ClassElement> getCheckedArguments() { |
| Set<ClassElement> checkedArguments = new Set<ClassElement>(); |
| for (DartType type in isChecks) { |
| addAllInterfaceTypeArguments(type, checkedArguments); |
| } |
| return checkedArguments; |
| } |
| |
| Iterable<DartType> get isChecks { |
| return compiler.enqueuer.resolution.universe.isChecks; |
| } |
| |
| Iterable<DartType> get instantiatedTypes { |
| return compiler.codegenWorld.instantiatedTypes; |
| } |
| |
| void addAllInterfaceTypeArguments(DartType type, Set<ClassElement> classes) { |
| if (type is !InterfaceType) return; |
| for (DartType argument in type.typeArguments) { |
| forEachInterfaceType(argument, (InterfaceType t) { |
| ClassElement cls = t.element; |
| if (cls != compiler.dynamicClass) { |
| classes.add(cls); |
| } |
| }); |
| } |
| } |
| |
| void forEachInterfaceType(DartType type, f(InterfaceType type)) { |
| if (type.kind == TypeKind.INTERFACE) { |
| f(type); |
| InterfaceType interface = type; |
| for (DartType argument in interface.typeArguments) { |
| forEachInterfaceType(argument, f); |
| } |
| } |
| } |
| |
| /// Return the unique name for the element as an unquoted string. |
| String getNameAsString(Element element) { |
| JavaScriptBackend backend = compiler.backend; |
| return backend.namer.getName(element); |
| } |
| |
| /// Return the unique JS name for the element, which is a quoted string for |
| /// native classes and the isolate acccess to the constructor for classes. |
| String getJsName(Element element) { |
| JavaScriptBackend backend = compiler.backend; |
| Namer namer = backend.namer; |
| return namer.isolateAccess(element); |
| } |
| |
| String getRawTypeRepresentation(DartType type) { |
| String name = getNameAsString(type.element); |
| if (!type.element.isClass()) return name; |
| InterfaceType interface = type; |
| Link<DartType> variables = interface.element.typeVariables; |
| if (variables.isEmpty) return name; |
| String arguments = variables.map((_) => 'dynamic').join(', '); |
| return '$name<$arguments>'; |
| } |
| |
| // TODO(karlklose): maybe precompute this value and store it in typeChecks? |
| bool isTrivialSubstitution(ClassElement cls, ClassElement check) { |
| if (cls.isClosure()) { |
| // TODO(karlklose): handle closures. |
| return true; |
| } |
| |
| // If there are no type variables or the type is the same, we do not need |
| // a substitution. |
| if (check.typeVariables.isEmpty || cls == check) { |
| return true; |
| } |
| |
| InterfaceType originalType = cls.computeType(compiler); |
| InterfaceType type = originalType.asInstanceOf(check); |
| // [type] is not a subtype of [check]. we do not generate a check and do not |
| // need a substitution. |
| if (type == null) return true; |
| |
| // Run through both lists of type variables and check if the type variables |
| // are identical at each position. If they are not, we need to calculate a |
| // substitution function. |
| Link<DartType> variables = cls.typeVariables; |
| Link<DartType> arguments = type.typeArguments; |
| while (!variables.isEmpty && !arguments.isEmpty) { |
| if (variables.head.element != arguments.head.element) { |
| return false; |
| } |
| variables = variables.tail; |
| arguments = arguments.tail; |
| } |
| return (variables.isEmpty == arguments.isEmpty); |
| } |
| |
| // TODO(karlklose): rewrite to use js.Expressions. |
| /** |
| * Compute a JavaScript expression that describes the necessary substitution |
| * for type arguments in a subtype test. |
| * |
| * The result can be: |
| * 1) [:null:], if no substituted check is necessary, because the |
| * type variables are the same or there are no type variables in the class |
| * that is checked for. |
| * 2) A list expression describing the type arguments to be used in the |
| * subtype check, if the type arguments to be used in the check do not |
| * depend on the type arguments of the object. |
| * 3) A function mapping the type variables of the object to be checked to |
| * a list expression. |
| */ |
| String getSupertypeSubstitution(ClassElement cls, ClassElement check, |
| {alwaysGenerateFunction: false}) { |
| if (isTrivialSubstitution(cls, check)) return null; |
| |
| // TODO(karlklose): maybe precompute this value and store it in typeChecks? |
| InterfaceType type = cls.computeType(compiler); |
| InterfaceType target = type.asInstanceOf(check); |
| String substitution = target.typeArguments |
| .map((type) => _getTypeRepresentation(type, (v) => v.toString())) |
| .join(', '); |
| substitution = '[$substitution]'; |
| if (cls.typeVariables.isEmpty && !alwaysGenerateFunction) { |
| return substitution; |
| } else { |
| String parameters = cls.typeVariables.toList().join(', '); |
| return 'function ($parameters) { return $substitution; }'; |
| } |
| } |
| |
| String getTypeRepresentation(DartType type, void onVariable(variable)) { |
| // Create a type representation. For type variables call the original |
| // callback for side effects and return a template placeholder. |
| return _getTypeRepresentation(type, (variable) { |
| onVariable(variable); |
| return '#'; |
| }); |
| } |
| |
| // TODO(karlklose): rewrite to use js.Expressions. |
| String _getTypeRepresentation(DartType type, String onVariable(variable)) { |
| StringBuffer builder = new StringBuffer(); |
| void build(DartType part) { |
| if (part is TypeVariableType) { |
| builder.write(onVariable(part)); |
| } else { |
| bool hasArguments = part is InterfaceType && !part.isRaw; |
| Element element = part.element; |
| if (element == compiler.dynamicClass) { |
| builder.write('null'); |
| } else { |
| String name = getJsName(element); |
| if (!hasArguments) { |
| builder.write(name); |
| } else { |
| builder.write('['); |
| builder.write(name); |
| InterfaceType interface = part; |
| for (DartType argument in interface.typeArguments) { |
| builder.write(', '); |
| build(argument); |
| } |
| builder.write(']'); |
| } |
| } |
| } |
| } |
| build(type); |
| return builder.toString(); |
| } |
| |
| static bool hasTypeArguments(DartType type) { |
| if (type is InterfaceType) { |
| InterfaceType interfaceType = type; |
| return !interfaceType.isRaw; |
| } |
| return false; |
| } |
| |
| static int getTypeVariableIndex(TypeVariableType variable) { |
| ClassElement classElement = variable.element.getEnclosingClass(); |
| Link<DartType> variables = classElement.typeVariables; |
| for (int index = 0; !variables.isEmpty; |
| index++, variables = variables.tail) { |
| if (variables.head == variable) return index; |
| } |
| } |
| } |
| |
| class TypeCheckMapping implements TypeChecks { |
| final Map<ClassElement, Set<ClassElement>> map = |
| new Map<ClassElement, Set<ClassElement>>(); |
| |
| Iterable<ClassElement> operator[](ClassElement element) { |
| Set<ClassElement> result = map[element]; |
| return result != null ? result : const <ClassElement>[]; |
| } |
| |
| void add(ClassElement cls, ClassElement check) { |
| map.putIfAbsent(cls, () => new Set<ClassElement>()); |
| map[cls].add(check); |
| } |
| |
| Iterator<ClassElement> get iterator => map.keys.iterator; |
| |
| String toString() { |
| StringBuffer sb = new StringBuffer(); |
| for (ClassElement holder in this) { |
| for (ClassElement check in [holder]) { |
| sb.add('${holder.name.slowToString()}.${check.name.slowToString()}, '); |
| } |
| } |
| return '[$sb]'; |
| } |
| } |