mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 13:47:50 +00:00
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:
parent
58c07f8d61
commit
18a73f7591
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}) {
|
||||
|
|
Loading…
Reference in a new issue