Add language tests for issues 33298 and 56666.

Yesterday I discovered some code in the front end type inference
engine that I didn't expect: logic that added constraints to type
inference based on the result of applying type coercions. To figure
out why it was necessary, I disabled it and ran the code through
trybots. It turns out that it's needed to prevent the following
issues:
- https://github.com/dart-lang/sdk/issues/33298
- https://github.com/dart-lang/sdk/issues/56666

Fortunately, we had regression tests for these issues:
- `pkg/front_end/testcases/general/bug33298.dart`
- `pkg/front_end/testcases/general/issue56666.dart`

Unfortunately, those regression tests are front-end specific, meaning
we didn't have any coverage for the analyzer.

This CL adds coverage for the analyzer by replicating the regression
tests in `tests/language`.

Change-Id: Ia53b3c898549e991d6685a414d177a466a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/494563
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
This commit is contained in:
Paul Berry
2026-04-13 09:30:21 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 00b6621e50
commit 25bb8ce922
2 changed files with 97 additions and 0 deletions
@@ -0,0 +1,58 @@
// 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.
// This test is based on the repro for
// https://github.com/dart-lang/sdk/issues/33298. It illustrates that the types
// that arise from a type coercion need to be accounted for in type inference.
//
// Specifically, these tests verify that, after type inference has finished
// visiting all the arguments of an invocation, made a preliminary assignment of
// types to type parameters, and then performed assignability checks on each of
// the arguments, if any of those assignability checks resulted in the insertion
// of a coercion, then the static type of the coerced expression is then used to
// generate additional type constraints.
//
// For example, in the invocation `List<String> list2 = ['a', 'b',
// 'c'].map(a).toList()` below, the assignability check to see if `a` is usable
// as an argument to `map` results in a coercion, causing `a` to be treated as
// `a.call`. After this coercion is generated, type inference needs to then use
// the static type of `a.call` to generate additional type constraints. This
// results in a constraint that the type argument to `map` must be a supertype
// of `String`, which in turn ensures that the type of `['a', 'b', 'c'].map(a)`
// is `Iterable<String>`. Without this extra constraint generation step, the
// type of `['a', 'b', 'c']` would be `Iterable<dynamic>`.
import 'package:expect/expect.dart';
class A {
String call(String s) => '$s$s';
}
class B<T> {
T call(T t) => t;
}
class C {
T call<T>(T t) => t;
}
main() {
A a = A();
List<String> list1 = ['a', 'b', 'c'].map(a.call).toList();
Expect.listEquals(['aa', 'bb', 'cc'], list1);
List<String> list2 = ['a', 'b', 'c'].map(a).toList();
Expect.listEquals(['aa', 'bb', 'cc'], list2);
B<String> b = B();
List<String> list3 = ['a', 'b', 'c'].map(b.call).toList();
Expect.listEquals(['a', 'b', 'c'], list3);
List<String> list4 = ['a', 'b', 'c'].map(b).toList();
Expect.listEquals(['a', 'b', 'c'], list4);
C c = C();
List<String> list5 = ['a', 'b', 'c'].map(c.call).toList();
Expect.listEquals(['a', 'b', 'c'], list5);
List<String> list6 = ['a', 'b', 'c'].map(c).toList();
Expect.listEquals(['a', 'b', 'c'], list6);
}
@@ -0,0 +1,39 @@
// 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.
// This test is based on the repro for
// https://github.com/dart-lang/sdk/issues/56666. It illustrates that the types
// that arise from a type coercion need to be accounted for in type inference.
//
// Specifically, these tests verify that, after type inference has finished
// visiting all the arguments of an invocation, made a preliminary assignment of
// types to type parameters, and then performed assignability checks on each of
// the arguments, if any of those assignability checks resulted in the insertion
// of a coercion, then the static type of the coerced expression is then used to
// generate additional type constraints.
//
// For example, in the invocation `var g = f(C());` below, the assignability
// check to see if `C()` is usable as an argument to `f` results in a coercion,
// causing `C()` to be treated as `C().call`. After this coercion is generated,
// type inference needs to then use the static type of `C().call` to generate
// additional type constraints. This results in a constraint that the type
// argument to `f` must be a supertype of `String`, which in turn ensures that
// the type of `f(C())` is `String Function(String)`. Without this extra
// constraint generation step, the type of `f(C())` would be `dynamic
// Function(String)`.
import 'package:expect/expect.dart';
import '../static_type_helper.dart';
class C {
T call<T>(T t) => t;
}
X Function(String) f<X>(X Function(String) g) => g;
void main() {
var g = f(C());
g.expectStaticType<Exactly<String Function(String)>>();
Expect.equals('s', g('s'));
}