[dart2js] Convert first argument to `main`

- When `main` has arguments, use a callMain shim to convert first
  argument passed to `main` to a `List<String>`.

- Make js_runtime/preambles/{d8,jsshell}.js pass command line arguments to
  Dart `main`.

Change-Id: I8c04bbe49366e756cfe84d1c705767e0366ee74c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/158373
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index 8d84fe7..0cb4190 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -449,6 +449,8 @@
 
   FunctionEntity getInstantiateFunction(int typeArgumentCount);
 
+  FunctionEntity get convertMainArgumentList;
+
   // From dart:_rti
 
   FunctionEntity get setRuntimeTypeInfo;
@@ -1834,6 +1836,10 @@
         cls.name.startsWith('Instantiation');
   }
 
+  @override
+  FunctionEntity get convertMainArgumentList =>
+      _findHelperFunction('convertMainArgumentList');
+
   // From dart:_rti
 
   ClassEntity _findRtiClass(String name) => _findClass(rtiLibrary, name);
diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart
index 5f63cfb..39c5e4f 100644
--- a/pkg/compiler/lib/src/js_backend/backend_impact.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart
@@ -117,10 +117,13 @@
   BackendImpact _mainWithArguments;
 
   BackendImpact get mainWithArguments {
-    return _mainWithArguments ??= new BackendImpact(instantiatedClasses: [
-      _commonElements.jsArrayClass,
-      _commonElements.jsStringClass
-    ]);
+    return _mainWithArguments ??= new BackendImpact(
+      globalUses: [_commonElements.convertMainArgumentList],
+      instantiatedClasses: [
+        _commonElements.jsArrayClass,
+        _commonElements.jsStringClass
+      ],
+    );
   }
 
   BackendImpact _asyncBody;
diff --git a/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart
index e20f889..d26bb2e 100644
--- a/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart
+++ b/pkg/compiler/lib/src/js_emitter/main_call_stub_generator.dart
@@ -9,16 +9,58 @@
 import '../elements/entities.dart';
 import '../js/js.dart' as jsAst;
 import '../js/js.dart' show js;
+import '../common_elements.dart';
 
 import 'code_emitter_task.dart' show Emitter;
 
 class MainCallStubGenerator {
   static jsAst.Statement generateInvokeMain(
-      Emitter emitter, FunctionEntity main) {
-    jsAst.Expression mainCallClosure = emitter.staticFunctionAccess(main);
+      CommonElements commonElements, Emitter emitter, FunctionEntity main) {
+    jsAst.Expression mainAccess = emitter.staticFunctionAccess(main);
     jsAst.Expression currentScriptAccess =
         emitter.generateEmbeddedGlobalAccess(embeddedNames.CURRENT_SCRIPT);
 
+    // TODO(https://github.com/dart-lang/language/issues/1120#issuecomment-670802088):
+    // Validate constraints on `main()` in resolution for dart2js, and in DDC.
+
+    final parameterStructure = main.parameterStructure;
+
+    // The forwarding stub passes all arguments, i.e. both required and optional
+    // positional arguments. We ignore named arguments, assuming the `main()`
+    // has been validated earlier.
+    int positionalParameters = parameterStructure.positionalParameters;
+
+    jsAst.Expression mainCallClosure;
+    if (positionalParameters == 0) {
+      if (parameterStructure.namedParameters.isEmpty) {
+        // e.g. `void main()`.
+        // No parameters. The compiled Dart `main` has no parameters and will
+        // ignore any extra parameters passed in, so it can be used directly.
+        mainCallClosure = mainAccess;
+      } else {
+        // e.g. `void main({arg})`.  We should not get here. Drop the named
+        // arguments as we don't know how to convert them.
+        mainCallClosure = js(r'''function() { return #(); }''', mainAccess);
+      }
+    } else {
+      jsAst.Expression convertArgumentList =
+          emitter.staticFunctionAccess(commonElements.convertMainArgumentList);
+      if (positionalParameters == 1) {
+        // e.g. `void main(List<String> args)`,  `main([args])`.
+        mainCallClosure = js(
+          r'''function(args) { return #(#(args)); }''',
+          [mainAccess, convertArgumentList],
+        );
+      } else {
+        // positionalParameters == 2.
+        // e.g. `void main(List<String> args, Object? extra)`
+        mainCallClosure = js(
+          r'''function(args, extra) { return #(#(args), extra); }''',
+          [mainAccess, convertArgumentList],
+        );
+      }
+    }
+
     // This code finds the currently executing script by listening to the
     // onload event of all script tags and getting the first script which
     // finishes. Since onload is called immediately after execution this should
@@ -48,11 +90,11 @@
         }
       })(function(currentScript) {
         #currentScript = currentScript;
-
+        var callMain = #mainCallClosure;
         if (typeof dartMainRunner === "function") {
-          dartMainRunner(#mainCallClosure, []);
+          dartMainRunner(callMain, []);
         } else {
-          #mainCallClosure([]);
+          callMain([]);
         }
       })''', {
       'currentScript': currentScriptAccess,
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index 351d6bc..99e7544 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -388,7 +388,7 @@
 
   js.Statement _buildInvokeMain() {
     return MainCallStubGenerator.generateInvokeMain(
-        _task.emitter, _mainFunction);
+        _commonElements, _task.emitter, _mainFunction);
   }
 
   DeferredFragment _buildDeferredFragment(LibrariesMap librariesMap) {
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index 3b1cca4..570aa15 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -2960,24 +2960,20 @@
   return completer.future;
 }
 
-class MainError extends Error implements NoSuchMethodError {
-  final String _message;
-
-  MainError(this._message);
-
-  String toString() => 'NoSuchMethodError: $_message';
-}
-
-void missingMain() {
-  throw new MainError("No top-level function named 'main'.");
-}
-
-void badMain() {
-  throw new MainError("'main' is not a function.");
-}
-
-void mainHasTooManyParameters() {
-  throw new MainError("'main' expects too many parameters.");
+/// Converts a raw JavaScript array into a `List<String>`.
+/// Called from generated code.
+List<String> convertMainArgumentList(Object? args) {
+  List<String> result = [];
+  if (args == null) return result;
+  if (args is JSArray) {
+    for (int i = 0; i < args.length; i++) {
+      JS('', '#.push(String(#[#]))', result, args, i);
+    }
+    return result;
+  }
+  // Single non-Array element. Convert to a String.
+  JS('', '#.push(String(#))', result, args);
+  return result;
 }
 
 class _AssertionError extends AssertionError {
diff --git a/sdk/lib/_internal/js_runtime/lib/preambles/d8.js b/sdk/lib/_internal/js_runtime/lib/preambles/d8.js
index 0174d95..6604c8a 100644
--- a/sdk/lib/_internal/js_runtime/lib/preambles/d8.js
+++ b/sdk/lib/_internal/js_runtime/lib/preambles/d8.js
@@ -10,7 +10,7 @@
 var self = this;
 if (typeof global != "undefined") self = global;  // Node.js.
 
-(function(self) {
+(function(self, scriptArguments) {
   // Using strict mode to avoid accidentally defining global variables.
   "use strict"; // Should be first statement of this function.
 
@@ -270,9 +270,9 @@
   // Global properties. "self" refers to the global object, so adding a
   // property to "self" defines a global variable.
   self.self = self;
-  self.dartMainRunner = function(main, args) {
+  self.dartMainRunner = function(main, ignored_args) {
     // Initialize.
-    var action = function() { main(args); }
+    var action = function() { main(scriptArguments, null); }
     eventLoop(action);
   };
   self.setTimeout = addTimer;
@@ -345,4 +345,4 @@
   // so pretend they don't exist.
   // TODO(30217): Try to use D8's worker.
   delete self.Worker;
-})(self);
+})(self, arguments);
diff --git a/sdk/lib/_internal/js_runtime/lib/preambles/jsshell.js b/sdk/lib/_internal/js_runtime/lib/preambles/jsshell.js
index a6b41c4..48f60cd 100644
--- a/sdk/lib/_internal/js_runtime/lib/preambles/jsshell.js
+++ b/sdk/lib/_internal/js_runtime/lib/preambles/jsshell.js
@@ -4,7 +4,7 @@
 
 // Javascript preamble, that lets the output of dart2js run on JSShell.
 
-(function(self) {
+(function(self, scriptArguments) {
   // Using strict mode to avoid accidentally defining global variables.
   "use strict"; // Should be first statement of this function.
 
@@ -262,9 +262,9 @@
     }
   }
 
-  self.dartMainRunner = function(main, args) {
+  self.dartMainRunner = function(main, ignored_args) {
     // Initialize.
-    var action = function() { main(args); }
+    var action = function() { main(scriptArguments, null); }
     eventLoop(action);
   };
   self.setTimeout = addTimer;
@@ -329,7 +329,7 @@
       array[i] = Math.random() * 256;
     }
   }};
-})(this)
+})(this, typeof scriptArgs == "undefined" ? [] : scriptArgs)
 
 var getKeys = function(obj){
    var keys = [];