Skip to content

Commit 2f0cbeb

Browse files
eernstgCommit Queue
authored andcommitted
Report type parameters whose name is a type in scope
See #59517 for background and discussion. Change-Id: I6d02d07cdd95e8b2b647cf3f7f87a9f27a1c48b3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381841 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Commit-Queue: Erik Ernst <[email protected]> Reviewed-by: Paul Berry <[email protected]>
1 parent 2454982 commit 2f0cbeb

File tree

8 files changed

+159
-11
lines changed

8 files changed

+159
-11
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1985,7 +1985,11 @@ LintCode.avoid_type_to_string:
19851985
notes: |-
19861986
Theoretically we could replace some uses with `is` checks, but it's probably
19871987
uncommon enough to not be worth the cost.
1988-
LintCode.avoid_types_as_parameter_names:
1988+
LintCode.avoid_types_as_parameter_names_type_parameter:
1989+
status: noFix
1990+
notes: |-
1991+
The type variable needs to be renamed, but that's a refactoring.
1992+
LintCode.avoid_types_as_parameter_names_formal_parameter:
19891993
status: hasFix
19901994
LintCode.avoid_types_on_closure_parameters:
19911995
status: hasFix

pkg/analysis_server/lib/src/services/correction/fix_internal.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,9 @@ final _builtInLintGenerators = <LintCode, List<ProducerGenerator>>{
311311
// diagnostics and should also be available as an assist.
312312
ReplaceCascadeWithDot.new,
313313
],
314-
LinterLintCode.avoid_types_as_parameter_names: [ConvertToOnType.new],
314+
LinterLintCode.avoid_types_as_parameter_names_formal_parameter: [
315+
ConvertToOnType.new,
316+
],
315317
LinterLintCode.avoid_types_on_closure_parameters: [
316318
ReplaceWithIdentifier.new,
317319
RemoveTypeAnnotation.other,

pkg/analyzer/lib/src/fine/library_manifest.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,9 @@ class LibraryManifestBuilder {
708708

709709
/// Returns either the existing item from [declaredItems], or builds a new one.
710710
Item _getOrBuildElementItem<
711-
Element extends ElementImpl2,
711+
E extends ElementImpl2,
712712
Item extends ManifestItem
713-
>(Element element, Item Function() build) {
713+
>(E element, Item Function() build) {
714714
// We assume that when matching elements against the structure of
715715
// the item, we put into [itemMap] only the type of the item that
716716
// corresponds the type of the element.

pkg/analyzer/test/generated/error_suppression_test.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,13 @@ int y = (0 as int);
234234
// ignore: type=wrong
235235
void f(arg1(int)) {} // AVOID_TYPES_AS_PARAMETER_NAMES
236236
''',
237-
[error(LinterLintCode.avoid_types_as_parameter_names, 34, 3)],
237+
[
238+
error(
239+
LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
240+
34,
241+
3,
242+
),
243+
],
238244
);
239245
}
240246

pkg/linter/lib/src/lint_codes.g.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,13 +360,25 @@ class LinterLintCode extends LintCode {
360360
hasPublishedDocs: true,
361361
);
362362

363-
static const LintCode avoid_types_as_parameter_names = LinterLintCode(
363+
static const LintCode
364+
avoid_types_as_parameter_names_formal_parameter = LinterLintCode(
364365
LintNames.avoid_types_as_parameter_names,
365366
"The parameter name '{0}' matches a visible type name.",
366367
correctionMessage:
367368
"Try adding a name for the parameter or changing the parameter name to "
368369
"not match an existing type.",
369370
hasPublishedDocs: true,
371+
uniqueName: 'avoid_types_as_parameter_names_formal_parameter',
372+
);
373+
374+
static const LintCode
375+
avoid_types_as_parameter_names_type_parameter = LinterLintCode(
376+
LintNames.avoid_types_as_parameter_names,
377+
"The type parameter name '{0}' matches a visible type name.",
378+
correctionMessage:
379+
"Try changing the type parameter name to not match an existing type.",
380+
hasPublishedDocs: true,
381+
uniqueName: 'avoid_types_as_parameter_names_type_parameter',
370382
);
371383

372384
static const LintCode avoid_types_on_closure_parameters = LinterLintCode(

pkg/linter/lib/src/rules/avoid_types_as_parameter_names.dart

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ class AvoidTypesAsParameterNames extends LintRule {
1919
: super(name: LintNames.avoid_types_as_parameter_names, description: _desc);
2020

2121
@override
22-
LintCode get lintCode => LinterLintCode.avoid_types_as_parameter_names;
22+
List<LintCode> get lintCodes => [
23+
LinterLintCode.avoid_types_as_parameter_names_type_parameter,
24+
LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
25+
];
2326

2427
@override
2528
void registerNodeProcessors(
@@ -29,6 +32,7 @@ class AvoidTypesAsParameterNames extends LintRule {
2932
var visitor = _Visitor(this, context);
3033
registry.addFormalParameterList(this, visitor);
3134
registry.addCatchClause(this, visitor);
35+
registry.addTypeParameterList(this, visitor);
3236
}
3337
}
3438

@@ -42,7 +46,12 @@ class _Visitor extends SimpleAstVisitor<void> {
4246
void visitCatchClause(CatchClause node) {
4347
var parameter = node.exceptionParameter;
4448
if (parameter != null && _isTypeName(node, parameter.name)) {
45-
rule.reportAtNode(parameter, arguments: [parameter.name.lexeme]);
49+
rule.reportAtNode(
50+
parameter,
51+
arguments: [parameter.name.lexeme],
52+
errorCode:
53+
LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
54+
);
4655
}
4756
}
4857

@@ -56,19 +65,76 @@ class _Visitor extends SimpleAstVisitor<void> {
5665
declaredElement.hasImplicitType &&
5766
name != null &&
5867
_isTypeName(node, name)) {
59-
rule.reportAtToken(name, arguments: [name.lexeme]);
68+
rule.reportAtToken(
69+
name,
70+
arguments: [name.lexeme],
71+
errorCode:
72+
LinterLintCode.avoid_types_as_parameter_names_formal_parameter,
73+
);
6074
}
6175
}
6276
}
6377

64-
bool _isTypeName(AstNode scope, Token name) {
78+
@override
79+
void visitTypeParameterList(TypeParameterList node) {
80+
for (var typeParameter in node.typeParameters) {
81+
var declaredElement = typeParameter.declaredFragment?.element;
82+
var name = typeParameter.name;
83+
var scope = node.parent;
84+
var isShadowedByTypeParameter = false;
85+
// Step out into enclosing scope where this type parameter isn't
86+
// itself in scope (the type parameter doesn't shadow itself).
87+
while (scope != null) {
88+
if (scope is ClassDeclaration ||
89+
scope is FunctionDeclaration ||
90+
(scope is FunctionExpression &&
91+
scope.parent is! FunctionDeclaration) ||
92+
scope is GenericFunctionType ||
93+
scope is MethodDeclaration) {
94+
if (scope is MethodDeclaration) {
95+
isShadowedByTypeParameter = true;
96+
}
97+
scope = scope.parent;
98+
break;
99+
}
100+
scope = scope.parent;
101+
}
102+
if (declaredElement != null &&
103+
scope != null &&
104+
_isTypeName(
105+
scope,
106+
name,
107+
isShadowedByMethodTypeParameter: isShadowedByTypeParameter,
108+
)) {
109+
rule.reportAtToken(
110+
name,
111+
arguments: [name.lexeme],
112+
errorCode:
113+
LinterLintCode.avoid_types_as_parameter_names_type_parameter,
114+
);
115+
}
116+
}
117+
}
118+
119+
bool _isTypeName(
120+
AstNode scope,
121+
Token name, {
122+
bool isShadowedByMethodTypeParameter = false,
123+
}) {
65124
var result = resolveNameInScope(
66125
name.lexeme,
67126
scope,
68127
shouldResolveSetter: false,
69128
);
70129
if (result.isRequestedName) {
71130
var element = result.element;
131+
if (isShadowedByMethodTypeParameter && element is TypeParameterElement) {
132+
// A type parameter of a static method can only shadow another type
133+
// parameter when the latter is a type parameter of the enclosing
134+
// declaration (class, mixin, mixin class, enum, extension, or
135+
// extension type). We do not lint this case.
136+
return false;
137+
}
72138
return element is ClassElement ||
73139
element is ExtensionTypeElement ||
74140
element is TypeAliasElement ||

pkg/linter/messages.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2347,11 +2347,17 @@ LintCode:
23472347
return getThingFromDatabase(key: myThing.thingTypeKey);
23482348
}
23492349
```
2350-
avoid_types_as_parameter_names:
2350+
avoid_types_as_parameter_names_formal_parameter:
2351+
sharedName: avoid_types_as_parameter_names
23512352
problemMessage: "The parameter name '{0}' matches a visible type name."
23522353
correctionMessage: "Try adding a name for the parameter or changing the parameter name to not match an existing type."
2354+
hasPublishedDocs: true
2355+
avoid_types_as_parameter_names_type_parameter:
2356+
sharedName: avoid_types_as_parameter_names
2357+
problemMessage: "The type parameter name '{0}' matches a visible type name."
23532358
state:
23542359
stable: "2.0"
2360+
correctionMessage: "Try changing the type parameter name to not match an existing type."
23552361
categories: [unintentional]
23562362
hasPublishedDocs: true
23572363
documentation: |-
@@ -2368,6 +2374,11 @@ LintCode:
23682374
shadow the existing type, which can lead to bugs that are difficult to
23692375
diagnose.
23702376
2377+
The analyzer also produces this diagnostic when the name of a type
2378+
parameter in a type parameter list is the same as a type whose name is
2379+
in scope. It is again recommended that the type parameter is renamed
2380+
such that the error-prone shadowing is avoided.
2381+
23712382
#### Example
23722383
23732384
The following code produces this diagnostic because the function `f` has a

pkg/linter/test/rules/avoid_types_as_parameter_names_test.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ void f() {
3030
);
3131
}
3232

33+
test_classTypeParameter_instanceMethodTypeParameter() async {
34+
await assertNoDiagnostics(r'''
35+
class A<X> {
36+
X instanceMethod<X>(X x) => x;
37+
}
38+
''');
39+
}
40+
41+
test_classTypeParameter_staticMethodTypeParameter() async {
42+
await assertNoDiagnostics(r'''
43+
class A<X> {
44+
static X staticMethod<X>(X x) => x;
45+
}
46+
''');
47+
}
48+
3349
test_extensionType() async {
3450
await assertDiagnostics(
3551
r'''
@@ -201,6 +217,37 @@ typedef T = int Function(int);
201217
''');
202218
}
203219

220+
test_typeParameter_class() async {
221+
await assertDiagnostics(
222+
r'''
223+
class A<Object> {}
224+
''',
225+
[lint(8, 6)],
226+
);
227+
}
228+
229+
test_typeParameter_function() async {
230+
await assertDiagnostics(
231+
r'''
232+
void f<int>() {}
233+
''',
234+
[lint(7, 3)],
235+
);
236+
}
237+
238+
test_typeParameter_instanceMethod_class() async {
239+
await assertDiagnostics(
240+
r'''
241+
class A<X> {
242+
C instanceMethod<C>(C c) => c;
243+
}
244+
245+
class C {}
246+
''',
247+
[lint(32, 1)],
248+
);
249+
}
250+
204251
test_typeParameter_wildcard() async {
205252
await assertDiagnostics(
206253
r'''

0 commit comments

Comments
 (0)