diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart index 26c0537f399..8109b5f1dda 100644 --- a/pkg/compiler/lib/src/ssa/builder_kernel.dart +++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart @@ -3697,10 +3697,18 @@ class KernelSsaGraphBuilder extends ir.Visitor with ir.VisitorVoidMixin { /// Fills [typeArguments] with the type arguments needed for [selector] and /// returns the selector corresponding to the passed type arguments. + /// + /// If [isImplicitCall] is `true`, the target of the invocation can be either + /// the target of the [selector] or of the corresponding `.call` selector. In + /// this case we need to check both selectors to see if we need to pass type + /// arguments. This occurs for field/getter invocations. Selector _fillDynamicTypeArguments( - Selector selector, ir.Arguments arguments, List typeArguments) { + Selector selector, ir.Arguments arguments, List typeArguments, + {bool isImplicitCall = false}) { if (selector.typeArgumentCount > 0) { - if (_rtiNeed.selectorNeedsTypeArguments(selector)) { + if (_rtiNeed.selectorNeedsTypeArguments(selector) || + (isImplicitCall && + _rtiNeed.selectorNeedsTypeArguments(selector.toCallSelector()))) { typeArguments.addAll(arguments.types.map(_elementMap.getDartType)); } else { return selector.toNonGeneric(); @@ -5216,12 +5224,14 @@ class KernelSsaGraphBuilder extends ir.Visitor with ir.VisitorVoidMixin { } void _handleMethodInvocation( - ir.Expression node, ir.Expression receiver, ir.Arguments arguments) { + ir.Expression node, ir.Expression receiver, ir.Arguments arguments, + {bool isImplicitCall = false}) { receiver.accept(this); HInstruction receiverInstruction = pop(); Selector selector = _elementMap.getSelector(node); List typeArguments = []; - selector = _fillDynamicTypeArguments(selector, arguments, typeArguments); + selector = _fillDynamicTypeArguments(selector, arguments, typeArguments, + isImplicitCall: isImplicitCall); _pushDynamicInvocation( node, _getStaticType(receiver), @@ -5242,7 +5252,8 @@ class KernelSsaGraphBuilder extends ir.Visitor with ir.VisitorVoidMixin { @override void visitInstanceGetterInvocation(ir.InstanceGetterInvocation node) { - _handleMethodInvocation(node, node.receiver, node.arguments); + _handleMethodInvocation(node, node.receiver, node.arguments, + isImplicitCall: true); } @override diff --git a/pkg/compiler/test/impact/data/issue48304.dart b/pkg/compiler/test/impact/data/issue48304.dart new file mode 100644 index 00000000000..fe7da9a2e26 --- /dev/null +++ b/pkg/compiler/test/impact/data/issue48304.dart @@ -0,0 +1,232 @@ +// Copyright (c) 2022, 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. + +abstract class B { + call(); +} + +/*member: C.:static=[Object.(0)]*/ +class C implements B { + /*member: C.call: + static=[ + Rti._bind(1), + Rti._eval(1), + _arrayInstanceType(1), + _asBool(1), + _asBoolQ(1), + _asBoolS(1), + _asDouble(1), + _asDoubleQ(1), + _asDoubleS(1), + _asInt(1), + _asIntQ(1), + _asIntS(1), + _asNum(1), + _asNumQ(1), + _asNumS(1), + _asObject(1), + _asString(1), + _asStringQ(1), + _asStringS(1), + _asTop(1), + _generalAsCheckImplementation(1), + _generalIsTestImplementation(1), + _generalNullableAsCheckImplementation(1), + _generalNullableIsTestImplementation(1), + _installSpecializedAsCheck(1), + _installSpecializedIsTest(1), + _instanceType(1), + _isBool(1), + _isInt(1), + _isNum(1), + _isObject(1), + _isString(1), + _isTop(1), + _setArrayType(2), + createRuntimeType(1), + findType(1), + instanceType(1), + print(1), + typeLiteral(1)], + type=[ + inst:Closure, + inst:JSArray, + inst:JSBool, + inst:JSExtendableArray, + inst:JSFixedArray, + inst:JSInt, + inst:JSMutableArray, + inst:JSNumNotInt, + inst:JSNumber, + inst:JSPositiveInt, + inst:JSUInt31, + inst:JSUInt32, + inst:JSUnmodifiableArray, + inst:Type, + inst:_Type, + lit:call.T, + param:Object?] + */ + call() => print(T); +} + +abstract class A {} + +class Wrapper { + /*member: Wrapper.: + static=[ + Object.(0), + Rti._bind(1), + Rti._eval(1), + _arrayInstanceType(1), + _asBool(1), + _asBoolQ(1), + _asBoolS(1), + _asDouble(1), + _asDoubleQ(1), + _asDoubleS(1), + _asInt(1), + _asIntQ(1), + _asIntS(1), + _asNum(1), + _asNumQ(1), + _asNumS(1), + _asObject(1), + _asString(1), + _asStringQ(1), + _asStringS(1), + _asTop(1), + _generalAsCheckImplementation(1), + _generalIsTestImplementation(1), + _generalNullableAsCheckImplementation(1), + _generalNullableIsTestImplementation(1), + _installSpecializedAsCheck(1), + _installSpecializedIsTest(1), + _instanceType(1), + _isBool(1), + _isInt(1), + _isNum(1), + _isObject(1), + _isString(1), + _isTop(1), + findType(1), + init:Wrapper.b, + init:Wrapper.call, + instanceType(1)], + type=[ + inst:Closure, + inst:JSBool, + param:B] + */ + Wrapper(this.b, this.call); + /*member: Wrapper.b: + static=[ + Rti._bind(1), + Rti._eval(1), + _arrayInstanceType(1), + _asBool(1), + _asBoolQ(1), + _asBoolS(1), + _asDouble(1), + _asDoubleQ(1), + _asDoubleS(1), + _asInt(1), + _asIntQ(1), + _asIntS(1), + _asNum(1), + _asNumQ(1), + _asNumS(1), + _asObject(1), + _asString(1), + _asStringQ(1), + _asStringS(1), + _asTop(1), + _generalAsCheckImplementation(1), + _generalIsTestImplementation(1), + _generalNullableAsCheckImplementation(1), + _generalNullableIsTestImplementation(1), + _installSpecializedAsCheck(1), + _installSpecializedIsTest(1), + _instanceType(1), + _isBool(1), + _isInt(1), + _isNum(1), + _isObject(1), + _isString(1), + _isTop(1), + findType(1), + instanceType(1)], + type=[ + inst:Closure, + inst:JSBool, + inst:JSNull, + param:B] + */ + final B b; + /*member: Wrapper.call: + static=[ + Rti._bind(1), + Rti._eval(1), + _arrayInstanceType(1), + _asBool(1), + _asBoolQ(1), + _asBoolS(1), + _asDouble(1), + _asDoubleQ(1), + _asDoubleS(1), + _asInt(1), + _asIntQ(1), + _asIntS(1), + _asNum(1), + _asNumQ(1), + _asNumS(1), + _asObject(1), + _asString(1), + _asStringQ(1), + _asStringS(1), + _asTop(1), + _generalAsCheckImplementation(1), + _generalIsTestImplementation(1), + _generalNullableAsCheckImplementation(1), + _generalNullableIsTestImplementation(1), + _installSpecializedAsCheck(1), + _installSpecializedIsTest(1), + _instanceType(1), + _isBool(1), + _isInt(1), + _isNum(1), + _isObject(1), + _isString(1), + _isTop(1), + findType(1), + instanceType(1)], + type=[ + inst:Closure, + inst:JSBool, + inst:JSNull, + param:B] + */ + final B call; +} + +/*member: main: + dynamic=[ + B.call(0), + exact:C.call(0), + exact:Wrapper.b, + exact:Wrapper.b(0), + exact:Wrapper.call, + exact:Wrapper.call(0)], + static=[ + C.(0), + Wrapper.(2)] +*/ +void main() { + B b = C(); + b(); + Wrapper(b, b).b(); + (Wrapper(b, b).b)(); + Wrapper(b, b).call(); + (Wrapper(b, b).call)(); +} diff --git a/pkg/compiler/test/inference/data/issue48304.dart b/pkg/compiler/test/inference/data/issue48304.dart new file mode 100644 index 00000000000..dc77fc37dcc --- /dev/null +++ b/pkg/compiler/test/inference/data/issue48304.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2022, 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. + +abstract class B { + call(); +} + +/*member: C.:[exact=C]*/ +class C implements B { + /*member: C.call:[null]*/ + call() => print(T); +} + +abstract class A {} + +class Wrapper { + /*member: Wrapper.:[exact=Wrapper]*/ + Wrapper(this. /*[exact=C]*/ b, this. /*[exact=C]*/ call); + /*member: Wrapper.b:[exact=C]*/ + final B b; + /*member: Wrapper.call:[exact=C]*/ + final B call; +} + +/*member: main:[null]*/ +void main() { + B b = C(); + b/*invoke: [exact=C]*/ (); + Wrapper(b, b).b /*invoke: [exact=Wrapper]*/ (); + (Wrapper(b, b). /*[exact=Wrapper]*/ b) /*invoke: [exact=C]*/ (); + Wrapper(b, b).call /*invoke: [exact=Wrapper]*/ (); + (Wrapper(b, b). /*[exact=Wrapper]*/ call) /*invoke: [exact=C]*/ (); +} diff --git a/pkg/compiler/test/rti/data/issue48304.dart b/pkg/compiler/test/rti/data/issue48304.dart new file mode 100644 index 00000000000..dfb51bee565 --- /dev/null +++ b/pkg/compiler/test/rti/data/issue48304.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2022, 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. + +/*spec.class: B:explicit=[B]*/ +abstract class B { + call(); +} + +class C implements B { + /*member: C.call:exp,needsArgs,selectors=[Selector(call, call, arity=0, types=1)]*/ + call() => print(T); +} + +abstract class A {} + +class Wrapper { + Wrapper(this.b, this.call); + final B b; + final B call; +} + +void main() { + B b = C(); + b(); + Wrapper(b, b).b(); + (Wrapper(b, b).b)(); + Wrapper(b, b).call(); + (Wrapper(b, b).call)(); +} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart b/pkg/front_end/testcases/dart2js/issue48304.dart new file mode 100644 index 00000000000..89351f8ac5b --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart @@ -0,0 +1,28 @@ +// Copyright (c) 2022, 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. + +abstract class B { + call(); +} + +class C implements B { + call() => print(T); +} + +abstract class A {} + +class Wrapper { + Wrapper(this.b, this.call); + final B b; + final B call; +} + +void main() { + B b = C(); + b(); + Wrapper(b, b).b(); + (Wrapper(b, b).b)(); + Wrapper(b, b).call(); + (Wrapper(b, b).call)(); +} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.strong.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.strong.expect new file mode 100644 index 00000000000..9f1d34d6e2b --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.strong.expect @@ -0,0 +1,41 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + abstract method call() → dynamic; +} +class C extends core::Object implements self::B { + synthetic constructor •() → self::C + : super core::Object::•() + ; + method call() → dynamic + return core::print(self::C::call::T%); + static method _#new#tearOff() → self::C + return new self::C::•(); +} +abstract class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +} +class Wrapper extends core::Object { + final field self::B b; + final field self::B call; + constructor •(self::B b, self::B call) → self::Wrapper + : self::Wrapper::b = b, self::Wrapper::call = call, super core::Object::•() + ; + static method _#new#tearOff(self::B b, self::B call) → self::Wrapper + return new self::Wrapper::•(b, call); +} +static method main() → void { + self::B b = new self::C::•(); + b.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}{self::B}.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}{self::B}.{self::B::call}(){() → dynamic}; +} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.strong.transformed.expect new file mode 100644 index 00000000000..9f1d34d6e2b --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.strong.transformed.expect @@ -0,0 +1,41 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + abstract method call() → dynamic; +} +class C extends core::Object implements self::B { + synthetic constructor •() → self::C + : super core::Object::•() + ; + method call() → dynamic + return core::print(self::C::call::T%); + static method _#new#tearOff() → self::C + return new self::C::•(); +} +abstract class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +} +class Wrapper extends core::Object { + final field self::B b; + final field self::B call; + constructor •(self::B b, self::B call) → self::Wrapper + : self::Wrapper::b = b, self::Wrapper::call = call, super core::Object::•() + ; + static method _#new#tearOff(self::B b, self::B call) → self::Wrapper + return new self::Wrapper::•(b, call); +} +static method main() → void { + self::B b = new self::C::•(); + b.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}{self::B}.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}{self::B}.{self::B::call}(){() → dynamic}; +} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.textual_outline.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.textual_outline.expect new file mode 100644 index 00000000000..05d076b5dad --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.textual_outline.expect @@ -0,0 +1,17 @@ +abstract class B { + call(); +} + +class C implements B { + call() => print(T); +} + +abstract class A {} + +class Wrapper { + Wrapper(this.b, this.call); + final B b; + final B call; +} + +void main() {} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.textual_outline_modelled.expect new file mode 100644 index 00000000000..6997d4b1e67 --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.textual_outline_modelled.expect @@ -0,0 +1,17 @@ +abstract class A {} + +abstract class B { + call(); +} + +class C implements B { + call() => print(T); +} + +class Wrapper { + Wrapper(this.b, this.call); + final B b; + final B call; +} + +void main() {} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.weak.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.expect new file mode 100644 index 00000000000..9f1d34d6e2b --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.expect @@ -0,0 +1,41 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + abstract method call() → dynamic; +} +class C extends core::Object implements self::B { + synthetic constructor •() → self::C + : super core::Object::•() + ; + method call() → dynamic + return core::print(self::C::call::T%); + static method _#new#tearOff() → self::C + return new self::C::•(); +} +abstract class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +} +class Wrapper extends core::Object { + final field self::B b; + final field self::B call; + constructor •(self::B b, self::B call) → self::Wrapper + : self::Wrapper::b = b, self::Wrapper::call = call, super core::Object::•() + ; + static method _#new#tearOff(self::B b, self::B call) → self::Wrapper + return new self::Wrapper::•(b, call); +} +static method main() → void { + self::B b = new self::C::•(); + b.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}{self::B}.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}{self::B}.{self::B::call}(){() → dynamic}; +} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.weak.modular.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.modular.expect new file mode 100644 index 00000000000..9f1d34d6e2b --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.modular.expect @@ -0,0 +1,41 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + abstract method call() → dynamic; +} +class C extends core::Object implements self::B { + synthetic constructor •() → self::C + : super core::Object::•() + ; + method call() → dynamic + return core::print(self::C::call::T%); + static method _#new#tearOff() → self::C + return new self::C::•(); +} +abstract class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +} +class Wrapper extends core::Object { + final field self::B b; + final field self::B call; + constructor •(self::B b, self::B call) → self::Wrapper + : self::Wrapper::b = b, self::Wrapper::call = call, super core::Object::•() + ; + static method _#new#tearOff(self::B b, self::B call) → self::Wrapper + return new self::Wrapper::•(b, call); +} +static method main() → void { + self::B b = new self::C::•(); + b.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}{self::B}.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}{self::B}.{self::B::call}(){() → dynamic}; +} diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.outline.expect new file mode 100644 index 00000000000..f6c8da7eaa3 --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.outline.expect @@ -0,0 +1,31 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class B extends core::Object { + synthetic constructor •() → self::B + ; + abstract method call() → dynamic; +} +class C extends core::Object implements self::B { + synthetic constructor •() → self::C + ; + method call() → dynamic + ; + static method _#new#tearOff() → self::C + return new self::C::•(); +} +abstract class A extends core::Object { + synthetic constructor •() → self::A + ; +} +class Wrapper extends core::Object { + final field self::B b; + final field self::B call; + constructor •(self::B b, self::B call) → self::Wrapper + ; + static method _#new#tearOff(self::B b, self::B call) → self::Wrapper + return new self::Wrapper::•(b, call); +} +static method main() → void + ; diff --git a/pkg/front_end/testcases/dart2js/issue48304.dart.weak.transformed.expect b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.transformed.expect new file mode 100644 index 00000000000..9f1d34d6e2b --- /dev/null +++ b/pkg/front_end/testcases/dart2js/issue48304.dart.weak.transformed.expect @@ -0,0 +1,41 @@ +library /*isNonNullableByDefault*/; +import self as self; +import "dart:core" as core; + +abstract class B extends core::Object { + synthetic constructor •() → self::B + : super core::Object::•() + ; + abstract method call() → dynamic; +} +class C extends core::Object implements self::B { + synthetic constructor •() → self::C + : super core::Object::•() + ; + method call() → dynamic + return core::print(self::C::call::T%); + static method _#new#tearOff() → self::C + return new self::C::•(); +} +abstract class A extends core::Object { + synthetic constructor •() → self::A + : super core::Object::•() + ; +} +class Wrapper extends core::Object { + final field self::B b; + final field self::B call; + constructor •(self::B b, self::B call) → self::Wrapper + : self::Wrapper::b = b, self::Wrapper::call = call, super core::Object::•() + ; + static method _#new#tearOff(self::B b, self::B call) → self::Wrapper + return new self::Wrapper::•(b, call); +} +static method main() → void { + self::B b = new self::C::•(); + b.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::b}{self::B}.{self::B::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}(){() → dynamic}; + new self::Wrapper::•(b, b).{self::Wrapper::call}{self::B}.{self::B::call}(){() → dynamic}; +} diff --git a/tests/language/regress/regress48304_test.dart b/tests/language/regress/regress48304_test.dart new file mode 100644 index 00000000000..04de267aa29 --- /dev/null +++ b/tests/language/regress/regress48304_test.dart @@ -0,0 +1,32 @@ +// Copyright (c) 2022, 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. + +import 'package:expect/expect.dart'; + +final String expected = '$A'; + +abstract class B { + call(); +} + +class C implements B { + call() => '$T'; +} + +abstract class A {} + +class Wrapper { + Wrapper(this.b, this.call); + final B b; + final B call; +} + +void main() { + B b = C(); + Expect.equals(b(), expected); + Expect.equals(Wrapper(b, b).b(), expected); + Expect.equals((Wrapper(b, b).b)(), expected); + Expect.equals(Wrapper(b, b).call(), expected); + Expect.equals((Wrapper(b, b).call)(), expected); +}