Skip to content

Commit 58ff654

Browse files
FMorschelCommit Queue
authored andcommitted
[DAS] Adds 'Create extension method/operator' fixes
[email protected] Fixes: #60203 Change-Id: I2527c130dd68cf678ac4529eda42397ef80ca53c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412060 Commit-Queue: Phil Quitslund <[email protected]> Reviewed-by: Phil Quitslund <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]> Auto-Submit: Felipe Morschel <[email protected]> Commit-Queue: Samuel Rawlins <[email protected]>
1 parent 91f5aad commit 58ff654

File tree

7 files changed

+860
-242
lines changed

7 files changed

+860
-242
lines changed

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

Lines changed: 250 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
import 'package:analysis_server/src/services/correction/fix.dart';
66
import 'package:analysis_server/src/services/correction/util.dart';
77
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
8+
import 'package:analyzer/dart/ast/token.dart';
9+
import 'package:analyzer/dart/element/element2.dart';
810
import 'package:analyzer/dart/element/type.dart';
911
import 'package:analyzer/src/dart/ast/ast.dart';
12+
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
1013
import 'package:analyzer/src/dart/element/type.dart';
1114
import 'package:analyzer/src/dart/resolver/applicable_extensions.dart';
1215
import 'package:analyzer/src/utilities/extensions/ast.dart';
1316
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
1417
import 'package:analyzer_plugin/utilities/change_builder/change_builder_dart.dart';
1518
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
19+
import 'package:collection/collection.dart';
1620

1721
class CreateExtensionGetter extends _CreateExtensionMember {
1822
String _getterName = '';
@@ -53,6 +57,8 @@ class CreateExtensionGetter extends _CreateExtensionMember {
5357
return;
5458
}
5559

60+
// TODO(FMorschel): We should take into account if the target type contains
61+
// a setter for the same name and stop the fix from being applied.
5662
// We need the type for the extension.
5763
var targetType = target.staticType;
5864
if (targetType == null ||
@@ -67,7 +73,7 @@ class CreateExtensionGetter extends _CreateExtensionMember {
6773

6874
void writeGetter(DartEditBuilder builder) {
6975
if (fieldType != null) {
70-
builder.writeType(fieldType);
76+
builder.writeType(fieldType, methodBeingCopied: methodBeingCopied);
7177
builder.write(' ');
7278
}
7379
builder.write('get $_getterName => ');
@@ -89,7 +95,13 @@ class CreateExtensionGetter extends _CreateExtensionMember {
8995
return;
9096
}
9197

92-
await _addNewExtension(builder, targetType, nameNode, writeGetter);
98+
await _addNewExtension(
99+
builder,
100+
targetType,
101+
nameNode,
102+
writeGetter,
103+
involvedTypes: [fieldType],
104+
);
93105
}
94106
}
95107

@@ -137,16 +149,32 @@ class CreateExtensionMethod extends _CreateExtensionMember {
137149
var returnType = inferUndefinedExpressionType(invocation);
138150

139151
void writeMethod(DartEditBuilder builder) {
140-
if (builder.writeType(returnType, groupName: 'RETURN_TYPE')) {
152+
if (builder.writeType(
153+
returnType,
154+
groupName: 'RETURN_TYPE',
155+
methodBeingCopied: methodBeingCopied,
156+
)) {
141157
builder.write(' ');
142158
}
143159

144160
builder.addLinkedEdit('NAME', (builder) {
145161
builder.write(_methodName);
146162
});
147163

164+
builder.writeTypeParameters(
165+
[
166+
returnType,
167+
...invocation.argumentList.arguments.map((e) => e.staticType),
168+
].typeParameters
169+
.whereNot([targetType].typeParameters.contains)
170+
.toList(),
171+
);
172+
148173
builder.write('(');
149-
builder.writeParametersMatchingArguments(invocation.argumentList);
174+
builder.writeParametersMatchingArguments(
175+
invocation.argumentList,
176+
methodBeingCopied: methodBeingCopied,
177+
);
150178
builder.write(') {}');
151179
}
152180

@@ -166,6 +194,136 @@ class CreateExtensionMethod extends _CreateExtensionMember {
166194
}
167195
}
168196

197+
class CreateExtensionOperator extends _CreateExtensionMember {
198+
String _operator = '';
199+
200+
CreateExtensionOperator({required super.context});
201+
202+
@override
203+
List<String>? get fixArguments => [_operator];
204+
205+
@override
206+
FixKind get fixKind => DartFixKind.CREATE_EXTENSION_OPERATOR;
207+
208+
@override
209+
Future<void> compute(ChangeBuilder builder) async {
210+
bool indexSetter = false;
211+
bool innerParameter = true;
212+
var node = this.node;
213+
if (node is! Expression) {
214+
return;
215+
}
216+
217+
Expression? target;
218+
DartType? parameterType;
219+
DartType? assigningType;
220+
221+
switch (node) {
222+
case IndexExpression(:var parent):
223+
if (node.target?.staticType?.element3.declaresIndex ?? true) {
224+
return;
225+
}
226+
target = node.target;
227+
_operator = TokenType.INDEX.lexeme;
228+
parameterType = node.index.staticType;
229+
if (parameterType == null) {
230+
return;
231+
}
232+
if (parent case AssignmentExpression(
233+
:var leftHandSide,
234+
:var rightHandSide,
235+
) when leftHandSide == node) {
236+
assigningType = rightHandSide.staticType;
237+
indexSetter = true;
238+
_operator = TokenType.INDEX_EQ.lexeme;
239+
}
240+
case BinaryExpression():
241+
target = node.leftOperand;
242+
_operator = node.operator.lexeme;
243+
parameterType = node.rightOperand.staticType;
244+
if (parameterType == null) {
245+
return;
246+
}
247+
case PrefixExpression():
248+
target = node.operand;
249+
_operator = node.operator.lexeme;
250+
innerParameter = false;
251+
}
252+
253+
if (target == null) {
254+
return;
255+
}
256+
257+
// We need the type for the extension.
258+
var targetType = target.staticType;
259+
if (targetType == null ||
260+
targetType is DynamicType ||
261+
targetType is InvalidType) {
262+
return;
263+
}
264+
265+
DartType returnType;
266+
// If this is an index setter, the return type must be void.
267+
if (indexSetter) {
268+
returnType = VoidTypeImpl.instance;
269+
} else {
270+
// Try to find the return type.
271+
returnType = inferUndefinedExpressionType(node) ?? VoidTypeImpl.instance;
272+
}
273+
274+
void writeMethod(DartEditBuilder builder) {
275+
if (builder.writeType(
276+
returnType,
277+
groupName: 'RETURN_TYPE',
278+
methodBeingCopied: methodBeingCopied,
279+
)) {
280+
builder.write(' ');
281+
}
282+
283+
builder.write('operator ');
284+
builder.write(_operator);
285+
286+
builder.write('(');
287+
if (innerParameter) {
288+
builder.writeFormalParameter(
289+
indexSetter ? 'index' : 'other',
290+
type: parameterType,
291+
methodBeingCopied: methodBeingCopied,
292+
);
293+
}
294+
if (indexSetter) {
295+
builder.write(', ');
296+
builder.writeFormalParameter(
297+
'newValue',
298+
type: assigningType,
299+
methodBeingCopied: methodBeingCopied,
300+
);
301+
}
302+
builder.write(') {}');
303+
}
304+
305+
var updatedExisting = await _updateExistingExtension(builder, targetType, (
306+
extension,
307+
builder,
308+
) {
309+
builder.insertMethod(extension, (builder) {
310+
writeMethod(builder);
311+
});
312+
});
313+
if (updatedExisting) {
314+
return;
315+
}
316+
317+
await _addNewExtension(
318+
builder,
319+
targetType,
320+
target,
321+
writeMethod,
322+
involvedTypes: [parameterType, returnType],
323+
);
324+
}
325+
}
326+
169327
class CreateExtensionSetter extends _CreateExtensionMember {
170328
String _setterName = '';
171329

@@ -205,6 +363,8 @@ class CreateExtensionSetter extends _CreateExtensionMember {
205363
return;
206364
}
207365

366+
// TODO(FMorschel): We should take into account if the target type contains
367+
// a setter for the same name and stop the fix from being applied.
208368
// We need the type for the extension.
209369
var targetType = target.staticType;
210370
if (targetType == null ||
@@ -223,6 +383,7 @@ class CreateExtensionSetter extends _CreateExtensionMember {
223383
nameGroupName: 'NAME',
224384
parameterType: fieldType,
225385
parameterTypeGroupName: 'TYPE',
386+
methodBeingCopied: methodBeingCopied,
226387
);
227388
}
228389

@@ -238,7 +399,13 @@ class CreateExtensionSetter extends _CreateExtensionMember {
238399
return;
239400
}
240401

241-
await _addNewExtension(builder, targetType, nameNode, writeSetter);
402+
await _addNewExtension(
403+
builder,
404+
targetType,
405+
nameNode,
406+
writeSetter,
407+
involvedTypes: [fieldType],
408+
);
242409
}
243410
}
244411

@@ -251,24 +418,50 @@ abstract class _CreateExtensionMember extends ResolvedCorrectionProducer {
251418
return CorrectionApplicability.singleLocation;
252419
}
253420

421+
ExecutableElement2? get methodBeingCopied =>
422+
_enclosingFunction?.declaredFragment?.element;
423+
424+
FunctionDeclaration? get _enclosingFunction => node.thisOrAncestorOfType();
425+
426+
/// Creates a change for creating a new extension on the given [targetType].
427+
///
428+
/// The new extension should be added after the [nameNode].
429+
///
430+
/// The [write] function is used to write the body of the new extension.
431+
/// Meaning a method, getter, setter or operator.
432+
///
433+
/// The [involvedTypes] are the types that are used in the new extension and
434+
/// it's member. This is used to determine the type parameters of the new
435+
/// extension.
254436
Future<void> _addNewExtension(
255437
ChangeBuilder builder,
256438
DartType targetType,
257-
SimpleIdentifier nameNode,
258-
void Function(DartEditBuilder builder) write,
259-
) async {
439+
AstNode nameNode,
440+
void Function(DartEditBuilder builder) write, {
441+
List<DartType?> involvedTypes = const [],
442+
}) async {
260443
// The new extension should be added after it.
261444
var enclosingUnitChild = nameNode.enclosingUnitChild;
262445
if (enclosingUnitChild == null) {
263446
return;
264447
}
265448

449+
var extensionTypeParameters = [targetType, ...involvedTypes].typeParameters;
450+
266451
await builder.addDartFileEdit(file, (builder) {
267452
builder.addInsertion(enclosingUnitChild.end, (builder) {
268453
builder.writeln();
269454
builder.writeln();
270-
builder.write('extension on ');
271-
builder.writeType(targetType);
455+
builder.write('extension ');
456+
if (extensionTypeParameters.isNotEmpty) {
457+
builder.writeTypeParameters(
458+
extensionTypeParameters,
459+
methodBeingCopied: methodBeingCopied,
460+
);
461+
builder.write(' ');
462+
}
463+
builder.write('on ');
464+
builder.writeType(targetType, methodBeingCopied: methodBeingCopied);
272465
builder.writeln(' {');
273466
builder.write(' ');
274467
write(builder);
@@ -312,3 +505,50 @@ abstract class _CreateExtensionMember extends ResolvedCorrectionProducer {
312505
return true;
313506
}
314507
}
508+
509+
extension on List<DartType?> {
510+
/// Returns a list of type parameters that are used in the types.
511+
///
512+
/// Iterates over every type in the list:
513+
/// - If it is itself a [TypeParameterType], it is added to the list.
514+
/// - If it is itself a [TypeParameterType] and it has a
515+
/// [TypeParameterType.bound], we get the type parameters with this getter.
516+
/// - If it is an [InterfaceType], we get the [InterfaceType.typeArguments]
517+
/// it uses and get any type parameters they use by using this same getter.
518+
///
519+
/// These types are added internally to a set so that we don't add duplicates.
520+
List<TypeParameterElement2> get typeParameters =>
521+
{
522+
for (var type in whereType<TypeParameterType>()) ...[
523+
type.element3,
524+
...[type.bound].typeParameters,
525+
],
526+
for (var type in whereType<InterfaceType>())
527+
...type.typeArguments.typeParameters,
528+
}.toList();
529+
}
530+
531+
extension on Element2? {
532+
bool get declaresIndex {
533+
var element = this;
534+
if (element is! InterfaceElement2) {
535+
return false;
536+
}
537+
var inheritanceManager3 = InheritanceManager3();
538+
var interface = inheritanceManager3.getInterface2(element);
539+
var bracesGetter =
540+
interface.map2[Name.forLibrary(
541+
element.library2,
542+
TokenType.INDEX.lexeme,
543+
)];
544+
if (bracesGetter != null) {
545+
return true;
546+
}
547+
var bracesSetter =
548+
interface.map2[Name.forLibrary(
549+
element.library2,
550+
TokenType.INDEX_EQ.lexeme,
551+
)];
552+
return bracesSetter != null;
553+
}
554+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,11 @@ abstract final class DartFixKind {
758758
DartFixKindPriority.standard - 20,
759759
"Create extension method '{0}'",
760760
);
761+
static const CREATE_EXTENSION_OPERATOR = FixKind(
762+
'dart.fix.create.extension.operator',
763+
DartFixKindPriority.standard - 20,
764+
"Create extension operator '{0}'",
765+
);
761766
static const CREATE_EXTENSION_SETTER = FixKind(
762767
'dart.fix.create.extension.setter',
763768
DartFixKindPriority.standard - 20,

0 commit comments

Comments
 (0)