blob: 1e24de4fad4c16afeddbdebeb354cd3dab064773 [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.
library dart2js.js_emitter.full_emitter.class_emitter;
import '../../common.dart';
import '../../common/names.dart' show Names;
import '../../common_elements.dart';
import '../../deferred_load.dart' show OutputUnit;
import '../../elements/entities.dart';
import '../../js/js.dart' as jsAst;
import '../../js/js.dart' show js;
import '../../js_backend/js_backend.dart' show CompoundName, Namer;
import '../../world.dart' show JClosedWorld;
import '../js_emitter.dart' hide Emitter, EmitterFactory;
import '../model.dart';
import 'emitter.dart';
class ClassEmitter extends CodeEmitterHelper {
final JClosedWorld closedWorld;
ClassEmitter(this.closedWorld);
ClassStubGenerator get _stubGenerator => new ClassStubGenerator(task.emitter,
closedWorld.commonElements, namer, codegenWorldBuilder, closedWorld,
enableMinification: compiler.options.enableMinification);
ElementEnvironment get _elementEnvironment => closedWorld.elementEnvironment;
/**
* Documentation wanted -- johnniwinther
*/
void emitClass(Class cls, ClassBuilder enclosingBuilder, Fragment fragment) {
ClassEntity classElement = cls.element;
emitter.needsClassSupport = true;
ClassEntity superclass = _elementEnvironment.getSuperClass(classElement);
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.rtiFieldJsName);
}
emitCheckedClassSetters(cls, builder);
emitClassGettersSettersForCSP(cls, builder);
emitInstanceMembers(cls, builder);
emitStubs(cls.callStubs, builder);
emitRuntimeTypeInformation(cls, builder);
emitNativeInfo(cls, builder);
if (classElement == closedWorld.commonElements.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) {
if (!compiler.options.useContentSecurityPolicy) return;
List<jsAst.Name> fieldNames = <jsAst.Name>[];
if (!cls.onlyForRti && !cls.isNative) {
fieldNames = cls.fields.map((Field field) => field.name).toList();
}
ClassEntity classElement = cls.element;
jsAst.Expression constructorAst = _stubGenerator.generateClassConstructor(
classElement, fieldNames, cls.hasRtiField);
jsAst.Name constructorName = namer.className(classElement);
OutputUnit outputUnit =
compiler.backend.outputUnitData.outputUnitForClass(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;
}
bool fieldsAdded = false;
for (Field field in fields) {
FieldEntity 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) {
List<jsAst.Literal> fieldNameParts = <jsAst.Literal>[];
if (field.nullInitializerInAllocator) {
fieldNameParts.add(js.stringPart('0'));
}
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.setterForMember(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]));
}
jsAst.Literal fieldNameAst = js.concatenateStrings(fieldNameParts);
builder.addField(fieldNameAst);
// Add 1 because adding a field to the class also requires a comma
compiler.dumpInfoTask.registerEntityAst(fieldElement, fieldNameAst);
fieldsAdded = true;
}
}
return fieldsAdded;
}
/// Emits checked setters for fields.
void emitCheckedClassSetters(Class cls, ClassBuilder builder) {
if (cls.onlyForRti) return;
for (StubMethod method in cls.checkedSetters) {
MemberEntity member = method.element;
assert(member != null);
jsAst.Expression code = method.code;
jsAst.Name setterName = method.name;
compiler.dumpInfoTask
.registerEntityAst(member, builder.addProperty(setterName, code));
}
}
/// 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) {
FieldEntity 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.registerEntityAst(method.element, property);
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [classElement] must be a declaration element.
*/
void emitInstanceMembers(Class cls, ClassBuilder builder) {
ClassEntity classElement = cls.element;
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(method.element.isInstanceMember, failedAt(classElement));
emitter.containerBuilder.addMemberMethod(method, builder);
}
if (classElement == closedWorld.commonElements.objectClass &&
closedWorld.backendUsage.isNoSuchMethodUsed) {
// 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) {
ClassEntity classEntity = cls.element;
jsAst.Name className = cls.name;
List<jsAst.Property> statics = new List<jsAst.Property>();
ClassBuilder staticsBuilder =
new ClassBuilder.forStatics(classEntity, namer);
if (emitFields(cls, staticsBuilder, emitStatics: true)) {
jsAst.ObjectInitializer initializer =
staticsBuilder.toObjectInitializer();
compiler.dumpInfoTask.registerEntityAst(classEntity, initializer);
jsAst.Node property = initializer.properties.single;
compiler.dumpInfoTask.registerEntityAst(classEntity, property);
statics.add(property);
}
// TODO(herhut): Do not grab statics out of the properties.
ClassBuilder classProperties =
emitter.classDescriptors[fragment]?.remove(classEntity);
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
.registerEntityAst(classBuilder.element, propertyValue);
enclosingBuilder.addProperty(className, propertyValue);
String reflectionName =
emitter.getReflectionClassName(classEntity, className);
if (reflectionName != null) {
// TODO(herhut): Fix use of reflection name here.
enclosingBuilder.addPropertyByName("+$reflectionName", js.number(0));
}
}
void emitGetterForCSP(FieldEntity member, jsAst.Name fieldName,
jsAst.Name accessorName, ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateGetter(member, fieldName);
jsAst.Name getterName = namer.deriveGetterName(accessorName);
ClassEntity cls = member.enclosingClass;
jsAst.Name className = namer.className(cls);
OutputUnit outputUnit =
compiler.backend.outputUnitData.outputUnitForMember(member);
emitter
.cspPrecompiledFunctionFor(outputUnit)
.add(js('#.prototype.# = #', [className, getterName, function]));
}
void emitSetterForCSP(FieldEntity member, jsAst.Name fieldName,
jsAst.Name accessorName, ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateSetter(member, fieldName);
jsAst.Name setterName = namer.deriveSetterName(accessorName);
ClassEntity cls = member.enclosingClass;
jsAst.Name className = namer.className(cls);
OutputUnit outputUnit =
compiler.backend.outputUnitData.outputUnitForMember(member);
emitter
.cspPrecompiledFunctionFor(outputUnit)
.add(js('#.prototype.# = #', [className, setterName, function]));
}
}