content: // Copyright (c) 2012, 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;
  
  class NativeEmitter {
  
    CodeEmitterTask emitter;
    CodeBuffer nativeBuffer;
  
    // Native classes found in the application.
    Set<ClassElement> nativeClasses = new Set<ClassElement>();
  
    // Caches the native subtypes of a native class.
    Map<ClassElement, List<ClassElement>> subtypes;
  
    // Caches the direct native subtypes of a native class.
    Map<ClassElement, List<ClassElement>> directSubtypes;
  
    // Caches the methods that have a native body.
    Set<FunctionElement> nativeMethods;
  
    // Do we need the native emitter to take care of handling
    // noSuchMethod for us? This flag is set to true in the emitter if
    // it finds any native class that needs noSuchMethod handling.
    bool handleNoSuchMethod = false;
  
    NativeEmitter(this.emitter)
        : subtypes = new Map<ClassElement, List<ClassElement>>(),
          directSubtypes = new Map<ClassElement, List<ClassElement>>(),
          nativeMethods = new Set<FunctionElement>(),
          nativeBuffer = new CodeBuffer();
  
    Compiler get compiler => emitter.compiler;
    JavaScriptBackend get backend => compiler.backend;
  
    String get _ => emitter.space;
    String get n => emitter.n;
    String get N => emitter.N;
  
    String get dynamicName {
      Element element = compiler.findHelper('dynamicFunction');
      return backend.namer.isolateAccess(element);
    }
  
    String get dynamicFunctionTableName {
      Element element = compiler.findHelper('dynamicFunctionTable');
      return backend.namer.isolateAccess(element);
    }
  
    String get typeNameOfName {
      Element element = compiler.findHelper('getTypeNameOf');
      return backend.namer.isolateAccess(element);
    }
  
    String get defPropName {
      Element element = compiler.findHelper('defineProperty');
      return backend.namer.isolateAccess(element);
    }
  
    String get toStringHelperName {
      Element element = compiler.findHelper('toStringForNativeObject');
      return backend.namer.isolateAccess(element);
    }
  
    String get hashCodeHelperName {
      Element element = compiler.findHelper('hashCodeForNativeObject');
      return backend.namer.isolateAccess(element);
    }
  
    String get dispatchPropertyNameVariable {
      Element element = compiler.findInterceptor('dispatchPropertyName');
      return backend.namer.isolateAccess(element);
    }
  
    // The tags string contains comma-separated 'words' which are either dispatch
    // tags (having JavaScript identifier syntax) and directives that begin with
    // `!`.
    List<String> nativeTagsOfClassRaw(ClassElement cls) {
      String quotedName = cls.nativeTagInfo;
      return quotedName.substring(1, quotedName.length - 1).split(',');
    }
  
    List<String> nativeTagsOfClass(ClassElement cls) {
      return nativeTagsOfClassRaw(cls).where((s) => !s.startsWith('!')).toList();
    }
  
    bool nativeHasTagsMarker(ClassElement cls, String marker) {
      return nativeTagsOfClassRaw(cls).contains(marker);
    }
  
    bool nativeForcedNonLeaf(ClassElement cls) =>
        nativeHasTagsMarker(cls, '!nonleaf');
  
    /**
     * Writes the class definitions for the interceptors to [mainBuffer].
     * Writes code to associate dispatch tags with interceptors to [nativeBuffer].
     *
     * The interceptors are filtered to avoid emitting trivial interceptors.  For
     * example, if the program contains no code that can distinguish between the
     * numerous subclasses of `Element` then we can pretend that `Element` is a
     * leaf class, and all instances of subclasses of `Element` are instances of
     * `Element`.
     *
     * There is also a performance benefit (in addition to the obvious code size
     * benefit), due to how [getNativeInterceptor] works.  Finding the interceptor
     * of a leaf class in the hierarchy is more efficient that a non-leaf, so it
     * improves performance when more classes can be treated as leaves.
     *
     * [classes] contains native classes, mixin applications, and user subclasses
     * of native classes.  ONLY the native classes are generated here.  [classes]
     * is sorted in desired output order.
     *
     * [additionalProperties] is used to collect properties that are pushed up
     * from the above optimizations onto a non-native class, e.g, `Interceptor`.
     */
    void generateNativeClasses(
        List<ClassElement> classes,
        CodeBuffer mainBuffer,
        Map<ClassElement, Map<String, jsAst.Expression>> additionalProperties) {
      // Compute a pre-order traversal of the subclass forest.  We actually want a
      // post-order traversal but it is easier to compute the pre-order and use it
      // in reverse.
  
      List<ClassElement> preOrder = <ClassElement>[];
      Set<ClassElement> seen = new Set<ClassElement>();
      seen..add(compiler.objectClass)
          ..add(backend.jsInterceptorClass);
      void walk(ClassElement element) {
        if (seen.contains(element)) return;
        seen.add(element);
        walk(element.superclass);
        preOrder.add(element);
      }
      classes.forEach(walk);
  
      // Generate code for each native class into [ClassBuilder]s.
  
      Map<ClassElement, ClassBuilder> builders =
          new Map<ClassElement, ClassBuilder>();
      for (ClassElement classElement in classes) {
        if (classElement.isNative()) {
          ClassBuilder builder = generateNativeClass(classElement);
          builders[classElement] = builder;
        }
      }
  
      // Find which classes are needed and which are non-leaf classes.  Any class
      // that is not needed can be treated as a leaf class equivalent to some
      // needed class.
  
      Set<ClassElement> neededClasses = new Set<ClassElement>();
      Set<ClassElement> nonleafClasses = new Set<ClassElement>();
  
      Map<ClassElement, List<ClassElement>> extensionPoints =
          computeExtensionPoints(preOrder);
  
      neededClasses.add(compiler.objectClass);
  
      Set<ClassElement> neededByConstant =
          emitter.interceptorEmitter.interceptorsReferencedFromConstants();
      Set<ClassElement> modifiedClasses =
          emitter.typeTestEmitter.classesModifiedByEmitRuntimeTypeSupport();
  
      for (ClassElement classElement in preOrder.reversed) {
        // Post-order traversal ensures we visit the subclasses before their
        // superclass.  This makes it easy to tell if a class is needed because a
        // subclass is needed.
        ClassBuilder builder = builders[classElement];
        bool needed = false;
        if (builder == null) {
          // Mixin applications (native+mixin) are non-native, so [classElement]
          // has already been emitted as a regular class.  Mark [classElement] as
          // 'needed' to ensure the native superclass is needed.
          needed = true;
        } else if (!builder.isTrivial) {
          needed = true;
        } else if (neededByConstant.contains(classElement)) {
          needed = true;
        } else if (modifiedClasses.contains(classElement)) {
          // TODO(9556): Remove this test when [emitRuntimeTypeSupport] no longer
          // adds information to a class prototype or constructor.
          needed = true;
        } else if (extensionPoints.containsKey(classElement)) {
          needed = true;
        }
        if (classElement.isNative() && nativeForcedNonLeaf(classElement)) {
          needed = true;
          nonleafClasses.add(classElement);
        }
  
        if (needed || neededClasses.contains(classElement)) {
          neededClasses.add(classElement);
          neededClasses.add(classElement.superclass);
          nonleafClasses.add(classElement.superclass);
        }
      }
  
      // Collect all the tags that map to each native class.
  
      Map<ClassElement, Set<String>> leafTags =
          new Map<ClassElement, Set<String>>();
      Map<ClassElement, Set<String>> nonleafTags =
          new Map<ClassElement, Set<String>>();
  
      for (ClassElement classElement in classes) {
        if (!classElement.isNative()) continue;
        List<String> nativeTags = nativeTagsOfClass(classElement);
  
        if (nonleafClasses.contains(classElement) ||
            extensionPoints.containsKey(classElement)) {
          nonleafTags
              .putIfAbsent(classElement, () => new Set<String>())
              .addAll(nativeTags);
        } else {
          ClassElement sufficingInterceptor = classElement;
          while (!neededClasses.contains(sufficingInterceptor)) {
            sufficingInterceptor = sufficingInterceptor.superclass;
          }
          if (sufficingInterceptor == compiler.objectClass) {
            sufficingInterceptor = backend.jsInterceptorClass;
          }
          leafTags
              .putIfAbsent(sufficingInterceptor, () => new Set<String>())
              .addAll(nativeTags);
        }
      }
  
      // Add properties containing the information needed to construct maps used
      // by getNativeInterceptor and custom elements.
      if (compiler.enqueuer.codegen.nativeEnqueuer
          .hasInstantiatedNativeClasses()) {
        void generateClassInfo(ClassElement classElement) {
          // Property has the form:
          //
          //    "%": "leafTag1|leafTag2|...;nonleafTag1|...;Class1|Class2|...",
          //
          // If there is no data following a semicolon, the semicolon can be
          // omitted.
  
          String formatTags(Iterable<String> tags) {
            if (tags == null) return '';
            return (tags.toList()..sort()).join('|');
          }
  
          List<ClassElement> extensions = extensionPoints[classElement];
  
          String leafStr = formatTags(leafTags[classElement]);
          String nonleafStr = formatTags(nonleafTags[classElement]);
  
          StringBuffer sb = new StringBuffer(leafStr);
          if (nonleafStr != '') {
            sb..write(';')..write(nonleafStr);
          }
          if (extensions != null) {
            sb..write(';')
              ..writeAll(extensions.map(backend.namer.getNameOfClass), '|');
          }
          String encoding = sb.toString();
  
          ClassBuilder builder = builders[classElement];
          if (builder == null) {
            // No builder because this is an intermediate mixin application or
            // Interceptor - these are not direct native classes.
            if (encoding != '') {
              Map<String, jsAst.Expression> properties =
                  additionalProperties.putIfAbsent(classElement,
                      () => new LinkedHashMap<String, jsAst.Expression>());
              properties[backend.namer.nativeSpecProperty] = js.string(encoding);
            }
          } else {
            builder.addProperty(
                backend.namer.nativeSpecProperty, js.string(encoding));
          }
        }
        generateClassInfo(backend.jsInterceptorClass);
        for (ClassElement classElement in classes) {
          generateClassInfo(classElement);
        }
      }
  
      // Emit the native class interceptors that were actually used.
      for (ClassElement classElement in classes) {
        if (!classElement.isNative()) continue;
        if (neededClasses.contains(classElement)) {
          // Define interceptor class for [classElement].
          emitter.classEmitter.emitClassBuilderWithReflectionData(
              backend.namer.getNameOfClass(classElement),
              classElement, builders[classElement],
              emitter.bufferForElement(classElement, mainBuffer));
          emitter.needsDefineClass = true;
        }
      }
    }
  
    /**
     * Computes the native classes that are extended (subclassed) by non-native
     * classes and the set non-mative classes that extend them.  (A List is used
     * instead of a Set for out stability).
     */
    Map<ClassElement, List<ClassElement>> computeExtensionPoints(
        List<ClassElement> classes) {
      ClassElement nativeSuperclassOf(ClassElement element) {
        if (element == null) return null;
        if (element.isNative()) return element;
        return nativeSuperclassOf(element.superclass);
      }
  
      ClassElement nativeAncestorOf(ClassElement element) {
        return nativeSuperclassOf(element.superclass);
      }
  
      Map<ClassElement, List<ClassElement>> map =
          new Map<ClassElement, List<ClassElement>>();
  
      for (ClassElement classElement in classes) {
        if (classElement.isNative()) continue;
        ClassElement nativeAncestor = nativeAncestorOf(classElement);
        if (nativeAncestor != null) {
          map
            .putIfAbsent(nativeAncestor, () => <ClassElement>[])
            .add(classElement);
        }
      }
      return map;
    }
  
    ClassBuilder generateNativeClass(ClassElement classElement) {
      // TODO(sra): Issue #13731- this is commented out as part of custom element
      // constructor work.
      //assert(!classElement.hasBackendMembers);
      nativeClasses.add(classElement);
  
      ClassElement superclass = classElement.superclass;
      assert(superclass != null);
      // Fix superclass.  TODO(sra): make native classes inherit from Interceptor.
      assert(superclass != compiler.objectClass);
      if (superclass == compiler.objectClass) {
        superclass = backend.jsInterceptorClass;
      }
  
      String superName = backend.namer.getNameOfClass(superclass);
  
      ClassBuilder builder = new ClassBuilder();
      emitter.classEmitter.emitClassConstructor(classElement, builder, null);
      bool hasFields = emitter.classEmitter.emitFields(
          classElement, builder, superName, classIsNative: true);
      int propertyCount = builder.properties.length;
      emitter.classEmitter.emitClassGettersSetters(classElement, builder);
      emitter.classEmitter.emitInstanceMembers(classElement, builder);
      emitter.typeTestEmitter.emitIsTests(classElement, builder);
  
      if (!hasFields &&
          builder.properties.length == propertyCount &&
          superclass is! MixinApplicationElement) {
        builder.isTrivial = true;
      }
  
      return builder;
    }
  
    void finishGenerateNativeClasses() {
      // TODO(sra): Put specialized version of getNativeMethods on
      // `Object.prototype` to avoid checking in `getInterceptor` and
      // specializations.
    }
  
    void potentiallyConvertDartClosuresToJs(
        List<jsAst.Statement> statements,
        FunctionElement member,
        List<jsAst.Parameter> stubParameters) {
      FunctionSignature parameters = member.computeSignature(compiler);
      Element converter =
          compiler.findHelper('convertDartClosureToJS');
      String closureConverter = backend.namer.isolateAccess(converter);
      Set<String> stubParameterNames = new Set<String>.from(
          stubParameters.map((param) => param.name));
      parameters.forEachParameter((Element parameter) {
        String name = parameter.name;
        // If [name] is not in [stubParameters], then the parameter is an optional
        // parameter that was not provided for this stub.
        for (jsAst.Parameter stubParameter in stubParameters) {
          if (stubParameter.name == name) {
            DartType type = parameter.computeType(compiler).unalias(compiler);
            if (type is FunctionType) {
              // The parameter type is a function type either directly or through
              // typedef(s).
              FunctionType functionType = type;
              int arity = functionType.computeArity();
              statements.add(
                  js('$name = $closureConverter($name, $arity)').toStatement());
              break;
            }
          }
        }
      });
    }
  
    List<jsAst.Statement> generateParameterStubStatements(
        Element member,
        bool isInterceptedMethod,
        String invocationName,
        List<jsAst.Parameter> stubParameters,
        List<jsAst.Expression> argumentsBuffer,
        int indexOfLastOptionalArgumentInParameters) {
      // The target JS function may check arguments.length so we need to
      // make sure not to pass any unspecified optional arguments to it.
      // For example, for the following Dart method:
      //   foo([x, y, z]);
      // The call:
      //   foo(y: 1)
      // must be turned into a JS call to:
      //   foo(null, y).
  
      ClassElement classElement = member.enclosingElement;
  
      List<jsAst.Statement> statements = <jsAst.Statement>[];
      potentiallyConvertDartClosuresToJs(statements, member, stubParameters);
  
      String target;
      jsAst.Expression receiver;
      List<jsAst.Expression> arguments;
  
      assert(invariant(member, nativeMethods.contains(member)));
      // When calling a JS method, we call it with the native name, and only the
      // arguments up until the last one provided.
      target = member.fixedBackendName();
  
      if (isInterceptedMethod) {
        receiver = argumentsBuffer[0];
        arguments = argumentsBuffer.sublist(1,
            indexOfLastOptionalArgumentInParameters + 1);
      } else {
        receiver = js('this');
        arguments = argumentsBuffer.sublist(0,
            indexOfLastOptionalArgumentInParameters + 1);
      }
      statements.add(new jsAst.Return(receiver[target](arguments)));
  
      return statements;
    }
  
    bool isSupertypeOfNativeClass(Element element) {
      if (element.isTypeVariable()) {
        compiler.cancel("Is check for type variable", element: element);
        return false;
      }
      if (element.computeType(compiler).unalias(compiler) is FunctionType) {
        // The element type is a function type either directly or through
        // typedef(s).
        return false;
      }
  
      if (!element.isClass()) {
        compiler.cancel("Is check does not handle element", element: element);
        return false;
      }
  
      if (backend.classesMixedIntoNativeClasses.contains(element)) return true;
  
      return subtypes[element] != null;
    }
  
    bool requiresNativeIsCheck(Element element) {
      // TODO(sra): Remove this function.  It determines if a native type may
      // satisfy a check against [element], in which case an interceptor must be
      // used.  We should also use an interceptor if the check can't be satisfied
      // by a native class in case we get a native instance that tries to spoof
      // the type info.  i.e the criteria for whether or not to use an interceptor
      // is whether the receiver can be native, not the type of the test.
      if (!element.isClass()) return false;
      ClassElement cls = element;
      if (Elements.isNativeOrExtendsNative(cls)) return true;
      return isSupertypeOfNativeClass(element);
    }
  
    void assembleCode(CodeBuffer targetBuffer) {
      List<jsAst.Property> objectProperties = <jsAst.Property>[];
  
      void addProperty(String name, jsAst.Expression value) {
        objectProperties.add(new jsAst.Property(js.string(name), value));
      }
  
      if (!nativeClasses.isEmpty) {
        // If the native emitter has been asked to take care of the
        // noSuchMethod handlers, we do that now.
        if (handleNoSuchMethod) {
          emitter.nsmEmitter.emitNoSuchMethodHandlers(addProperty);
        }
      }
  
      // If we have any properties to add to Object.prototype, we run
      // through them and add them using defineProperty.
      if (!objectProperties.isEmpty) {
        jsAst.Expression init =
            js.fun(['table'],
                new jsAst.ForIn(
                    new jsAst.VariableDeclarationList(
                        [new jsAst.VariableInitialization(
                            new jsAst.VariableDeclaration('key'),
                            null)]),
                    js('table'),
                    new jsAst.ExpressionStatement(
                        js('$defPropName(Object.prototype, key, table[key])'))))(
                new jsAst.ObjectInitializer(objectProperties));
  
        if (emitter.compiler.enableMinification) targetBuffer.add(';');
        targetBuffer.add(jsAst.prettyPrint(
            new jsAst.ExpressionStatement(init), compiler));
        targetBuffer.add('\n');
      }
  
      targetBuffer.add(nativeBuffer);
      targetBuffer.add('\n');
    }
  }
  