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:
pq 2019-07-24 13:37:13 +00:00 committed by Phil Quitslund
parent af6edd1040
commit 0677649e8a
4 changed files with 281 additions and 120 deletions

View file

@ -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);
}

View file

@ -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.

View file

@ -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;
}

View file

@ -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 {}