Skip to content

Commit 002bcd0

Browse files
eernstgCommit Queue
authored and
Commit Queue
committed
Add lint omit_obvious_local_variable_types
This CL adds an implementation of a lint named 'omit_obvious_local_variable_types' which is a variant of 'omit_local_variable_types' that only flags type annotations of local variables when the static type of the initializing expression is obvious, as defined in https://github.com/dart-lang/linter/issues/3480. Change-Id: I7e9b5adde5839ad8f9dfa88833da82d7bc1b65de Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374601 Commit-Queue: Erik Ernst <[email protected]> Reviewed-by: Phil Quitslund <[email protected]>
1 parent 75617a1 commit 002bcd0

File tree

8 files changed

+587
-3
lines changed

8 files changed

+587
-3
lines changed

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -2202,6 +2202,11 @@ LintCode.null_closures:
22022202
status: hasFix
22032203
LintCode.omit_local_variable_types:
22042204
status: hasFix
2205+
LintCode.omit_obvious_local_variable_types:
2206+
status: needsEvaluation
2207+
notes: |-
2208+
The fix `ReplaceWithVar` which is used with omit_local_variable_types
2209+
should work for this one as well.
22052210
LintCode.one_member_abstracts:
22062211
status: noFix
22072212
notes: |-
@@ -3755,4 +3760,4 @@ WarningCode.UNUSED_SHOWN_NAME:
37553760
WarningCode.URI_DOES_NOT_EXIST_IN_DOC_IMPORT:
37563761
status: needsFix
37573762
notes: |-
3758-
The same fix as CompileTimeErrorCode.URI_DOES_NOT_EXIST
3763+
The same fix as CompileTimeErrorCode.URI_DOES_NOT_EXIST

pkg/linter/example/all.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ linter:
109109
- null_check_on_nullable_type_parameter
110110
- null_closures
111111
- omit_local_variable_types
112+
- omit_obvious_local_variable_types
112113
- one_member_abstracts
113114
- only_throw_errors
114115
- overridden_fields

pkg/linter/lib/src/rules.dart

+2
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ import 'rules/noop_primitive_operations.dart';
119119
import 'rules/null_check_on_nullable_type_parameter.dart';
120120
import 'rules/null_closures.dart';
121121
import 'rules/omit_local_variable_types.dart';
122+
import 'rules/omit_obvious_local_variable_types.dart';
122123
import 'rules/one_member_abstracts.dart';
123124
import 'rules/only_throw_errors.dart';
124125
import 'rules/overridden_fields.dart';
@@ -358,6 +359,7 @@ void registerLintRules() {
358359
..register(NullCheckOnNullableTypeParameter())
359360
..register(NullClosures())
360361
..register(OmitLocalVariableTypes())
362+
..register(OmitObviousLocalVariableTypes())
361363
..register(OneMemberAbstracts())
362364
..register(OnlyThrowErrors())
363365
..register(OverriddenFields())

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,11 @@ class AlwaysSpecifyTypes extends LintRule {
7373
categories: {Category.style});
7474

7575
@override
76-
List<String> get incompatibleRules =>
77-
const ['avoid_types_on_closure_parameters', 'omit_local_variable_types'];
76+
List<String> get incompatibleRules => const [
77+
'avoid_types_on_closure_parameters',
78+
'omit_local_variable_types',
79+
'omit_obvious_local_variable_types',
80+
];
7881

7982
@override
8083
LintCode get lintCode => code;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/ast/visitor.dart';
7+
import 'package:analyzer/dart/element/type.dart';
8+
9+
import '../analyzer.dart';
10+
import '../extensions.dart';
11+
12+
const _desc = r'Omit obvious type annotations for local variables.';
13+
14+
const _details = r'''
15+
Don't type annotate initialized local variables when the type is obvious.
16+
17+
Local variables, especially in modern code where functions tend to be small,
18+
have very little scope. Omitting the type focuses the reader's attention on the
19+
more important *name* of the variable and its initialized value. Hence, local
20+
variable type annotations that are obvious should be omitted.
21+
22+
**BAD:**
23+
```dart
24+
List<List<Ingredient>> possibleDesserts(Set<Ingredient> pantry) {
25+
List<List<Ingredient>> desserts = <List<Ingredient>>[];
26+
for (List<Ingredient> recipe in cookbook) {
27+
if (pantry.containsAll(recipe)) {
28+
desserts.add(recipe);
29+
}
30+
}
31+
32+
return desserts;
33+
}
34+
```
35+
36+
**GOOD:**
37+
```dart
38+
List<List<Ingredient>> possibleDesserts(Set<Ingredient> pantry) {
39+
var desserts = <List<Ingredient>>[];
40+
for (List<Ingredient> recipe in cookbook) {
41+
if (pantry.containsAll(recipe)) {
42+
desserts.add(recipe);
43+
}
44+
}
45+
46+
return desserts;
47+
}
48+
```
49+
50+
Sometimes the inferred type is not the type you want the variable to have. For
51+
example, you may intend to assign values of other types later. You may also
52+
wish to write a type annotation explicitly because the type of the initializing
53+
expression is non-obvious and it will be helpful for future readers of the
54+
code to document this type. Or you may wish to commit to a specific type such
55+
that future updates of dependencies (in nearby code, in imports, anywhere)
56+
will not silently change the type of that variable, thus introducing
57+
compile-time errors or run-time bugs in locations where this variable is used.
58+
In those cases, go ahead and annotate the variable with the type you want.
59+
60+
**GOOD:**
61+
```dart
62+
Widget build(BuildContext context) {
63+
Widget result = someGenericFunction(42) ?? Text('You won!');
64+
if (applyPadding) {
65+
result = Padding(padding: EdgeInsets.all(8.0), child: result);
66+
}
67+
return result;
68+
}
69+
```
70+
71+
**This rule is experimental.** It is being evaluated, and it may be changed
72+
or removed. Feedback on its behavior is welcome! The main issue is here:
73+
https://github.com/dart-lang/linter/issues/3480.
74+
''';
75+
76+
class OmitObviousLocalVariableTypes extends LintRule {
77+
static const LintCode code = LintCode('omit_obvious_local_variable_types',
78+
'Unnecessary and obvious type annotation on a local variable.',
79+
correctionMessage: 'Try removing the type annotation.');
80+
81+
OmitObviousLocalVariableTypes()
82+
: super(
83+
name: 'omit_obvious_local_variable_types',
84+
description: _desc,
85+
details: _details,
86+
state: State.experimental(),
87+
categories: {Category.style});
88+
89+
@override
90+
List<String> get incompatibleRules => const ['always_specify_types'];
91+
92+
@override
93+
LintCode get lintCode => code;
94+
95+
@override
96+
void registerNodeProcessors(
97+
NodeLintRegistry registry, LinterContext context) {
98+
var visitor = _Visitor(this);
99+
registry.addForStatement(this, visitor);
100+
registry.addVariableDeclarationStatement(this, visitor);
101+
}
102+
}
103+
104+
class _Visitor extends SimpleAstVisitor<void> {
105+
final LintRule rule;
106+
107+
_Visitor(this.rule);
108+
109+
@override
110+
void visitForStatement(ForStatement node) {
111+
var loopParts = node.forLoopParts;
112+
if (loopParts is ForPartsWithDeclarations) {
113+
_visitVariableDeclarationList(loopParts.variables);
114+
} else if (loopParts is ForEachPartsWithDeclaration) {
115+
var loopVariableType = loopParts.loopVariable.type;
116+
var staticType = loopVariableType?.type;
117+
if (staticType == null || staticType is DynamicType) {
118+
return;
119+
}
120+
var iterable = loopParts.iterable;
121+
if (!iterable.hasObviousType) {
122+
return;
123+
}
124+
var iterableType = iterable.staticType;
125+
if (iterableType.elementTypeOfIterable == staticType) {
126+
rule.reportLint(loopVariableType);
127+
}
128+
}
129+
}
130+
131+
@override
132+
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
133+
_visitVariableDeclarationList(node.variables);
134+
}
135+
136+
void _visitVariableDeclarationList(VariableDeclarationList node) {
137+
var staticType = node.type?.type;
138+
if (staticType == null ||
139+
staticType is DynamicType ||
140+
staticType.isDartCoreNull) {
141+
return;
142+
}
143+
for (var child in node.variables) {
144+
var initializer = child.initializer;
145+
if (initializer != null && !initializer.hasObviousType) {
146+
return;
147+
}
148+
if (initializer?.staticType != staticType) {
149+
return;
150+
}
151+
}
152+
rule.reportLint(node.type);
153+
}
154+
}
155+
156+
extension on CollectionElement {
157+
DartType? get elementType {
158+
var self = this; // Enable promotion.
159+
switch (self) {
160+
case MapLiteralEntry():
161+
return null;
162+
case ForElement():
163+
// No need to compute the type of a non-obvious element.
164+
return null;
165+
case IfElement():
166+
// We just need a candidate type, ignore `else`.
167+
return self.thenElement.elementType;
168+
case Expression():
169+
return self.staticType;
170+
case SpreadElement():
171+
return self.expression.staticType.elementTypeOfIterable;
172+
}
173+
}
174+
175+
bool get hasObviousType {
176+
var self = this; // Enable promotion.
177+
switch (self) {
178+
case MapLiteralEntry():
179+
return self.key.hasObviousType && self.value.hasObviousType;
180+
case ForElement():
181+
return false;
182+
case IfElement():
183+
return self.thenElement.hasObviousType &&
184+
(self.elseElement?.hasObviousType ?? true);
185+
case Expression():
186+
return self.hasObviousType;
187+
case SpreadElement():
188+
return self.expression.hasObviousType;
189+
}
190+
}
191+
}
192+
193+
extension on DartType? {
194+
DartType? get elementTypeOfIterable {
195+
var self = this; // Enable promotion.
196+
if (self == null) return null;
197+
if (self is InterfaceType) {
198+
var iterableInterfaces =
199+
self.implementedInterfaces.where((type) => type.isDartCoreIterable);
200+
if (iterableInterfaces.length == 1) {
201+
return iterableInterfaces.first.typeArguments.first;
202+
}
203+
}
204+
return null;
205+
}
206+
}
207+
208+
extension on Expression {
209+
bool get hasObviousType {
210+
var self = this; // Enable promotion.
211+
switch (self) {
212+
case TypedLiteral():
213+
if (self.typeArguments != null) {
214+
// A collection literal with explicit type arguments is trivial.
215+
return true;
216+
}
217+
// A collection literal with no explicit type arguments.
218+
var anyElementIsObvious = false;
219+
DartType? theObviousType;
220+
NodeList<CollectionElement> elements = switch (self) {
221+
ListLiteral() => self.elements,
222+
SetOrMapLiteral() => self.elements
223+
};
224+
for (var element in elements) {
225+
if (element.hasObviousType) {
226+
if (anyElementIsObvious) {
227+
continue;
228+
}
229+
anyElementIsObvious = true;
230+
theObviousType = element.elementType;
231+
}
232+
}
233+
if (anyElementIsObvious) {
234+
var theSelfElementType = self.staticType.elementTypeOfIterable;
235+
return theSelfElementType == theObviousType;
236+
}
237+
return false;
238+
case Literal():
239+
// An atomic literal: `Literal` and not `TypedLiteral`.
240+
if (self is IntegerLiteral &&
241+
(self.staticType?.isDartCoreDouble ?? false)) {
242+
return false;
243+
}
244+
return true;
245+
case InstanceCreationExpression():
246+
var createdType = self.constructorName.type;
247+
if (createdType.typeArguments != null) {
248+
// Explicit type arguments provided.
249+
return true;
250+
} else {
251+
DartType? dartType = createdType.type;
252+
if (dartType != null) {
253+
if (dartType is InterfaceType &&
254+
dartType.element.typeParameters.isNotEmpty) {
255+
// A raw type is not trivial.
256+
return false;
257+
}
258+
// A non-generic class or extension type.
259+
return true;
260+
} else {
261+
// An unknown type is not trivial.
262+
return false;
263+
}
264+
}
265+
case CascadeExpression():
266+
return self.target.hasObviousType;
267+
}
268+
return false;
269+
}
270+
}

pkg/linter/test/rules/all.dart

+3
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ import 'null_check_on_nullable_type_parameter_test.dart'
147147
as null_check_on_nullable_type_parameter;
148148
import 'null_closures_test.dart' as null_closures;
149149
import 'omit_local_variable_types_test.dart' as omit_local_variable_types;
150+
import 'omit_obvious_local_variable_types_test.dart'
151+
as omit_obvious_local_variable_types;
150152
import 'one_member_abstracts_test.dart' as one_member_abstracts;
151153
import 'only_throw_errors_test.dart' as only_throw_errors;
152154
import 'overridden_fields_test.dart' as overridden_fields;
@@ -393,6 +395,7 @@ void main() {
393395
null_check_on_nullable_type_parameter.main();
394396
null_closures.main();
395397
omit_local_variable_types.main();
398+
omit_obvious_local_variable_types.main();
396399
one_member_abstracts.main();
397400
only_throw_errors.main();
398401
overridden_fields.main();

0 commit comments

Comments
 (0)