Support for resolving RecordType fields through type parameters bound with it.

Bug: https://github.com/dart-lang/sdk/issues/50006
Change-Id: I8b9fcff2c7bb560a764a336c8dceccbfdd06cdaa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260420
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2022-09-21 21:17:46 +00:00 committed by Commit Bot
parent 58c07f8d61
commit 18a73f7591
9 changed files with 137 additions and 56 deletions

View file

@ -62,9 +62,13 @@ class LexicalLookupResult {
/// The [FunctionType] referenced with `call`.
final FunctionType? callFunctionType;
/// The field referenced in a [RecordType].
final RecordTypeField? recordField;
LexicalLookupResult({
this.requested,
this.recovery,
this.callFunctionType,
this.recordField,
});
}

View file

@ -34,14 +34,18 @@ class PrefixedIdentifierResolver {
if (prefixElement is! PrefixElement) {
final prefixType = node.prefix.staticType;
// TODO(scheglov) It would be nice to rewrite all such cases.
if (prefixType is RecordType) {
final propertyAccess = PropertyAccessImpl(
node.prefix,
node.period,
node.identifier,
);
NodeReplacer.replace(node, propertyAccess);
return propertyAccess;
if (prefixType != null) {
final prefixTypeResolved =
_resolver.typeSystem.resolveToBound(prefixType);
if (prefixTypeResolved is RecordType) {
final propertyAccess = PropertyAccessImpl(
node.prefix,
node.period,
node.identifier,
);
NodeReplacer.replace(node, propertyAccess);
return propertyAccess;
}
}
}

View file

@ -238,12 +238,21 @@ class PropertyElementResolver with ScopeHelpers {
if (hasRead) {
var readLookup = LexicalLookup.resolveGetter(scopeLookupResult) ??
_resolver.thisLookupGetter(node);
final callFunctionType = readLookup?.callFunctionType;
if (callFunctionType != null) {
return PropertyElementResolverResult(
functionTypeCallType: callFunctionType,
);
}
final recordField = readLookup?.recordField;
if (recordField != null) {
return PropertyElementResolverResult(
recordField: recordField,
);
}
readElementRequested = _resolver.toLegacyElement(readLookup?.requested);
if (readElementRequested is PropertyAccessorElement &&
!readElementRequested.isStatic) {
@ -450,24 +459,6 @@ class PropertyElementResolver with ScopeHelpers {
return PropertyElementResolverResult();
}
if (targetType is RecordType) {
final name = propertyName.name;
final field = targetType.fieldByName(name);
if (field != null) {
if (hasWrite) {
AssignmentVerifier(_definingLibrary, errorReporter).verify(
node: propertyName,
requested: null,
recovery: null,
receiverType: targetType,
);
}
return PropertyElementResolverResult(
recordField: field,
);
}
}
var result = _resolver.typePropertyResolver.resolve(
receiver: target,
receiverType: targetType,
@ -511,6 +502,7 @@ class PropertyElementResolver with ScopeHelpers {
readElementRecovery: result.setter,
writeElementRequested: result.setter,
writeElementRecovery: result.getter,
recordField: result.recordField,
);
}

View file

@ -47,6 +47,9 @@ class ResolutionResult {
/// The [FunctionType] referenced with `call`.
final FunctionType? callFunctionType;
/// The field referenced in a [RecordType].
final RecordTypeField? recordField;
/// Initialize a newly created result to represent resolving a single
/// reading and / or writing result.
ResolutionResult({
@ -55,6 +58,7 @@ class ResolutionResult {
this.setter,
this.needsSetterError = true,
this.callFunctionType,
this.recordField,
}) : state = _ResolutionResultState.single;
/// Initialize a newly created result with no elements and the given [state].
@ -63,7 +67,8 @@ class ResolutionResult {
needsGetterError = true,
setter = null,
needsSetterError = true,
callFunctionType = null;
callFunctionType = null,
recordField = null;
/// Return `true` if this result represents the case where multiple ambiguous
/// elements were found.

View file

@ -9,7 +9,6 @@ import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/resolver/invocation_inference_helper.dart';
@ -38,18 +37,6 @@ class SimpleIdentifierResolver with ScopeHelpers {
return;
}
final thisType = _resolver.thisType;
if (thisType is RecordType) {
final wasResolved = _resolveOfThisRecordType(
node: node,
recordType: thisType,
contextType: contextType,
);
if (wasResolved) {
return;
}
}
_resolver.checkUnreachableNode(node);
_resolver.checkReadOfNotAssignedLocalVariable(node, node.staticElement);
@ -194,6 +181,14 @@ class SimpleIdentifierResolver with ScopeHelpers {
return;
}
final recordField = result.recordField;
if (recordField != null) {
_inferenceHelper.recordStaticType(node, recordField.type,
contextType: contextType);
_currentAlreadyResolved = true;
return;
}
var element = hasRead ? result.readElement : result.writeElement;
var enclosingClass = _resolver.enclosingClass;
@ -294,22 +289,6 @@ class SimpleIdentifierResolver with ScopeHelpers {
contextType: contextType);
}
/// This can happen only inside an extension.
bool _resolveOfThisRecordType({
required SimpleIdentifierImpl node,
required RecordType recordType,
required DartType? contextType,
}) {
final field = recordType.fieldByName(node.name);
if (field != null) {
_inferenceHelper.recordStaticType(node, field.type,
contextType: contextType);
return true;
} else {
return false;
}
}
/// TODO(scheglov) this is duplicate
void _setExtensionIdentifierType(IdentifierImpl node) {
if (node is SimpleIdentifierImpl && node.inDeclarationContext()) {

View file

@ -40,6 +40,13 @@ class ThisLookup {
);
}
final recordField = propertyResult.recordField;
if (recordField != null) {
return LexicalLookupResult(
recordField: recordField,
);
}
var getterElement = propertyResult.getter;
if (getterElement != null) {
return LexicalLookupResult(requested: getterElement);

View file

@ -8,6 +8,7 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/extensions.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
@ -203,6 +204,13 @@ class TypePropertyResolver {
}
if (receiverTypeResolved is RecordType) {
final field = receiverTypeResolved.fieldByName(name);
if (field != null) {
return ResolutionResult(
recordField: field,
needsGetterError: false,
);
}
_needsGetterError = true;
_needsSetterError = true;
}

View file

@ -399,6 +399,29 @@ PropertyAccess
''');
}
test_ofRecordType_namedField_ofTypeParameter() async {
await assertNoErrorsInCode(r'''
void f<T extends ({int foo})>(T r) {
r.foo;
}
''');
final node = findNode.propertyAccess(r'foo;');
assertResolvedNodeText(node, r'''
PropertyAccess
target: SimpleIdentifier
token: r
staticElement: self::@function::f::@parameter::r
staticType: T
operator: .
propertyName: SimpleIdentifier
token: foo
staticElement: <null>
staticType: int
staticType: int
''');
}
test_ofRecordType_Object_hashCode() async {
await assertNoErrorsInCode('''
void f(({int foo}) r) {
@ -622,6 +645,29 @@ PropertyAccess
''');
}
test_ofRecordType_positionalField_ofTypeParameter() async {
await assertNoErrorsInCode(r'''
void f<T extends (int, String)>(T r) {
r.$0;
}
''');
final node = findNode.propertyAccess(r'$0;');
assertResolvedNodeText(node, r'''
PropertyAccess
target: SimpleIdentifier
token: r
staticElement: self::@function::f::@parameter::r
staticType: T
operator: .
propertyName: SimpleIdentifier
token: $0
staticElement: <null>
staticType: int
staticType: int
''');
}
test_ofRecordType_unresolved() async {
await assertErrorsInCode('''
void f(({int foo}) r) {

View file

@ -116,6 +116,42 @@ SimpleIdentifier
''');
}
test_inExtension_onRecordType_fromTypeParameterBound_named() async {
await assertNoErrorsInCode('''
extension E<T extends ({int foo})> on T {
void f() {
foo;
}
}
''');
final node = findNode.simple('foo;');
assertResolvedNodeText(node, r'''
SimpleIdentifier
token: foo
staticElement: <null>
staticType: int
''');
}
test_inExtension_onRecordType_fromTypeParameterBound_positional() async {
await assertNoErrorsInCode(r'''
extension E<T extends (int, String)> on T {
void f() {
$0;
}
}
''');
final node = findNode.simple(r'$0;');
assertResolvedNodeText(node, r'''
SimpleIdentifier
token: $0
staticElement: <null>
staticType: int
''');
}
test_inExtension_onRecordType_named() async {
await assertNoErrorsInCode('''
extension E on ({int foo}) {