Index: sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart b/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
index c04ff051db9c9ff6222ff049cd3f03351f233e30..fecfb7e685058c2fc0bd28dbdecde6b0308a34dd 100644 |
--- a/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
+++ b/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart |
@@ -48,6 +48,13 @@ class ClassBuilder { |
} |
} |
+// Function signatures used in the generation of runtime type information. |
+typedef void FunctionTypeSignatureEmitter(Element method, |
+ FunctionType methodType); |
+// TODO(johnniwinther): Clean up terminology for rti in the emitter. |
+typedef void FunctionTypeTestEmitter(FunctionType functionType); |
+typedef void SubstitutionEmitter(Element element, {bool emitNull}); |
+ |
/** |
* Generates the code for all used classes in the program. Static fields (even |
* in classes) are ignored, since they can be treated as non-class elements. |
@@ -112,11 +119,26 @@ class CodeEmitterTask extends CompilerTask { |
Set<ClassElement> checkedClasses; |
/** |
- * Raw Typedef symbols occuring in is-checks and type assertions. If the |
- * program contains `x is F<int>` and `x is F<bool>` then the TypedefElement |
- * `F` will occur once in [checkedTypedefs]. |
+ * The set of function types that checked, both explicity through tests of |
+ * typedefs and implicitly through type annotations in checked mode. |
*/ |
- Set<TypedefElement> checkedTypedefs; |
+ Set<FunctionType> checkedFunctionTypes; |
+ |
+ Map<ClassElement, Set<FunctionType>> checkedGenericFunctionTypes = |
+ new Map<ClassElement, Set<FunctionType>>(); |
+ |
+ Set<FunctionType> checkedNonGenericFunctionTypes = |
+ new Set<FunctionType>(); |
+ |
+ void registerDynamicFunctionTypeCheck(FunctionType functionType) { |
+ ClassElement classElement = Types.getClassContext(functionType); |
+ if (classElement != null) { |
+ checkedGenericFunctionTypes.putIfAbsent(classElement, |
+ () => new Set<FunctionType>()).add(functionType); |
+ } else { |
+ checkedNonGenericFunctionTypes.add(functionType); |
+ } |
+ } |
final bool generateSourceMap; |
@@ -147,18 +169,20 @@ class CodeEmitterTask extends CompilerTask { |
} |
void computeRequiredTypeChecks() { |
- assert(checkedClasses == null && checkedTypedefs == null); |
+ assert(checkedClasses == null && checkedFunctionTypes == null); |
backend.rti.addImplicitChecks(compiler.codegenWorld, |
classesUsingTypeVariableTests); |
checkedClasses = new Set<ClassElement>(); |
- checkedTypedefs = new Set<TypedefElement>(); |
+ checkedFunctionTypes = new Set<FunctionType>(); |
compiler.codegenWorld.isChecks.forEach((DartType t) { |
- if (t is InterfaceType) { |
- checkedClasses.add(t.element); |
- } else if (t is TypedefType) { |
- checkedTypedefs.add(t.element); |
+ if (!t.isMalformed) { |
+ if (t is InterfaceType) { |
+ checkedClasses.add(t.element); |
+ } else if (t is FunctionType) { |
+ checkedFunctionTypes.add(t); |
+ } |
} |
}); |
} |
@@ -1319,9 +1343,34 @@ class CodeEmitterTask extends CompilerTask { |
builder.addProperty(namer.operatorIs(other), js('true')); |
} |
+ void generateIsFunctionTypeTest(FunctionType type) { |
+ String operator = namer.operatorIsType(type); |
+ builder.addProperty(operator, new jsAst.LiteralBool(true)); |
+ } |
+ |
+ void generateFunctionTypeSignature(Element method, FunctionType type) { |
+ assert(method.isImplementation); |
+ String thisAccess = 'this'; |
+ Node node = method.parseNode(compiler); |
+ ClosureClassMap closureData = |
+ compiler.closureToClassMapper.closureMappingCache[node]; |
+ if (closureData != null) { |
+ Element thisElement = |
+ closureData.freeVariableMapping[closureData.thisElement]; |
+ if (thisElement != null) { |
+ String thisName = backend.namer.getName(thisElement); |
+ thisAccess = 'this.$thisName'; |
+ } |
+ } |
+ RuntimeTypes rti = backend.rti; |
+ String encoding = rti.getSignatureEncoding(type, () => '$thisAccess'); |
+ String operatorSignature = namer.operatorSignature(); |
+ builder.addProperty(operatorSignature, |
+ new jsAst.LiteralExpression(encoding)); |
+ } |
+ |
void generateSubstitution(Element other, {bool emitNull: false}) { |
RuntimeTypes rti = backend.rti; |
- // TODO(karlklose): support typedefs with variables. |
jsAst.Expression expression; |
bool needsNativeCheck = nativeEmitter.requiresNativeIsCheck(other); |
if (other.kind == ElementKind.CLASS) { |
@@ -1338,7 +1387,9 @@ class CodeEmitterTask extends CompilerTask { |
} |
} |
- generateIsTestsOn(classElement, generateIsTest, generateSubstitution); |
+ generateIsTestsOn(classElement, generateIsTest, |
+ generateIsFunctionTypeTest, generateFunctionTypeSignature, |
+ generateSubstitution); |
} |
void emitRuntimeTypeSupport(CodeBuffer buffer) { |
@@ -1358,6 +1409,17 @@ class CodeEmitterTask extends CompilerTask { |
} |
}; |
} |
+ |
+ void addSignature(FunctionType type) { |
+ String encoding = rti.getTypeEncoding(type); |
+ buffer.add('${namer.signatureName(type)}$_=${_}$encoding$N'); |
+ } |
+ |
+ checkedNonGenericFunctionTypes.forEach(addSignature); |
+ |
+ checkedGenericFunctionTypes.forEach((_, Set<FunctionType> functionTypes) { |
+ functionTypes.forEach(addSignature); |
+ }); |
} |
/** |
@@ -1489,12 +1551,14 @@ class CodeEmitterTask extends CompilerTask { |
DartType type = member.computeType(compiler); |
// TODO(ahe): Generate a dynamic type error here. |
if (type.element.isErroneous()) return; |
- FunctionElement helperElement |
- = backend.getCheckedModeHelper(type, typeCast: false); |
+ type = type.unalias(compiler); |
+ CheckedModeHelper helper = |
+ backend.getCheckedModeHelper(type, typeCast: false); |
+ FunctionElement helperElement = helper.getElement(compiler); |
String helperName = namer.isolateAccess(helperElement); |
List<jsAst.Expression> arguments = <jsAst.Expression>[js('v')]; |
if (helperElement.computeSignature(compiler).parameterCount != 1) { |
- arguments.add(js.string(namer.operatorIs(type.element))); |
+ arguments.add(js.string(namer.operatorIsType(type))); |
} |
String setterName = namer.setterNameFromAccessorName(accessorName); |
@@ -1735,14 +1799,27 @@ class CodeEmitterTask extends CompilerTask { |
return _selectorRank(selector1) - _selectorRank(selector2); |
} |
- Iterable<Element> getTypedefChecksOn(DartType type) { |
- bool isSubtype(TypedefElement typedef) { |
- FunctionType typedefType = |
- typedef.computeType(compiler).unalias(compiler); |
- return compiler.types.isSubtype(type, typedefType); |
+ /** |
+ * Returns a mapping containing all checked function types for which [type] |
+ * can be a subtype. A function type is mapped to [:true:] if [type] is |
+ * statically known to be a subtype of it and to [:false:] if [type] might |
+ * be a subtype, provided with the right type arguments. |
+ */ |
+ // TODO(johnniwinther): Change to return a mapping from function types to |
+ // a set of variable points and use this to detect statically/dynamically |
+ // known subtype relations. |
+ Map<FunctionType, bool> getFunctionTypeChecksOn(DartType type) { |
+ Map<FunctionType, bool> functionTypeMap = |
+ new LinkedHashMap<FunctionType, bool>(); |
+ for (FunctionType functionType in checkedFunctionTypes) { |
+ if (compiler.types.isSubtype(type, functionType)) { |
+ functionTypeMap[functionType] = true; |
+ } else if (compiler.types.isPotentialSubtype(type, functionType)) { |
+ functionTypeMap[functionType] = false; |
+ } |
} |
- return checkedTypedefs.where(isSubtype).toList() |
- ..sort(Elements.compareByPosition); |
+ // TODO(johnniwinther): Ensure stable ordering of the keys. |
+ return functionTypeMap; |
} |
/** |
@@ -1754,7 +1831,9 @@ class CodeEmitterTask extends CompilerTask { |
*/ |
void generateIsTestsOn(ClassElement cls, |
void emitIsTest(Element element), |
- void emitSubstitution(Element element, {emitNull})) { |
+ FunctionTypeTestEmitter emitIsFunctionTypeTest, |
+ FunctionTypeSignatureEmitter emitFunctionTypeSignature, |
+ SubstitutionEmitter emitSubstitution) { |
if (checkedClasses.contains(cls)) { |
emitIsTest(cls); |
emitSubstitution(cls); |
@@ -1777,7 +1856,7 @@ class CodeEmitterTask extends CompilerTask { |
Set<ClassElement> emitted = new Set<ClassElement>(); |
// TODO(karlklose): move the computation of these checks to |
// RuntimeTypeInformation. |
- if (backend.needsRti(cls)) { |
+ if (backend.classNeedsRti(cls)) { |
emitSubstitution(superclass, emitNull: true); |
emitted.add(superclass); |
} |
@@ -1805,7 +1884,7 @@ class CodeEmitterTask extends CompilerTask { |
// A class that defines a [:call:] method implicitly implements |
// [Function] and needs checks for all typedefs that are used in is-checks. |
if (checkedClasses.contains(compiler.functionClass) || |
- !checkedTypedefs.isEmpty) { |
+ !checkedFunctionTypes.isEmpty) { |
Element call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); |
if (call == null) { |
// If [cls] is a closure, it has a synthetic call operator method. |
@@ -1816,8 +1895,12 @@ class CodeEmitterTask extends CompilerTask { |
emitIsTest, |
emitSubstitution, |
generated); |
- getTypedefChecksOn(call.computeType(compiler)).forEach(emitIsTest); |
- } |
+ FunctionType callType = call.computeType(compiler); |
+ Map<FunctionType, bool> functionTypeChecks = |
+ getFunctionTypeChecksOn(callType); |
+ generateFunctionTypeTests(call, callType, functionTypeChecks, |
+ emitFunctionTypeSignature, emitIsFunctionTypeTest); |
+ } |
} |
for (DartType interfaceType in cls.interfaces) { |
@@ -1831,7 +1914,7 @@ class CodeEmitterTask extends CompilerTask { |
*/ |
void generateInterfacesIsTests(ClassElement cls, |
void emitIsTest(ClassElement element), |
- void emitSubstitution(ClassElement element), |
+ SubstitutionEmitter emitSubstitution, |
Set<Element> alreadyGenerated) { |
void tryEmitTest(ClassElement check) { |
if (!alreadyGenerated.contains(check) && checkedClasses.contains(check)) { |
@@ -1859,6 +1942,45 @@ class CodeEmitterTask extends CompilerTask { |
} |
} |
+ static const int MAX_FUNCTION_TYPE_PREDICATES = 10; |
+ |
+ /** |
+ * Generates function type checks on [method] with type [methodType] against |
+ * the function type checks in [functionTypeChecks]. |
+ */ |
+ void generateFunctionTypeTests( |
+ Element method, |
+ FunctionType methodType, |
+ Map<FunctionType, bool> functionTypeChecks, |
+ FunctionTypeSignatureEmitter emitFunctionTypeSignature, |
+ FunctionTypeTestEmitter emitIsFunctionTypeTest) { |
+ bool hasDynamicFunctionTypeCheck = false; |
+ int neededPredicates = 0; |
+ functionTypeChecks.forEach((FunctionType functionType, bool knownSubtype) { |
+ if (!knownSubtype) { |
+ registerDynamicFunctionTypeCheck(functionType); |
+ hasDynamicFunctionTypeCheck = true; |
+ } else { |
+ neededPredicates++; |
+ } |
+ }); |
+ bool alwaysUseSignature = false; |
+ if (hasDynamicFunctionTypeCheck || |
+ neededPredicates > MAX_FUNCTION_TYPE_PREDICATES) { |
+ emitFunctionTypeSignature(method, methodType); |
+ alwaysUseSignature = true; |
+ } |
+ functionTypeChecks.forEach((FunctionType functionType, bool knownSubtype) { |
+ if (knownSubtype) { |
+ if (alwaysUseSignature) { |
+ registerDynamicFunctionTypeCheck(functionType); |
+ } else { |
+ emitIsFunctionTypeTest(functionType); |
+ } |
+ } |
+ }); |
+ } |
+ |
/** |
* Return a function that returns true if its argument is a class |
* that needs to be emitted. |
@@ -2021,12 +2143,6 @@ class CodeEmitterTask extends CompilerTask { |
addParameterStubs(callElement, closureBuilder.addProperty); |
- DartType type = element.computeType(compiler); |
- getTypedefChecksOn(type).forEach((Element typedef) { |
- String operator = namer.operatorIs(typedef); |
- closureBuilder.addProperty(operator, js('true')); |
- }); |
- |
// TODO(ngeoffray): Cache common base classes for closures, bound |
// closures, and static closures that have common type checks. |
boundClosures.add( |
@@ -2034,6 +2150,28 @@ class CodeEmitterTask extends CompilerTask { |
closureBuilder.toObjectInitializer())); |
staticGetters[element] = closureClassElement; |
+ |
+ void emitFunctionTypeSignature(Element method, FunctionType methodType) { |
+ RuntimeTypes rti = backend.rti; |
+ // [:() => null:] is dummy encoding of [this] which is never needed for |
+ // the encoding of the type of the static [method]. |
+ String encoding = rti.getSignatureEncoding(methodType, () => 'null'); |
+ String operatorSignature = namer.operatorSignature(); |
+ // TODO(johnniwinther): Make MiniJsParser support function expressions. |
+ closureBuilder.addProperty(operatorSignature, |
+ new jsAst.LiteralExpression(encoding)); |
+ } |
+ |
+ void emitIsFunctionTypeTest(FunctionType functionType) { |
+ String operator = namer.operatorIsType(functionType); |
+ closureBuilder.addProperty(operator, js('true')); |
+ } |
+ |
+ FunctionType methodType = element.computeType(compiler); |
+ Map<FunctionType, bool> functionTypeChecks = |
+ getFunctionTypeChecksOn(methodType); |
+ generateFunctionTypeTests(element, methodType, functionTypeChecks, |
+ emitFunctionTypeSignature, emitIsFunctionTypeTest); |
} |
} |
@@ -2091,12 +2229,14 @@ class CodeEmitterTask extends CompilerTask { |
fieldNames.add(namer.getName(field)); |
}); |
- Iterable<Element> typedefChecks = |
- getTypedefChecksOn(member.computeType(compiler)); |
- bool hasTypedefChecks = !typedefChecks.isEmpty; |
+ DartType memberType = member.computeType(compiler); |
+ Map<FunctionType, bool> functionTypeChecks = |
+ getFunctionTypeChecksOn(memberType); |
+ bool hasFunctionTypeChecks = !functionTypeChecks.isEmpty; |
- bool canBeShared = !hasOptionalParameters && !hasTypedefChecks; |
+ bool canBeShared = !hasOptionalParameters && !hasFunctionTypeChecks; |
+ ClassElement classElement = member.getEnclosingClass(); |
String closureClass = canBeShared ? cache[parameterCount] : null; |
if (closureClass == null) { |
// Either the class was not cached yet, or there are optional parameters. |
@@ -2152,10 +2292,23 @@ class CodeEmitterTask extends CompilerTask { |
boundClosureBuilder.addProperty(invocationName, fun); |
addParameterStubs(callElement, boundClosureBuilder.addProperty); |
- typedefChecks.forEach((Element typedef) { |
- String operator = namer.operatorIs(typedef); |
- boundClosureBuilder.addProperty(operator, js('true')); |
- }); |
+ |
+ void emitFunctionTypeSignature(Element method, FunctionType methodType) { |
+ String encoding = backend.rti.getSignatureEncoding( |
+ methodType, () => 'this.${fieldNames[0]}'); |
+ String operatorSignature = namer.operatorSignature(); |
+ boundClosureBuilder.addProperty(operatorSignature, |
+ new jsAst.LiteralExpression(encoding)); |
+ } |
+ |
+ void emitIsFunctionTypeTest(FunctionType functionType) { |
+ String operator = namer.operatorIsType(functionType); |
+ boundClosureBuilder.addProperty(operator, |
+ new jsAst.LiteralBool(true)); |
+ } |
+ |
+ generateFunctionTypeTests(member, memberType, functionTypeChecks, |
+ emitFunctionTypeSignature, emitIsFunctionTypeTest); |
boundClosures.add( |
js('$classesCollector.$mangledName = #', |
@@ -2187,8 +2340,7 @@ class CodeEmitterTask extends CompilerTask { |
} |
jsAst.Expression getterFunction = js.fun( |
- parameters, |
- js.return_(js(closureClass).newWith(arguments))); |
+ parameters, js.return_(js(closureClass).newWith(arguments))); |
defineStub(getterName, getterFunction); |
} |
@@ -2228,7 +2380,6 @@ class CodeEmitterTask extends CompilerTask { |
// identical stubs for each we track untyped selectors which already have |
// stubs. |
Set<Selector> generatedSelectors = new Set<Selector>(); |
- |
for (Selector selector in selectors) { |
if (selector.applies(member, compiler)) { |
selector = selector.asUntyped; |
@@ -2833,6 +2984,15 @@ if (typeof document !== "undefined" && document.readyState !== "complete") { |
rti.getClassesUsedInSubstitutions(backend, requiredChecks); |
addClassesWithSuperclasses(classesUsedInSubstitutions); |
+ // 3c. Add classes that contain checked generic function types. These are |
+ // needed to store the signature encoding. |
+ for (FunctionType type in checkedFunctionTypes) { |
+ ClassElement contextClass = Types.getClassContext(type); |
+ if (contextClass != null) { |
+ neededClasses.add(contextClass); |
+ } |
+ } |
+ |
// 4. Finally, sort the classes. |
List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses); |