blob: 5a6fb44effa7a0dc76fb1ac47806d2f5bfb7657a [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.full_emitter;
class ClassEmitter extends CodeEmitterHelper {
ClassStubGenerator get _stubGenerator =>
new ClassStubGenerator(compiler, namer, backend);
/**
* Documentation wanted -- johnniwinther
*/
void emitClass(Class cls, ClassBuilder enclosingBuilder, Fragment fragment) {
ClassElement classElement = cls.element;
assert(invariant(classElement, classElement.isDeclaration));
emitter.needsClassSupport = true;
ClassElement superclass = classElement.superclass;
jsAst.Name superName;
if (superclass != null) {
superName = namer.className(superclass);
}
if (cls.isMixinApplication) {
MixinApplication mixinApplication = cls;
jsAst.Name mixinName = mixinApplication.mixinClass.name;
superName = new CompoundName([superName, Namer.literalPlus, mixinName]);
emitter.needsMixinSupport = true;
}
ClassBuilder builder = new ClassBuilder.forClass(classElement, namer);
builder.superName = superName;
emitConstructorsForCSP(cls);
emitFields(cls, builder);
if (cls.hasRtiField) {
builder.addField(namer.rtiFieldName);
}
emitCheckedClassSetters(cls, builder);
emitClassGettersSettersForCSP(cls, builder);
emitInstanceMembers(cls, builder);
emitStubs(cls.callStubs, builder);
emitStubs(cls.typeVariableReaderStubs, builder);
emitRuntimeTypeInformation(cls, builder);
emitNativeInfo(cls, builder);
if (classElement == backend.helpers.closureClass) {
// We add a special getter here to allow for tearing off a closure from
// itself.
jsAst.Fun function = js('function() { return this; }');
jsAst.Name name = namer.getterForMember(Names.call);
builder.addProperty(name, function);
}
emitClassBuilderWithReflectionData(
cls, builder, enclosingBuilder, fragment);
}
/**
* Emits the precompiled constructor when in CSP mode.
*/
void emitConstructorsForCSP(Class cls) {
List<jsAst.Name> fieldNames = <jsAst.Name>[];
if (!compiler.options.useContentSecurityPolicy) return;
if (!cls.onlyForRti && !cls.isNative) {
fieldNames = cls.fields.map((Field field) => field.name).toList();
}
ClassElement classElement = cls.element;
jsAst.Expression constructorAst = _stubGenerator.generateClassConstructor(
classElement, fieldNames, cls.hasRtiField);
jsAst.Name constructorName = namer.className(classElement);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForElement(classElement);
emitter.assemblePrecompiledConstructor(
outputUnit, constructorName, constructorAst, fieldNames);
}
/// Returns `true` if fields added.
bool emitFields(FieldContainer container, ClassBuilder builder,
{bool classIsNative: false, bool emitStatics: false}) {
Iterable<Field> fields;
if (container is Class) {
if (emitStatics) {
fields = container.staticFieldsForReflection;
} else if (container.onlyForRti) {
return false;
} else {
fields = container.fields;
}
} else {
assert(container is Library);
assert(emitStatics);
fields = container.staticFieldsForReflection;
}
var fieldMetadata = [];
bool hasMetadata = false;
bool fieldsAdded = false;
for (Field field in fields) {
FieldElement fieldElement = field.element;
jsAst.Name name = field.name;
jsAst.Name accessorName = field.accessorName;
bool needsGetter = field.needsGetter;
bool needsSetter = field.needsUncheckedSetter;
// Ignore needsCheckedSetter - that is handled below.
bool needsAccessor = (needsGetter || needsSetter);
// We need to output the fields for non-native classes so we can auto-
// generate the constructor. For native classes there are no
// constructors, so we don't need the fields unless we are generating
// accessors at runtime.
bool needsFieldsForConstructor = !emitStatics && !classIsNative;
if (needsFieldsForConstructor || needsAccessor) {
var metadata =
task.metadataCollector.buildMetadataFunction(fieldElement);
if (metadata != null) {
hasMetadata = true;
} else {
metadata = new jsAst.LiteralNull();
}
fieldMetadata.add(metadata);
recordMangledField(fieldElement, accessorName,
namer.privateName(fieldElement.memberName));
List<jsAst.Literal> fieldNameParts = <jsAst.Literal>[];
if (!needsAccessor) {
// Emit field for constructor generation.
assert(!classIsNative);
fieldNameParts.add(name);
} else {
// Emit (possibly renaming) field name so we can add accessors at
// runtime.
if (name != accessorName) {
fieldNameParts.add(accessorName);
fieldNameParts.add(js.stringPart(':'));
}
fieldNameParts.add(name);
if (field.needsInterceptedGetter) {
emitter.interceptorEmitter.interceptorInvocationNames
.add(namer.getterForElement(fieldElement));
}
// TODO(16168): The setter creator only looks at the getter-name.
// Even though the setter could avoid the interceptor convention we
// currently still need to add the additional argument.
if (field.needsInterceptedGetter || field.needsInterceptedSetter) {
emitter.interceptorEmitter.interceptorInvocationNames
.add(namer.setterForElement(fieldElement));
}
int code = field.getterFlags + (field.setterFlags << 2);
if (code == 0) {
reporter.internalError(
fieldElement, 'Field code is 0 ($fieldElement).');
}
fieldNameParts.add(
js.stringPart(FIELD_CODE_CHARACTERS[code - FIRST_FIELD_CODE]));
}
// Fields can only be reflected if their declaring class is reflectable
// (as they are only accessible via [ClassMirror.declarations]).
// However, set/get operations can be performed on them, so they are
// reflectable in some sense, which leads to [isAccessibleByReflection]
// reporting `true`.
if (backend.isAccessibleByReflection(fieldElement)) {
fieldNameParts.add(new jsAst.LiteralString('-'));
if (fieldElement.isTopLevel ||
backend.isAccessibleByReflection(fieldElement.enclosingClass)) {
DartType type = fieldElement.type;
fieldNameParts.add(task.metadataCollector.reifyType(type));
}
}
jsAst.Literal fieldNameAst = js.concatenateStrings(fieldNameParts);
builder.addField(fieldNameAst);
// Add 1 because adding a field to the class also requires a comma
compiler.dumpInfoTask.registerElementAst(fieldElement, fieldNameAst);
fieldsAdded = true;
}
}
if (hasMetadata) {
builder.fieldMetadata = fieldMetadata;
}
return fieldsAdded;
}
/// Emits checked setters for fields.
void emitCheckedClassSetters(Class cls, ClassBuilder builder) {
if (cls.onlyForRti) return;
for (StubMethod method in cls.checkedSetters) {
Element member = method.element;
assert(member != null);
jsAst.Expression code = method.code;
jsAst.Name setterName = method.name;
compiler.dumpInfoTask
.registerElementAst(member, builder.addProperty(setterName, code));
generateReflectionDataForFieldGetterOrSetter(member, setterName, builder,
isGetter: false);
}
}
/// Emits getters/setters for fields if compiling in CSP mode.
void emitClassGettersSettersForCSP(Class cls, ClassBuilder builder) {
if (!compiler.options.useContentSecurityPolicy || cls.onlyForRti) return;
for (Field field in cls.fields) {
Element member = field.element;
reporter.withCurrentElement(member, () {
if (field.needsGetter) {
emitGetterForCSP(member, field.name, field.accessorName, builder);
}
if (field.needsUncheckedSetter) {
emitSetterForCSP(member, field.name, field.accessorName, builder);
}
});
}
}
void emitStubs(Iterable<StubMethod> stubs, ClassBuilder builder) {
for (Method method in stubs) {
jsAst.Property property = builder.addProperty(method.name, method.code);
compiler.dumpInfoTask.registerElementAst(method.element, property);
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [classElement] must be a declaration element.
*/
void emitInstanceMembers(Class cls, ClassBuilder builder) {
ClassElement classElement = cls.element;
assert(invariant(classElement, classElement.isDeclaration));
if (cls.onlyForRti || cls.isMixinApplication) return;
// TODO(herhut): This is a no-op. Should it be removed?
for (Field field in cls.fields) {
emitter.containerBuilder.addMemberField(field, builder);
}
for (Method method in cls.methods) {
assert(invariant(classElement, method.element.isDeclaration));
assert(invariant(classElement, method.element.isInstanceMember));
emitter.containerBuilder.addMemberMethod(method, builder);
}
if (classElement.isObject && backend.enabledNoSuchMethod) {
// Emit the noSuchMethod handlers on the Object prototype now,
// so that the code in the dynamicFunction helper can find
// them. Note that this helper is invoked before analyzing the
// full JS script.
emitter.nsmEmitter.emitNoSuchMethodHandlers(builder.addProperty);
}
}
/// Emits the members from the model.
void emitRuntimeTypeInformation(Class cls, ClassBuilder builder) {
assert(builder.functionType == null);
if (cls.functionTypeIndex != null) {
builder.functionType = cls.functionTypeIndex;
}
for (Method method in cls.isChecks) {
builder.addProperty(method.name, method.code);
}
}
void emitNativeInfo(Class cls, ClassBuilder builder) {
jsAst.Expression nativeInfo = NativeGenerator.encodeNativeInfo(cls);
if (nativeInfo != null) {
builder.addPropertyByName(namer.nativeSpecProperty, nativeInfo);
}
}
void emitClassBuilderWithReflectionData(Class cls, ClassBuilder classBuilder,
ClassBuilder enclosingBuilder, Fragment fragment) {
ClassElement classElement = cls.element;
jsAst.Name className = cls.name;
var metadata = task.metadataCollector.buildMetadataFunction(classElement);
if (metadata != null) {
classBuilder.addPropertyByName("@", metadata);
}
if (backend.isAccessibleByReflection(classElement)) {
List<DartType> typeVars = classElement.typeVariables;
Iterable typeVariableProperties =
emitter.typeVariableHandler.typeVariablesOf(classElement);
ClassElement superclass = classElement.superclass;
bool hasSuper = superclass != null;
if ((!typeVariableProperties.isEmpty && !hasSuper) ||
(hasSuper && !equalElements(superclass.typeVariables, typeVars))) {
classBuilder.addPropertyByName(
'<>', new jsAst.ArrayInitializer(typeVariableProperties.toList()));
}
}
List<jsAst.Property> statics = new List<jsAst.Property>();
ClassBuilder staticsBuilder =
new ClassBuilder.forStatics(classElement, namer);
if (emitFields(cls, staticsBuilder, emitStatics: true)) {
jsAst.ObjectInitializer initializer =
staticsBuilder.toObjectInitializer();
compiler.dumpInfoTask.registerElementAst(classElement, initializer);
jsAst.Node property = initializer.properties.single;
compiler.dumpInfoTask.registerElementAst(classElement, property);
statics.add(property);
}
// TODO(herhut): Do not grab statics out of the properties.
ClassBuilder classProperties =
emitter.elementDescriptors[fragment].remove(classElement);
if (classProperties != null) {
statics.addAll(classProperties.properties);
}
if (!statics.isEmpty) {
classBuilder.addProperty(
namer.staticsPropertyName, // 'static' or its minified name.
new jsAst.ObjectInitializer(statics, isOneLiner: false));
}
// TODO(ahe): This method (generateClass) should return a jsAst.Expression.
jsAst.ObjectInitializer propertyValue = classBuilder.toObjectInitializer();
compiler.dumpInfoTask
.registerElementAst(classBuilder.element, propertyValue);
enclosingBuilder.addProperty(className, propertyValue);
String reflectionName = emitter.getReflectionName(classElement, className);
if (reflectionName != null) {
if (!backend.isAccessibleByReflection(classElement) || cls.onlyForRti) {
// TODO(herhut): Fix use of reflection name here.
enclosingBuilder.addPropertyByName("+$reflectionName", js.number(0));
} else {
List<jsAst.Expression> types = <jsAst.Expression>[];
if (classElement.supertype != null) {
types.add(task.metadataCollector.reifyType(classElement.supertype));
}
for (DartType interface in classElement.interfaces) {
types.add(task.metadataCollector.reifyType(interface));
}
// TODO(herhut): Fix use of reflection name here.
enclosingBuilder.addPropertyByName(
"+$reflectionName", new jsAst.ArrayInitializer(types));
}
}
}
void recordMangledField(
Element member, jsAst.Name accessorName, String memberName) {
if (!backend.shouldRetainGetter(member)) return;
String previousName;
if (member.isInstanceMember) {
previousName = emitter.mangledFieldNames
.putIfAbsent(namer.deriveGetterName(accessorName), () => memberName);
} else {
previousName = emitter.mangledGlobalFieldNames
.putIfAbsent(accessorName, () => memberName);
}
assert(invariant(member, previousName == memberName,
message: '$previousName != ${memberName}'));
}
void emitGetterForCSP(Element member, jsAst.Name fieldName,
jsAst.Name accessorName, ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateGetter(member, fieldName);
jsAst.Name getterName = namer.deriveGetterName(accessorName);
ClassElement cls = member.enclosingClass;
jsAst.Name className = namer.className(cls);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForElement(member);
emitter
.cspPrecompiledFunctionFor(outputUnit)
.add(js('#.prototype.# = #', [className, getterName, function]));
if (backend.isAccessibleByReflection(member)) {
emitter.cspPrecompiledFunctionFor(outputUnit).add(js(
'#.prototype.#.${namer.reflectableField} = 1',
[className, getterName]));
}
}
void emitSetterForCSP(Element member, jsAst.Name fieldName,
jsAst.Name accessorName, ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateSetter(member, fieldName);
jsAst.Name setterName = namer.deriveSetterName(accessorName);
ClassElement cls = member.enclosingClass;
jsAst.Name className = namer.className(cls);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForElement(member);
emitter
.cspPrecompiledFunctionFor(outputUnit)
.add(js('#.prototype.# = #', [className, setterName, function]));
if (backend.isAccessibleByReflection(member)) {
emitter.cspPrecompiledFunctionFor(outputUnit).add(js(
'#.prototype.#.${namer.reflectableField} = 1',
[className, setterName]));
}
}
void generateReflectionDataForFieldGetterOrSetter(
Element member, jsAst.Name name, ClassBuilder builder,
{bool isGetter}) {
Selector selector = isGetter
? new Selector.getter(new Name(member.name, member.library))
: new Selector.setter(
new Name(member.name, member.library, isSetter: true));
String reflectionName = emitter.getReflectionName(selector, name);
if (reflectionName != null) {
var reflectable =
js(backend.isAccessibleByReflection(member) ? '1' : '0');
builder.addPropertyByName('+$reflectionName', reflectable);
}
}
}