blob: 9a5c8e987940a9ec62bb27bc9291ab808012f7ee [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 js_backend;
/**
* Support for Custom Elements.
*
* The support for custom elements the compiler builds a table that maps the
* custom element class's [Type] to the interceptor for the class and the
* constructor(s) for the class.
*
* We want the table to contain only the custom element classes used, and we
* want to avoid resolving and compiling constructors that are not used since
* that may bring in unused code. This class controls the resolution and code
* generation to restrict the impact.
*
* The following line of code requires the generation of the generative
* constructor factory function(s) for FancyButton, and their insertion into the
* table:
*
* document.register(FancyButton, 'x-fancy-button');
*
* We detect this by 'joining' the classes that are referenced as type literals
* with the classes that are custom elements, enabled by detecting the presence
* of the table access code used by document.register.
*
* We have to be more conservative when the type is unknown, e.g.
*
* document.register(classMirror.reflectedType, tagFromMetadata);
*
* and
*
* class Component<T> {
* final tag;
* Component(this.tag);
* void register() => document.register(T, tag);
* }
* const Component<FancyButton>('x-fancy-button').register();
*
* In these cases we conservatively generate all viable entries in the table.
*/
class CustomElementsAnalysis {
final JavaScriptBackend backend;
final Compiler compiler;
final CustomElementsAnalysisJoin resolutionJoin;
final CustomElementsAnalysisJoin codegenJoin;
bool fetchedTableAccessorMethod = false;
Element tableAccessorMethod;
CustomElementsAnalysis(JavaScriptBackend backend)
: this.backend = backend,
this.compiler = backend.compiler,
resolutionJoin = new CustomElementsAnalysisJoin(backend),
codegenJoin = new CustomElementsAnalysisJoin(backend) {
// TODO(sra): Remove this work-around. We should mark allClassesSelected in
// both joins only when we see a construct generating an unknown [Type] but
// we can't currently recognize all cases. In particular, the work-around
// for the unimplemented `ClassMirror.reflectedType` is not recognizable.
// TODO(12607): Match on [ClassMirror.reflectedType]
resolutionJoin.allClassesSelected = true;
codegenJoin.allClassesSelected = true;
}
CustomElementsAnalysisJoin joinFor(Enqueuer enqueuer) =>
enqueuer.isResolutionQueue ? resolutionJoin : codegenJoin;
void registerInstantiatedClass(ClassElement classElement, Enqueuer enqueuer) {
classElement.ensureResolved(compiler);
if (!Elements.isNativeOrExtendsNative(classElement)) return;
if (classElement.isMixinApplication) return;
if (classElement.isAbstract) return;
joinFor(enqueuer).instantiatedClasses.add(classElement);
}
void registerTypeLiteral(DartType type, Registry registry) {
assert(registry.isForResolution);
// In codegen we see the TypeConstants instead.
if (!registry.isForResolution) return;
if (type.isInterfaceType) {
// TODO(sra): If we had a flow query from the type literal expression to
// the Type argument of the metadata lookup, we could tell if this type
// literal is really a demand for the metadata.
resolutionJoin.selectedClasses.add(type.element);
} else if (type.isTypeVariable) {
// This is a type parameter of a parameterized class.
// TODO(sra): Is there a way to determine which types are bound to the
// parameter?
resolutionJoin.allClassesSelected = true;
}
}
void registerTypeConstant(Element element, Enqueuer enqueuer) {
assert(element.isClass);
assert(!enqueuer.isResolutionQueue);
codegenJoin.selectedClasses.add(element);
}
void registerStaticUse(Element element, Enqueuer enqueuer) {
assert(element != null);
if (!fetchedTableAccessorMethod) {
fetchedTableAccessorMethod = true;
tableAccessorMethod = backend.findInterceptor(
'findIndexForNativeSubclassType');
}
if (element == tableAccessorMethod) {
joinFor(enqueuer).demanded = true;
}
}
void onQueueEmpty(Enqueuer enqueuer) {
joinFor(enqueuer).flush(enqueuer);
}
bool get needsTable => codegenJoin.demanded;
bool needsClass(ClassElement classElement) =>
codegenJoin.activeClasses.contains(classElement);
List<Element> constructors(ClassElement classElement) =>
codegenJoin.computeEscapingConstructors(classElement);
}
class CustomElementsAnalysisJoin {
final JavaScriptBackend backend;
Compiler get compiler => backend.compiler;
// Classes that are candidates for needing constructors. Classes are moved to
// [activeClasses] when we know they need constructors.
final instantiatedClasses = new Set<ClassElement>();
// Classes explicitly named.
final selectedClasses = new Set<ClassElement>();
// True if we must conservatively include all extension classes.
bool allClassesSelected = false;
// Did we see a demand for the data?
bool demanded = false;
// ClassesOutput: classes requiring metadata.
final activeClasses = new Set<ClassElement>();
CustomElementsAnalysisJoin(this.backend);
void flush(Enqueuer enqueuer) {
if (!demanded) return;
var newActiveClasses = new Set<ClassElement>();
for (ClassElement classElement in instantiatedClasses) {
bool isNative = classElement.isNative;
bool isExtension =
!isNative && Elements.isNativeOrExtendsNative(classElement);
// Generate table entries for native classes that are explicitly named and
// extensions that fix our criteria.
if ((isNative && selectedClasses.contains(classElement)) ||
(isExtension &&
(allClassesSelected || selectedClasses.contains(classElement)))) {
newActiveClasses.add(classElement);
Iterable<Element> escapingConstructors =
computeEscapingConstructors(classElement);
escapingConstructors.forEach(enqueuer.registerStaticUse);
escapingConstructors
.forEach(compiler.globalDependencies.registerDependency);
// Force the generaton of the type constant that is the key to an entry
// in the generated table.
ConstantValue constant = makeTypeConstant(classElement);
backend.registerCompileTimeConstant(
constant, compiler.globalDependencies);
backend.constants.addCompileTimeConstantForEmission(constant);
}
}
activeClasses.addAll(newActiveClasses);
instantiatedClasses.removeAll(newActiveClasses);
}
TypeConstantValue makeTypeConstant(ClassElement element) {
DartType elementType = element.rawType;
DartType constantType = backend.typeImplementation.rawType;
return new TypeConstantValue(elementType, constantType);
}
List<Element> computeEscapingConstructors(ClassElement classElement) {
List<Element> result = <Element>[];
// Only classes that extend native classes have constructors in the table.
// We could refine this to classes that extend Element, but that would break
// the tests and there is no sane reason to subclass other native classes.
if (classElement.isNative) return result;
selectGenerativeConstructors(ClassElement enclosing, Element member) {
if (member.isGenerativeConstructor) {
// Ignore constructors that cannot be called with zero arguments.
FunctionElement constructor = member;
FunctionSignature parameters = constructor.functionSignature;
if (parameters.requiredParameterCount == 0) {
result.add(member);
}
}
}
classElement.forEachMember(selectGenerativeConstructors,
includeBackendMembers: false,
includeSuperAndInjectedMembers: false);
return result;
}
}