content: // 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;
      joinFor(enqueuer).instantiatedClasses.add(classElement);
    }
  
    void registerTypeLiteral(Element element, Enqueuer enqueuer) {
      // In codegen we see the TypeConstants instead.
      if (!enqueuer.isResolutionQueue) return;
  
      if (element.isClass()) {
        // 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(element);
      } else {
        // 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 = compiler.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.escapingConstructors(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);
          escapingConstructors(classElement).forEach(enqueuer.registerStaticUse);
          // Force the generaton of the type constant that is the key to an entry
          // in the generated table.
          Constant constant = makeTypeConstant(classElement);
          backend.registerCompileTimeConstant(
              constant, compiler.globalDependencies);
          compiler.constantHandler.addCompileTimeConstantForEmission(constant);
        }
      }
      activeClasses.addAll(newActiveClasses);
      instantiatedClasses.removeAll(newActiveClasses);
    }
  
    TypeConstant makeTypeConstant(ClassElement element) {
      DartType elementType = element.computeType(compiler).asRaw();
      DartType constantType = backend.typeImplementation.computeType(compiler);
      return new TypeConstant(elementType, constantType);
    }
  
    List<Element> escapingConstructors(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.computeSignature(compiler);
          if (parameters.requiredParameterCount == 0) {
            result.add(member);
          }
        }
      }
      classElement.forEachMember(selectGenerativeConstructors,
          includeBackendMembers: false,
          includeSuperAndInjectedMembers: false);
      return result;
    }
  }
  