blob: aba512bd66dec10ea23f8da7eb0beac753ecdc3d [file] [log] [blame]
// Copyright (c) 2013, 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 dart2js.js_emitter;
class TypeTestEmitter extends CodeEmitterHelper {
Set<ClassElement> get checkedClasses =>
emitter.typeTestRegistry.checkedClasses;
Iterable<ClassElement> get classesUsingTypeVariableTests =>
emitter.typeTestRegistry.classesUsingTypeVariableTests;
Set<FunctionType> get checkedFunctionTypes =>
emitter.typeTestRegistry.checkedFunctionTypes;
void emitIsTests(ClassElement classElement, ClassBuilder builder) {
assert(invariant(classElement, classElement.isDeclaration));
void generateIsTest(Element other) {
if (other == compiler.objectClass && other != classElement) {
// Avoid emitting [:$isObject:] on all classes but [Object].
return;
}
builder.addProperty(namer.operatorIs(other), js('true'));
}
void generateFunctionTypeSignature(FunctionElement method,
FunctionType type) {
assert(method.isImplementation);
jsAst.Expression thisAccess = new jsAst.This();
Node node = method.node;
ClosureClassMap closureData =
compiler.closureToClassMapper.closureMappingCache[node];
if (closureData != null) {
ClosureFieldElement thisLocal =
closureData.getFreeVariableElement(closureData.thisLocal);
if (thisLocal != null) {
String thisName = namer.instanceFieldPropertyName(thisLocal);
thisAccess = js('this.#', thisName);
}
}
RuntimeTypes rti = backend.rti;
jsAst.Expression encoding = rti.getSignatureEncoding(type, thisAccess);
String operatorSignature = namer.operatorSignature;
if (!type.containsTypeVariables) {
builder.functionType = '${emitter.metadataEmitter.reifyType(type)}';
} else {
builder.addProperty(operatorSignature, encoding);
}
}
void generateSubstitution(ClassElement cls, {bool emitNull: false}) {
if (cls.typeVariables.isEmpty) return;
RuntimeTypes rti = backend.rti;
jsAst.Expression expression;
bool needsNativeCheck = emitter.nativeEmitter.requiresNativeIsCheck(cls);
expression = rti.getSupertypeSubstitution(classElement, cls);
if (expression == null && (emitNull || needsNativeCheck)) {
expression = new jsAst.LiteralNull();
}
if (expression != null) {
builder.addProperty(namer.substitutionName(cls), expression);
}
}
generateIsTestsOn(classElement, generateIsTest,
generateFunctionTypeSignature,
generateSubstitution);
}
/**
* Generate "is tests" for [cls]: itself, and the "is tests" for the
* classes it implements and type argument substitution functions for these
* tests. We don't need to add the "is tests" of the super class because
* they will be inherited at runtime, but we may need to generate the
* substitutions, because they may have changed.
*/
void generateIsTestsOn(ClassElement cls,
void emitIsTest(Element element),
FunctionTypeSignatureEmitter emitFunctionTypeSignature,
SubstitutionEmitter emitSubstitution) {
if (checkedClasses.contains(cls)) {
emitIsTest(cls);
emitSubstitution(cls);
}
RuntimeTypes rti = backend.rti;
ClassElement superclass = cls.superclass;
bool haveSameTypeVariables(ClassElement a, ClassElement b) {
if (a.isClosure) return true;
return backend.rti.isTrivialSubstitution(a, b);
}
if (superclass != null && superclass != compiler.objectClass &&
!haveSameTypeVariables(cls, superclass)) {
// We cannot inherit the generated substitutions, because the type
// variable layout for this class is different. Instead we generate
// substitutions for all checks and make emitSubstitution a NOP for the
// rest of this function.
Set<ClassElement> emitted = new Set<ClassElement>();
// TODO(karlklose): move the computation of these checks to
// RuntimeTypeInformation.
while (superclass != null) {
if (backend.classNeedsRti(superclass)) {
emitSubstitution(superclass, emitNull: true);
emitted.add(superclass);
}
superclass = superclass.superclass;
}
for (DartType supertype in cls.allSupertypes) {
ClassElement superclass = supertype.element;
if (classesUsingTypeVariableTests.contains(superclass)) {
emitSubstitution(superclass, emitNull: true);
emitted.add(superclass);
}
for (ClassElement check in checkedClasses) {
if (supertype.element == check && !emitted.contains(check)) {
// Generate substitution. If no substitution is necessary, emit
// [:null:] to overwrite a (possibly) existing substitution from the
// super classes.
emitSubstitution(check, emitNull: true);
emitted.add(check);
}
}
}
void emitNothing(_, {emitNull}) {};
emitSubstitution = emitNothing;
}
Set<Element> generated = new Set<Element>();
// 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) ||
!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.
call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME);
}
if (call != null && call.isFunction) {
// A superclass might already implement the Function interface. In such
// a case, we can avoid emiting the is test here.
if (!cls.superclass.implementsFunction(compiler)) {
generateInterfacesIsTests(compiler.functionClass,
emitIsTest,
emitSubstitution,
generated);
}
FunctionType callType = call.computeType(compiler);
emitFunctionTypeSignature(call, callType);
}
}
for (DartType interfaceType in cls.interfaces) {
generateInterfacesIsTests(interfaceType.element, emitIsTest,
emitSubstitution, generated);
}
}
/**
* Generate "is tests" where [cls] is being implemented.
*/
void generateInterfacesIsTests(ClassElement cls,
void emitIsTest(ClassElement element),
SubstitutionEmitter emitSubstitution,
Set<Element> alreadyGenerated) {
void tryEmitTest(ClassElement check) {
if (!alreadyGenerated.contains(check) && checkedClasses.contains(check)) {
alreadyGenerated.add(check);
emitIsTest(check);
emitSubstitution(check);
}
};
tryEmitTest(cls);
for (DartType interfaceType in cls.interfaces) {
Element element = interfaceType.element;
tryEmitTest(element);
generateInterfacesIsTests(element, emitIsTest, emitSubstitution,
alreadyGenerated);
}
// We need to also emit "is checks" for the superclass and its supertypes.
ClassElement superclass = cls.superclass;
if (superclass != null) {
tryEmitTest(superclass);
generateInterfacesIsTests(superclass, emitIsTest, emitSubstitution,
alreadyGenerated);
}
}
void emitRuntimeTypeSupport(CodeBuffer buffer, OutputUnit outputUnit) {
emitter.addComment('Runtime type support', buffer);
RuntimeTypes rti = backend.rti;
TypeChecks typeChecks = rti.requiredChecks;
// Add checks to the constructors of instantiated classes.
// TODO(sigurdm): We should avoid running through this list for each
// output unit.
jsAst.Statement variables = js.statement('var TRUE = !0, _;');
List<jsAst.Statement> statements = <jsAst.Statement>[];
for (ClassElement cls in typeChecks) {
OutputUnit destination =
compiler.deferredLoadTask.outputUnitForElement(cls);
if (destination != outputUnit) continue;
// TODO(9556). The properties added to 'holder' should be generated
// directly as properties of the class object, not added later.
// Each element is a pair: [propertyName, valueExpression]
List<List> properties = <List>[];
for (TypeCheck check in typeChecks[cls]) {
ClassElement checkedClass = check.cls;
properties.add([namer.operatorIs(checkedClass), js('TRUE')]);
Substitution substitution = check.substitution;
if (substitution != null) {
jsAst.Expression body = substitution.getCode(rti);
properties.add([namer.substitutionName(checkedClass), body]);
}
}
jsAst.Expression holder = backend.emitter.classAccess(cls);
if (properties.length > 1) {
// Use temporary shortened reference.
statements.add(js.statement('_ = #;', holder));
holder = js('#', '_');
}
for (List nameAndValue in properties) {
statements.add(
js.statement('#.# = #',
[holder, nameAndValue[0], nameAndValue[1]]));
}
}
if (statements.isNotEmpty) {
buffer.write(';');
buffer.write(
jsAst.prettyPrint(
js.statement('(function() { #; #; })()', [variables, statements]),
compiler));
buffer.write('$N');
}
}
}