diff --git a/pkg/compiler/lib/src/inferrer/record_tracer.dart b/pkg/compiler/lib/src/inferrer/record_tracer.dart index 67ac0172108..eeaf2814cd1 100644 --- a/pkg/compiler/lib/src/inferrer/record_tracer.dart +++ b/pkg/compiler/lib/src/inferrer/record_tracer.dart @@ -1,6 +1,9 @@ // Copyright (c) 2023, 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 '../common/names.dart'; +import '../elements/entities.dart'; + import 'node_tracer.dart'; import 'type_graph_nodes.dart'; @@ -16,4 +19,34 @@ class RecordTracerVisitor extends TracerVisitor { } return false; } + + @override + void visitClosureCallSiteTypeInformation( + ClosureCallSiteTypeInformation info, + ) { + bailout('Passed to a closure'); + } + + @override + void visitStaticCallSiteTypeInformation(StaticCallSiteTypeInformation info) { + super.visitStaticCallSiteTypeInformation(info); + MemberEntity called = info.calledElement; + if (inferrer.closedWorld.commonElements.isForeign(called) && + called.name == Identifiers.js) { + bailout('Used in JS ${info.debugName}'); + } + } + + @override + void visitDynamicCallSiteTypeInformation( + DynamicCallSiteTypeInformation info, + ) { + super.visitDynamicCallSiteTypeInformation(info); + final selector = info.selector!; + if (selector.isCall && + (info.hasClosureCallTargets || dynamicCallTargetsNonFunction(info))) { + bailout('Passed to a closure'); + return; + } + } } diff --git a/tests/language/records/closure_flow_test.dart b/tests/language/records/closure_flow_test.dart new file mode 100644 index 00000000000..dd16afd61e5 --- /dev/null +++ b/tests/language/records/closure_flow_test.dart @@ -0,0 +1,56 @@ +// 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. + +typedef F = void Function(T chunk); +typedef A = ({F sendChunk, int whatever}); + +class M { + final List content; + M({required this.content}); +} + +class G { + final M _chunk; + G(this._chunk); + + String get text => _chunk.content.join(''); +} + +void produceIt(A ctx) { + int base(A b) { + b.sendChunk(M(content: ["content-value"])); + return 1; + } + + final composeModel = _values.fold( + base, + (next, mw) => + (c) => mw.f(c, next), + ); + composeModel(( + whatever: 1, + sendChunk: (c) => ctx.sendChunk(M(content: c.content)), + )); +} + +void useIt(G g) { + print(g.text); +} + +class Wrapper { + int f(A c, int Function(A) next) { + return next(c); + } +} + +List _values = [Wrapper(), Wrapper(), Wrapper()]; + +void main() { + produceIt(( + whatever: 2, + sendChunk: (c) { + useIt(G(c)); + }, + )); +}