mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 01:10:27 +00:00
extension property access
Change-Id: Icf51679607166e0a449fe0ebeb5f432f17afb34b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/110125 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
af6edd1040
commit
0677649e8a
|
@ -0,0 +1,162 @@
|
|||
// Copyright (c) 2019, 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:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/resolver/scope.dart';
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer/src/generated/resolver.dart';
|
||||
import 'package:analyzer/src/generated/type_system.dart';
|
||||
|
||||
class ExtensionMemberResolver {
|
||||
final ResolverVisitor _resolver;
|
||||
ExtensionMemberResolver(this._resolver);
|
||||
|
||||
Scope get _nameScope => _resolver.nameScope;
|
||||
|
||||
TypeSystem get _typeSystem => _resolver.typeSystem;
|
||||
|
||||
/// Return the most specific extension or `null` if no single one can be
|
||||
/// identified.
|
||||
ExtensionElement chooseMostSpecificExtension(
|
||||
List<ExtensionElement> extensions, InterfaceType receiverType) {
|
||||
//
|
||||
// https://github.com/dart-lang/language/blob/master/accepted/future-releases/static-extension-methods/feature-specification.md#extension-conflict-resolution:
|
||||
//
|
||||
// If more than one extension applies to a specific member invocation, then
|
||||
// we resort to a heuristic to choose one of the extensions to apply. If
|
||||
// exactly one of them is "more specific" than all the others, that one is
|
||||
// chosen. Otherwise it is a compile-time error.
|
||||
//
|
||||
// An extension with on type clause T1 is more specific than another
|
||||
// extension with on type clause T2 iff
|
||||
//
|
||||
// 1. T2 is declared in a platform library, and T1 is not, or
|
||||
// 2. they are both declared in platform libraries or both declared in
|
||||
// non-platform libraries, and
|
||||
// 3. the instantiated type (the type after applying type inference from the
|
||||
// receiver) of T1 is a subtype of the instantiated type of T2 and either
|
||||
// not vice versa, or
|
||||
// 4. the instantiate-to-bounds type of T1 is a subtype of the
|
||||
// instantiate-to-bounds type of T2 and not vice versa.
|
||||
//
|
||||
|
||||
int moreSpecific(ExtensionElement e1, ExtensionElement e2) {
|
||||
var t1 = _instantiateToBounds(e1.extendedType);
|
||||
var t2 = _instantiateToBounds(e2.extendedType);
|
||||
|
||||
bool inSdk(DartType type) {
|
||||
if (type.isDynamic || type.isVoid) {
|
||||
return true;
|
||||
}
|
||||
return t2.element.library.isInSdk;
|
||||
}
|
||||
|
||||
if (inSdk(t2)) {
|
||||
// 1. T2 is declared in a platform library, and T1 is not
|
||||
if (!inSdk(t1)) {
|
||||
return -1;
|
||||
}
|
||||
} else if (inSdk(t1)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. they are both declared in platform libraries or both declared in
|
||||
// non-platform libraries, and
|
||||
if (_subtypeOf(t1, t2)) {
|
||||
// 3. the instantiated type (the type after applying type inference from the
|
||||
// receiver) of T1 is a subtype of the instantiated type of T2 and either
|
||||
// not vice versa
|
||||
if (!_subtypeOf(t2, t1)) {
|
||||
return -1;
|
||||
} else {
|
||||
// or:
|
||||
// 4. the instantiate-to-bounds type of T1 is a subtype of the
|
||||
// instantiate-to-bounds type of T2 and not vice versa.
|
||||
|
||||
// todo(pq): implement
|
||||
|
||||
}
|
||||
} else if (_subtypeOf(t2, t1)) {
|
||||
if (!_subtypeOf(t1, t2)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extensions.sort(moreSpecific);
|
||||
|
||||
// If the first extension is definitively more specific, return it.
|
||||
if (moreSpecific(extensions[0], extensions[1]) == -1) {
|
||||
return extensions[0];
|
||||
}
|
||||
|
||||
// Otherwise fail.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Return an extension for this [type] that matches the given [name] in the
|
||||
/// current scope; if the match is ambiguous, report an error.
|
||||
ExtensionElement findExtension(
|
||||
InterfaceType type, String name, Expression target) {
|
||||
var extensions = getApplicableExtensions(type, name);
|
||||
if (extensions.length == 1) {
|
||||
return extensions[0];
|
||||
}
|
||||
if (extensions.length > 1) {
|
||||
ExtensionElement extension =
|
||||
chooseMostSpecificExtension(extensions, type);
|
||||
if (extension != null) {
|
||||
return extension;
|
||||
}
|
||||
_resolver.errorReporter.reportErrorForNode(
|
||||
CompileTimeErrorCode.AMBIGUOUS_EXTENSION_METHOD_ACCESS,
|
||||
target,
|
||||
[
|
||||
name,
|
||||
extensions[0].name,
|
||||
extensions[1].name,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Return extensions for this [type] that match the given [name] in the
|
||||
/// current scope.
|
||||
List<ExtensionElement> getApplicableExtensions(DartType type, String name) {
|
||||
final List<ExtensionElement> extensions = [];
|
||||
void checkElement(Element element, ExtensionElement extension) {
|
||||
if (element.displayName == name && !extensions.contains(extension)) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
var targetType = _instantiateToBounds(type);
|
||||
for (var extension in _nameScope.extensions) {
|
||||
var extensionType = _instantiateToBounds(extension.extendedType);
|
||||
if (_subtypeOf(targetType, extensionType)) {
|
||||
for (var accessor in extension.accessors) {
|
||||
checkElement(accessor, extension);
|
||||
}
|
||||
for (var method in extension.methods) {
|
||||
checkElement(method, extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/// Ask the type system to instantiate the given type to its bounds.
|
||||
DartType _instantiateToBounds(DartType type) =>
|
||||
_typeSystem.instantiateToBounds(type);
|
||||
|
||||
/// Ask the type system for a subtype check.
|
||||
bool _subtypeOf(DartType type1, DartType type2) =>
|
||||
_typeSystem.isSubtypeOf(type1, type2);
|
||||
}
|
|
@ -10,6 +10,7 @@ import 'package:analyzer/src/dart/ast/ast.dart';
|
|||
import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
|
||||
import 'package:analyzer/src/dart/element/member.dart';
|
||||
import 'package:analyzer/src/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
|
||||
import 'package:analyzer/src/dart/resolver/scope.dart';
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer/src/generated/resolver.dart';
|
||||
|
@ -45,12 +46,17 @@ class MethodInvocationResolver {
|
|||
/// The [Name] object of the invocation being resolved by [resolve].
|
||||
Name _currentName;
|
||||
|
||||
/// Helper for extension method resolution.
|
||||
//todo(pq): consider sharing instance with element_resolver
|
||||
final ExtensionMemberResolver _extensionMemberResolver;
|
||||
|
||||
MethodInvocationResolver(this._resolver)
|
||||
: _typeType = _resolver.typeProvider.typeType,
|
||||
_inheritance = _resolver.inheritance,
|
||||
_definingLibrary = _resolver.definingLibrary,
|
||||
_definingLibraryUri = _resolver.definingLibrary.source.uri,
|
||||
_localVariableTypeProvider = _resolver.localVariableTypeProvider;
|
||||
_localVariableTypeProvider = _resolver.localVariableTypeProvider,
|
||||
_extensionMemberResolver = ExtensionMemberResolver(_resolver);
|
||||
|
||||
/// The scope used to resolve identifiers.
|
||||
Scope get nameScope => _resolver.nameScope;
|
||||
|
@ -130,87 +136,6 @@ class MethodInvocationResolver {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the most specific extension or `null` if no single one can be
|
||||
/// identified.
|
||||
ExtensionElement _chooseMostSpecificExtension(
|
||||
List<ExtensionElement> extensions, InterfaceType receiverType) {
|
||||
//
|
||||
// https://github.com/dart-lang/language/blob/master/accepted/future-releases/static-extension-methods/feature-specification.md#extension-conflict-resolution:
|
||||
//
|
||||
// If more than one extension applies to a specific member invocation, then
|
||||
// we resort to a heuristic to choose one of the extensions to apply. If
|
||||
// exactly one of them is "more specific" than all the others, that one is
|
||||
// chosen. Otherwise it is a compile-time error.
|
||||
//
|
||||
// An extension with on type clause T1 is more specific than another
|
||||
// extension with on type clause T2 iff
|
||||
//
|
||||
// 1. T2 is declared in a platform library, and T1 is not, or
|
||||
// 2. they are both declared in platform libraries or both declared in
|
||||
// non-platform libraries, and
|
||||
// 3. the instantiated type (the type after applying type inference from the
|
||||
// receiver) of T1 is a subtype of the instantiated type of T2 and either
|
||||
// not vice versa, or
|
||||
// 4. the instantiate-to-bounds type of T1 is a subtype of the
|
||||
// instantiate-to-bounds type of T2 and not vice versa.
|
||||
//
|
||||
|
||||
int moreSpecific(ExtensionElement e1, ExtensionElement e2) {
|
||||
var t1 = _instantiateToBounds(e1.extendedType);
|
||||
var t2 = _instantiateToBounds(e2.extendedType);
|
||||
|
||||
bool inSdk(DartType type) {
|
||||
if (type.isDynamic || type.isVoid) {
|
||||
return true;
|
||||
}
|
||||
return t2.element.library.isInSdk;
|
||||
}
|
||||
|
||||
if (inSdk(t2)) {
|
||||
// 1. T2 is declared in a platform library, and T1 is not
|
||||
if (!inSdk(t1)) {
|
||||
return -1;
|
||||
}
|
||||
} else if (inSdk(t1)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. they are both declared in platform libraries or both declared in
|
||||
// non-platform libraries, and
|
||||
if (_subtypeOf(t1, t2)) {
|
||||
// 3. the instantiated type (the type after applying type inference from the
|
||||
// receiver) of T1 is a subtype of the instantiated type of T2 and either
|
||||
// not vice versa
|
||||
if (!_subtypeOf(t2, t1)) {
|
||||
return -1;
|
||||
} else {
|
||||
// or:
|
||||
// 4. the instantiate-to-bounds type of T1 is a subtype of the
|
||||
// instantiate-to-bounds type of T2 and not vice versa.
|
||||
|
||||
// todo(pq): implement
|
||||
|
||||
}
|
||||
} else if (_subtypeOf(t2, t1)) {
|
||||
if (!_subtypeOf(t1, t2)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extensions.sort(moreSpecific);
|
||||
|
||||
// If the first extension is definitively more specific, return it.
|
||||
if (moreSpecific(extensions[0], extensions[1]) == -1) {
|
||||
return extensions[0];
|
||||
}
|
||||
|
||||
// Otherwise fail.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Given an [argumentList] and the executable [element] that will be invoked
|
||||
/// using those arguments, compute the list of parameters that correspond to
|
||||
/// the list of arguments. Return the parameters that correspond to the
|
||||
|
@ -230,31 +155,6 @@ class MethodInvocationResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Return extensions for this [type] that match the given [name] in the current
|
||||
/// [scope].
|
||||
List<ExtensionElement> _getApplicableExtensions(DartType type, String name) {
|
||||
final List<ExtensionElement> extensions = [];
|
||||
void checkElement(Element element, ExtensionElement extension) {
|
||||
if (element.name == name && !extensions.contains(extension)) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
}
|
||||
|
||||
var targetType = _instantiateToBounds(type);
|
||||
for (var extension in _resolver.nameScope.extensions) {
|
||||
var extensionType = _instantiateToBounds(extension.extendedType);
|
||||
if (_subtypeOf(targetType, extensionType)) {
|
||||
for (var accessor in extension.accessors) {
|
||||
checkElement(accessor, extension);
|
||||
}
|
||||
for (var method in extension.methods) {
|
||||
checkElement(method, extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
/// If the element of the invoked [targetType] is a getter, then actually
|
||||
/// the return type of the [targetType] is invoked. So, remember the
|
||||
/// [targetType] into [MethodInvocationImpl.methodNameType] and return the
|
||||
|
@ -300,10 +200,6 @@ class MethodInvocationResolver {
|
|||
return invokeType;
|
||||
}
|
||||
|
||||
/// Ask the type system to instantiate the given type to its bounds.
|
||||
DartType _instantiateToBounds(DartType type) =>
|
||||
_resolver.typeSystem.instantiateToBounds(type);
|
||||
|
||||
bool _isCoreFunction(DartType type) {
|
||||
// TODO(scheglov) Can we optimize this?
|
||||
return type is InterfaceType && type.isDartCoreFunction;
|
||||
|
@ -519,12 +415,13 @@ class MethodInvocationResolver {
|
|||
|
||||
// Look for an applicable extension.
|
||||
List<ExtensionElement> extensions =
|
||||
_getApplicableExtensions(receiverType, name);
|
||||
_extensionMemberResolver.getApplicableExtensions(receiverType, name);
|
||||
|
||||
if (extensions.length == 1) {
|
||||
return _resolveExtension(node, extensions[0], nameNode, name);
|
||||
} else if (extensions.length > 1) {
|
||||
ExtensionElement extension =
|
||||
_chooseMostSpecificExtension(extensions, receiverType);
|
||||
var extension = _extensionMemberResolver.chooseMostSpecificExtension(
|
||||
extensions, receiverType);
|
||||
if (extension == null) {
|
||||
_setDynamicResolution(node);
|
||||
_resolver.errorReporter.reportErrorForNode(
|
||||
|
@ -801,10 +698,6 @@ class MethodInvocationResolver {
|
|||
_reportInvocationOfNonFunction(node);
|
||||
}
|
||||
|
||||
/// Ask the type system for a subtype check.
|
||||
bool _subtypeOf(DartType type1, DartType type2) =>
|
||||
_resolver.typeSystem.isSubtypeOf(type1, type2);
|
||||
|
||||
/// Checks whether the given [expression] is a reference to a class. If it is
|
||||
/// then the element representing the class is returned, otherwise `null` is
|
||||
/// returned.
|
||||
|
|
|
@ -20,6 +20,7 @@ import 'package:analyzer/src/dart/ast/token.dart';
|
|||
import 'package:analyzer/src/dart/element/element.dart';
|
||||
import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
|
||||
import 'package:analyzer/src/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
|
||||
import 'package:analyzer/src/dart/resolver/method_invocation_resolver.dart';
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart';
|
||||
|
@ -115,6 +116,8 @@ class ElementResolver extends SimpleAstVisitor<void> {
|
|||
|
||||
final MethodInvocationResolver _methodInvocationResolver;
|
||||
|
||||
final ExtensionMemberResolver _extensionMemberResolver;
|
||||
|
||||
/**
|
||||
* Initialize a newly created visitor to work for the given [_resolver] to
|
||||
* resolve the nodes in a compilation unit.
|
||||
|
@ -122,6 +125,7 @@ class ElementResolver extends SimpleAstVisitor<void> {
|
|||
ElementResolver(this._resolver, {this.reportConstEvaluationErrors: true})
|
||||
: _inheritance = _resolver.inheritance,
|
||||
_definingLibrary = _resolver.definingLibrary,
|
||||
_extensionMemberResolver = ExtensionMemberResolver(_resolver),
|
||||
_methodInvocationResolver = new MethodInvocationResolver(_resolver) {
|
||||
_dynamicType = _resolver.typeProvider.dynamicType;
|
||||
_typeType = _resolver.typeProvider.typeType;
|
||||
|
@ -1208,8 +1212,17 @@ class ElementResolver extends SimpleAstVisitor<void> {
|
|||
Expression target, DartType type, String getterName) {
|
||||
type = _resolveTypeParameter(type);
|
||||
if (type is InterfaceType) {
|
||||
return type.lookUpInheritedGetter(getterName,
|
||||
var getter = type.lookUpInheritedGetter(getterName,
|
||||
library: _definingLibrary, thisType: target is! SuperExpression);
|
||||
if (getter != null) {
|
||||
return getter;
|
||||
}
|
||||
|
||||
var extension =
|
||||
_extensionMemberResolver.findExtension(type, getterName, target);
|
||||
if (extension != null) {
|
||||
return extension.getGetter(getterName);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1273,8 +1286,17 @@ class ElementResolver extends SimpleAstVisitor<void> {
|
|||
Expression target, DartType type, String setterName) {
|
||||
type = _resolveTypeParameter(type);
|
||||
if (type is InterfaceType) {
|
||||
return type.lookUpInheritedSetter(setterName,
|
||||
var setter = type.lookUpInheritedSetter(setterName,
|
||||
library: _definingLibrary, thisType: target is! SuperExpression);
|
||||
if (setter != null) {
|
||||
return setter;
|
||||
}
|
||||
|
||||
var extension =
|
||||
_extensionMemberResolver.findExtension(type, setterName, target);
|
||||
if (extension != null) {
|
||||
return extension.getSetter(setterName);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,61 @@ class ExtensionMethodTest extends DriverResolutionTest {
|
|||
..contextFeatures = new FeatureSet.forTesting(
|
||||
sdkVersion: '2.3.0', additionalFeatures: [Feature.extension_methods]);
|
||||
|
||||
test_getter_noMatch() async {
|
||||
await assertErrorCodesInCode(r'''
|
||||
class B { }
|
||||
|
||||
extension A on B { }
|
||||
|
||||
f() {
|
||||
B b = B();
|
||||
int x = b.a;
|
||||
}
|
||||
''', [StaticTypeWarningCode.UNDEFINED_GETTER]);
|
||||
}
|
||||
|
||||
test_getter_oneMatch() async {
|
||||
await assertNoErrorsInCode('''
|
||||
class B { }
|
||||
|
||||
extension A on B {
|
||||
int get a => 1;
|
||||
}
|
||||
|
||||
f() {
|
||||
B b = B();
|
||||
int x = b.a;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_getter_specificSubtypeMatchLocal() async {
|
||||
await assertNoErrorsInCode('''
|
||||
class A { }
|
||||
|
||||
class B extends A {
|
||||
int get b => 1;
|
||||
}
|
||||
|
||||
extension A_Ext on A {
|
||||
int get a => 1;
|
||||
}
|
||||
|
||||
extension B_Ext on B {
|
||||
int /*2*/ get a => 2;
|
||||
}
|
||||
|
||||
f() {
|
||||
B b = B();
|
||||
int x = b.a;
|
||||
}
|
||||
''');
|
||||
|
||||
var invocation = findNode.prefixed('b.a');
|
||||
var declaration = findNode.methodDeclaration('int /*2*/ get a');
|
||||
expect(invocation.identifier.staticElement, declaration.declaredElement);
|
||||
}
|
||||
|
||||
test_method_moreSpecificThanPlatform() async {
|
||||
//
|
||||
// An extension with on type clause T1 is more specific than another
|
||||
|
@ -256,6 +311,35 @@ extension E2 on A {}
|
|||
''');
|
||||
}
|
||||
|
||||
test_setter_noMatch() async {
|
||||
await assertErrorCodesInCode(r'''
|
||||
class B { }
|
||||
|
||||
extension A on B {
|
||||
}
|
||||
|
||||
f() {
|
||||
B b = B();
|
||||
b.a = 1;
|
||||
}
|
||||
''', [StaticTypeWarningCode.UNDEFINED_SETTER]);
|
||||
}
|
||||
|
||||
test_setter_oneMatch() async {
|
||||
await assertNoErrorsInCode('''
|
||||
class B { }
|
||||
|
||||
extension A on B {
|
||||
set a(int x) { }
|
||||
}
|
||||
|
||||
f() {
|
||||
B b = B();
|
||||
b.a = 1;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_unnamedExtension() async {
|
||||
await assertNoErrorsInCode('''
|
||||
class A {}
|
||||
|
|
Loading…
Reference in a new issue