| Index: pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart
 | 
| diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..67508365e99deeeffecb824415bf2bb9aaaca924
 | 
| --- /dev/null
 | 
| +++ b/pkg/front_end/lib/src/fasta/kernel/kernel_outline_shaker.dart
 | 
| @@ -0,0 +1,406 @@
 | 
| +// Copyright (c) 2016, 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.
 | 
| +
 | 
| +/// A transformation to create a self-contained modular kernel without
 | 
| +/// unnecessary references to other libraries.
 | 
| +library fasta.kernel.kernel_outline_shaker;
 | 
| +
 | 
| +import 'package:kernel/ast.dart';
 | 
| +import 'package:kernel/core_types.dart';
 | 
| +
 | 
| +import '../errors.dart' show internalError;
 | 
| +
 | 
| +/// Removes from [program] unnecessary libraries, classes, and members.
 | 
| +///
 | 
| +/// This applies a simple "tree-shaking" technique: the full body of libraries
 | 
| +/// whose URI match [isIncluded] is preserved, and so is the outline of the
 | 
| +/// members and classes which are indicated by [data] (which should
 | 
| +/// practically include all members and classes transitively visible from the
 | 
| +/// included libraries).
 | 
| +///
 | 
| +/// The intent is that the resulting program has the entire code that is meant
 | 
| +/// to be included and the minimum required to prevent dangling references and
 | 
| +/// allow modular program transformations.
 | 
| +///
 | 
| +/// Note that the resulting program may include libraries not in [isIncluded],
 | 
| +/// but those will be marked as external. There should be no method bodies for
 | 
| +/// any members of those libraries.
 | 
| +void trimProgram(Program program, RetainedData data, bool isIncluded(Uri uri)) {
 | 
| +  new KernelOutlineShaker(data, isIncluded).transform(program);
 | 
| +}
 | 
| +
 | 
| +/// Informs about which libraries, classes, and members should be retained by
 | 
| +/// the [KernelOutlineShaker] when tree-shaking.
 | 
| +abstract class RetainedData {
 | 
| +  /// Whether a library should be preserved and mark as external.
 | 
| +  bool isLibraryUsed(Library library);
 | 
| +
 | 
| +  /// Whether a class should be preserved. If a class is preserved, its
 | 
| +  /// supertypes will be preserved too, but some of it members may not be
 | 
| +  /// included.
 | 
| +  bool isClassUsed(Class cls);
 | 
| +
 | 
| +  /// Whether a member should be preserved. If so, its enclosing class/library
 | 
| +  /// will be preserved too.
 | 
| +  bool isMemberUsed(Member member);
 | 
| +}
 | 
| +
 | 
| +/// A builder of [RetainedData] that recursively marks transitive dependencies.
 | 
| +///
 | 
| +/// This builder contains APIs to mark the roots that are needed (e.g.
 | 
| +/// [markClass] and [markMember]). Note this builder does not determine what
 | 
| +/// roots to keep, that is done either directly by fasta while it is parsing, or
 | 
| +/// by using a visitor like the [RootsMarker] below.
 | 
| +class RetainedDataBuilder extends RetainedData {
 | 
| +  /// Libraries that contained code that is transitively reachable from the
 | 
| +  /// included libraries.
 | 
| +  final Set<Library> libraries = new Set<Library>();
 | 
| +
 | 
| +  /// Classes that are transitively reachable from the included libraries.
 | 
| +  final Set<Class> classes = new Set<Class>();
 | 
| +
 | 
| +  /// Members that are transitively reachable from the included libraries.
 | 
| +  final Set<Member> members = new Set<Member>();
 | 
| +
 | 
| +  TypeMarker typeMarker;
 | 
| +
 | 
| +  @override
 | 
| +  bool isLibraryUsed(Library library) => libraries.contains(library);
 | 
| +
 | 
| +  @override
 | 
| +  bool isClassUsed(Class cls) => classes.contains(cls);
 | 
| +
 | 
| +  @override
 | 
| +  bool isMemberUsed(Member m) => members.contains(m);
 | 
| +
 | 
| +  RetainedDataBuilder() {
 | 
| +    typeMarker = new TypeMarker(this);
 | 
| +  }
 | 
| +
 | 
| +  /// Mark a library as used.
 | 
| +  void markLibrary(Library lib) {
 | 
| +    libraries.add(lib);
 | 
| +  }
 | 
| +
 | 
| +  /// Mark a class and it's supertypes as used.
 | 
| +  void markClass(Class cls) {
 | 
| +    if (cls == null || !classes.add(cls)) return;
 | 
| +    markLibrary(cls.parent);
 | 
| +    // TODO(sigmund): retain annotations?
 | 
| +    // visitList(cls.annotations, this);
 | 
| +    markSupertype(cls.supertype);
 | 
| +    markSupertype(cls.mixedInType);
 | 
| +    cls.implementedTypes.forEach(markSupertype);
 | 
| +    cls.typeParameters.forEach((t) => t.bound.accept(typeMarker));
 | 
| +  }
 | 
| +
 | 
| +  /// Mark the class and type arguments of [node].
 | 
| +  void markSupertype(Supertype node) {
 | 
| +    if (node == null) return;
 | 
| +    markClass(node.classNode);
 | 
| +    node.typeArguments.forEach((t) => t.accept(typeMarker));
 | 
| +  }
 | 
| +
 | 
| +  /// Mark a member and types mentioned on its interface.
 | 
| +  void markMember(Member m) {
 | 
| +    if (m == null || !members.add(m)) return;
 | 
| +    markMemberInterface(m);
 | 
| +    var parent = m.parent;
 | 
| +    if (parent is Library) {
 | 
| +      markLibrary(parent);
 | 
| +    } else if (parent is Class) {
 | 
| +      markClass(parent);
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  void markMemberInterface(Member node) {
 | 
| +    if (node is Field) {
 | 
| +      node.type.accept(typeMarker);
 | 
| +    } else if (node is Procedure) {
 | 
| +      var function = node.function;
 | 
| +      function.typeParameters.forEach((p) => p.bound.accept(typeMarker));
 | 
| +      function.positionalParameters.forEach((p) => p.type.accept(typeMarker));
 | 
| +      function.namedParameters.forEach((p) => p.type.accept(typeMarker));
 | 
| +      function.returnType.accept(typeMarker);
 | 
| +    }
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// A helper visitor used to mark transitive types by the [RetainedDataBuilder].
 | 
| +class TypeMarker extends DartTypeVisitor {
 | 
| +  RetainedDataBuilder data;
 | 
| +
 | 
| +  TypeMarker(this.data);
 | 
| +
 | 
| +  visitInterfaceType(InterfaceType node) {
 | 
| +    data.markClass(node.classNode);
 | 
| +    node.typeArguments.forEach((t) => t.accept(this));
 | 
| +  }
 | 
| +
 | 
| +  visitFunctionType(FunctionType node) {
 | 
| +    node.typeParameters.forEach((t) => t.bound.accept(this));
 | 
| +    node.positionalParameters.forEach((t) => t.accept(this));
 | 
| +    node.namedParameters.forEach((t) => t.type.accept(this));
 | 
| +    node.returnType.accept(this);
 | 
| +  }
 | 
| +
 | 
| +  visitTypeParameterType(TypeParameterType node) {
 | 
| +    // Note: node.parameter is marked by marking the enclosing element.
 | 
| +  }
 | 
| +
 | 
| +  visitTypedefType(TypedefType node) {
 | 
| +    node.typeArguments.forEach((t) => t.accept(this));
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// Determines the root APIs that need to be retained before running the
 | 
| +/// tree-shaker.
 | 
| +///
 | 
| +/// This is implemented using a visitor that walks through the sources that are
 | 
| +/// intended to be part of the kernel output.
 | 
| +// TODO(sigmund): delete. We should collect this information while
 | 
| +// building kernel without having to run a visitor afterwards.
 | 
| +class RootsMarker extends RecursiveVisitor {
 | 
| +  final RetainedDataBuilder data;
 | 
| +  RootsMarker(this.data);
 | 
| +
 | 
| +  void run(Program program, bool isIncluded(Uri uri)) {
 | 
| +    markRequired(program);
 | 
| +    data.markMember(program.mainMethod);
 | 
| +    for (var library in program.libraries) {
 | 
| +      if (isIncluded(library.importUri)) {
 | 
| +        library.accept(this);
 | 
| +      }
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  /// Marks classes and members that are assumed to exist by fasta or by
 | 
| +  /// transformers.
 | 
| +  // TODO(sigmund): consider being more fine-grained and only marking what is
 | 
| +  // seen and used.
 | 
| +  void markRequired(Program program) {
 | 
| +    var coreTypes = new CoreTypes(program);
 | 
| +    coreTypes.objectClass.members.forEach(data.markMember);
 | 
| +
 | 
| +    // These are assumed to be available by fasta:
 | 
| +    data.markClass(coreTypes.objectClass);
 | 
| +    data.markClass(coreTypes.nullClass);
 | 
| +    data.markClass(coreTypes.boolClass);
 | 
| +    data.markClass(coreTypes.intClass);
 | 
| +    data.markClass(coreTypes.numClass);
 | 
| +    data.markClass(coreTypes.doubleClass);
 | 
| +    data.markClass(coreTypes.stringClass);
 | 
| +    data.markClass(coreTypes.listClass);
 | 
| +    data.markClass(coreTypes.mapClass);
 | 
| +    data.markClass(coreTypes.iterableClass);
 | 
| +    data.markClass(coreTypes.iteratorClass);
 | 
| +    data.markClass(coreTypes.futureClass);
 | 
| +    data.markClass(coreTypes.streamClass);
 | 
| +    data.markClass(coreTypes.symbolClass);
 | 
| +    data.markClass(coreTypes.internalSymbolClass);
 | 
| +    data.markClass(coreTypes.typeClass);
 | 
| +    data.markClass(coreTypes.functionClass);
 | 
| +    data.markClass(coreTypes.invocationClass);
 | 
| +    data.markMember(coreTypes.getMember("dart:_internal", "ExternalName", ""));
 | 
| +
 | 
| +    // These are needed by the continuation (async/await) transformer:
 | 
| +    data.markClass(coreTypes.getClass('dart:core', 'Iterator'));
 | 
| +    data.markClass(coreTypes.getClass('dart:async', 'Future'));
 | 
| +    data.markClass(coreTypes.getClass('dart:async', 'FutureOr'));
 | 
| +    data.markClass(coreTypes.getClass('dart:async', 'Completer'));
 | 
| +    data.markMember(coreTypes.getMember('dart:async', 'Completer', 'sync'));
 | 
| +    data.markMember(coreTypes.getMember('dart:core', '_SyncIterable', ''));
 | 
| +    data.markMember(coreTypes.getMember('dart:async', '_StreamIterator', ''));
 | 
| +    data.markMember(coreTypes.getMember('dart:async', 'Future', 'microtask'));
 | 
| +    data.markMember(
 | 
| +        coreTypes.getMember('dart:async', '_AsyncStarStreamController', ''));
 | 
| +    data.markMember(coreTypes.getTopLevelMember('dart:core', 'print'));
 | 
| +    data.markMember(
 | 
| +        coreTypes.getTopLevelMember('dart:async', '_asyncThenWrapperHelper'));
 | 
| +    data.markMember(
 | 
| +        coreTypes.getTopLevelMember('dart:async', '_asyncErrorWrapperHelper'));
 | 
| +    data.markMember(coreTypes.getTopLevelMember('dart:async', '_awaitHelper'));
 | 
| +
 | 
| +    // These are needed by the mixin transformer
 | 
| +    data.markMember(coreTypes.getMember('dart:core', '_InvocationMirror', ''));
 | 
| +    data.markMember(coreTypes.getMember('dart:core', 'List', 'from'));
 | 
| +  }
 | 
| +
 | 
| +  visitConstructor(Constructor node) {
 | 
| +    if (!node.initializers.any((i) => i is SuperInitializer)) {
 | 
| +      // super() is currently implicit.
 | 
| +      for (var ctor in node.enclosingClass.supertype.classNode.constructors) {
 | 
| +        if (ctor.name.name == '') data.markMember(ctor);
 | 
| +      }
 | 
| +    }
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitSuperInitializer(SuperInitializer node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitRedirectingInitializer(RedirectingInitializer node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitConstructorInvocation(ConstructorInvocation node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitStaticInvocation(StaticInvocation node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitDirectMethodInvocation(DirectMethodInvocation node) {
 | 
| +    if (node.receiver is! ThisExpression) {
 | 
| +      return internalError('Direct calls are only supported on "this"');
 | 
| +    }
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitMethodInvocation(MethodInvocation node) {
 | 
| +    data.markMember(node.interfaceTarget);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitStaticGet(StaticGet node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitStaticSet(StaticSet node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitDirectPropertyGet(DirectPropertyGet node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitDirectPropertySet(DirectPropertySet node) {
 | 
| +    data.markMember(node.target);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitSuperPropertyGet(SuperPropertyGet node) {
 | 
| +    data.markMember(node.interfaceTarget);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitSuperPropertySet(SuperPropertySet node) {
 | 
| +    data.markMember(node.interfaceTarget);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitPropertyGet(PropertyGet node) {
 | 
| +    data.markMember(node.interfaceTarget);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitPropertySet(PropertySet node) {
 | 
| +    data.markMember(node.interfaceTarget);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitInterfaceType(InterfaceType node) {
 | 
| +    data.markClass(node.classNode);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitSupertype(Supertype node) {
 | 
| +    data.markClass(node.classNode);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitTypedefReference(Typedef node) {
 | 
| +    return internalError('not implemented');
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// Transformer that trims everything in the excluded libraries that is not
 | 
| +/// marked as preserved by the given [RetainedData]. For every member in these
 | 
| +/// excluded libraries, this transformer also removes function bodies and
 | 
| +/// initializers.
 | 
| +class KernelOutlineShaker extends Transformer {
 | 
| +  final RetainedData data;
 | 
| +  final Filter isIncluded;
 | 
| +
 | 
| +  KernelOutlineShaker(this.data, this.isIncluded);
 | 
| +
 | 
| +  void transform(Program program) {
 | 
| +    var toRemove = new Set<Library>();
 | 
| +    for (var library in program.libraries) {
 | 
| +      if (!isIncluded(library.importUri)) {
 | 
| +        if (!data.isLibraryUsed(library)) {
 | 
| +          toRemove.add(library);
 | 
| +        } else {
 | 
| +          library.isExternal = true;
 | 
| +          library.transformChildren(this);
 | 
| +        }
 | 
| +      }
 | 
| +    }
 | 
| +    program.libraries.removeWhere(toRemove.contains);
 | 
| +  }
 | 
| +
 | 
| +  Class visitClass(Class node) {
 | 
| +    if (!data.isClassUsed(node)) {
 | 
| +      node.canonicalName?.unbind();
 | 
| +      return null; // Remove the class.
 | 
| +    } else {
 | 
| +      node.transformChildren(this);
 | 
| +      return node;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  Member defaultMember(Member node) {
 | 
| +    if (!data.isMemberUsed(node)) {
 | 
| +      node.canonicalName?.unbind();
 | 
| +      return null;
 | 
| +    } else {
 | 
| +      if (node is Procedure) {
 | 
| +        node.function.body = null;
 | 
| +      } else if (node is Field) {
 | 
| +        node.initializer = null;
 | 
| +      } else if (node is Constructor) {
 | 
| +        node.initializers.clear();
 | 
| +        node.function.body = null;
 | 
| +      }
 | 
| +      return node;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  /// Types appear to be encoded directly, so we have no need to preserve
 | 
| +  /// typedefs.
 | 
| +  // TODO(sigmund): revisit if this is not the case, the `inputError` in
 | 
| +  // [RootsMarker] is meant to detect this.
 | 
| +  Typedef visitTypedef(Typedef node) => null;
 | 
| +
 | 
| +  TreeNode defaultTreeNode(TreeNode node) => node;
 | 
| +}
 | 
| +
 | 
| +typedef bool Filter(Uri uri);
 | 
| 
 |