diff --git a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart index caa280939a6..332f707e352 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart @@ -538,9 +538,9 @@ class _ContextTypeVisitor extends SimpleAstVisitor { 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 { @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; } diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_class_to_mixin.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_class_to_mixin.dart index eb2cda24aa2..4b936e66e22 100644 --- a/pkg/analysis_server/lib/src/services/correction/dart/convert_class_to_mixin.dart +++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_class_to_mixin.dart @@ -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 = []; var interfaces = []; - 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 { - final List referencedClasses = []; + final List referencedClasses = []; _SuperclassReferenceFinder(); @@ -94,9 +95,9 @@ class _SuperclassReferenceFinder extends RecursiveAstVisitor { 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 { 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); } } diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart index 103d9b57764..257208195ce 100644 --- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart +++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart @@ -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 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 typeParameters, {ExecutableElement? methodBeingCopied}) { @@ -776,6 +914,22 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder { } } + @override + void writeTypeParameters2(List 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? 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 typeArguments, @@ -1347,6 +1669,45 @@ class DartEditBuilderImpl extends EditBuilderImpl implements DartEditBuilder { } } + void _writeTypeElementArguments2({ + required Element element, + required List 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; } diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart index 1e569793a6d..c82f370a87f 100644 --- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart +++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart @@ -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 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 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 typeParameters, + {ExecutableElement2? methodBeingCopied}); + /// Writes the code for a comma-separated list of [types], optionally prefixed /// by a [prefix]. ///