Skip to content

Commit 35b09df

Browse files
kallentuCommit Queue
authored andcommitted
[analysis_server] Dot shorthands: Update ReplaceWithVar fix and assist.
When the initializer of a variable declaration is exactly a dot shorthand, we can insert the declared type in front of the dot shorthand to ensure that we're retaining the necessary context type. `E e = .a;` -> `var e = E.a;` In other cases such as typed literals and constructor invocations, the correction already does what we need it to. When we're looking at a for-each and the iterable is a typed literal, if there's a dependent dot shorthand, we add explicit type arguments to the literal to retain the context type. Added tests for the fix and the assist which has the same code path. Bug: #60957, #60994 Change-Id: I31a05f10a7cdaae731c6f6c59deae12c2543a4a6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/441320 Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Kallen Tu <[email protected]>
1 parent 82a01bb commit 35b09df

File tree

3 files changed

+522
-19
lines changed

3 files changed

+522
-19
lines changed

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

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
88
import 'package:analyzer/dart/ast/ast.dart';
99
import 'package:analyzer/dart/element/type.dart';
1010
import 'package:analyzer/src/dart/ast/extensions.dart';
11+
import 'package:analyzer/src/utilities/dot_shorthands.dart';
1112
import 'package:analyzer_plugin/utilities/assist/assist.dart';
1213
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
1314
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
@@ -56,36 +57,42 @@ class ReplaceWithVar extends ResolvedCorrectionProducer {
5657
return;
5758
}
5859
var initializer = variables[0].initializer;
59-
String? typeArgumentsText;
60-
int? typeArgumentsOffset;
61-
if (type is NamedType) {
60+
String? insertionText;
61+
int? insertionOffset;
62+
if (initializer != null && isDotShorthand(initializer)) {
63+
// Inserts the type before the dot shorthand (e.g. `E.a` where type is
64+
// `E`) because we erase the required context type when we replace the
65+
// declared type with `var`.
66+
insertionText = utils.getNodeText(type);
67+
insertionOffset = initializer.beginToken.offset;
68+
} else if (type is NamedType) {
6269
var typeArguments = type.typeArguments;
6370
if (typeArguments != null) {
6471
if (initializer is CascadeExpression) {
6572
initializer = initializer.target;
6673
}
6774
if (initializer is TypedLiteral) {
6875
if (initializer.typeArguments == null) {
69-
typeArgumentsText = utils.getNodeText(typeArguments);
76+
insertionText = utils.getNodeText(typeArguments);
7077
if (initializer is ListLiteral) {
71-
typeArgumentsOffset = initializer.leftBracket.offset;
78+
insertionOffset = initializer.leftBracket.offset;
7279
} else if (initializer is SetOrMapLiteral) {
73-
typeArgumentsOffset = initializer.leftBracket.offset;
80+
insertionOffset = initializer.leftBracket.offset;
7481
} else {
7582
throw StateError('Unhandled subclass of TypedLiteral');
7683
}
7784
}
7885
} else if (initializer is InstanceCreationExpression) {
7986
if (initializer.constructorName.type.typeArguments == null) {
80-
typeArgumentsText = utils.getNodeText(typeArguments);
81-
typeArgumentsOffset = initializer.constructorName.type.end;
87+
insertionText = utils.getNodeText(typeArguments);
88+
insertionOffset = initializer.constructorName.type.end;
8289
}
8390
}
8491
}
8592
}
8693
if (initializer is SetOrMapLiteral &&
8794
initializer.typeArguments == null &&
88-
typeArgumentsText == null) {
95+
insertionText == null) {
8996
// This is to prevent the fix from converting a valid map or set literal
9097
// into an ambiguous literal. We could apply this in more places
9198
// by examining the elements of the collection.
@@ -94,8 +101,8 @@ class ReplaceWithVar extends ResolvedCorrectionProducer {
94101
await builder.addDartFileEdit(file, (builder) {
95102
builder.addSimpleReplacement(range.node(type), 'var');
96103

97-
if (typeArgumentsText != null && typeArgumentsOffset != null) {
98-
builder.addSimpleInsertion(typeArgumentsOffset, typeArgumentsText);
104+
if (insertionText != null && insertionOffset != null) {
105+
builder.addSimpleInsertion(insertionOffset, insertionText);
99106
}
100107
});
101108
} else if (parent is DeclaredIdentifier &&
@@ -105,15 +112,21 @@ class ReplaceWithVar extends ResolvedCorrectionProducer {
105112
return;
106113
}
107114

108-
String? typeArgumentsText;
109-
int? typeArgumentsOffset;
110-
if (type is NamedType) {
115+
String? insertionText;
116+
int? insertionOffset;
117+
var iterable = grandparent.iterable;
118+
if (hasDependentDotShorthand(iterable) && iterable is TypedLiteral) {
119+
// If there's a dependent shorthand in the literal, we need to
120+
// insert explicit type arguments to ensure we have an appropriate
121+
// context type to resolve the dot shorthand.
122+
insertionText = '<${utils.getNodeText(type)}>';
123+
insertionOffset = iterable.beginToken.offset;
124+
} else if (type is NamedType) {
111125
var typeArguments = type.typeArguments;
112126
if (typeArguments != null) {
113-
var iterable = grandparent.iterable;
114127
if (iterable is TypedLiteral && iterable.typeArguments == null) {
115-
typeArgumentsText = utils.getNodeText(typeArguments);
116-
typeArgumentsOffset = iterable.offset;
128+
insertionText = utils.getNodeText(typeArguments);
129+
insertionOffset = iterable.offset;
117130
}
118131
}
119132
}
@@ -123,8 +136,8 @@ class ReplaceWithVar extends ResolvedCorrectionProducer {
123136
} else {
124137
builder.addSimpleReplacement(range.node(type), 'var');
125138
}
126-
if (typeArgumentsText != null && typeArgumentsOffset != null) {
127-
builder.addSimpleInsertion(typeArgumentsOffset, typeArgumentsText);
139+
if (insertionText != null && insertionOffset != null) {
140+
builder.addSimpleInsertion(insertionOffset, insertionText);
128141
}
129142
});
130143
}

pkg/analysis_server/test/src/services/correction/assist/replace_with_var_test.dart

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,135 @@ void f(List<int> list) {
5353
''');
5454
}
5555

56+
Future<void> test_forEach_dotShorthands_functionType() async {
57+
await resolveTestCode('''
58+
enum E { a, b, c }
59+
void f() {
60+
for (E Fun^ction() e in [() => .a]) {
61+
print(e);
62+
}
63+
}
64+
''');
65+
await assertHasAssist('''
66+
enum E { a, b, c }
67+
void f() {
68+
for (var e in <E Function()>[() => .a]) {
69+
print(e);
70+
}
71+
}
72+
''');
73+
}
74+
75+
Future<void> test_forEach_dotShorthands_generic_nested() async {
76+
await resolveTestCode('''
77+
enum E { a, b, c }
78+
79+
T ff<T>(T t, E e) => t;
80+
81+
void f() {
82+
for (^E e in [ff(ff(.b, .b), .b)]) {
83+
print(e);
84+
}
85+
}
86+
''');
87+
await assertHasAssist('''
88+
enum E { a, b, c }
89+
90+
T ff<T>(T t, E e) => t;
91+
92+
void f() {
93+
for (var e in <E>[ff(ff(.b, .b), .b)]) {
94+
print(e);
95+
}
96+
}
97+
''');
98+
}
99+
100+
Future<void>
101+
test_forEach_dotShorthands_generic_nested_explicitTypeArguments() async {
102+
await resolveTestCode('''
103+
enum E { a, b, c }
104+
105+
T ff<T>(T t, E e) => t;
106+
107+
X fun<U, X>(U u, X x) => x;
108+
109+
void f() {
110+
for (^int e in [fun(ff<E>(.a, E.a), 2)]) {
111+
print(e);
112+
}
113+
}
114+
''');
115+
await assertHasAssist('''
116+
enum E { a, b, c }
117+
118+
T ff<T>(T t, E e) => t;
119+
120+
X fun<U, X>(U u, X x) => x;
121+
122+
void f() {
123+
for (var e in [fun(ff<E>(.a, E.a), 2)]) {
124+
print(e);
125+
}
126+
}
127+
''');
128+
}
129+
130+
Future<void> test_forEach_dotShorthands_list() async {
131+
await resolveTestCode('''
132+
enum E { a }
133+
void f() {
134+
for (^E e in [.a]) {
135+
print(e);
136+
}
137+
}
138+
''');
139+
await assertHasAssist('''
140+
enum E { a }
141+
void f() {
142+
for (var e in <E>[.a]) {
143+
print(e);
144+
}
145+
}
146+
''');
147+
}
148+
149+
Future<void> test_forEach_dotShorthands_set() async {
150+
await resolveTestCode('''
151+
enum E { a }
152+
void f() {
153+
for (^E e in {.a}) {
154+
print(e);
155+
}
156+
}
157+
''');
158+
await assertHasAssist('''
159+
enum E { a }
160+
void f() {
161+
for (var e in <E>{.a}) {
162+
print(e);
163+
}
164+
}
165+
''');
166+
}
167+
168+
Future<void> test_generic_instanceCreation_cascade_dotShorthand() async {
169+
await resolveTestCode('''
170+
enum E { a }
171+
Set f() {
172+
Se^t<E> s = { .a }..addAll([]);
173+
return s;
174+
}
175+
''');
176+
await assertHasAssist('''
177+
enum E { a }
178+
Set f() {
179+
var s = <E>{ .a }..addAll([]);
180+
return s;
181+
}
182+
''');
183+
}
184+
56185
Future<void> test_generic_instanceCreation_withArguments() async {
57186
await resolveTestCode('''
58187
C<int> f() {
@@ -102,6 +231,23 @@ List f() {
102231
''');
103232
}
104233

234+
Future<void> test_generic_listLiteral_dotShorthand() async {
235+
await resolveTestCode('''
236+
enum E { a, b }
237+
List f() {
238+
Li^st<E> l = [.a, .b];
239+
return l;
240+
}
241+
''');
242+
await assertHasAssist('''
243+
enum E { a, b }
244+
List f() {
245+
var l = <E>[.a, .b];
246+
return l;
247+
}
248+
''');
249+
}
250+
105251
Future<void> test_generic_mapLiteral() async {
106252
await resolveTestCode('''
107253
Map f() {
@@ -185,6 +331,61 @@ String f() {
185331
var s = '';
186332
return s;
187333
}
334+
''');
335+
}
336+
337+
Future<void> test_simple_dotShorthand_constructorInvocation() async {
338+
await resolveTestCode('''
339+
class E {}
340+
E f() {
341+
^E e = .new();
342+
return e;
343+
}
344+
''');
345+
await assertHasAssist('''
346+
class E {}
347+
E f() {
348+
var e = E.new();
349+
return e;
350+
}
351+
''');
352+
}
353+
354+
Future<void> test_simple_dotShorthand_methodInvocation() async {
355+
await resolveTestCode('''
356+
class E {
357+
static E method() => E();
358+
}
359+
E f() {
360+
^E e = .method();
361+
return e;
362+
}
363+
''');
364+
await assertHasAssist('''
365+
class E {
366+
static E method() => E();
367+
}
368+
E f() {
369+
var e = E.method();
370+
return e;
371+
}
372+
''');
373+
}
374+
375+
Future<void> test_simple_dotShorthand_propertyAccess() async {
376+
await resolveTestCode('''
377+
enum E { a }
378+
E f() {
379+
^E e = .a;
380+
return e;
381+
}
382+
''');
383+
await assertHasAssist('''
384+
enum E { a }
385+
E f() {
386+
var e = E.a;
387+
return e;
388+
}
188389
''');
189390
}
190391
}

0 commit comments

Comments
 (0)