Elements. Infer type arguments for redirected generic factory constructors.
Fix element model building to infer type arguments for redirected
factory constructors of generic classes (both named and unnamed). This
brings element construction in line with AST resolution so redirected
targets carry the correct type substitutions instead of defaulting to
uninstantiated or incorrect types.
What changed:
- Perform generic inference when resolving the redirect target type in
`NamedTypeResolver`, using the enclosing class as context.
- Replace the brittle `redirectedConstructor_namedType` tracking with
structural detection of redirecting constructor contexts.
- Ensure `ResolutionVisitor` provides the enclosing class to the type
resolver and visit the redirection directly.
- Adjust `AstResolver` ordering so enclosing declaration info is set
before resolving constructor children.
- Bump `DATA_VERSION` to invalidate stale serialized data.
Why:
- Previously, only AST resolution performed inference. Element model
building skipped it, producing incorrect constructor redirections in
summaries and downstream analyses. Aligning both paths fixes those
inconsistencies.
Impact:
- Correct substitutions are recorded for redirected constructors in
summaries; no public API changes expected.
- Adds targeted tests to prevent regressions (no behavioral changes
beyond the intended inference fix).
Fixes: flutter/flutter#174503
Bug: https://github.com/flutter/flutter/issues/174503
Change-Id: If231de40de9af15ec14dc26cda28bdc269d9896e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/447603
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index fcf94eb..4d1aab8 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -107,7 +107,7 @@
// TODO(scheglov): Clean up the list of implicitly analyzed files.
class AnalysisDriver {
/// The version of data format, should be incremented on every format change.
- static const int DATA_VERSION = 526;
+ static const int DATA_VERSION = 527;
/// The number of exception contexts allowed to write. Once this field is
/// zero, we stop writing any new exception contexts in this process.
diff --git a/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart
index c371a13..92b49f8 100644
--- a/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/named_type_resolver.dart
@@ -48,10 +48,6 @@
/// If not `null`, a direct child the [WithClause] in the [enclosingClass].
NamedType? withClause_namedType;
- /// If not `null`, the [NamedType] of the redirected constructor being
- /// resolved, in the [enclosingClass].
- NamedType? redirectedConstructor_namedType;
-
/// If [resolve] finds out that the given [NamedType] with a
/// [PrefixedIdentifier] name is actually the name of a class and the name of
/// the constructor, it rewrites the [ConstructorName] to correctly represent
@@ -266,7 +262,7 @@
}
}
- if (identical(node, redirectedConstructor_namedType)) {
+ if (_ErrorHelper._isRedirectingConstructor(node)) {
return _inferRedirectedConstructor(
element,
dataForTesting: dataForTesting,
@@ -374,9 +370,6 @@
typeArguments: null,
question: null,
)..element = importPrefixElement;
- if (identical(node, redirectedConstructor_namedType)) {
- redirectedConstructor_namedType = namedType;
- }
constructorName.type = namedType;
constructorName.period = importPrefix.period;
diff --git a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
index 7ee4bc7..e850629 100644
--- a/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/resolution_visitor.dart
@@ -177,6 +177,13 @@
TypeImpl get _dynamicType => _typeProvider.dynamicType;
+ /// Set information about enclosing declarations.
+ void prepareEnclosingDeclarations({
+ InterfaceElementImpl? enclosingClassElement,
+ }) {
+ _namedTypeResolver.enclosingClass = enclosingClassElement;
+ }
+
@override
void visitAnnotation(covariant AnnotationImpl node) {
if (_elementWalker == null) {
@@ -371,7 +378,7 @@
});
_defineFormalParameters(element.formalParameters);
- _resolveRedirectedConstructor(node);
+ node.redirectedConstructor?.accept(this);
node.initializers.accept(this);
node.body.accept(this);
});
@@ -1692,18 +1699,6 @@
);
}
- void _resolveRedirectedConstructor(ConstructorDeclaration node) {
- var redirectedConstructor = node.redirectedConstructor;
- if (redirectedConstructor == null) return;
-
- var namedType = redirectedConstructor.type;
- _namedTypeResolver.redirectedConstructor_namedType = namedType;
-
- redirectedConstructor.accept(this);
-
- _namedTypeResolver.redirectedConstructor_namedType = null;
- }
-
/// Resolves the given [namedType], reports errors if the resulting type
/// is not valid in the context of the [declaration] and [clause].
void _resolveType({
diff --git a/pkg/analyzer/lib/src/summary2/ast_resolver.dart b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
index 849be99..97984b9 100644
--- a/pkg/analyzer/lib/src/summary2/ast_resolver.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
@@ -91,10 +91,10 @@
node.redirectedConstructor?.accept(visitor);
}
+ _prepareEnclosingDeclarations();
visit(_resolutionVisitor);
visit(_scopeResolverVisitor);
- _prepareEnclosingDeclarations();
_flowAnalysis.bodyOrInitializer_enter(node, node.parameters, visit: visit);
visit(_resolverVisitor);
_resolverVisitor.checkIdle();
@@ -119,6 +119,10 @@
}
void _prepareEnclosingDeclarations() {
+ _resolutionVisitor.prepareEnclosingDeclarations(
+ enclosingClassElement: enclosingClassElement,
+ );
+
_resolverVisitor.prepareEnclosingDeclarations(
enclosingClassElement: enclosingClassElement,
enclosingExecutableElement: enclosingExecutableElement,
diff --git a/pkg/analyzer/test/src/summary/elements/class_test.dart b/pkg/analyzer/test/src/summary/elements/class_test.dart
index 42d1d12..9a49ac3 100644
--- a/pkg/analyzer/test/src/summary/elements/class_test.dart
+++ b/pkg/analyzer/test/src/summary/elements/class_test.dart
@@ -4377,6 +4377,81 @@
''');
}
+ test_class_constructor_redirected_factory_named_generic_inference() async {
+ var library = await buildLibrary('''
+class A<T, U> implements B<T, U> {
+ A.named();
+}
+
+class B<T2, U2> {
+ factory B() = A.named;
+}
+''');
+ checkElementText(library, r'''
+library
+ reference: <testLibrary>
+ fragments
+ #F0 <testLibraryFragment>
+ element: <testLibrary>
+ classes
+ #F1 class A (nameOffset:6) (firstTokenOffset:0) (offset:6)
+ element: <testLibrary>::@class::A
+ typeParameters
+ #F2 T (nameOffset:8) (firstTokenOffset:8) (offset:8)
+ element: #E0 T
+ #F3 U (nameOffset:11) (firstTokenOffset:11) (offset:11)
+ element: #E1 U
+ constructors
+ #F4 named (nameOffset:39) (firstTokenOffset:37) (offset:39)
+ element: <testLibrary>::@class::A::@constructor::named
+ typeName: A
+ typeNameOffset: 37
+ periodOffset: 38
+ #F5 class B (nameOffset:57) (firstTokenOffset:51) (offset:57)
+ element: <testLibrary>::@class::B
+ typeParameters
+ #F6 T2 (nameOffset:59) (firstTokenOffset:59) (offset:59)
+ element: #E2 T2
+ #F7 U2 (nameOffset:63) (firstTokenOffset:63) (offset:63)
+ element: #E3 U2
+ constructors
+ #F8 factory new (nameOffset:<null>) (firstTokenOffset:71) (offset:79)
+ element: <testLibrary>::@class::B::@constructor::new
+ typeName: B
+ typeNameOffset: 79
+ classes
+ class A
+ reference: <testLibrary>::@class::A
+ firstFragment: #F1
+ typeParameters
+ #E0 T
+ firstFragment: #F2
+ #E1 U
+ firstFragment: #F3
+ interfaces
+ B<T, U>
+ constructors
+ named
+ reference: <testLibrary>::@class::A::@constructor::named
+ firstFragment: #F4
+ class B
+ reference: <testLibrary>::@class::B
+ firstFragment: #F5
+ typeParameters
+ #E2 T2
+ firstFragment: #F6
+ #E3 U2
+ firstFragment: #F7
+ constructors
+ factory new
+ reference: <testLibrary>::@class::B::@constructor::new
+ firstFragment: #F8
+ redirectedConstructor: ConstructorMember
+ baseElement: <testLibrary>::@class::A::@constructor::named
+ substitution: {T: T2, U: U2}
+''');
+ }
+
test_class_constructor_redirected_factory_named_generic_viaTypeAlias() async {
var library = await buildLibrary('''
typedef A<T, U> = C<T, U>;
@@ -4939,6 +5014,129 @@
''');
}
+ test_class_constructor_redirected_factory_unnamed_generic_inference() async {
+ var library = await buildLibrary('''
+class A<T, U> implements B<T, U> {
+ A();
+}
+
+class B<T2, U2> {
+ factory B() = A;
+}
+''');
+ checkElementText(library, r'''
+library
+ reference: <testLibrary>
+ fragments
+ #F0 <testLibraryFragment>
+ element: <testLibrary>
+ classes
+ #F1 class A (nameOffset:6) (firstTokenOffset:0) (offset:6)
+ element: <testLibrary>::@class::A
+ typeParameters
+ #F2 T (nameOffset:8) (firstTokenOffset:8) (offset:8)
+ element: #E0 T
+ #F3 U (nameOffset:11) (firstTokenOffset:11) (offset:11)
+ element: #E1 U
+ constructors
+ #F4 new (nameOffset:<null>) (firstTokenOffset:37) (offset:37)
+ element: <testLibrary>::@class::A::@constructor::new
+ typeName: A
+ typeNameOffset: 37
+ #F5 class B (nameOffset:51) (firstTokenOffset:45) (offset:51)
+ element: <testLibrary>::@class::B
+ typeParameters
+ #F6 T2 (nameOffset:53) (firstTokenOffset:53) (offset:53)
+ element: #E2 T2
+ #F7 U2 (nameOffset:57) (firstTokenOffset:57) (offset:57)
+ element: #E3 U2
+ constructors
+ #F8 factory new (nameOffset:<null>) (firstTokenOffset:65) (offset:73)
+ element: <testLibrary>::@class::B::@constructor::new
+ typeName: B
+ typeNameOffset: 73
+ classes
+ class A
+ reference: <testLibrary>::@class::A
+ firstFragment: #F1
+ typeParameters
+ #E0 T
+ firstFragment: #F2
+ #E1 U
+ firstFragment: #F3
+ interfaces
+ B<T, U>
+ constructors
+ new
+ reference: <testLibrary>::@class::A::@constructor::new
+ firstFragment: #F4
+ class B
+ reference: <testLibrary>::@class::B
+ firstFragment: #F5
+ typeParameters
+ #E2 T2
+ firstFragment: #F6
+ #E3 U2
+ firstFragment: #F7
+ constructors
+ factory new
+ reference: <testLibrary>::@class::B::@constructor::new
+ firstFragment: #F8
+ redirectedConstructor: ConstructorMember
+ baseElement: <testLibrary>::@class::A::@constructor::new
+ substitution: {T: T2, U: U2}
+''');
+ }
+
+ test_class_constructor_redirected_factory_unnamed_generic_inference_self() async {
+ var library = await buildLibrary('''
+class A<T> {
+ A();
+ factory A.redirected() = A;
+}
+''');
+ checkElementText(library, r'''
+library
+ reference: <testLibrary>
+ fragments
+ #F0 <testLibraryFragment>
+ element: <testLibrary>
+ classes
+ #F1 class A (nameOffset:6) (firstTokenOffset:0) (offset:6)
+ element: <testLibrary>::@class::A
+ typeParameters
+ #F2 T (nameOffset:8) (firstTokenOffset:8) (offset:8)
+ element: #E0 T
+ constructors
+ #F3 new (nameOffset:<null>) (firstTokenOffset:15) (offset:15)
+ element: <testLibrary>::@class::A::@constructor::new
+ typeName: A
+ typeNameOffset: 15
+ #F4 factory redirected (nameOffset:32) (firstTokenOffset:22) (offset:32)
+ element: <testLibrary>::@class::A::@constructor::redirected
+ typeName: A
+ typeNameOffset: 30
+ periodOffset: 31
+ classes
+ class A
+ reference: <testLibrary>::@class::A
+ firstFragment: #F1
+ typeParameters
+ #E0 T
+ firstFragment: #F2
+ constructors
+ new
+ reference: <testLibrary>::@class::A::@constructor::new
+ firstFragment: #F3
+ factory redirected
+ reference: <testLibrary>::@class::A::@constructor::redirected
+ firstFragment: #F4
+ redirectedConstructor: ConstructorMember
+ baseElement: <testLibrary>::@class::A::@constructor::new
+ substitution: {T: T}
+''');
+ }
+
test_class_constructor_redirected_factory_unnamed_generic_viaTypeAlias() async {
var library = await buildLibrary('''
typedef A<T, U> = C<T, U>;