diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index f44e8ceafc4..c3ecd365171 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -245,11 +245,12 @@ abstract class AstCodeGenerator } final mayNeedToCheckTypes = translator.needToCheckTypesFor(member); if (mayNeedToCheckTypes) { - for (int i = 0; i < typeParametersToTypeCheck.length; i++) { - final typeParameter = typeParametersToTypeCheck[i]; + for (int i = 0; i < typeParameters.length; i++) { + final typeParameter = typeParameters[i]; if (translator.needToCheckTypeParameter(typeParameter)) { + final typeParameterToCheck = typeParametersToTypeCheck[i]; _generateTypeArgumentBoundCheck(typeParameter.name!, - typeLocals[typeParameter]!, typeParameter.bound); + typeLocals[typeParameter]!, typeParameterToCheck); } } } @@ -345,14 +346,13 @@ abstract class AstCodeGenerator for (int i = 0; i < positional.length; i++) { final bool isRequired = i < memberFunction.requiredParameterCount; - final typeToCheck = positionalToTypeCheck[i].type; + final typeToCheck = positionalToTypeCheck[i]; setupParamLocal( typeToCheck, positional[i], i, paramInfo.positional[i], isRequired); } - for (var param in named) { - final typeToCheck = identical(named, namedToTypeCheck) - ? param.type - : namedToTypeCheck.singleWhere((n) => n.name == param.name).type; + for (int i = 0; i < named.length; i++) { + final param = named[i]; + final typeToCheck = namedToTypeCheck[i]; setupParamLocal(typeToCheck, param, paramInfo.nameIndex[param.name]!, paramInfo.named[param.name], param.isRequired); } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 2dd2cef5778..a19ef16ef8e 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -1588,11 +1588,11 @@ class Translator with KernelNodes { ({ List typeParameters, - List typeParametersToTypeCheck, + List typeParametersToTypeCheck, List positional, - List positionalToTypeCheck, + List positionalToTypeCheck, List named, - List namedToTypeCheck + List namedToTypeCheck }) getParametersToCheck(Member member) { final memberFunction = member.function!; final List typeParameters = member is Constructor @@ -1608,26 +1608,72 @@ class Translator with KernelNodes { // This mirrors what the VM does in // - FlowGraphBuilder::BuildTypeArgumentTypeChecks // - FlowGraphBuilder::BuildArgumentTypeChecks - Procedure? forwardingTarget; + Member? procedureForwardingTarget; if (member is Procedure && member.isForwardingStub) { - forwardingTarget = member.concreteForwardingStubTarget as Procedure?; + final forwardingTarget = member.concreteForwardingStubTarget; + if (forwardingTarget is Field) { + assert( + typeParameters.isEmpty && named.isEmpty && positional.length == 1); + return ( + typeParameters: [], + typeParametersToTypeCheck: [], + positional: positional, + positionalToTypeCheck: [forwardingTarget.type], + named: named, + namedToTypeCheck: [], + ); + } + procedureForwardingTarget = forwardingTarget as Procedure; } - final List typeParametersToTypeCheck = - forwardingTarget?.typeParameters ?? typeParameters; - final List positionalToTypeCheck = - forwardingTarget?.function.positionalParameters ?? positional; - final List namedToTypeCheck = - forwardingTarget?.function.namedParameters ?? named; return ( typeParameters: typeParameters, - typeParametersToTypeCheck: typeParametersToTypeCheck, + typeParametersToTypeCheck: _typesFromTypeParameterBounds( + procedureForwardingTarget?.function?.typeParameters ?? + typeParameters), positional: positional, - positionalToTypeCheck: positionalToTypeCheck, + positionalToTypeCheck: _typesFromPositionalParameters( + procedureForwardingTarget?.function?.positionalParameters ?? + positional), named: named, - namedToTypeCheck: namedToTypeCheck, + namedToTypeCheck: _typeFromNamedParameters( + named, procedureForwardingTarget?.function?.namedParameters ?? named), ); } + List _typesFromTypeParameterBounds( + List typeParameters) { + if (typeParameters.isEmpty) return const []; + return [for (final param in typeParameters) param.bound]; + } + + List _typesFromPositionalParameters( + List typeParameters) { + if (typeParameters.isEmpty) return const []; + return [for (final param in typeParameters) param.type]; + } + + List _typeFromNamedParameters( + List namedOrder, + List namedType, + ) { + if (namedOrder.isEmpty) return const []; + final namedTypes = []; + for (int i = 0; i < namedOrder.length; ++i) { + final named = namedOrder[i]; + DartType? type; + + for (int j = 0; j < namedType.length; ++j) { + final other = namedType[j]; + if (named.name == other.name) { + type = other.type; + break; + } + } + namedTypes.add(type!); + } + return namedTypes; + } + DispatchTable dispatchTableForTarget(Reference target) { assert(target.asMember.isInstanceMember); if (!isDynamicSubmodule) return dispatchTable; diff --git a/tests/web/wasm/forwarding_stubs_regress_62662_test.dart b/tests/web/wasm/forwarding_stubs_regress_62662_test.dart new file mode 100644 index 00000000000..0c41f323827 --- /dev/null +++ b/tests/web/wasm/forwarding_stubs_regress_62662_test.dart @@ -0,0 +1,67 @@ +// Copyright (c) 2026, 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 https://github.com/dart-lang/sdk/issues/62662 + +import 'package:expect/expect.dart'; + +void main() { + testFieldForwardingStub(); + testTypeParameterForwardingStub(); + testPositionalParameterForwardingStub(); + testNamedParameterForwardingStub(); +} + +void testFieldForwardingStub() { + final Box object = Sub(); + object.field = 1; + Expect.throws(() => object.field = 'not an int'); + Expect.equals(1, object.field); +} + +void testTypeParameterForwardingStub() { + final Box object = Sub(); + object.foo>([1]); + Expect.throws(() => object.foo>(['a'])); +} + +void testPositionalParameterForwardingStub() { + final Box object = Sub(); + object.bar(1); + object.bar(2); + Expect.throws(() => object.bar('a')); +} + +void testNamedParameterForwardingStub() { + final Box object = Sub(); + object.baz(); + object.baz(w: Object()); + object.baz(x: -1); + object.baz(y: Object()); + object.baz(z: 0); + object.baz(w: Object(), x: 1); + object.baz(x: 2, w: Object()); + object.baz(w: Object(), x: 3, y: Object(), z: 4); + object.baz(z: 5, y: Object(), x: 6, w: Object()); + Expect.throws(() => object.baz(x: 'a')); + Expect.throws(() => object.baz(z: 'a')); +} + +abstract class Box { + T? field; + void foo>(H a); + void bar(T a); + void baz({T? z, Object? y, T? x, Object? w}); +} + +class Base { + int? field; + void foo>(List a) => + print('Base.foo<$H>(${1 + a[0]})'); + void bar(int a) => print('Base.bar($a)'); + void baz({Object? w, int? x, Object? y, int? z}) => + print('Base.baz({w: $w, x: $x, y: $y, z: $z})'); +} + +class Sub extends Base implements Box {}