Skip to content

Commit cbdae14

Browse files
author
Ilya Yanok
committed
Revert "linter: Refactor prefer_collection_literals to use context type more"
This reverts commit cd8a337. Reason for revert: lint starts barking at the wrong tree: b/298917960 Original change's description: > linter: Refactor prefer_collection_literals to use context type more > > There is a basic premise in this rule which we cannot satisfy exactly: > we need to disallow `LinkedHashSet()` unless the context type requires > the developer to use `LinkedHashSet`. But the context type is long > gone when the lint rule is run. > > This CL adds some logic to try to attempt figuring out the context > type in the cases where users have filed bugs, but it will never be > super accurate. > > Fixes https://github.com/dart-lang/linter/issues/4736 > Fixes https://github.com/dart-lang/linter/issues/3057 > Fixes https://github.com/dart-lang/linter/issues/1649 > Fixes https://github.com/dart-lang/linter/issues/2795 > > Change-Id: I3e6c6de81084dca2825488c89830ab3e7ea63186 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/323680 > Reviewed-by: Phil Quitslund <[email protected]> > Reviewed-by: Konstantin Shcheglov <[email protected]> > Commit-Queue: Samuel Rawlins <[email protected]> Change-Id: I980053dd51ffd4447721e0ad7436b07ca704b554 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/324021 Bot-Commit: Rubber Stamper <[email protected]> Reviewed-by: Alexander Thomas <[email protected]> Commit-Queue: Ilya Yanok <[email protected]>
1 parent 44bcd89 commit cbdae14

File tree

5 files changed

+93
-256
lines changed

5 files changed

+93
-256
lines changed

pkg/analysis_server/test/src/services/correction/fix/convert_to_map_literal_test.dart

+22
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,28 @@ var m = Map<String, int>();
8585
''');
8686
await assertHasFix('''
8787
var m = <String, int>{};
88+
''');
89+
}
90+
91+
Future<void> test_typedef() async {
92+
await resolveTestCode('''
93+
typedef M = Map<String, int>;
94+
var m = M();
95+
''');
96+
await assertHasFix('''
97+
typedef M = Map<String, int>;
98+
var m = <String, int>{};
99+
''');
100+
}
101+
102+
Future<void> test_typedef_declaredType() async {
103+
await resolveTestCode('''
104+
typedef M = Map<String, int>;
105+
Map<String, int> m = M();
106+
''');
107+
await assertHasFix('''
108+
typedef M = Map<String, int>;
109+
Map<String, int> m = {};
88110
''');
89111
}
90112
}

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

+65-144
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44

55
import 'package:analyzer/dart/ast/ast.dart';
66
import 'package:analyzer/dart/ast/visitor.dart';
7-
import 'package:analyzer/dart/element/element.dart';
87
import 'package:analyzer/dart/element/type.dart';
9-
import 'package:analyzer/dart/element/type_provider.dart';
10-
// ignore: implementation_imports
11-
import 'package:analyzer/src/dart/element/type.dart' show InvalidTypeImpl;
128

139
import '../analyzer.dart';
1410
import '../extensions.dart';
@@ -36,8 +32,8 @@ var coordinates = <int, int>{};
3632
3733
**EXCEPTIONS:**
3834
39-
When a `LinkedHashSet` or `LinkedHashMap` is expected, a collection literal is
40-
not preferred (or allowed).
35+
There are cases with `LinkedHashSet` or `LinkedHashMap` where a literal constructor
36+
will trigger a type error so those will be excluded from the lint.
4137
4238
```dart
4339
void main() {
@@ -76,49 +72,40 @@ class PreferCollectionLiterals extends LintRule {
7672
@override
7773
void registerNodeProcessors(
7874
NodeLintRegistry registry, LinterContext context) {
79-
var visitor = _Visitor(this, context.typeProvider);
75+
var visitor = _Visitor(this);
8076
registry.addInstanceCreationExpression(this, visitor);
8177
registry.addMethodInvocation(this, visitor);
8278
}
8379
}
8480

8581
class _Visitor extends SimpleAstVisitor<void> {
8682
final LintRule rule;
87-
final TypeProvider typeProvider;
88-
_Visitor(this.rule, this.typeProvider);
83+
_Visitor(this.rule);
8984

9085
@override
9186
void visitInstanceCreationExpression(InstanceCreationExpression node) {
9287
var constructorName = node.constructorName.name?.name;
9388

94-
if (node.constructorName.type.element is TypeAliasElement) {
95-
// Allow the use of typedef constructors.
96-
return;
97-
}
98-
9989
// Maps.
100-
if (node.isHashMap) {
101-
var approximateContextType = _approximateContextType(node);
102-
if (approximateContextType is InvalidType) return;
103-
if (approximateContextType.isTypeHashMap) return;
104-
}
105-
if (node.isMap || node.isHashMap) {
90+
if (_isMap(node) || _isHashMap(node)) {
91+
if (_shouldSkipLinkedHashLint(node, _isTypeHashMap)) {
92+
return;
93+
}
10694
if (constructorName == null && node.argumentList.arguments.isEmpty) {
10795
rule.reportLint(node);
10896
}
10997
return;
11098
}
11199

112100
// Sets.
113-
if (node.isHashSet) {
114-
var approximateContextType = _approximateContextType(node);
115-
if (approximateContextType is InvalidType) return;
116-
if (approximateContextType.isTypeHashSet) return;
117-
}
118-
if (node.isSet || node.isHashSet) {
101+
if (_isSet(node) || _isHashSet(node)) {
102+
if (_shouldSkipLinkedHashLint(node, _isTypeHashSet)) {
103+
return;
104+
}
105+
119106
var args = node.argumentList.arguments;
120107
if (constructorName == null) {
121-
// Allow `LinkedHashSet(equals: (a, b) => false, hashCode: (o) => 13)`.
108+
// Skip: LinkedHashSet(equals: (a, b) => false, hashCode: (o) => 13)
122109
if (args.isEmpty) {
123110
rule.reportLint(node);
124111
}
@@ -135,7 +122,7 @@ class _Visitor extends SimpleAstVisitor<void> {
135122

136123
@override
137124
void visitMethodInvocation(MethodInvocation node) {
138-
// Something like `['foo', 'bar', 'baz'].toSet()`.
125+
// ['foo', 'bar', 'baz'].toSet();
139126
if (node.methodName.name != 'toSet') {
140127
return;
141128
}
@@ -144,130 +131,64 @@ class _Visitor extends SimpleAstVisitor<void> {
144131
}
145132
}
146133

147-
/// A very, very rough approximation of the context type of [node].
148-
///
149-
/// This approximation will never be accurate for some expressions.
150-
DartType? _approximateContextType(Expression node) {
151-
var ancestor = node.parent;
152-
var ancestorChild = node;
153-
while (ancestor != null) {
154-
if (ancestor is ParenthesizedExpression) {
155-
ancestorChild = ancestor;
156-
ancestor = ancestor.parent;
157-
} else if (ancestor is CascadeExpression &&
158-
ancestorChild == ancestor.target) {
159-
ancestorChild = ancestor;
160-
ancestor = ancestor.parent;
161-
} else {
162-
break;
163-
}
164-
}
134+
bool _isHashMap(Expression expression) =>
135+
_isTypeHashMap(expression.staticType);
165136

166-
switch (ancestor) {
167-
// TODO(srawlins): Handle [AwaitExpression], [BinaryExpression],
168-
// [CascadeExpression], [ConditionalExpression], [SwitchExpressionCase],
169-
// likely others. Or move everything here to an analysis phase which
170-
// has the actual context type.
171-
case ArgumentList():
172-
// Allow `function(LinkedHashSet())` for `function(LinkedHashSet mySet)`
173-
// and `function(LinkedHashMap())` for `function(LinkedHashMap myMap)`.
174-
return node.staticParameterElement?.type ?? InvalidTypeImpl.instance;
175-
case AssignmentExpression():
176-
// Allow `x = LinkedHashMap()`.
177-
return ancestor.staticType;
178-
case ExpressionFunctionBody(parent: var function)
179-
when function is FunctionExpression:
180-
// Allow `<int, LinkedHashSet>{}.putIfAbsent(3, () => LinkedHashSet())`
181-
// and `<int, LinkedHashMap>{}.putIfAbsent(3, () => LinkedHashMap())`.
182-
var functionType = _approximateContextType(function);
183-
return functionType is FunctionType ? functionType.returnType : null;
184-
case NamedExpression():
185-
// Allow `void f({required LinkedHashSet<Foo> s})`.
186-
return ancestor.staticParameterElement?.type ??
187-
InvalidTypeImpl.instance;
188-
case ReturnStatement():
189-
return _expectedReturnType(
190-
ancestor.thisOrAncestorOfType<FunctionBody>(),
191-
);
192-
case VariableDeclaration(parent: VariableDeclarationList(:var type)):
193-
// Allow `LinkedHashSet<int> s = node` and
194-
// `LinkedHashMap<int> s = node`.
195-
return type?.type;
196-
case YieldStatement():
197-
return _expectedReturnType(
198-
ancestor.thisOrAncestorOfType<FunctionBody>(),
199-
);
200-
}
137+
bool _isHashSet(Expression expression) =>
138+
_isTypeHashSet(expression.staticType);
201139

202-
return null;
203-
}
140+
bool _isMap(Expression expression) =>
141+
expression.staticType?.isDartCoreMap ?? false;
204142

205-
/// Extracts the expected type for return statements or yield statements.
206-
///
207-
/// For example, for an asynchronous [body] in a function with a declared
208-
/// [returnType] of `Future<int>`, this returns `int`. (Note: it would be more
209-
/// accurate to use `FutureOr<int>` and an assignability check, but `int` is
210-
/// an approximation that works for now; this should probably be revisited.)
211-
DartType? _expectedReturnableOrYieldableType(
212-
DartType? returnType,
213-
FunctionBody body,
214-
) {
215-
if (returnType == null || returnType is InvalidType) return null;
216-
if (body.isAsynchronous) {
217-
if (!body.isGenerator && returnType.isDartAsyncFuture) {
218-
var typeArgs = (returnType as InterfaceType).typeArguments;
219-
return typeArgs.isEmpty ? null : typeArgs.first;
143+
bool _isSet(Expression expression) =>
144+
expression.staticType?.isDartCoreSet ?? false;
145+
146+
bool _isTypeHashMap(DartType? type) =>
147+
type.isSameAs('LinkedHashMap', 'dart.collection');
148+
149+
bool _isTypeHashSet(DartType? type) =>
150+
type.isSameAs('LinkedHashSet', 'dart.collection');
151+
152+
bool _shouldSkipLinkedHashLint(
153+
InstanceCreationExpression node, bool Function(DartType node) typeCheck) {
154+
if (_isHashMap(node) || _isHashSet(node)) {
155+
// Skip: LinkedHashSet<int> s = ...; or LinkedHashMap<int> s = ...;
156+
var parent = node.parent;
157+
if (parent is VariableDeclaration) {
158+
var parent2 = parent.parent;
159+
if (parent2 is VariableDeclarationList) {
160+
var assignmentType = parent2.type?.type;
161+
if (assignmentType != null && typeCheck(assignmentType)) {
162+
return true;
163+
}
164+
}
220165
}
221-
if (body.isGenerator && returnType.isDartAsyncStream) {
222-
var typeArgs = (returnType as InterfaceType).typeArguments;
223-
return typeArgs.isEmpty ? null : typeArgs.first;
166+
// Skip: function(LinkedHashSet()); when function(LinkedHashSet mySet) or
167+
// function(LinkedHashMap()); when function(LinkedHashMap myMap)
168+
if (parent is ArgumentList) {
169+
var paramType = node.staticParameterElement?.type;
170+
if (paramType == null || typeCheck(paramType)) {
171+
return true;
172+
}
224173
}
225-
} else {
226-
if (body.isGenerator && returnType.isDartCoreIterable) {
227-
var typeArgs = (returnType as InterfaceType).typeArguments;
228-
return typeArgs.isEmpty ? null : typeArgs.first;
174+
175+
// Skip: void f({required LinkedHashSet<Foo> s})
176+
if (parent is NamedExpression) {
177+
var paramType = parent.staticParameterElement?.type;
178+
if (paramType != null && typeCheck(paramType)) {
179+
return true;
180+
}
229181
}
230-
}
231-
return returnType;
232-
}
233182

234-
/// Attempts to calculate the expected return type of the function represented
235-
/// by [body], accounting for an approximation of the function's context type,
236-
/// in the case of a function literal.
237-
DartType? _expectedReturnType(FunctionBody? body) {
238-
if (body == null) return null;
239-
var parent = body.parent;
240-
if (parent is FunctionExpression) {
241-
var grandparent = parent.parent;
242-
if (grandparent is FunctionDeclaration) {
243-
var returnType = grandparent.declaredElement?.returnType;
244-
return _expectedReturnableOrYieldableType(returnType, body);
183+
// Skip: <int, LinkedHashSet>{}.putIfAbsent(3, () => LinkedHashSet());
184+
// or <int, LinkedHashMap>{}.putIfAbsent(3, () => LinkedHashMap());
185+
if (parent is ExpressionFunctionBody) {
186+
var expressionType = parent.expression.staticType;
187+
if (expressionType != null && typeCheck(expressionType)) {
188+
return true;
189+
}
245190
}
246-
var functionType = _approximateContextType(parent);
247-
if (functionType is! FunctionType) return null;
248-
var returnType = functionType.returnType;
249-
return _expectedReturnableOrYieldableType(returnType, body);
250191
}
251-
if (parent is MethodDeclaration) {
252-
var returnType = parent.declaredElement?.returnType;
253-
return _expectedReturnableOrYieldableType(returnType, body);
254-
}
255-
return null;
192+
return false;
256193
}
257194
}
258-
259-
extension on Expression {
260-
bool get isHashMap => staticType.isTypeHashMap;
261-
262-
bool get isHashSet => staticType.isTypeHashSet;
263-
264-
bool get isMap => staticType?.isDartCoreMap ?? false;
265-
266-
bool get isSet => staticType?.isDartCoreSet ?? false;
267-
}
268-
269-
extension on DartType? {
270-
bool get isTypeHashMap => isSameAs('LinkedHashMap', 'dart.collection');
271-
272-
bool get isTypeHashSet => isSameAs('LinkedHashSet', 'dart.collection');
273-
}

0 commit comments

Comments
 (0)