[package:js] Add static interop stub for outlines

Fixes b/235393918

@staticInterop replaces factories with a new named node, a static
method. In order to persist this transformation in modular
compilation, this needs to be done to outlines that can then be
consumed by the source library. In order to allow erasure at the
time of 'performOutlineTransformations', coreTypes is added to that
API.

Change-Id: I90d17fff8bbe143982fcd12cfb06dc3e8d58781a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/247928
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart b/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
index b499ca5..c98694e 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
@@ -28,6 +28,7 @@
   final Class _javaScriptObject;
   final CloneVisitorNotMembers _cloner = CloneVisitorNotMembers();
   late final _TypeSubstitutor _typeSubstitutor;
+  late Library currLibrary;
 
   StaticInteropClassEraser(CoreTypes coreTypes,
       {String libraryForJavaScriptObject = 'dart:_interceptors',
@@ -41,9 +42,10 @@
       '${factoryTarget.name}|staticInteropFactoryStub';
 
   /// Either finds or creates a static method stub to replace factories with a
-  /// body in a static interop class.
+  /// body with in a static interop class.
   ///
-  /// Modifies [factoryTarget]'s enclosing class to include the new method.
+  /// Modifies [factoryTarget]'s enclosing class to include the new method if we
+  /// create one.
   Procedure _findOrCreateFactoryStub(Procedure factoryTarget) {
     assert(factoryTarget.isFactory);
     var factoryClass = factoryTarget.enclosingClass!;
@@ -52,6 +54,12 @@
     var stubs = factoryClass.procedures
         .where((procedure) => procedure.name.text == stubName);
     if (stubs.isEmpty) {
+      // We should only create the stub if we're processing the library in which
+      // the stub should exist. Any static invocation of the factory that
+      // doesn't exist in the same library as the factory should be processed
+      // after the library in which the factory exists. In modular compilation,
+      // the outline of that library should already contain the needed stub.
+      assert(factoryClass.enclosingLibrary == currLibrary);
       // Note that the return type of the cloned function is transformed.
       var functionNode = super
               .visitFunctionNode(_cloner.cloneInContext(factoryTarget.function))
@@ -69,6 +77,12 @@
   }
 
   @override
+  TreeNode visitLibrary(Library node) {
+    currLibrary = node;
+    return super.visitLibrary(node);
+  }
+
+  @override
   TreeNode visitConstructor(Constructor node) {
     if (hasStaticInteropAnnotation(node.enclosingClass)) {
       // Transform children of the constructor node excluding the return type.
@@ -158,9 +172,10 @@
         // case where we visit the factory later. Also note that a cast is not
         // needed since the static method already has its type erased.
         var args = super.visitArguments(node.arguments) as Arguments;
-        return StaticInvocation(_findOrCreateFactoryStub(factoryTarget), args,
-            isConst: node.isConst)
-          ..fileOffset = node.fileOffset;
+        var stub = _findOrCreateFactoryStub(factoryTarget);
+        return StaticInvocation(stub, args, isConst: node.isConst)
+          ..fileOffset = node.fileOffset
+          ..targetReference = stub.reference;
       } else {
         // Add a cast so that the result gets typed as `JavaScriptObject`.
         var newInvocation = super.visitStaticInvocation(node) as Expression;
@@ -182,3 +197,23 @@
     return substitutedType != null ? substitutedType : type;
   }
 }
+
+/// Used to create stubs for factories when computing outlines.
+///
+/// These stubs can then be used in downstream dependencies in modular
+/// compilation.
+class StaticInteropStubCreator extends RecursiveVisitor {
+  final StaticInteropClassEraser _eraser;
+  StaticInteropStubCreator(this._eraser);
+
+  @override
+  void visitLibrary(Library node) {
+    _eraser.currLibrary = node;
+    super.visitLibrary(node);
+  }
+
+  @override
+  void visitProcedure(Procedure node) {
+    _eraser.visitProcedure(node);
+  }
+}
diff --git a/pkg/compiler/lib/src/kernel/dart2js_target.dart b/pkg/compiler/lib/src/kernel/dart2js_target.dart
index 5b51960..df7624c 100644
--- a/pkg/compiler/lib/src/kernel/dart2js_target.dart
+++ b/pkg/compiler/lib/src/kernel/dart2js_target.dart
@@ -138,6 +138,13 @@
   bool get errorOnUnexactWebIntLiterals => true;
 
   @override
+  void performOutlineTransformations(
+      ir.Component component, CoreTypes coreTypes) {
+    component
+        .accept(StaticInteropStubCreator(StaticInteropClassEraser(coreTypes)));
+  }
+
+  @override
   void performModularTransformationsOnLibraries(
       ir.Component component,
       CoreTypes coreTypes,
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
index 12b4408..3be12c7 100644
--- a/pkg/dart2wasm/lib/target.dart
+++ b/pkg/dart2wasm/lib/target.dart
@@ -81,6 +81,21 @@
       ..parent = host;
   }
 
+  StaticInteropClassEraser _staticInteropClassEraser(CoreTypes coreTypes) =>
+      StaticInteropClassEraser(coreTypes,
+          libraryForJavaScriptObject: 'dart:_js_helper',
+          classNameOfJavaScriptObject: 'JSValue');
+
+  void _performJSInteropTransformations(CoreTypes coreTypes,
+      ClassHierarchy hierarchy, List<Library> interopDependentLibraries) {
+    final jsUtilOptimizer = JsUtilWasmOptimizer(coreTypes, hierarchy);
+    final staticInteropClassEraser = _staticInteropClassEraser(coreTypes);
+    for (Library library in interopDependentLibraries) {
+      jsUtilOptimizer.visitLibrary(library);
+      staticInteropClassEraser.visitLibrary(library);
+    }
+  }
+
   @override
   void performPreConstantEvaluationTransformations(
       Component component,
@@ -93,6 +108,12 @@
   }
 
   @override
+  void performOutlineTransformations(Component component, CoreTypes coreTypes) {
+    component
+        .accept(StaticInteropStubCreator(_staticInteropClassEraser(coreTypes)));
+  }
+
+  @override
   void performModularTransformationsOnLibraries(
       Component component,
       CoreTypes coreTypes,
@@ -109,7 +130,7 @@
     if (transitiveImportingJSInterop == null) {
       logger?.call("Skipped JS interop transformations");
     } else {
-      performJSInteropTransformations(
+      _performJSInteropTransformations(
           coreTypes, hierarchy, transitiveImportingJSInterop);
       logger?.call("Transformed JS interop classes");
     }
@@ -242,15 +263,3 @@
   @override
   bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("wasm:");
 }
-
-void performJSInteropTransformations(CoreTypes coreTypes,
-    ClassHierarchy hierarchy, List<Library> interopDependentLibraries) {
-  final jsUtilOptimizer = JsUtilWasmOptimizer(coreTypes, hierarchy);
-  final staticInteropClassEraser = StaticInteropClassEraser(coreTypes,
-      libraryForJavaScriptObject: 'dart:_js_helper',
-      classNameOfJavaScriptObject: 'JSValue');
-  for (Library library in interopDependentLibraries) {
-    jsUtilOptimizer.visitLibrary(library);
-    staticInteropClassEraser.visitLibrary(library);
-  }
-}
diff --git a/pkg/dev_compiler/lib/src/kernel/target.dart b/pkg/dev_compiler/lib/src/kernel/target.dart
index 0a3e3ff..fa648bb 100644
--- a/pkg/dev_compiler/lib/src/kernel/target.dart
+++ b/pkg/dev_compiler/lib/src/kernel/target.dart
@@ -153,6 +153,12 @@
   bool get enableNoSuchMethodForwarders => true;
 
   @override
+  void performOutlineTransformations(Component component, CoreTypes coreTypes) {
+    component
+        .accept(StaticInteropStubCreator(StaticInteropClassEraser(coreTypes)));
+  }
+
+  @override
   void performModularTransformationsOnLibraries(
       Component component,
       CoreTypes coreTypes,
diff --git a/pkg/front_end/lib/src/kernel_generator_impl.dart b/pkg/front_end/lib/src/kernel_generator_impl.dart
index b8eb49e..0a41e53 100644
--- a/pkg/front_end/lib/src/kernel_generator_impl.dart
+++ b/pkg/front_end/lib/src/kernel_generator_impl.dart
@@ -188,7 +188,8 @@
     // summaries without building a full component (at this time, that's
     // the only need we have for these transformations).
     if (!buildComponent) {
-      options.target.performOutlineTransformations(trimmedSummaryComponent);
+      options.target.performOutlineTransformations(
+          trimmedSummaryComponent, kernelTarget.loader.coreTypes);
       options.ticker.logMs("Transformed outline");
     }
     // Don't include source (but do add it above to include importUris).
diff --git a/pkg/frontend_server/lib/compute_kernel.dart b/pkg/frontend_server/lib/compute_kernel.dart
index 918c891..d9334e3 100644
--- a/pkg/frontend_server/lib/compute_kernel.dart
+++ b/pkg/frontend_server/lib/compute_kernel.dart
@@ -396,7 +396,8 @@
         incrementalComponent.problemsAsJson = null;
         incrementalComponent.setMainMethodAndMode(
             null, true, incrementalComponent.mode);
-        target.performOutlineTransformations(incrementalComponent);
+        target.performOutlineTransformations(
+            incrementalComponent, incrementalCompilerResult.coreTypes!);
         makeStable(incrementalComponent);
         return Future.value(fe.serializeComponent(incrementalComponent,
             includeSources: false, includeOffsets: false));
diff --git a/pkg/kernel/lib/target/targets.dart b/pkg/kernel/lib/target/targets.dart
index 9c46dbb..ced147d 100644
--- a/pkg/kernel/lib/target/targets.dart
+++ b/pkg/kernel/lib/target/targets.dart
@@ -318,7 +318,8 @@
   /// transformation is not applied when compiling full kernel programs to
   /// prevent affecting the internal invariants of the compiler and accidentally
   /// slowing down compilation.
-  void performOutlineTransformations(Component component) {}
+  void performOutlineTransformations(
+      Component component, CoreTypes coreTypes) {}
 
   /// Perform target-specific transformations on the given libraries that must
   /// run before constant evaluation.
@@ -1010,8 +1011,8 @@
   }
 
   @override
-  void performOutlineTransformations(Component component) {
-    _target.performOutlineTransformations(component);
+  void performOutlineTransformations(Component component, CoreTypes coreTypes) {
+    _target.performOutlineTransformations(component, coreTypes);
   }
 
   @override
@@ -1076,8 +1077,8 @@
   bool get excludeNonSources;
 
   @override
-  void performOutlineTransformations(Component component) {
-    super.performOutlineTransformations(component);
+  void performOutlineTransformations(Component component, CoreTypes coreTypes) {
+    super.performOutlineTransformations(component, coreTypes);
     if (!excludeNonSources) return;
 
     List<Library> libraries = new List.of(component.libraries);
diff --git a/tests/modular/static_interop_erasure/main.dart b/tests/modular/static_interop_erasure/main.dart
index 80ccc3b..da54aa9 100644
--- a/tests/modular/static_interop_erasure/main.dart
+++ b/tests/modular/static_interop_erasure/main.dart
@@ -6,5 +6,5 @@
 
 void main() {
   setUp();
-  var staticJs = StaticJSClass.factory();
+  var staticJs = StaticJSClass.factory(StaticJSClass());
 }
diff --git a/tests/modular/static_interop_erasure/static_interop.dart b/tests/modular/static_interop_erasure/static_interop.dart
index a5c93c4..7f25a79 100644
--- a/tests/modular/static_interop_erasure/static_interop.dart
+++ b/tests/modular/static_interop_erasure/static_interop.dart
@@ -14,7 +14,7 @@
 @staticInterop
 class StaticJSClass {
   external StaticJSClass();
-  factory StaticJSClass.factory() {
+  factory StaticJSClass.factory(StaticJSClass _) {
     return StaticJSClass();
   }
 }