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:
Konstantin Shcheglov 2023-04-27 20:52:39 +00:00 committed by Commit Queue
parent 0eee9f1392
commit a0218e916c
7 changed files with 422 additions and 54 deletions

View file

@ -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,
);
}
}

View file

@ -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,

View file

@ -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 {

View file

@ -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 {

View file

@ -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;

View file

@ -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 {

View file

@ -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