From 6c60f506c82368bb0a996d0791109c2b4586267a Mon Sep 17 00:00:00 2001 From: Konstantin Shcheglov Date: Fri, 21 Oct 2022 09:10:27 +0000 Subject: [PATCH] Resolve RecordPattern(s). Change-Id: I229efb591cbb2d6cfe77b38155526f881fee7dbe Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264891 Reviewed-by: Brian Wilkerson Commit-Queue: Konstantin Shcheglov --- pkg/analyzer/lib/src/dart/ast/ast.dart | 13 +- .../resolver/record_pattern_resolver.dart | 73 +++ pkg/analyzer/lib/src/fasta/ast_builder.dart | 9 +- pkg/analyzer/lib/src/generated/resolver.dart | 4 + .../dart/resolution/record_pattern_test.dart | 471 +++++++++++++++++- 5 files changed, 549 insertions(+), 21 deletions(-) create mode 100644 pkg/analyzer/lib/src/dart/resolver/record_pattern_resolver.dart diff --git a/pkg/analyzer/lib/src/dart/ast/ast.dart b/pkg/analyzer/lib/src/dart/ast/ast.dart index 6288cfabb8c..7346f22cbbc 100644 --- a/pkg/analyzer/lib/src/dart/ast/ast.dart +++ b/pkg/analyzer/lib/src/dart/ast/ast.dart @@ -10543,7 +10543,7 @@ class RecordPatternFieldNameImpl extends AstNodeImpl /// '(' [RecordPatternField] (',' [RecordPatternField])* ')' @experimental class RecordPatternImpl extends DartPatternImpl implements RecordPattern { - final NodeListImpl _fields = NodeListImpl._(); + final NodeListImpl _fields = NodeListImpl._(); @override final Token leftParenthesis; @@ -10553,7 +10553,7 @@ class RecordPatternImpl extends DartPatternImpl implements RecordPattern { RecordPatternImpl({ required this.leftParenthesis, - required List fields, + required List fields, required this.rightParenthesis, }) { _fields._initialize(this, fields); @@ -10566,7 +10566,7 @@ class RecordPatternImpl extends DartPatternImpl implements RecordPattern { Token get endToken => rightParenthesis; @override - NodeList get fields => _fields; + NodeList get fields => _fields; @override ChildEntities get _childEntities => super._childEntities @@ -10587,7 +10587,12 @@ class RecordPatternImpl extends DartPatternImpl implements RecordPattern { DartType matchedType, Map> typeInfos, MatchContext context) { - // TODO(scheglov) https://github.com/dart-lang/sdk/issues/50066 + resolverVisitor.recordPatternResolver.resolve( + node: this, + matchedType: matchedType, + typeInfos: typeInfos, + context: context, + ); } @override diff --git a/pkg/analyzer/lib/src/dart/resolver/record_pattern_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/record_pattern_resolver.dart new file mode 100644 index 00000000000..ab373921a16 --- /dev/null +++ b/pkg/analyzer/lib/src/dart/resolver/record_pattern_resolver.dart @@ -0,0 +1,73 @@ +// 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/type.dart'; +import 'package:analyzer/src/dart/ast/ast.dart'; +import 'package:analyzer/src/dart/element/extensions.dart'; +import 'package:analyzer/src/dart/element/type_provider.dart'; +import 'package:analyzer/src/error/codes.dart'; +import 'package:analyzer/src/generated/resolver.dart'; + +class RecordPatternResolver { + final ResolverVisitor resolverVisitor; + + int _positionalFieldIndex = 0; + + RecordPatternResolver(this.resolverVisitor); + + TypeProviderImpl get _typeProvider => resolverVisitor.typeProvider; + + void resolve({ + required RecordPatternImpl node, + required DartType matchedType, + required Map> + typeInfos, + required MatchContext context, + }) { + _positionalFieldIndex = 0; + for (var field in node.fields) { + var fieldType = _resolveFieldType(matchedType, field); + field.pattern + .resolvePattern(resolverVisitor, fieldType, typeInfos, context); + } + } + + DartType _resolveFieldType( + DartType matchedType, + RecordPatternFieldImpl field, + ) { + if (matchedType is RecordType) { + var fieldNameNode = field.fieldName; + if (fieldNameNode != null) { + var nameToken = fieldNameNode.name; + nameToken ??= field.pattern.variablePattern?.name; + if (nameToken == null) { + resolverVisitor.errorReporter.reportErrorForNode( + CompileTimeErrorCode.MISSING_EXTRACTOR_PATTERN_GETTER_NAME, + field, + ); + return _typeProvider.dynamicType; + } + + var fieldName = nameToken.lexeme; + var recordField = matchedType.fieldByName(fieldName); + if (recordField != null) { + return recordField.type; + } + } else { + if (_positionalFieldIndex < matchedType.positionalFields.length) { + return matchedType.positionalFields[_positionalFieldIndex++].type; + } + } + } else if (matchedType.isDynamic) { + return _typeProvider.dynamicType; + } + + return resolverVisitor.typeSystem.objectQuestion; + } +} diff --git a/pkg/analyzer/lib/src/fasta/ast_builder.dart b/pkg/analyzer/lib/src/fasta/ast_builder.dart index 741c70d8649..dd6132906ef 100644 --- a/pkg/analyzer/lib/src/fasta/ast_builder.dart +++ b/pkg/analyzer/lib/src/fasta/ast_builder.dart @@ -4634,11 +4634,14 @@ class AstBuilder extends StackListener { void handleRecordPattern(Token token, int count) { debugEvent("RecordPattern"); - var fields = popTypedList2(count); - push(RecordPatternImpl( + var fields = popTypedList2(count); + push( + RecordPatternImpl( leftParenthesis: token, fields: fields, - rightParenthesis: token.endGroup!)); + rightParenthesis: token.endGroup!, + ), + ); } @override diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart index 794172a0e27..e8c4ef6aa93 100644 --- a/pkg/analyzer/lib/src/generated/resolver.dart +++ b/pkg/analyzer/lib/src/generated/resolver.dart @@ -57,6 +57,7 @@ import 'package:analyzer/src/dart/resolver/prefix_expression_resolver.dart'; import 'package:analyzer/src/dart/resolver/prefixed_identifier_resolver.dart'; import 'package:analyzer/src/dart/resolver/property_element_resolver.dart'; import 'package:analyzer/src/dart/resolver/record_literal_resolver.dart'; +import 'package:analyzer/src/dart/resolver/record_pattern_resolver.dart'; import 'package:analyzer/src/dart/resolver/scope.dart'; import 'package:analyzer/src/dart/resolver/shared_type_analyzer.dart'; import 'package:analyzer/src/dart/resolver/simple_identifier_resolver.dart'; @@ -281,6 +282,9 @@ class ResolverVisitor extends ThrowingAstVisitor late final ExtractorPatternResolver extractorPatternResolver = ExtractorPatternResolver(this); + late final RecordPatternResolver recordPatternResolver = + RecordPatternResolver(this); + final bool genericMetadataIsEnabled; /// Stack for obtaining rewritten expressions. Prior to visiting an diff --git a/pkg/analyzer/test/src/dart/resolution/record_pattern_test.dart b/pkg/analyzer/test/src/dart/resolution/record_pattern_test.dart index fea346589af..fa8549d381c 100644 --- a/pkg/analyzer/test/src/dart/resolution/record_pattern_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/record_pattern_test.dart @@ -2,6 +2,7 @@ // 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:analyzer/src/error/codes.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'context_collection_resolution.dart'; @@ -14,7 +15,7 @@ main() { @reflectiveTest class RecordPatternResolutionTest extends PatternsResolutionTest { - test_fields_empty() async { + test_dynamicType_empty() async { await assertNoErrorsInCode(r''' void f(x) { switch (x) { @@ -24,42 +25,171 @@ void f(x) { } '''); final node = findNode.switchPatternCase('case').pattern; - assertParsedNodeText(node, r''' + assertResolvedNodeText(node, r''' RecordPattern leftParenthesis: ( rightParenthesis: ) '''); } - test_fields_pair() async { + test_dynamicType_named_variable_untyped() async { await assertNoErrorsInCode(r''' void f(x) { switch (x) { - case (1, 2): + case (foo: var y): break; } } '''); final node = findNode.switchPatternCase('case').pattern; - assertParsedNodeText(node, r''' + assertResolvedNodeText(node, r''' RecordPattern leftParenthesis: ( fields RecordPatternField - pattern: ConstantPattern - expression: IntegerLiteral - literal: 1 - RecordPatternField - pattern: ConstantPattern - expression: IntegerLiteral - literal: 2 + fieldName: RecordPatternFieldName + name: foo + colon: : + pattern: VariablePattern + keyword: var + name: y + declaredElement: hasImplicitType y@46 + type: dynamic + fieldElement: rightParenthesis: ) '''); } - test_fields_singleton() async { + test_dynamicType_positional_variable_untyped() async { await assertNoErrorsInCode(r''' void f(x) { + switch (x) { + case (var y): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +ParenthesizedPattern + leftParenthesis: ( + pattern: VariablePattern + keyword: var + name: y + declaredElement: hasImplicitType y@41 + type: dynamic + rightParenthesis: ) +'''); + } + + test_interfaceType_empty() async { + await assertNoErrorsInCode(r''' +void f(Object? x) { + switch (x) { + case (): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + rightParenthesis: ) +'''); + } + + test_interfaceType_named_constant() async { + await assertNoErrorsInCode(r''' +void f(Object? x) { + switch (x) { + case (foo: 0): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + name: foo + colon: : + pattern: ConstantPattern + expression: IntegerLiteral + literal: 0 + staticType: int + fieldElement: + rightParenthesis: ) +'''); + } + + test_interfaceType_named_variable_typed() async { + await assertNoErrorsInCode(r''' +void f(Object? x) { + switch (x) { + case (foo: int y): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + name: foo + colon: : + pattern: VariablePattern + type: NamedType + name: SimpleIdentifier + token: int + staticElement: dart:core::@class::int + staticType: null + type: int + name: y + declaredElement: y@54 + type: int + fieldElement: + rightParenthesis: ) +'''); + } + + test_interfaceType_named_variable_untyped() async { + await assertNoErrorsInCode(r''' +void f(Object? x) { + switch (x) { + case (foo: var y): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + name: foo + colon: : + pattern: VariablePattern + keyword: var + name: y + declaredElement: hasImplicitType y@54 + type: Object? + fieldElement: + rightParenthesis: ) +'''); + } + + test_interfaceType_positional_constant() async { + await assertNoErrorsInCode(r''' +void f(Object? x) { switch (x) { case (0,): break; @@ -67,7 +197,7 @@ void f(x) { } '''); final node = findNode.switchPatternCase('case').pattern; - assertParsedNodeText(node, r''' + assertResolvedNodeText(node, r''' RecordPattern leftParenthesis: ( fields @@ -75,6 +205,319 @@ RecordPattern pattern: ConstantPattern expression: IntegerLiteral literal: 0 + staticType: int + fieldElement: + rightParenthesis: ) +'''); + } + + test_interfaceType_positional_variable_typed() async { + await assertNoErrorsInCode(r''' +void f(Object? x) { + switch (x) { + case (int y,): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + pattern: VariablePattern + type: NamedType + name: SimpleIdentifier + token: int + staticElement: dart:core::@class::int + staticType: null + type: int + name: y + declaredElement: y@49 + type: int + fieldElement: + rightParenthesis: ) +'''); + } + + test_interfaceType_positional_variable_untyped() async { + await assertNoErrorsInCode(r''' +void f(Object? x) { + switch (x) { + case (var y,): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + pattern: VariablePattern + keyword: var + name: y + declaredElement: hasImplicitType y@49 + type: Object? + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_empty() async { + await assertNoErrorsInCode(r''' +void f(() x) { + switch (x) { + case (): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + rightParenthesis: ) +'''); + } + + test_recordType_mixed() async { + await assertNoErrorsInCode(r''' +void f((int, double, {String foo}) x) { + switch (x) { + case (var a, foo: var b, var c): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + pattern: VariablePattern + keyword: var + name: a + declaredElement: hasImplicitType a@69 + type: int + fieldElement: + RecordPatternField + fieldName: RecordPatternFieldName + name: foo + colon: : + pattern: VariablePattern + keyword: var + name: b + declaredElement: hasImplicitType b@81 + type: String + fieldElement: + RecordPatternField + pattern: VariablePattern + keyword: var + name: c + declaredElement: hasImplicitType c@88 + type: double + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_named_hasName_unresolved() async { + await assertNoErrorsInCode(r''' +void f(({int foo}) x) { + switch (x) { + case (bar: var a): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + name: bar + colon: : + pattern: VariablePattern + keyword: var + name: a + declaredElement: hasImplicitType a@58 + type: Object? + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_named_hasName_variable() async { + await assertNoErrorsInCode(r''' +void f(({int foo}) x) { + switch (x) { + case (foo: var y): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + name: foo + colon: : + pattern: VariablePattern + keyword: var + name: y + declaredElement: hasImplicitType y@58 + type: int + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_named_noName_constant() async { + await assertErrorsInCode(r''' +void f(({int foo}) x) { + switch (x) { + case (: 0): + break; + } +} +''', [ + error(CompileTimeErrorCode.MISSING_EXTRACTOR_PATTERN_GETTER_NAME, 49, 3), + ]); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + colon: : + pattern: ConstantPattern + expression: IntegerLiteral + literal: 0 + staticType: int + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_named_noName_variable() async { + await assertNoErrorsInCode(r''' +void f(({int foo}) x) { + switch (x) { + case (: var foo): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + colon: : + pattern: VariablePattern + keyword: var + name: foo + declaredElement: hasImplicitType foo@55 + type: int + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_named_noName_variable_nullCheck() async { + await assertNoErrorsInCode(r''' +void f(({int? foo}) x) { + switch (x) { + case (: var foo?): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + fieldName: RecordPatternFieldName + colon: : + pattern: PostfixPattern + operand: VariablePattern + keyword: var + name: foo + declaredElement: hasImplicitType foo@56 + type: int + operator: ? + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_positional_tooMany() async { + await assertNoErrorsInCode(r''' +void f((int,) x) { + switch (x) { + case (var a, var b): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + pattern: VariablePattern + keyword: var + name: a + declaredElement: hasImplicitType a@48 + type: int + fieldElement: + RecordPatternField + pattern: VariablePattern + keyword: var + name: b + declaredElement: hasImplicitType b@55 + type: Object? + fieldElement: + rightParenthesis: ) +'''); + } + + test_recordType_positional_variable() async { + await assertNoErrorsInCode(r''' +void f((int,) x) { + switch (x) { + case (var a,): + break; + } +} +'''); + final node = findNode.switchPatternCase('case').pattern; + assertResolvedNodeText(node, r''' +RecordPattern + leftParenthesis: ( + fields + RecordPatternField + pattern: VariablePattern + keyword: var + name: a + declaredElement: hasImplicitType a@48 + type: int + fieldElement: rightParenthesis: ) '''); }