[cfe] Provide inferred type context to initializing formals
In response to https://github.com/flutter/flutter/issues/160551
Change-Id: I0e4123c76f8d82a6900e12452f0deef2cbb05d36
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404822
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
diff --git a/pkg/front_end/lib/src/source/source_factory_builder.dart b/pkg/front_end/lib/src/source/source_factory_builder.dart
index 48575ec..8629118 100644
--- a/pkg/front_end/lib/src/source/source_factory_builder.dart
+++ b/pkg/front_end/lib/src/source/source_factory_builder.dart
@@ -582,6 +582,32 @@
.createBodyBuilderForOutlineExpression(libraryBuilder,
createBodyBuilderContext(), declarationBuilder.scope, fileUri);
Builder? targetBuilder = redirectionTarget.target;
+
+ // Inference of target's formals should happen before building of the
+ // outline expressions in members and before inferring target's type
+ // arguments.
+ //
+ // (1) The outline expressions, such as formal parameter initializers,
+ // need properly inferred type contexts. Among other things, it ensures
+ // that the required coercions, such as int-to-double conversion, are
+ // run.
+ //
+ // (2) Type arguments for the targets of redirecting factories can only
+ // be inferred if the formal parameters of the targets are inferred too.
+ // That may not be the case when the target's parameters are initializing
+ // parameters referring to fields with types that are to be inferred.
+ if (targetBuilder is SourceFunctionBuilderImpl) {
+ List<FormalParameterBuilder>? formals = targetBuilder.formals;
+ if (formals != null) {
+ for (FormalParameterBuilder formal in formals) {
+ TypeBuilder formalType = formal.type;
+ if (formalType is InferableTypeBuilder) {
+ formalType.inferType(classHierarchy);
+ }
+ }
+ }
+ }
+
if (targetBuilder is SourceMemberBuilder) {
// Ensure that target has been built.
targetBuilder.buildOutlineExpressions(
@@ -598,22 +624,6 @@
fileOffset, fileUri);
}
- // Type arguments for the targets of redirecting factories can only be
- // inferred if the formal parameters of the targets are inferred too.
- // That may not be the case when the target's parameters are initializing
- // parameters referring to fields with types that are to be inferred.
- if (targetBuilder is SourceFunctionBuilderImpl) {
- List<FormalParameterBuilder>? formals = targetBuilder.formals;
- if (formals != null) {
- for (FormalParameterBuilder formal in formals) {
- TypeBuilder formalType = formal.type;
- if (formalType is InferableTypeBuilder) {
- formalType.inferType(classHierarchy);
- }
- }
- }
- }
-
typeArguments = inferrer.inferRedirectingFactoryTypeArguments(
helper,
_procedureInternal.function.returnType,
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index fe92305..73e7208 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -510,6 +510,7 @@
coerces
coercing
coercion
+coercions
coincides
coinductively
collapses
diff --git a/pkg/front_end/testcases/inference/flutter_issue160551.dart b/pkg/front_end/testcases/inference/flutter_issue160551.dart
new file mode 100644
index 0000000..5c1fb7b
--- /dev/null
+++ b/pkg/front_end/testcases/inference/flutter_issue160551.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.
+
+void main() {
+ print(B().foo);
+}
+
+abstract class A {
+ A();
+
+ factory A.redir({double foo}) = B;
+}
+
+class B<T> extends A {
+ B({this.foo = 10});
+ final double foo;
+}
diff --git a/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.expect b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.expect
new file mode 100644
index 0000000..91834f0
--- /dev/null
+++ b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.expect
@@ -0,0 +1,24 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+abstract class A extends core::Object {
+ constructor •() → self::A
+ : super core::Object::•()
+ ;
+ static factory redir({core::double foo = #C1}) → self::A /* redirection-target: self::B::•<dynamic>*/
+ return new self::B::•<dynamic>(foo: foo);
+}
+class B<T extends core::Object? = dynamic> extends self::A {
+ final field core::double foo;
+ constructor •({core::double foo = #C1}) → self::B<self::B::T%>
+ : self::B::foo = foo, super self::A::•()
+ ;
+}
+static method main() → void {
+ core::print(new self::B::•<dynamic>().{self::B::foo}{core::double});
+}
+
+constants {
+ #C1 = 10.0
+}
diff --git a/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.modular.expect b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.modular.expect
new file mode 100644
index 0000000..91834f0
--- /dev/null
+++ b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.modular.expect
@@ -0,0 +1,24 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+abstract class A extends core::Object {
+ constructor •() → self::A
+ : super core::Object::•()
+ ;
+ static factory redir({core::double foo = #C1}) → self::A /* redirection-target: self::B::•<dynamic>*/
+ return new self::B::•<dynamic>(foo: foo);
+}
+class B<T extends core::Object? = dynamic> extends self::A {
+ final field core::double foo;
+ constructor •({core::double foo = #C1}) → self::B<self::B::T%>
+ : self::B::foo = foo, super self::A::•()
+ ;
+}
+static method main() → void {
+ core::print(new self::B::•<dynamic>().{self::B::foo}{core::double});
+}
+
+constants {
+ #C1 = 10.0
+}
diff --git a/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.outline.expect b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.outline.expect
new file mode 100644
index 0000000..12e985c
--- /dev/null
+++ b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.outline.expect
@@ -0,0 +1,17 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+abstract class A extends core::Object {
+ constructor •() → self::A
+ ;
+ static factory redir({core::double foo = null}) → self::A /* redirection-target: self::B::•<dynamic>*/
+ return new self::B::•<dynamic>(foo: foo);
+}
+class B<T extends core::Object? = dynamic> extends self::A {
+ final field core::double foo;
+ constructor •({core::double foo = 10.0}) → self::B<self::B::T%>
+ ;
+}
+static method main() → void
+ ;
diff --git a/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.transformed.expect b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.transformed.expect
new file mode 100644
index 0000000..91834f0
--- /dev/null
+++ b/pkg/front_end/testcases/inference/flutter_issue160551.dart.strong.transformed.expect
@@ -0,0 +1,24 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+abstract class A extends core::Object {
+ constructor •() → self::A
+ : super core::Object::•()
+ ;
+ static factory redir({core::double foo = #C1}) → self::A /* redirection-target: self::B::•<dynamic>*/
+ return new self::B::•<dynamic>(foo: foo);
+}
+class B<T extends core::Object? = dynamic> extends self::A {
+ final field core::double foo;
+ constructor •({core::double foo = #C1}) → self::B<self::B::T%>
+ : self::B::foo = foo, super self::A::•()
+ ;
+}
+static method main() → void {
+ core::print(new self::B::•<dynamic>().{self::B::foo}{core::double});
+}
+
+constants {
+ #C1 = 10.0
+}
diff --git a/pkg/front_end/testcases/inference/flutter_issue160551.dart.textual_outline.expect b/pkg/front_end/testcases/inference/flutter_issue160551.dart.textual_outline.expect
new file mode 100644
index 0000000..5f1500d
--- /dev/null
+++ b/pkg/front_end/testcases/inference/flutter_issue160551.dart.textual_outline.expect
@@ -0,0 +1,11 @@
+void main() {}
+
+abstract class A {
+ A();
+ factory A.redir({double foo}) = B;
+}
+
+class B<T> extends A {
+ B({this.foo = 10});
+ final double foo;
+}
diff --git a/pkg/front_end/testcases/inference/flutter_issue160551.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/inference/flutter_issue160551.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..fe81633
--- /dev/null
+++ b/pkg/front_end/testcases/inference/flutter_issue160551.dart.textual_outline_modelled.expect
@@ -0,0 +1,11 @@
+abstract class A {
+ A();
+ factory A.redir({double foo}) = B;
+}
+
+class B<T> extends A {
+ B({this.foo = 10});
+ final double foo;
+}
+
+void main() {}