Basic resolution for ExtractorPattern.

Change-Id: Id129aa245ec792a169d43e9658f1531ffd1e67a5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/263044
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2022-10-07 14:56:08 +00:00 committed by Commit Queue
parent 51cf541243
commit f07b293ab8
7 changed files with 423 additions and 61 deletions

View file

@ -4337,6 +4337,10 @@ abstract class RecordPattern implements DartPattern {
/// Clients may not extend, implement or mix-in this class.
@experimental
abstract class RecordPatternField implements AstNode {
/// The element referenced explicitly by [fieldName], or implicitly by the
/// variable pattern inside [pattern].
Element? get fieldElement;
/// The name of the field, or `null` if the field is a positional field.
RecordPatternFieldName? get fieldName;

View file

@ -3359,6 +3359,9 @@ abstract class DartPatternImpl extends AstNodeImpl implements DartPattern {
// have constants for pattern-related precedence values.
Precedence get precedence => throw UnimplementedError();
/// The variable pattern, itself, or wrapped in a unary pattern.
VariablePatternImpl? get variablePattern => null;
DartType computePatternSchema(ResolverVisitor resolverVisitor);
void resolvePattern(
@ -4712,7 +4715,7 @@ class ExtensionOverrideImpl extends ExpressionImpl
/// [Identifier] [TypeArgumentList]? '(' [RecordPatternField] ')'
@experimental
class ExtractorPatternImpl extends DartPatternImpl implements ExtractorPattern {
final NodeListImpl<RecordPatternField> _fields = NodeListImpl._();
final NodeListImpl<RecordPatternFieldImpl> _fields = NodeListImpl._();
@override
final Token leftParenthesis;
@ -4726,7 +4729,7 @@ class ExtractorPatternImpl extends DartPatternImpl implements ExtractorPattern {
ExtractorPatternImpl(
{required this.type,
required this.leftParenthesis,
required List<RecordPatternField> fields,
required List<RecordPatternFieldImpl> fields,
required this.rightParenthesis}) {
_becomeParentOf(type);
_fields._initialize(this, fields);
@ -4739,7 +4742,7 @@ class ExtractorPatternImpl extends DartPatternImpl implements ExtractorPattern {
Token get endToken => rightParenthesis;
@override
NodeList<RecordPatternField> get fields => _fields;
NodeList<RecordPatternFieldImpl> get fields => _fields;
@override
ChildEntities get _childEntities => super._childEntities
@ -4757,11 +4760,17 @@ class ExtractorPatternImpl extends DartPatternImpl implements ExtractorPattern {
@override
void resolvePattern(
ResolverVisitor resolverVisitor,
DartType matchedType,
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
MatchContext<AstNode, Expression> context) {
// TODO(scheglov) https://github.com/dart-lang/sdk/issues/50066
ResolverVisitor resolverVisitor,
DartType matchedType,
Map<PromotableElement, VariableTypeInfo<AstNode, DartType>> typeInfos,
MatchContext<AstNode, Expression> context,
) {
resolverVisitor.extractorPatternResolver.resolve(
node: this,
matchedType: matchedType,
typeInfos: typeInfos,
context: context,
);
}
@override
@ -9588,6 +9597,9 @@ class ParenthesizedPatternImpl extends DartPatternImpl
@override
Token get endToken => rightParenthesis;
@override
VariablePatternImpl? get variablePattern => pattern.variablePattern;
@override
ChildEntities get _childEntities => super._childEntities
..addToken('leftParenthesis', leftParenthesis)
@ -10041,6 +10053,9 @@ class PostfixPatternImpl extends DartPatternImpl implements PostfixPattern {
@override
Token get endToken => operator;
@override
VariablePatternImpl? get variablePattern => operand.variablePattern;
@override
ChildEntities get _childEntities => super._childEntities
..addNode('operand', operand)
@ -10435,6 +10450,9 @@ class RecordLiteralImpl extends LiteralImpl implements RecordLiteral {
/// [RecordPatternFieldName]? [DartPattern]
@experimental
class RecordPatternFieldImpl extends AstNodeImpl implements RecordPatternField {
@override
Element? fieldElement;
@override
final RecordPatternFieldNameImpl? fieldName;
@ -13560,6 +13578,9 @@ class VariablePatternImpl extends DartPatternImpl implements VariablePattern {
@override
Token get endToken => name;
@override
VariablePatternImpl? get variablePattern => this;
@override
ChildEntities get _childEntities => super._childEntities
..addToken('keyword', keyword)

View file

@ -0,0 +1,118 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// 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:_fe_analyzer_shared/src/type_inference/type_analysis_result.dart';
import 'package:_fe_analyzer_shared/src/type_inference/type_analyzer.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.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/src/dart/element/generic_inferrer.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/generated/resolver.dart';
class ExtractorPatternResolver {
final ResolverVisitor resolverVisitor;
ExtractorPatternResolver(this.resolverVisitor);
TypeProviderImpl get _typeProvider => resolverVisitor.typeProvider;
void resolve({
required ExtractorPatternImpl node,
required DartType matchedType,
required Map<PromotableElement, VariableTypeInfo<AstNode, DartType>>
typeInfos,
required MatchContext<AstNode, Expression> context,
}) {
var inferredType = _inferType(
matchedType: matchedType,
typeNode: node.type,
);
for (var field in node.fields) {
// TODO(scheglov) Report an error.
var nameToken =
field.fieldName?.name ?? field.pattern.variablePattern!.name;
var result = resolverVisitor.typePropertyResolver.resolve(
receiver: null,
receiverType: inferredType,
name: nameToken.lexeme,
propertyErrorEntity: nameToken,
nameErrorEntity: node,
);
// TODO(scheglov) Report an error.
var getter = result.getter;
field.fieldElement = getter;
var fieldType = getter?.returnType ?? _typeProvider.dynamicType;
field.pattern
.resolvePattern(resolverVisitor, fieldType, typeInfos, context);
}
}
DartType _inferType({
required DartType matchedType,
required NamedTypeImpl typeNode,
}) {
if (typeNode.typeArguments == null) {
var typeNameElement = typeNode.name.staticElement;
if (typeNameElement is InterfaceElement) {
var typeParameters = typeNameElement.typeParameters;
if (typeParameters.isNotEmpty) {
var typeArguments = _inferTypeArguments(
typeParameters: typeParameters,
errorNode: typeNode,
declaredType: typeNameElement.thisType,
matchedType: matchedType,
);
return typeNode.type = typeNameElement.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: NullabilitySuffix.none,
);
}
} else if (typeNameElement is TypeAliasElement) {
var typeParameters = typeNameElement.typeParameters;
if (typeParameters.isNotEmpty) {
var typeArguments = _inferTypeArguments(
typeParameters: typeParameters,
errorNode: typeNode,
declaredType: typeNameElement.aliasedType,
matchedType: matchedType,
);
return typeNode.type = typeNameElement.instantiate(
typeArguments: typeArguments,
nullabilitySuffix: NullabilitySuffix.none,
);
}
}
}
return typeNode.typeOrThrow;
}
List<DartType> _inferTypeArguments({
required List<TypeParameterElement> typeParameters,
required AstNode errorNode,
required DartType declaredType,
required DartType matchedType,
}) {
var inferrer = GenericInferrer(
resolverVisitor.typeSystem,
typeParameters,
errorReporter: resolverVisitor.errorReporter,
errorNode: errorNode,
genericMetadataIsEnabled: resolverVisitor.genericMetadataIsEnabled,
);
inferrer.constrainReturnType(declaredType, matchedType);
return inferrer.partialInfer().map((typeArgument) {
if (typeArgument is UnknownInferredType) {
return _typeProvider.dynamicType;
} else {
return typeArgument;
}
}).toList();
}
}

View file

@ -3787,7 +3787,7 @@ class AstBuilder extends StackListener {
void handleExtractorPatternFields(
int count, Token beginToken, Token endToken) {
debugEvent("ExtractorPatternFields");
var fields = popTypedList2<RecordPatternField>(count);
var fields = popTypedList2<RecordPatternFieldImpl>(count);
push(_ExtractorPatternFields(beginToken, endToken, fields));
}
@ -5312,7 +5312,7 @@ class _ExtensionDeclarationBuilder extends _ClassLikeDeclarationBuilder {
class _ExtractorPatternFields {
final Token leftParenthesis;
final Token rightParenthesis;
final List<RecordPatternField> fields;
final List<RecordPatternFieldImpl> fields;
_ExtractorPatternFields(
this.leftParenthesis, this.rightParenthesis, this.fields);

View file

@ -41,6 +41,7 @@ import 'package:analyzer/src/dart/resolver/binary_expression_resolver.dart';
import 'package:analyzer/src/dart/resolver/body_inference_context.dart';
import 'package:analyzer/src/dart/resolver/constructor_reference_resolver.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
import 'package:analyzer/src/dart/resolver/extractor_pattern_resolver.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:analyzer/src/dart/resolver/for_resolver.dart';
import 'package:analyzer/src/dart/resolver/function_expression_invocation_resolver.dart';
@ -277,6 +278,9 @@ class ResolverVisitor extends ThrowingAstVisitor<void>
late final AnnotationResolver _annotationResolver = AnnotationResolver(this);
late final ExtractorPatternResolver extractorPatternResolver =
ExtractorPatternResolver(this);
final bool genericMetadataIsEnabled;
/// Stack for obtaining rewritten expressions. Prior to visiting an

View file

@ -14,122 +14,336 @@ main() {
@reflectiveTest
class ExtractorPatternResolutionTest extends PatternsResolutionTest {
test_identifier_noTypeArguments() async {
test_generic_noTypeArguments_infer_interfaceType() async {
await assertNoErrorsInCode(r'''
class C {}
void f(x) {
class A<T> {}
class B<T> extends A<T> {}
void f(A<int> x) {
switch (x) {
case C():
case B():
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertParsedNodeText(node, r'''
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: SimpleIdentifier
token: C
token: B
staticElement: self::@class::B
staticType: null
type: B<int>
leftParenthesis: (
rightParenthesis: )
''');
}
test_identifier_withTypeArguments() async {
test_generic_noTypeArguments_infer_interfaceType_viaTypeAlias() async {
await assertNoErrorsInCode(r'''
class C<T> {}
void f(x) {
class A<T, U> {}
class B<T, U> extends A<T, U> {}
typedef L<T> = B<T, String>;
void f(A<int, String> x) {
switch (x) {
case C<int>():
case L():
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertParsedNodeText(node, r'''
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: SimpleIdentifier
token: C
token: L
staticElement: self::@typeAlias::L
staticType: null
type: B<int, String>
alias: self::@typeAlias::L
typeArguments
int
leftParenthesis: (
rightParenthesis: )
''');
}
test_generic_withTypeArguments_hasName_variable_untyped() async {
await assertNoErrorsInCode(r'''
abstract class A<T> {
T get foo;
}
void f(x) {
switch (x) {
case A<int>(foo: var foo2):
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: SimpleIdentifier
token: A
staticElement: self::@class::A
staticType: null
typeArguments: TypeArgumentList
leftBracket: <
arguments
NamedType
name: SimpleIdentifier
token: int
staticElement: dart:core::@class::int
staticType: null
type: int
rightBracket: >
type: A<int>
leftParenthesis: (
fields
RecordPatternField
fieldName: RecordPatternFieldName
name: foo
colon: :
pattern: VariablePattern
keyword: var
name: foo2
declaredElement: hasImplicitType foo2@90
type: int
fieldElement: PropertyAccessorMember
base: self::@class::A::@getter::foo
substitution: {T: int}
rightParenthesis: )
''');
}
test_prefixedIdentifier_noTypeArguments() async {
newFile('$testPackageLibPath/a.dart', r'''
class C {}
''');
test_notGeneric_noTypeArguments_hasName_constant() async {
await assertNoErrorsInCode(r'''
import 'a.dart' as prefix;
abstract class A {
int get foo;
}
void f(x) {
switch (x) {
case prefix.C():
case A(foo: 0):
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertParsedNodeText(node, r'''
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: PrefixedIdentifier
prefix: SimpleIdentifier
token: prefix
period: .
identifier: SimpleIdentifier
token: C
name: SimpleIdentifier
token: A
staticElement: self::@class::A
staticType: null
type: A
leftParenthesis: (
fields
RecordPatternField
fieldName: RecordPatternFieldName
name: foo
colon: :
pattern: ConstantPattern
expression: IntegerLiteral
literal: 0
staticType: int
fieldElement: self::@class::A::@getter::foo
rightParenthesis: )
''');
}
test_prefixedIdentifier_withTypeArguments() async {
newFile('$testPackageLibPath/a.dart', r'''
class C<T> {}
''');
test_notGeneric_noTypeArguments_hasName_extensionGetter() async {
await assertNoErrorsInCode(r'''
import 'a.dart' as prefix;
abstract class A {}
extension E on A {
int get foo => 0;
}
void f(x) {
switch (x) {
case prefix.C<int>():
case A(foo: 0):
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertParsedNodeText(node, r'''
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: PrefixedIdentifier
prefix: SimpleIdentifier
token: prefix
period: .
identifier: SimpleIdentifier
token: C
typeArguments: TypeArgumentList
leftBracket: <
arguments
NamedType
name: SimpleIdentifier
token: int
rightBracket: >
name: SimpleIdentifier
token: A
staticElement: self::@class::A
staticType: null
type: A
leftParenthesis: (
fields
RecordPatternField
fieldName: RecordPatternFieldName
name: foo
colon: :
pattern: ConstantPattern
expression: IntegerLiteral
literal: 0
staticType: int
fieldElement: self::@extension::E::@getter::foo
rightParenthesis: )
''');
}
test_notGeneric_noTypeArguments_hasName_variable_untyped() async {
await assertNoErrorsInCode(r'''
abstract class A {
int get foo;
}
void f(x) {
switch (x) {
case A(foo: var foo2):
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: SimpleIdentifier
token: A
staticElement: self::@class::A
staticType: null
type: A
leftParenthesis: (
fields
RecordPatternField
fieldName: RecordPatternFieldName
name: foo
colon: :
pattern: VariablePattern
keyword: var
name: foo2
declaredElement: hasImplicitType foo2@84
type: int
fieldElement: self::@class::A::@getter::foo
rightParenthesis: )
''');
}
test_notGeneric_noTypeArguments_noName_variable() async {
await assertNoErrorsInCode(r'''
abstract class A {
int get foo;
}
void f(x) {
switch (x) {
case A(: var foo):
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: SimpleIdentifier
token: A
staticElement: self::@class::A
staticType: null
type: A
leftParenthesis: (
fields
RecordPatternField
fieldName: RecordPatternFieldName
colon: :
pattern: VariablePattern
keyword: var
name: foo
declaredElement: hasImplicitType foo@81
type: int
fieldElement: self::@class::A::@getter::foo
rightParenthesis: )
''');
}
test_notGeneric_noTypeArguments_noName_variable_nullCheck() async {
await assertNoErrorsInCode(r'''
abstract class A {
int? get foo;
}
void f(x) {
switch (x) {
case A(: var foo?):
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: SimpleIdentifier
token: A
staticElement: self::@class::A
staticType: null
type: A
leftParenthesis: (
fields
RecordPatternField
fieldName: RecordPatternFieldName
colon: :
pattern: PostfixPattern
operand: VariablePattern
keyword: var
name: foo
declaredElement: hasImplicitType foo@82
type: int
operator: ?
fieldElement: self::@class::A::@getter::foo
rightParenthesis: )
''');
}
test_notGeneric_noTypeArguments_noName_variable_parenthesis() async {
await assertNoErrorsInCode(r'''
abstract class A {
int get foo;
}
void f(x) {
switch (x) {
case A(: (var foo)):
break;
}
}
''');
final node = findNode.switchPatternCase('case').pattern;
assertResolvedNodeText(node, r'''
ExtractorPattern
type: NamedType
name: SimpleIdentifier
token: A
staticElement: self::@class::A
staticType: null
type: A
leftParenthesis: (
fields
RecordPatternField
fieldName: RecordPatternFieldName
colon: :
pattern: ParenthesizedPattern
leftParenthesis: (
pattern: VariablePattern
keyword: var
name: foo
declaredElement: hasImplicitType foo@82
type: int
rightParenthesis: )
fieldElement: self::@class::A::@getter::foo
rightParenthesis: )
''');
}

View file

@ -1086,6 +1086,7 @@ class ResolvedAstPrinter extends ThrowingAstVisitor<void> {
_writeln('RecordPatternField');
_withIndent(() {
_writeNamedChildEntities(node);
_writeElement('fieldElement', node.fieldElement);
});
}