Elements. Migrate ConvertClassToMixin.

Adds element2 helpers to `DartEditBuilder`.

Change-Id: I3bf7123cf15f9023f81c2b7ffcb9a7753b4ecdd5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/387100
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2024-09-26 19:15:50 +00:00 committed by Commit Queue
parent 13d84fea3f
commit 87076710e4
4 changed files with 448 additions and 18 deletions

View file

@ -538,9 +538,9 @@ class _ContextTypeVisitor extends SimpleAstVisitor<DartType> {
if (node.operator.type == TokenType.EQ) {
return node.writeType;
}
var method = node.staticElement;
var method = node.element;
if (method != null) {
var parameters = method.parameters;
var parameters = method.formalParameters;
if (parameters.isNotEmpty) {
return parameters[0].type;
}
@ -748,10 +748,8 @@ class _ContextTypeVisitor extends SimpleAstVisitor<DartType> {
@override
DartType? visitIndexExpression(IndexExpression node) {
if (range.endStart(node.leftBracket, node.rightBracket).contains(offset)) {
var parameters = node.staticElement?.parameters;
if (parameters != null && parameters.isNotEmpty) {
return parameters[0].type;
}
var formalParameters = node.element?.formalParameters;
return formalParameters?.firstOrNull?.type;
}
return null;
}

View file

@ -6,7 +6,7 @@ import 'package:analysis_server/src/services/correction/assist.dart';
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer_plugin/utilities/assist/assist.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@ -48,9 +48,10 @@ class ConvertClassToMixin extends ResolvedCorrectionProducer {
var superclassConstraints = <InterfaceType>[];
var interfaces = <InterfaceType>[];
var classElement = classDeclaration.declaredElement!;
var classFragment = classDeclaration.declaredFragment!;
var classElement = classFragment.element;
for (var type in classElement.mixins) {
if (referencedClasses.contains(type.element)) {
if (referencedClasses.contains(type.element3)) {
superclassConstraints.add(type);
} else {
interfaces.add(type);
@ -74,7 +75,7 @@ class ConvertClassToMixin extends ResolvedCorrectionProducer {
classDeclaration.leftBracket), (builder) {
builder.write('mixin ');
builder.write(classDeclaration.name.lexeme);
builder.writeTypeParameters(classElement.typeParameters);
builder.writeTypeParameters2(classElement.typeParameters2);
builder.writeTypes(superclassConstraints, prefix: ' on ');
builder.writeTypes(interfaces, prefix: ' implements ');
builder.write(' ');
@ -86,7 +87,7 @@ class ConvertClassToMixin extends ResolvedCorrectionProducer {
/// A visitor used to find all of the classes that define members referenced via
/// `super`.
class _SuperclassReferenceFinder extends RecursiveAstVisitor<void> {
final List<ClassElement> referencedClasses = <ClassElement>[];
final List<ClassElement2> referencedClasses = [];
_SuperclassReferenceFinder();
@ -94,9 +95,9 @@ class _SuperclassReferenceFinder extends RecursiveAstVisitor<void> {
void visitSuperExpression(SuperExpression node) {
var parent = node.parent;
if (parent is BinaryExpression) {
_addElement(parent.staticElement);
_addElement(parent.element);
} else if (parent is IndexExpression) {
_addElement(parent.staticElement);
_addElement(parent.element);
} else if (parent is MethodInvocation) {
_addIdentifier(parent.methodName);
} else if (parent is PrefixedIdentifier) {
@ -107,16 +108,16 @@ class _SuperclassReferenceFinder extends RecursiveAstVisitor<void> {
return super.visitSuperExpression(node);
}
void _addElement(Element? element) {
if (element is ExecutableElement) {
var enclosingElement = element.enclosingElement3;
if (enclosingElement is ClassElement) {
void _addElement(Element2? element) {
if (element is ExecutableElement2) {
var enclosingElement = element.enclosingElement2;
if (enclosingElement is ClassElement2) {
referencedClasses.add(enclosingElement);
}
}
}
void _addIdentifier(SimpleIdentifier identifier) {
_addElement(identifier.staticElement);
_addElement(identifier.element);
}
}

View file

@ -7,6 +7,7 @@ import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
@ -44,11 +45,22 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
/// This field is lazily initialized in [_initializeEnclosingElements].
ClassElement? _enclosingClass;
/// The enclosing class element, or `null` if the region that will be modified
/// by the edit isn't inside a class declaration.
///
/// This field is lazily initialized in [_initializeEnclosingElements].
ClassElement2? _enclosingClass2;
/// The enclosing executable element, possibly `null`.
///
/// This field is lazily initialized in [_initializeEnclosingElements].
ExecutableElement? _enclosingExecutable;
/// The enclosing executable element, possibly `null`.
///
/// This field is lazily initialized in [_initializeEnclosingElements].
ExecutableElement2? _enclosingExecutable2;
/// If not `null`, [write] will copy everything into this buffer.
StringBuffer? _carbonCopyBuffer;
@ -223,6 +235,122 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
write(';');
}
@override
void writeFormalParameter(String name,
{bool isCovariant = false,
bool isRequiredNamed = false,
ExecutableElement2? methodBeingCopied,
String? nameGroupName,
DartType? type,
String? typeGroupName,
bool isRequiredType = false}) {
bool writeType() {
if (typeGroupName != null) {
late bool hasType;
addLinkedEdit(typeGroupName, (DartLinkedEditBuilder builder) {
hasType = _writeType2(type,
methodBeingCopied: methodBeingCopied, required: isRequiredType);
builder.addSuperTypesAsSuggestions(type);
});
return hasType;
}
return _writeType2(type, methodBeingCopied: methodBeingCopied);
}
void writeName() {
if (nameGroupName != null) {
addLinkedEdit(nameGroupName, (DartLinkedEditBuilder builder) {
write(name);
});
} else {
write(name);
}
}
if (isCovariant) {
write('covariant ');
}
if (isRequiredNamed) {
write('required ');
}
if (type != null) {
var hasType = writeType();
if (name.isNotEmpty) {
if (hasType) {
write(' ');
}
writeName();
}
} else {
writeName();
}
}
@override
void writeFormalParameters(Iterable<FormalParameterElement> parameters,
{ExecutableElement2? methodBeingCopied,
bool includeDefaultValues = true,
bool requiredTypes = false}) {
var parameterNames = {
for (var parameter in parameters.where((p) => p.name.isNotEmpty))
parameter.name,
};
write('(');
var sawNamed = false;
var sawPositional = false;
for (var i = 0; i < parameters.length; i++) {
var parameter = parameters.elementAt(i);
if (i > 0) {
write(', ');
}
// Might be optional.
if (parameter.isNamed) {
if (!sawNamed) {
write('{');
sawNamed = true;
}
} else if (parameter.isOptionalPositional) {
if (!sawPositional) {
write('[');
sawPositional = true;
}
}
// Parameter.
var name = parameter.name;
if (name.isEmpty) {
name = _generateUniqueName(parameterNames, 'p');
parameterNames.add(name);
}
var groupPrefix =
methodBeingCopied != null ? '${methodBeingCopied.name}:' : '';
writeFormalParameter(name,
isCovariant: parameter.isCovariant,
isRequiredNamed: parameter.isRequiredNamed,
methodBeingCopied: methodBeingCopied,
nameGroupName: parameter.isNamed ? null : '${groupPrefix}PARAM$i',
type: parameter.type,
typeGroupName: '${groupPrefix}TYPE$i',
isRequiredType: requiredTypes);
// default value
if (includeDefaultValues) {
var defaultCode = parameter.defaultValueCode;
if (defaultCode != null) {
write(' = ');
write(defaultCode);
}
}
}
// close parameters
if (sawNamed) {
write('}');
}
if (sawPositional) {
write(']');
}
write(')');
}
@override
void writeFunctionDeclaration(String name,
{void Function()? bodyWriter,
@ -761,6 +889,16 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
}
}
@override
void writeTypeParameter2(TypeParameterElement2 typeParameter,
{ExecutableElement2? methodBeingCopied}) {
write(typeParameter.name);
if (typeParameter.bound != null) {
write(' extends ');
_writeType2(typeParameter.bound, methodBeingCopied: methodBeingCopied);
}
}
@override
void writeTypeParameters(List<TypeParameterElement> typeParameters,
{ExecutableElement? methodBeingCopied}) {
@ -776,6 +914,22 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
}
}
@override
void writeTypeParameters2(List<TypeParameterElement2> typeParameters,
{ExecutableElement2? methodBeingCopied}) {
if (typeParameters.isNotEmpty) {
write('<');
writeTypeParameter2(typeParameters.first,
methodBeingCopied: methodBeingCopied);
for (var typeParameter in typeParameters.skip(1)) {
write(', ');
writeTypeParameter2(typeParameter,
methodBeingCopied: methodBeingCopied);
}
write('>');
}
}
@override
void writeTypes(Iterable<DartType>? types, {String? prefix}) {
if (types == null || types.isEmpty) {
@ -1108,13 +1262,47 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
return type;
}
/// If the given [type] is visible in either the [_enclosingExecutable] or
/// [_enclosingClass], or if there is a local equivalent to the type (such as
/// in the case of a type parameter from a superclass), then returns the type
/// that is locally visible. Otherwise, return `null`.
DartType? _getVisibleType2(DartType? type,
{ExecutableElement2? methodBeingCopied}) {
if (type is InterfaceType) {
var element = type.element;
if (element.isPrivate &&
!dartFileEditBuilder._isDefinedLocally(element)) {
return null;
}
return type;
}
if (type is TypeParameterType) {
_initializeEnclosingElements();
var element = type.element3;
var enclosing = element.enclosingElement2;
while (enclosing is GenericFunctionTypeElement2 ||
enclosing is FormalParameterElement) {
enclosing = enclosing!.enclosingElement2;
}
if (enclosing == _enclosingExecutable2 ||
enclosing == _enclosingClass2 ||
enclosing == methodBeingCopied) {
return type;
}
return null;
}
return type;
}
/// Initializes the [_enclosingClass] and [_enclosingExecutable].
void _initializeEnclosingElements() {
if (!_hasEnclosingElementsInitialized) {
var finder = _EnclosingElementFinder();
finder.find(dartFileEditBuilder.resolvedUnit.unit, offset);
_enclosingClass = finder.enclosingClass;
_enclosingClass2 = finder.enclosingClass2;
_enclosingExecutable = finder.enclosingExecutable;
_enclosingExecutable2 = finder.enclosingExecutable2;
_hasEnclosingElementsInitialized = true;
}
}
@ -1308,6 +1496,140 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
throw UnimplementedError('(${type.runtimeType}) $type');
}
/// Writes the code to reference [type] in this compilation unit.
///
/// If a [methodBeingCopied] is provided, then the type parameters of that
/// method will be duplicated in the copy and will therefore be visible.
///
/// If [required] it `true`, then the type will be written even if it would
/// normally be omitted, such as with `dynamic`.
///
/// Causes any libraries whose elements are used by the generated code, to be
/// imported.
bool _writeType2(DartType? type,
{ExecutableElement2? methodBeingCopied, bool required = false}) {
type = _getVisibleType2(type, methodBeingCopied: methodBeingCopied);
// If not a useful type, don't write it.
if (type == null) {
return false;
}
if (type is DynamicType || type is InvalidType) {
if (required) {
write('dynamic');
return true;
}
return false;
}
if (type.isBottom) {
write('Never');
return true;
}
var alias = type.alias;
if (alias != null) {
_writeTypeElementArguments2(
element: alias.element,
typeArguments: alias.typeArguments,
methodBeingCopied: methodBeingCopied,
);
_writeTypeNullability(type);
return true;
}
if (type is FunctionType) {
if (_writeType2(type.returnType, methodBeingCopied: methodBeingCopied)) {
write(' ');
}
write('Function');
writeTypeParameters2(type.typeParameters,
methodBeingCopied: methodBeingCopied);
writeFormalParameters(
type.formalParameters,
methodBeingCopied: methodBeingCopied,
includeDefaultValues: false,
requiredTypes: true,
);
if (type.nullabilitySuffix == NullabilitySuffix.question) {
write('?');
}
return true;
}
if (type is InterfaceType) {
_writeTypeElementArguments2(
element: type.element,
typeArguments: type.typeArguments,
methodBeingCopied: methodBeingCopied,
);
_writeTypeNullability(type);
return true;
}
if (type is NeverType) {
write('Never');
_writeTypeNullability(type);
return true;
}
if (type is TypeParameterType) {
write(type.element.name);
_writeTypeNullability(type);
return true;
}
if (type is VoidType) {
write('void');
return true;
}
if (type is RecordType) {
// TODO(brianwilkerson): This should return `false` if the `records`
// feature is not enabled. More importantly, we can't currently return
// `false` if some portion of a type has already been written, so we
// need to figure out what to do when a record type is nested in another
// type in a context where it isn't allowed. For example, we might
// enhance `_canWriteType` to be recursive, then guard all invocations of
// this method with a call to `_canWriteType` (and remove the return type
// from this method).
write('(');
var isFirst = true;
for (var field in type.positionalFields) {
if (isFirst) {
isFirst = false;
} else {
write(', ');
}
_writeType(field.type);
}
var namedFields = type.namedFields;
if (namedFields.isNotEmpty) {
if (isFirst) {
write('{');
} else {
write(', {');
}
isFirst = true;
for (var field in namedFields) {
if (isFirst) {
isFirst = false;
} else {
write(', ');
}
_writeType(field.type);
write(' ');
write(field.name);
}
write('}');
}
write(')');
_writeTypeNullability(type);
return true;
}
throw UnimplementedError('(${type.runtimeType}) $type');
}
void _writeTypeElementArguments({
required Element element,
required List<DartType> typeArguments,
@ -1347,6 +1669,45 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder {
}
}
void _writeTypeElementArguments2({
required Element element,
required List<DartType> typeArguments,
required ExecutableElement2? methodBeingCopied,
}) {
// Ensure that the element is imported.
_writeLibraryReference(element);
// Write the simple name.
var name = element.displayName;
write(name);
// Write type arguments.
if (typeArguments.isNotEmpty) {
// Check if has arguments.
var hasArguments = false;
var allArgumentsVisible = true;
for (var argument in typeArguments) {
hasArguments = hasArguments || argument is! DynamicType;
allArgumentsVisible = allArgumentsVisible &&
_getVisibleType2(argument, methodBeingCopied: methodBeingCopied) !=
null;
}
// Write type arguments only if they are useful.
if (hasArguments && allArgumentsVisible) {
write('<');
for (var i = 0; i < typeArguments.length; i++) {
var argument = typeArguments[i];
if (i != 0) {
write(', ');
}
_writeType2(argument,
required: true, methodBeingCopied: methodBeingCopied);
}
write('>');
}
}
}
void _writeTypeNullability(DartType type) {
if (type.nullabilitySuffix == NullabilitySuffix.question) {
write('?');
@ -2406,7 +2767,9 @@ class ImportLibraryElementResultImpl implements ImportLibraryElementResult {
class _EnclosingElementFinder {
ClassElement? enclosingClass;
ClassElement2? enclosingClass2;
ExecutableElement? enclosingExecutable;
ExecutableElement2? enclosingExecutable2;
_EnclosingElementFinder();
@ -2415,12 +2778,16 @@ class _EnclosingElementFinder {
while (node != null) {
if (node is ClassDeclaration) {
enclosingClass = node.declaredElement;
enclosingClass2 = node.declaredFragment?.element;
} else if (node is ConstructorDeclaration) {
enclosingExecutable = node.declaredElement;
enclosingExecutable2 = node.declaredFragment?.element;
} else if (node is MethodDeclaration) {
enclosingExecutable = node.declaredElement;
enclosingExecutable2 = node.declaredFragment?.element;
} else if (node is FunctionDeclaration) {
enclosingExecutable = node.declaredElement;
enclosingExecutable2 = node.declaredFragment?.element;
}
node = node.parent;
}

View file

@ -5,6 +5,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/element2.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_provider.dart';
import 'package:analyzer/dart/element/type_system.dart';
@ -107,6 +108,50 @@ abstract class DartEditBuilder implements EditBuilder {
DartType? type,
String? typeGroupName});
/// Writes the code for a single parameter with the given [name].
///
/// If a [methodBeingCopied] is provided, then type parameters defined by that
/// method are assumed to be part of what is being written and hence valid
/// types.
///
/// If a [type] is provided, then it will be used as the type of the
/// parameter.
///
/// If a [nameGroupName] is provided, then the name of the parameter will be
/// included in a linked edit.
///
/// If a [type] and [typeGroupName] are both provided, then the type of the
/// parameter will be included in a linked edit.
///
/// If [isCovariant] is `true` then the keyword `covariant` will be included
/// in the parameter declaration.
///
/// If [isRequiredNamed] is `true` then the keyword `required` will be
/// included in the parameter declaration.
///
/// If [isRequiredType] is `true` then the type is always written.
void writeFormalParameter(String name,
{bool isCovariant,
bool isRequiredNamed,
ExecutableElement2? methodBeingCopied,
String? nameGroupName,
DartType? type,
String? typeGroupName,
bool isRequiredType});
/// Writes the code for a list of [parameters], including the surrounding
/// parentheses and default values (unless [includeDefaultValues] is `false`).
///
/// If a [methodBeingCopied] is provided, then type parameters defined by that
/// method are assumed to be part of what is being written and hence valid
/// types.
///
/// If [requiredTypes] is `true`, then the types are always written.
void writeFormalParameters(Iterable<FormalParameterElement> parameters,
{ExecutableElement2? methodBeingCopied,
bool includeDefaultValues = true,
bool requiredTypes});
/// Writes the code for a declaration of a function with the given [name].
///
/// If a [bodyWriter] is provided, it will be invoked to write the body of the
@ -319,6 +364,16 @@ abstract class DartEditBuilder implements EditBuilder {
void writeTypeParameter(TypeParameterElement typeParameter,
{ExecutableElement? methodBeingCopied});
/// Writes the code to declare the given [typeParameter].
///
/// The enclosing angle brackets are not automatically written.
///
/// If a [methodBeingCopied] is provided, then type parameters defined by that
/// method are assumed to be part of what is being written and hence valid
/// types.
void writeTypeParameter2(TypeParameterElement2 typeParameter,
{ExecutableElement2? methodBeingCopied});
/// Writes the code to declare the given list of [typeParameters]. The
/// enclosing angle brackets are automatically written.
///
@ -328,6 +383,15 @@ abstract class DartEditBuilder implements EditBuilder {
void writeTypeParameters(List<TypeParameterElement> typeParameters,
{ExecutableElement? methodBeingCopied});
/// Writes the code to declare the given list of [typeParameters]. The
/// enclosing angle brackets are automatically written.
///
/// If a [methodBeingCopied] is provided, then type parameters defined by that
/// method are assumed to be part of what is being written and hence valid
/// types.
void writeTypeParameters2(List<TypeParameterElement2> typeParameters,
{ExecutableElement2? methodBeingCopied});
/// Writes the code for a comma-separated list of [types], optionally prefixed
/// by a [prefix].
///