[linter] Suppress prefer_initializing_formals when not a subtype.

Fixes the `prefer_initializing_formals` lint so that it no longer
fires on code like this:

    class C {
      int? _x;

      C({dynamic x}) : _x = x;
    }

The reason is that the type of an initializing formal parameter is
required to be a subtype of the type of the field; coercions and
dynamic downcasts are not allowed. So in the example above, there is
no semantics-preserving way to convert `x` to an initializing formal.

Note that this situation occasionally crops up due to the parameter
having an implicit type, e.g.:

    class C {
      int? _x;

      C({x}) : _x = x;
    }

Fixes https://github.com/dart-lang/sdk/issues/62765.

Change-Id: I6a6a69648d3b2bd2f631ef01093b199125a71fa7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/483864
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Paul Berry
2026-02-27 07:31:33 -08:00
committed by Commit Queue
parent 276118d5f0
commit 3875050f81
2 changed files with 81 additions and 0 deletions
@@ -142,6 +142,15 @@ class _ConstructorChecker {
if (parameter is! FormalParameterElement) return;
if (!_parameters.contains(parameter)) return;
// An initializing formal is required to have a type that's a subtype of the
// field type (assignability is not sufficient). If this requirement isn't
// met, don't lint, because the corresponding fix will lead to a
// compile-time error.
var library = parameter.library!;
if (!library.typeSystem.isSubtypeOf(parameter.type, field.type)) {
return;
}
// Must be the same name (modulo privacy for private named parameters).
if (field.isPrivate) {
// Never lint on private names if the feature isn't supported.
@@ -396,6 +396,42 @@ class C {
''');
}
test_dynamicParameterType_dynamicField() async {
await assertDiagnostics(
r'''
class C {
dynamic _x;
C({dynamic x}) : _x = x;
}
''',
[lint(44, 6)],
);
}
test_dynamicParameterType_nonTopTypeField() async {
await assertNoDiagnostics(r'''
class C {
String? _x;
C({dynamic x}) : _x = x;
}
''');
}
test_dynamicParameterType_objectQuestionField() async {
await assertDiagnostics(
r'''
class C {
Object? _x;
C({dynamic x}) : _x = x;
}
''',
[lint(44, 6)],
);
}
test_factoryConstructor() async {
// https://github.com/dart-lang/linter/issues/2441
await assertNoDiagnostics(r'''
@@ -429,6 +465,42 @@ class C {
''');
}
test_implicitParameterType_dynamicField() async {
await assertDiagnostics(
r'''
class C {
dynamic _x;
C({x}) : _x = x;
}
''',
[lint(36, 6)],
);
}
test_implicitParameterType_nonTopTypeField() async {
await assertNoDiagnostics(r'''
class C {
String? _x;
C({x}) : _x = x;
}
''');
}
test_implicitParameterType_objectQuestionField() async {
await assertDiagnostics(
r'''
class C {
Object? _x;
C({x}) : _x = x;
}
''',
[lint(36, 6)],
);
}
test_initializeFromOtherParameter() async {
await assertNoDiagnostics(r'''
class C {