[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;
+}