Report type parameters whose name is a type in scope
See https://github.com/dart-lang/sdk/issues/59517 for background and discussion.
Change-Id: I6d02d07cdd95e8b2b647cf3f7f87a9f27a1c48b3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381841
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Erik Ernst <eernst@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index e97b962..589e5ec 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -1985,7 +1985,11 @@
notes: |-
Theoretically we could replace some uses with `is` checks, but it's probably
uncommon enough to not be worth the cost.
-LintCode.avoid_types_as_parameter_names:
+LintCode.avoid_types_as_parameter_names_type_parameter:
+ status: noFix
+ notes: |-
+ The type variable needs to be renamed, but that's a refactoring.
+LintCode.avoid_types_as_parameter_names_formal_parameter:
status: hasFix
LintCode.avoid_types_on_closure_parameters:
status: hasFix
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index fb6fc2b..9a3c7e5 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -311,7 +311,9 @@
// diagnostics and should also be available as an assist.
ReplaceCascadeWithDot.new,
],
- LinterLintCode.avoid_types_as_parameter_names: [ConvertToOnType.new],
+ LinterLintCode.avoid_types_as_parameter_names_formal_parameter: [
+ ConvertToOnType.new,
+ ],
LinterLintCode.avoid_types_on_closure_parameters: [
ReplaceWithIdentifier.new,
RemoveTypeAnnotation.other,
diff --git a/pkg/analyzer/lib/src/fine/library_manifest.dart b/pkg/analyzer/lib/src/fine/library_manifest.dart
index a91c166..ffc9c62 100644
--- a/pkg/analyzer/lib/src/fine/library_manifest.dart
+++ b/pkg/analyzer/lib/src/fine/library_manifest.dart
@@ -708,9 +708,9 @@
/// Returns either the existing item from [declaredItems], or builds a new one.
Item _getOrBuildElementItem<
- Element extends ElementImpl2,
+ E extends ElementImpl2,
Item extends ManifestItem
- >(Element element, Item Function() build) {
+ >(E element, Item Function() build) {
// We assume that when matching elements against the structure of
// the item, we put into [itemMap] only the type of the item that
// corresponds the type of the element.
diff --git a/pkg/analyzer/test/generated/error_suppression_test.dart b/pkg/analyzer/test/generated/error_suppression_test.dart
index 5af8bac..cacac24 100644
--- a/pkg/analyzer/test/generated/error_suppression_test.dart
+++ b/pkg/analyzer/test/generated/error_suppression_test.dart
@@ -234,7 +234,13 @@
// ignore: type=wrong
void f(arg1(int)) {} // AVOID_TYPES_AS_PARAMETER_NAMES
''',
- [error(LinterLintCode.avoid_types_as_parameter_names, 34, 3)],
+ [
+ error(
+ LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
+ 34,
+ 3,
+ ),
+ ],
);
}
diff --git a/pkg/linter/lib/src/lint_codes.g.dart b/pkg/linter/lib/src/lint_codes.g.dart
index 9a5799c..18709ea 100644
--- a/pkg/linter/lib/src/lint_codes.g.dart
+++ b/pkg/linter/lib/src/lint_codes.g.dart
@@ -360,13 +360,25 @@
hasPublishedDocs: true,
);
- static const LintCode avoid_types_as_parameter_names = LinterLintCode(
+ static const LintCode
+ avoid_types_as_parameter_names_formal_parameter = LinterLintCode(
LintNames.avoid_types_as_parameter_names,
"The parameter name '{0}' matches a visible type name.",
correctionMessage:
"Try adding a name for the parameter or changing the parameter name to "
"not match an existing type.",
hasPublishedDocs: true,
+ uniqueName: 'avoid_types_as_parameter_names_formal_parameter',
+ );
+
+ static const LintCode
+ avoid_types_as_parameter_names_type_parameter = LinterLintCode(
+ LintNames.avoid_types_as_parameter_names,
+ "The type parameter name '{0}' matches a visible type name.",
+ correctionMessage:
+ "Try changing the type parameter name to not match an existing type.",
+ hasPublishedDocs: true,
+ uniqueName: 'avoid_types_as_parameter_names_type_parameter',
);
static const LintCode avoid_types_on_closure_parameters = LinterLintCode(
diff --git a/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart b/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
index f8fa24f..567c788 100644
--- a/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
+++ b/pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart
@@ -19,7 +19,10 @@
: super(name: LintNames.avoid_types_as_parameter_names, description: _desc);
@override
- LintCode get lintCode => LinterLintCode.avoid_types_as_parameter_names;
+ List<LintCode> get lintCodes => [
+ LinterLintCode.avoid_types_as_parameter_names_type_parameter,
+ LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
+ ];
@override
void registerNodeProcessors(
@@ -29,6 +32,7 @@
var visitor = _Visitor(this, context);
registry.addFormalParameterList(this, visitor);
registry.addCatchClause(this, visitor);
+ registry.addTypeParameterList(this, visitor);
}
}
@@ -42,7 +46,12 @@
void visitCatchClause(CatchClause node) {
var parameter = node.exceptionParameter;
if (parameter != null && _isTypeName(node, parameter.name)) {
- rule.reportAtNode(parameter, arguments: [parameter.name.lexeme]);
+ rule.reportAtNode(
+ parameter,
+ arguments: [parameter.name.lexeme],
+ errorCode:
+ LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
+ );
}
}
@@ -56,12 +65,62 @@
declaredElement.hasImplicitType &&
name != null &&
_isTypeName(node, name)) {
- rule.reportAtToken(name, arguments: [name.lexeme]);
+ rule.reportAtToken(
+ name,
+ arguments: [name.lexeme],
+ errorCode:
+ LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
+ );
}
}
}
- bool _isTypeName(AstNode scope, Token name) {
+ @override
+ void visitTypeParameterList(TypeParameterList node) {
+ for (var typeParameter in node.typeParameters) {
+ var declaredElement = typeParameter.declaredFragment?.element;
+ var name = typeParameter.name;
+ var scope = node.parent;
+ var isShadowedByTypeParameter = false;
+ // Step out into enclosing scope where this type parameter isn't
+ // itself in scope (the type parameter doesn't shadow itself).
+ while (scope != null) {
+ if (scope is ClassDeclaration ||
+ scope is FunctionDeclaration ||
+ (scope is FunctionExpression &&
+ scope.parent is! FunctionDeclaration) ||
+ scope is GenericFunctionType ||
+ scope is MethodDeclaration) {
+ if (scope is MethodDeclaration) {
+ isShadowedByTypeParameter = true;
+ }
+ scope = scope.parent;
+ break;
+ }
+ scope = scope.parent;
+ }
+ if (declaredElement != null &&
+ scope != null &&
+ _isTypeName(
+ scope,
+ name,
+ isShadowedByMethodTypeParameter: isShadowedByTypeParameter,
+ )) {
+ rule.reportAtToken(
+ name,
+ arguments: [name.lexeme],
+ errorCode:
+ LinterLintCode.avoid_types_as_parameter_names_type_parameter,
+ );
+ }
+ }
+ }
+
+ bool _isTypeName(
+ AstNode scope,
+ Token name, {
+ bool isShadowedByMethodTypeParameter = false,
+ }) {
var result = resolveNameInScope(
name.lexeme,
scope,
@@ -69,6 +128,13 @@
);
if (result.isRequestedName) {
var element = result.element;
+ if (isShadowedByMethodTypeParameter && element is TypeParameterElement) {
+ // A type parameter of a static method can only shadow another type
+ // parameter when the latter is a type parameter of the enclosing
+ // declaration (class, mixin, mixin class, enum, extension, or
+ // extension type). We do not lint this case.
+ return false;
+ }
return element is ClassElement ||
element is ExtensionTypeElement ||
element is TypeAliasElement ||
diff --git a/pkg/linter/messages.yaml b/pkg/linter/messages.yaml
index 362e7b7..48ff5e4 100644
--- a/pkg/linter/messages.yaml
+++ b/pkg/linter/messages.yaml
@@ -2347,11 +2347,17 @@
return getThingFromDatabase(key: myThing.thingTypeKey);
}
```
- avoid_types_as_parameter_names:
+ avoid_types_as_parameter_names_formal_parameter:
+ sharedName: avoid_types_as_parameter_names
problemMessage: "The parameter name '{0}' matches a visible type name."
correctionMessage: "Try adding a name for the parameter or changing the parameter name to not match an existing type."
+ hasPublishedDocs: true
+ avoid_types_as_parameter_names_type_parameter:
+ sharedName: avoid_types_as_parameter_names
+ problemMessage: "The type parameter name '{0}' matches a visible type name."
state:
stable: "2.0"
+ correctionMessage: "Try changing the type parameter name to not match an existing type."
categories: [unintentional]
hasPublishedDocs: true
documentation: |-
@@ -2368,6 +2374,11 @@
shadow the existing type, which can lead to bugs that are difficult to
diagnose.
+ The analyzer also produces this diagnostic when the name of a type
+ parameter in a type parameter list is the same as a type whose name is
+ in scope. It is again recommended that the type parameter is renamed
+ such that the error-prone shadowing is avoided.
+
#### Example
The following code produces this diagnostic because the function `f` has a
diff --git a/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart b/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
index f254176..3ee23a7 100644
--- a/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
+++ b/pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart
@@ -30,6 +30,22 @@
);
}
+ test_classTypeParameter_instanceMethodTypeParameter() async {
+ await assertNoDiagnostics(r'''
+class A<X> {
+ X instanceMethod<X>(X x) => x;
+}
+''');
+ }
+
+ test_classTypeParameter_staticMethodTypeParameter() async {
+ await assertNoDiagnostics(r'''
+class A<X> {
+ static X staticMethod<X>(X x) => x;
+}
+''');
+ }
+
test_extensionType() async {
await assertDiagnostics(
r'''
@@ -201,6 +217,37 @@
''');
}
+ test_typeParameter_class() async {
+ await assertDiagnostics(
+ r'''
+class A<Object> {}
+''',
+ [lint(8, 6)],
+ );
+ }
+
+ test_typeParameter_function() async {
+ await assertDiagnostics(
+ r'''
+void f<int>() {}
+''',
+ [lint(7, 3)],
+ );
+ }
+
+ test_typeParameter_instanceMethod_class() async {
+ await assertDiagnostics(
+ r'''
+class A<X> {
+ C instanceMethod<C>(C c) => c;
+}
+
+class C {}
+''',
+ [lint(32, 1)],
+ );
+ }
+
test_typeParameter_wildcard() async {
await assertDiagnostics(
r'''