[dart2js] Detect non-trivial substitution between type and this-type in optimization

Closes #37267

Change-Id: I317c2b8e5fb3f1df10691bdaf9c10f20b8042b8c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106347
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index ac8f779..be6a444 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -2903,6 +2903,7 @@
 
   @override
   ThisLocal get sourceElement => super.sourceElement;
+
   @override
   void set sourceElement(covariant ThisLocal local) {
     super.sourceElement = local;
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 1a27e5b..e90128d 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -1639,20 +1639,50 @@
 
       for (HInstruction argument in node.inputs) {
         if (argument is HTypeInfoReadVariable) {
+          ClassEntity context = DartTypes.getClassContext(argument.variable);
           HInstruction nextSource = argument.object;
+          if (nextSource is HRef) {
+            HRef ref = nextSource;
+            nextSource = ref.value;
+          }
           if (nextSource is HThis) {
             if (source == null) {
               source = nextSource;
-              ClassEntity contextClass =
-                  nextSource.sourceElement.enclosingClass;
-              if (node.inputs.length !=
-                  _closedWorld.elementEnvironment
-                      .getThisType(contextClass)
-                      .typeArguments
-                      .length) {
+              ClassEntity thisClass = nextSource.sourceElement.enclosingClass;
+              InterfaceType thisType =
+                  _closedWorld.elementEnvironment.getThisType(thisClass);
+              if (node.inputs.length != thisType.typeArguments.length) {
                 return null;
               }
-              if (needsSubstitutionForTypeVariableAccess(contextClass)) {
+              if (needsSubstitutionForTypeVariableAccess(thisClass)) {
+                return null;
+              }
+              if (context != null &&
+                  !_rtiSubstitutions.isTrivialSubstitution(
+                      thisClass, context)) {
+                // If inlining, the [context] is not the same as [thisClass].
+                // If this is the case, then the substitution must be trivial.
+                // Consider this:
+                //
+                //    class A {
+                //      final Object value;
+                //      A(this.value);
+                //    }
+                //    class B<T> extends A {
+                //      B(T value) : super(value);
+                //      T get value => super.value as T;
+                //    }
+                //    class C<S> extends B<List<S>> {
+                //      C(List<S> value) : super(value);
+                //      S get first => value.first;
+                //    }
+                //
+                // If `B.value` is inlined into `C.first` the type info
+                // expression is the list `[B.T]` on a `this` of type `C<S>`.
+                // Since the substitution from C to B is not trivial
+                // (S -> List<S>) this type info expression cannot be replaced
+                // with the type arguments `C<S>` (it would yield [S] instead of
+                // [List<S>]).
                 return null;
               }
             }
diff --git a/tests/compiler/dart2js_extra/type_argument_optimization_test.dart b/tests/compiler/dart2js_extra/type_argument_optimization_test.dart
new file mode 100644
index 0000000..21b41c5
--- /dev/null
+++ b/tests/compiler/dart2js_extra/type_argument_optimization_test.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2019, 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.
+
+// Regression test for issue 37267.
+
+typedef UpdateShouldNotify<T> = bool Function(T previous, T current);
+
+typedef ValueWidgetBuilder<T> = Widget Function(
+    BuildContext context, T value, Widget child);
+
+class BuildContext {}
+
+class Widget {}
+
+abstract class ProxyWidget extends Widget {
+  final Widget child;
+
+  ProxyWidget({this.child});
+}
+
+abstract class InheritedWidget extends ProxyWidget {
+  InheritedWidget({Widget child}) : super(child: child);
+}
+
+class InheritedProvider<T> extends InheritedWidget {
+  final T _value;
+  final UpdateShouldNotify<T> _updateShouldNotify;
+
+  InheritedProvider(
+      {T value, UpdateShouldNotify<T> updateShouldNotify, Widget child})
+      : _value = value,
+        _updateShouldNotify = updateShouldNotify,
+        super(child: child);
+}
+
+class StateDelegate {}
+
+abstract class ValueStateDelegate<T> extends StateDelegate {
+  T get value;
+}
+
+class ValueStateDelegateImpl<T> implements ValueStateDelegate<T> {
+  final T value;
+
+  ValueStateDelegateImpl(this.value);
+}
+
+class DelegateWidget {
+  final StateDelegate delegate;
+
+  DelegateWidget(this.delegate);
+}
+
+abstract class Listenable {}
+
+abstract class ValueListenable<T> extends Listenable {
+  T get value;
+}
+
+class ValueListenableImpl<T> implements ValueListenable<T> {
+  final T value;
+
+  ValueListenableImpl(this.value);
+}
+
+class ValueDelegateWidget<T> extends DelegateWidget {
+  ValueDelegateWidget(ValueStateDelegate<T> delegate) : super(delegate);
+
+  @pragma('dart2js:tryInline')
+  ValueStateDelegate<T> get delegate => super.delegate as ValueStateDelegate<T>;
+}
+
+class ValueListenableProvider<T>
+    extends ValueDelegateWidget<ValueListenable<T>> {
+  final Widget child;
+
+  final UpdateShouldNotify<T> updateShouldNotify;
+
+  ValueListenableProvider(ValueStateDelegate<ValueListenable<T>> delegate,
+      this.updateShouldNotify, this.child)
+      : super(delegate);
+
+  Widget build() {
+    return ValueListenableBuilder<T>(
+      valueListenable: delegate.value,
+      builder: (_, value, child) {
+        return InheritedProvider<T>(
+          value: value,
+          updateShouldNotify: updateShouldNotify,
+          child: child,
+        );
+      },
+      child: child,
+    );
+  }
+}
+
+class ValueListenableBuilder<T> extends Widget {
+  final ValueListenable<T> valueListenable;
+  final ValueWidgetBuilder<T> builder;
+  final Widget child;
+
+  ValueListenableBuilder({this.valueListenable, this.builder, this.child});
+}
+
+void main() {
+  print(create(42).valueListenable.value);
+  print(create('foo').valueListenable.value);
+}
+
+ValueListenableBuilder<T> create<T>(T value) {
+  ValueListenableImpl<T> valueListenable = new ValueListenableImpl<T>(value);
+  ValueStateDelegateImpl<ValueListenable<T>> valueStateDelegate =
+      new ValueStateDelegateImpl<ValueListenable<T>>(valueListenable);
+  ValueListenableProvider<T> valueListenableProvider =
+      new ValueListenableProvider<T>(valueStateDelegate, null, null);
+  Widget widget = valueListenableProvider.build();
+  print(value);
+  return widget;
+}