mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 16:31:07 +00:00
Issue 52176. Support for ojbect pattern in 'Create Getter' and 'CCreate Field'.
Bug: https://github.com/dart-lang/sdk/issues/52176 Change-Id: Ic9c97bec1981c5613b23206e872f6f708d53792d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/298920 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
0eee9f1392
commit
a0218e916c
|
@ -2,16 +2,17 @@
|
|||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
|
||||
import 'package:analysis_server/src/services/correction/dart/create_getter.dart';
|
||||
import 'package:analysis_server/src/services/correction/fix.dart';
|
||||
import 'package:analysis_server/src/services/correction/util.dart';
|
||||
import 'package:analysis_server/src/utilities/extensions/ast.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
|
||||
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
|
||||
|
||||
class CreateField extends CorrectionProducer {
|
||||
class CreateField extends CreateFieldOrGetter {
|
||||
/// The name of the field to be created.
|
||||
String _fieldName = '';
|
||||
|
||||
|
@ -21,8 +22,29 @@ class CreateField extends CorrectionProducer {
|
|||
@override
|
||||
FixKind get fixKind => DartFixKind.CREATE_FIELD;
|
||||
|
||||
@override
|
||||
Future<void> addForObjectPattern({
|
||||
required ChangeBuilder builder,
|
||||
required InterfaceElement? targetElement,
|
||||
required String fieldName,
|
||||
required DartType? fieldType,
|
||||
}) async {
|
||||
_fieldName = fieldName;
|
||||
|
||||
await _addDeclaration(
|
||||
builder: builder,
|
||||
staticModifier: false,
|
||||
targetElement: targetElement,
|
||||
fieldType: fieldType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> compute(ChangeBuilder builder) async {
|
||||
if (await compute0(builder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parameter = node.thisOrAncestorOfType<FieldFormalParameter>();
|
||||
if (parameter != null) {
|
||||
await _proposeFromFieldFormalParameter(builder, parameter);
|
||||
|
@ -31,6 +53,58 @@ class CreateField extends CorrectionProducer {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _addDeclaration({
|
||||
required ChangeBuilder builder,
|
||||
required bool staticModifier,
|
||||
required InterfaceElement? targetElement,
|
||||
required DartType? fieldType,
|
||||
}) async {
|
||||
if (targetElement == null) {
|
||||
return;
|
||||
}
|
||||
if (targetElement.library.isInSdk) {
|
||||
return;
|
||||
}
|
||||
utils.targetClassElement = targetElement;
|
||||
// prepare target ClassDeclaration
|
||||
var targetDeclarationResult =
|
||||
await sessionHelper.getElementDeclaration(targetElement);
|
||||
if (targetDeclarationResult == null) {
|
||||
return;
|
||||
}
|
||||
var targetNode = targetDeclarationResult.node;
|
||||
if (targetNode is! CompilationUnitMember) {
|
||||
return;
|
||||
}
|
||||
if (!(targetNode is ClassDeclaration || targetNode is MixinDeclaration)) {
|
||||
return;
|
||||
}
|
||||
// prepare location
|
||||
var targetUnit = targetDeclarationResult.resolvedUnit;
|
||||
if (targetUnit == null) {
|
||||
return;
|
||||
}
|
||||
var targetLocation =
|
||||
CorrectionUtils(targetUnit).prepareNewFieldLocation(targetNode);
|
||||
if (targetLocation == null) {
|
||||
return;
|
||||
}
|
||||
// build field source
|
||||
var targetSource = targetElement.source;
|
||||
var targetFile = targetSource.fullName;
|
||||
await builder.addDartFileEdit(targetFile, (builder) {
|
||||
builder.addInsertion(targetLocation.offset, (builder) {
|
||||
builder.write(targetLocation.prefix);
|
||||
builder.writeFieldDeclaration(_fieldName,
|
||||
isStatic: staticModifier,
|
||||
nameGroupName: 'NAME',
|
||||
type: fieldType,
|
||||
typeGroupName: 'TYPE');
|
||||
builder.write(targetLocation.suffix);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _proposeFromFieldFormalParameter(
|
||||
ChangeBuilder builder, FieldFormalParameter parameter) async {
|
||||
var targetClassNode = parameter.thisOrAncestorOfType<ClassDeclaration>();
|
||||
|
@ -93,51 +167,15 @@ class CreateField extends CorrectionProducer {
|
|||
targetClassElement = node.enclosingInterfaceElement;
|
||||
staticModifier = inStaticContext;
|
||||
}
|
||||
if (targetClassElement == null) {
|
||||
return;
|
||||
}
|
||||
if (targetClassElement.library.isInSdk) {
|
||||
return;
|
||||
}
|
||||
utils.targetClassElement = targetClassElement;
|
||||
// prepare target ClassDeclaration
|
||||
var targetDeclarationResult =
|
||||
await sessionHelper.getElementDeclaration(targetClassElement);
|
||||
if (targetDeclarationResult == null) {
|
||||
return;
|
||||
}
|
||||
var targetNode = targetDeclarationResult.node;
|
||||
if (targetNode is! CompilationUnitMember) {
|
||||
return;
|
||||
}
|
||||
if (!(targetNode is ClassDeclaration || targetNode is MixinDeclaration)) {
|
||||
return;
|
||||
}
|
||||
// prepare location
|
||||
var targetUnit = targetDeclarationResult.resolvedUnit;
|
||||
if (targetUnit == null) {
|
||||
return;
|
||||
}
|
||||
var targetLocation =
|
||||
CorrectionUtils(targetUnit).prepareNewFieldLocation(targetNode);
|
||||
if (targetLocation == null) {
|
||||
return;
|
||||
}
|
||||
// build field source
|
||||
var targetSource = targetClassElement.source;
|
||||
var targetFile = targetSource.fullName;
|
||||
await builder.addDartFileEdit(targetFile, (builder) {
|
||||
var fieldTypeNode = climbPropertyAccess(nameNode);
|
||||
var fieldType = inferUndefinedExpressionType(fieldTypeNode);
|
||||
builder.addInsertion(targetLocation.offset, (builder) {
|
||||
builder.write(targetLocation.prefix);
|
||||
builder.writeFieldDeclaration(_fieldName,
|
||||
isStatic: staticModifier,
|
||||
nameGroupName: 'NAME',
|
||||
type: fieldType,
|
||||
typeGroupName: 'TYPE');
|
||||
builder.write(targetLocation.suffix);
|
||||
});
|
||||
});
|
||||
|
||||
var fieldTypeNode = climbPropertyAccess(nameNode);
|
||||
var fieldType = inferUndefinedExpressionType(fieldTypeNode);
|
||||
|
||||
await _addDeclaration(
|
||||
builder: builder,
|
||||
staticModifier: staticModifier,
|
||||
targetElement: targetClassElement,
|
||||
fieldType: fieldType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,86 @@ import 'package:analysis_server/src/utilities/extensions/ast.dart';
|
|||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/ast/ast.dart';
|
||||
import 'package:analyzer/src/dart/ast/extensions.dart';
|
||||
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
|
||||
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
class CreateGetter extends CorrectionProducer {
|
||||
/// Shared implementation that identifies what getter should be added,
|
||||
/// but delegates to the subtypes to produce the fix code.
|
||||
abstract class CreateFieldOrGetter extends CorrectionProducer {
|
||||
/// Adds the declaration that makes a [fieldName] available.
|
||||
Future<void> addForObjectPattern({
|
||||
required ChangeBuilder builder,
|
||||
required InterfaceElement? targetElement,
|
||||
required String fieldName,
|
||||
required DartType? fieldType,
|
||||
});
|
||||
|
||||
@protected
|
||||
Future<bool> compute0(ChangeBuilder builder) async {
|
||||
final node = this.node;
|
||||
|
||||
if (node is DeclaredVariablePatternImpl) {
|
||||
final fieldName = node.fieldNameWithImplicitName;
|
||||
if (fieldName != null) {
|
||||
await _patternFieldName(
|
||||
builder: builder,
|
||||
fieldName: fieldName,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (node is PatternFieldName) {
|
||||
await _patternFieldName(
|
||||
builder: builder,
|
||||
fieldName: node,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _patternFieldName({
|
||||
required ChangeBuilder builder,
|
||||
required PatternFieldName fieldName,
|
||||
}) async {
|
||||
final patternField = node.parent;
|
||||
if (patternField is! PatternField) {
|
||||
return;
|
||||
}
|
||||
|
||||
final effectiveName = patternField.effectiveName;
|
||||
if (effectiveName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final objectPattern = patternField.parent;
|
||||
if (objectPattern is! ObjectPattern) {
|
||||
return;
|
||||
}
|
||||
|
||||
final matchedType = objectPattern.type.typeOrThrow;
|
||||
if (matchedType is! InterfaceType) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fieldType = patternField.pattern.requiredType;
|
||||
fieldType ??= typeProvider.objectQuestionType;
|
||||
|
||||
await addForObjectPattern(
|
||||
builder: builder,
|
||||
targetElement: matchedType.element,
|
||||
fieldName: effectiveName,
|
||||
fieldType: fieldType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CreateGetter extends CreateFieldOrGetter {
|
||||
String _getterName = '';
|
||||
|
||||
@override
|
||||
|
@ -21,8 +97,28 @@ class CreateGetter extends CorrectionProducer {
|
|||
@override
|
||||
FixKind get fixKind => DartFixKind.CREATE_GETTER;
|
||||
|
||||
@override
|
||||
Future<void> addForObjectPattern({
|
||||
required ChangeBuilder builder,
|
||||
required InterfaceElement? targetElement,
|
||||
required String fieldName,
|
||||
required DartType? fieldType,
|
||||
}) async {
|
||||
_getterName = fieldName;
|
||||
|
||||
await _addDeclaration(
|
||||
builder: builder,
|
||||
staticModifier: false,
|
||||
targetElement: targetElement,
|
||||
fieldType: fieldType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> compute(ChangeBuilder builder) async {
|
||||
if (await compute0(builder)) {
|
||||
return;
|
||||
}
|
||||
var nameNode = node;
|
||||
if (nameNode is! SimpleIdentifier) {
|
||||
return;
|
||||
|
@ -64,16 +160,35 @@ class CreateGetter extends CorrectionProducer {
|
|||
staticModifier = targetElement?.kind == ElementKind.CLASS;
|
||||
}
|
||||
} else {
|
||||
targetElement =
|
||||
node.enclosingInterfaceElement ?? node.enclosingExtensionElement;
|
||||
targetElement = nameNode.enclosingInterfaceElement ??
|
||||
nameNode.enclosingExtensionElement;
|
||||
if (targetElement == null) {
|
||||
return;
|
||||
}
|
||||
staticModifier = inStaticContext;
|
||||
}
|
||||
|
||||
var fieldTypeNode = climbPropertyAccess(nameNode);
|
||||
var fieldType = inferUndefinedExpressionType(fieldTypeNode);
|
||||
|
||||
await _addDeclaration(
|
||||
builder: builder,
|
||||
staticModifier: staticModifier,
|
||||
targetElement: targetElement,
|
||||
fieldType: fieldType,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addDeclaration({
|
||||
required ChangeBuilder builder,
|
||||
required bool staticModifier,
|
||||
required Element? targetElement,
|
||||
required DartType? fieldType,
|
||||
}) async {
|
||||
if (targetElement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var targetSource = targetElement.source;
|
||||
if (targetSource == null || targetElement.library?.isInSdk == true) {
|
||||
return;
|
||||
|
@ -108,8 +223,6 @@ class CreateGetter extends CorrectionProducer {
|
|||
var targetFile = targetSource.fullName;
|
||||
await builder.addDartFileEdit(targetFile, (builder) {
|
||||
builder.addInsertion(targetLocation.offset, (builder) {
|
||||
var fieldTypeNode = climbPropertyAccess(nameNode);
|
||||
var fieldType = inferUndefinedExpressionType(fieldTypeNode);
|
||||
builder.write(targetLocation.prefix);
|
||||
builder.writeGetterDeclaration(_getterName,
|
||||
isStatic: staticModifier,
|
||||
|
|
|
@ -415,6 +415,98 @@ class C {
|
|||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_explicitName_variablePattern_typed() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
int test;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_explicitName_variablePattern_untyped() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: var y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: var y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
Object? test;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_explicitName_wildcardPattern_typed() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int _)) {}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int _)) {}
|
||||
}
|
||||
|
||||
class A {
|
||||
int test;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_implicitName_variablePattern_typed() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(:int test)) {
|
||||
test;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(:int test)) {
|
||||
test;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
int test;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_setter_generic_BAD() async {
|
||||
await resolveTestCode('''
|
||||
class A {
|
||||
|
|
|
@ -205,6 +205,98 @@ void f(C c) {
|
|||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_explicitName_variablePattern_typed() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
int get test => null;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_explicitName_variablePattern_untyped() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: var y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: var y)) {
|
||||
y;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
Object? get test => null;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_explicitName_wildcardPattern() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int _)) {}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(test: int _)) {}
|
||||
}
|
||||
|
||||
class A {
|
||||
int get test => null;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_objectPattern_implicitName_variablePattern() async {
|
||||
await resolveTestCode('''
|
||||
void f(Object? x) {
|
||||
if (x case A(:int test)) {
|
||||
test;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
}
|
||||
''');
|
||||
await assertHasFix('''
|
||||
void f(Object? x) {
|
||||
if (x case A(:int test)) {
|
||||
test;
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
int get test => null;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
Future<void> test_override() async {
|
||||
await resolveTestCode('''
|
||||
extension E on String {
|
||||
|
|
|
@ -96,6 +96,12 @@ abstract class TypeProvider {
|
|||
/// Return the type representing the built-in type `num`.
|
||||
InterfaceType get numType;
|
||||
|
||||
/// Return the element representing the built-in class `Object`.
|
||||
ClassElement get objectElement;
|
||||
|
||||
/// Return the type representing the built-in type `Object?`.
|
||||
InterfaceType get objectQuestionType;
|
||||
|
||||
/// Return the type representing the built-in type `Object`.
|
||||
InterfaceType get objectType;
|
||||
|
||||
|
|
|
@ -124,6 +124,20 @@ extension DartPatternExtension on DartPattern {
|
|||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
DartType? get requiredType {
|
||||
final self = this;
|
||||
if (self is DeclaredVariablePattern) {
|
||||
return self.type?.typeOrThrow;
|
||||
} else if (self is ListPattern) {
|
||||
return self.requiredType;
|
||||
} else if (self is MapPattern) {
|
||||
return self.requiredType;
|
||||
} else if (self is WildcardPattern) {
|
||||
return self.type?.typeOrThrow;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
extension ExpressionExtension on Expression {
|
||||
|
|
|
@ -143,6 +143,7 @@ class TypeProviderImpl extends TypeProviderBase {
|
|||
InterfaceType? _numType;
|
||||
InterfaceType? _numTypeQuestion;
|
||||
InterfaceType? _objectType;
|
||||
InterfaceType? _objectQuestionType;
|
||||
InterfaceType? _recordType;
|
||||
InterfaceType? _stackTraceType;
|
||||
InterfaceType? _streamDynamicType;
|
||||
|
@ -384,13 +385,25 @@ class TypeProviderImpl extends TypeProviderBase {
|
|||
_numTypeQuestion ??= (numType as InterfaceTypeImpl)
|
||||
.withNullability(NullabilitySuffix.question);
|
||||
|
||||
@override
|
||||
ClassElement get objectElement {
|
||||
return _objectElement ??= _getClassElement(_coreLibrary, 'Object');
|
||||
}
|
||||
|
||||
@override
|
||||
InterfaceType get objectQuestionType {
|
||||
return _objectQuestionType ??= objectElement.instantiate(
|
||||
typeArguments: const [],
|
||||
nullabilitySuffix: NullabilitySuffix.question,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
InterfaceType get objectType {
|
||||
return _objectType ??= _getType(_coreLibrary, "Object");
|
||||
return _objectType ??= objectElement.instantiate(
|
||||
typeArguments: const [],
|
||||
nullabilitySuffix: _nullabilitySuffix,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
Loading…
Reference in a new issue