fix #34273, dartdevk should use the default bound for generic functions

When dynamically calling a generic function or method without passing
any explicit type arguments, the default bounds are used. The default
should be `dynamic` unless an explicit bound was provided for the type
parameter.

Change-Id: I82f7166303f936adbecedd0c633b518f2700def4
Reviewed-on: https://dart-review.googlesource.com/72081
Commit-Queue: Jenny Messerly <jmesserly@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index a5f6cec..f9ce5c8 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -2498,9 +2498,37 @@
       typeParts = [addTypeFormalsAsParameters(typeParts)];
 
       helperCall = 'gFnType(#)';
+
+      /// Whether the type parameter [t] has an explicit bound, like
+      /// `<T extends C>`, `<T extends Object>` or `<T extends dynamic>`.
+      ///
+      /// In contrast, a type parameter like `<T>` has an implicit bound.
+      /// Implicit bounds are a bit unusual, in that `Object` is used as the
+      /// bound for checking, but `dynamic` is filled in as the default value.
+      ///
+      /// Kernel represents `<T>` as `<T extends Object = dynamic>`. We can find
+      /// explicit bounds by looking for anything *except* that.
+      typeParameterHasExplicitBound(TypeParameter t) =>
+          t.bound != types.objectType || t.defaultType != const DynamicType();
+
       // If any explicit bounds were passed, emit them.
-      if (typeFormals.any((t) => t.bound != null)) {
-        var bounds = typeFormals.map((t) => _emitType(t.bound)).toList();
+      if (typeFormals.any(typeParameterHasExplicitBound)) {
+        /// Emits the bound of the type parameter [t] for use in runtime
+        /// checking and the default value (e.g. for dynamic class).
+        ///
+        /// For most type parameters we can use [TypeParameter.bound]. However,
+        /// for *implicit* bounds such as `<T>` (represented in Kernel as
+        /// `<T extends Object = dynamic>`) we need to emit `dynamic` so we use
+        /// the correct default value at runtime.
+        ///
+        /// Because `dynamic` and `Object` are both top types, they'll behave
+        /// identically for the purposes of type checks.
+        emitTypeParameterBound(TypeParameter t) =>
+            typeParameterHasExplicitBound(t)
+                ? _emitType(t.bound)
+                : visitDynamicType(const DynamicType());
+
+        var bounds = typeFormals.map(emitTypeParameterBound).toList();
         typeParts.add(addTypeFormalsAsParameters(bounds));
       }
     } else {
diff --git a/tests/language_2/language_2_dartdevc.status b/tests/language_2/language_2_dartdevc.status
index 0e8f40f..cb05f47 100644
--- a/tests/language_2/language_2_dartdevc.status
+++ b/tests/language_2/language_2_dartdevc.status
@@ -238,7 +238,6 @@
 call_method_implicit_tear_off_implements_function_test/06: RuntimeError # Kernel is missing the implicit `call` tearoff for assignment `Function`
 call_method_must_not_be_field_test/06: RuntimeError # Kernel does not distinguish `d()` from `d.call()`
 call_method_must_not_be_getter_test/06: RuntimeError # Kernel does not distinguish `d()` from `d.call()`
-closure_type_arguments_test: RuntimeError # Issue 34273
 compile_time_constant_c_test/02: MissingCompileTimeError
 compile_time_constant_k_test/01: MissingCompileTimeError
 compile_time_constant_k_test/02: MissingCompileTimeError