From 3875050f81b5fbe471c6bb6784fb7cf2b0a1f96e Mon Sep 17 00:00:00 2001 From: Paul Berry Date: Fri, 27 Feb 2026 07:31:33 -0800 Subject: [PATCH] [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 Reviewed-by: Brian Wilkerson --- .../rules/prefer_initializing_formals.dart | 9 +++ .../prefer_initializing_formals_test.dart | 72 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/pkg/linter/lib/src/rules/prefer_initializing_formals.dart b/pkg/linter/lib/src/rules/prefer_initializing_formals.dart index 233d8179f16..c79dced67f6 100644 --- a/pkg/linter/lib/src/rules/prefer_initializing_formals.dart +++ b/pkg/linter/lib/src/rules/prefer_initializing_formals.dart @@ -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. diff --git a/pkg/linter/test/rules/prefer_initializing_formals_test.dart b/pkg/linter/test/rules/prefer_initializing_formals_test.dart index 23960ffbe01..d55ebaf269f 100644 --- a/pkg/linter/test/rules/prefer_initializing_formals_test.dart +++ b/pkg/linter/test/rules/prefer_initializing_formals_test.dart @@ -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 {