Skip to content

Commit

Permalink
Initial support for extension methods inference.
Browse files Browse the repository at this point in the history
Only the case with ExtensionOverride is supported for now.
Still fixes some co19 tests.

[email protected], [email protected]

Change-Id: I6c7c98f4839a1fc40504d93107648d952a4176d2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/111866
Reviewed-by: Brian Wilkerson <[email protected]>
Commit-Queue: Konstantin Shcheglov <[email protected]>
  • Loading branch information
scheglov authored and [email protected] committed Aug 2, 2019
1 parent 6653611 commit 4bebfeb
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 16 deletions.
11 changes: 11 additions & 0 deletions pkg/analyzer/lib/dart/ast/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2156,9 +2156,20 @@ abstract class ExtensionOverride implements Expression {
/// contain a single argument, which evaluates to the object being extended.
ArgumentList get argumentList;

/// Return the actual type extended by this override, produced by applying
/// [typeArgumentTypes] to the generic type extended by the extension.
///
/// Return `null` if the AST structure has not been resolved.
DartType get extendedType;

/// Return the name of the extension being selected.
Identifier get extensionName;

/// Return the forced extension element.
///
/// Return `null` if the AST structure has not been resolved.
ExtensionElement get staticElement;

/// Return the type arguments to be applied to the extension, or `null` if no
/// type arguments were provided.
TypeArgumentList get typeArguments;
Expand Down
16 changes: 10 additions & 6 deletions pkg/analyzer/lib/src/dart/ast/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3952,6 +3952,12 @@ class ExtensionOverrideImpl extends ExpressionImpl
/// arguments were provided.
TypeArgumentListImpl _typeArguments;

@override
List<DartType> typeArgumentTypes;

@override
DartType extendedType;

ExtensionOverrideImpl(IdentifierImpl extensionName,
TypeArgumentListImpl typeArguments, ArgumentListImpl argumentList) {
_extensionName = _becomeParentOf(extensionName);
Expand All @@ -3976,7 +3982,7 @@ class ExtensionOverrideImpl extends ExpressionImpl
..add(_argumentList);

@override
Token get endToken => _argumentList?.endToken;
Token get endToken => _argumentList.endToken;

@override
Identifier get extensionName => _extensionName;
Expand All @@ -3988,18 +3994,16 @@ class ExtensionOverrideImpl extends ExpressionImpl
@override
Precedence get precedence => Precedence.postfix;

@override
ExtensionElement get staticElement => extensionName.staticElement;

@override
TypeArgumentList get typeArguments => _typeArguments;

void set typeArguments(TypeArgumentList typeArguments) {
_typeArguments = _becomeParentOf(typeArguments as TypeArgumentListImpl);
}

@override
// TODO(brianwilkerson) Either implement this getter or remove it if it isn't
// needed.
List<DartType> get typeArgumentTypes => null;

@override
E accept<E>(AstVisitor<E> visitor) {
return visitor.visitExtensionOverride(this);
Expand Down
18 changes: 12 additions & 6 deletions pkg/analyzer/lib/src/dart/element/member.dart
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,15 @@ class FieldMember extends VariableMember implements FieldElement {
FieldElement get baseElement => super.baseElement as FieldElement;

@override
ClassElement get enclosingElement => baseElement.enclosingElement;
Element get enclosingElement => baseElement.enclosingElement;

@override
PropertyAccessorElement get getter {
var definingType = _substitution.substituteType(enclosingElement.type);
return PropertyAccessorMember.from(baseElement.getter, definingType);
var baseGetter = baseElement.getter;
if (baseGetter == null) {
return null;
}
return PropertyAccessorMember(baseGetter, substitution);
}

@override
Expand All @@ -300,8 +303,11 @@ class FieldMember extends VariableMember implements FieldElement {

@override
PropertyAccessorElement get setter {
var definingType = _substitution.substituteType(enclosingElement.type);
return PropertyAccessorMember.from(baseElement.setter, definingType);
var baseSetter = baseElement.setter;
if (baseSetter == null) {
return null;
}
return PropertyAccessorMember(baseSetter, substitution);
}

@override
Expand Down Expand Up @@ -631,7 +637,7 @@ class MethodMember extends ExecutableMember implements MethodElement {
MethodElement get baseElement => super.baseElement as MethodElement;

@override
ClassElement get enclosingElement => baseElement.enclosingElement;
Element get enclosingElement => baseElement.enclosingElement;

@override
T accept<T>(ElementVisitor<T> visitor) => visitor.visitMethodElement(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ 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/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.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';
Expand Down Expand Up @@ -388,6 +390,12 @@ class MethodInvocationResolver {
return;
}

var substitution = Substitution.fromPairs(
element.typeParameters,
override.typeArgumentTypes,
);
member = ExecutableMember.from2(member, substitution);

if (member is ExecutableElement && member.isStatic) {
_resolver.errorReporter.reportErrorForNode(
CompileTimeErrorCode.EXTENSION_OVERRIDE_ACCESS_TO_STATIC_MEMBER,
Expand Down
12 changes: 12 additions & 0 deletions pkg/analyzer/lib/src/generated/element_resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import 'package:analyzer/src/dart/ast/ast.dart'
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_algebra.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';
Expand Down Expand Up @@ -744,6 +746,16 @@ class ElementResolver extends SimpleAstVisitor<void> {
CompileTimeErrorCode.EXTENSION_OVERRIDE_ACCESS_TO_STATIC_MEMBER,
propertyName);
}

// TODO(scheglov) Improve from() instead.
if (element.typeParameters.isNotEmpty) {
var substitution = Substitution.fromPairs(
element.typeParameters,
target.typeArgumentTypes,
);
member = ExecutableMember.from2(member, substitution);
}

propertyName.staticElement = member;
return;
}
Expand Down
9 changes: 5 additions & 4 deletions pkg/analyzer/lib/src/generated/error_verifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -739,10 +739,11 @@ class ErrorVerifier extends RecursiveAstVisitor<void> {
NodeList<Expression> arguments = node.argumentList.arguments;
int argCount = arguments.length;
if (argCount == 1) {
DartType extendedType =
(node.extensionName.staticElement as ExtensionElement).extendedType;
_checkForAssignableExpression(arguments[0], extendedType,
CompileTimeErrorCode.EXTENSION_OVERRIDE_ARGUMENT_NOT_ASSIGNABLE);
_checkForAssignableExpression(
arguments[0],
node.extendedType,
CompileTimeErrorCode.EXTENSION_OVERRIDE_ARGUMENT_NOT_ASSIGNABLE,
);
} else {
_errorReporter.reportErrorForNode(
CompileTimeErrorCode.INVALID_EXTENSION_ARGUMENT_COUNT,
Expand Down
37 changes: 37 additions & 0 deletions pkg/analyzer/lib/src/generated/static_type_analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,43 @@ class StaticTypeAnalyzer extends SimpleAstVisitor<void> {
_recordStaticType(node, _nonNullable(_typeProvider.doubleType));
}

@override
void visitExtensionOverride(ExtensionOverride node) {
var element = node.staticElement;
var typeParameters = element.typeParameters;

List<DartType> typeArgumentTypes;
if (node.typeArguments != null) {
var arguments = node.typeArguments.arguments;
if (arguments.length == typeParameters.length) {
typeArgumentTypes = arguments.map((a) => a.type).toList();
} else {
// TODO(scheglov) Report an error.
typeArgumentTypes = List.filled(typeParameters.length, _dynamicType);
}
} else {
var arguments = node.argumentList.arguments;
if (arguments.length == 1) {
var inferrer =
GenericInferrer(_typeProvider, _typeSystem, typeParameters);
inferrer.constrainArgument(
arguments[0].staticType,
element.extendedType,
'extendedType',
);
typeArgumentTypes = inferrer.infer(typeParameters);
} else {
typeArgumentTypes = List.filled(typeParameters.length, _dynamicType);
}
}

var nodeImpl = node as ExtensionOverrideImpl;
nodeImpl.typeArgumentTypes = typeArgumentTypes;
nodeImpl.extendedType =
Substitution.fromPairs(typeParameters, typeArgumentTypes)
.substituteType(element.extendedType);
}

@override
void visitFunctionDeclaration(FunctionDeclaration node) {
FunctionExpression function = node.functionExpression;
Expand Down
9 changes: 9 additions & 0 deletions pkg/analyzer/lib/src/test_utilities/find_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ class FindElement {
throw StateError('Not found: $targetUri');
}

ExtensionElement extension_(String name) {
for (var extension_ in unitElement.extensions) {
if (extension_.name == name) {
return extension_;
}
}
throw StateError('Not found: $name');
}

FieldElement field(String name, {String of}) {
FieldElement result;

Expand Down
2 changes: 2 additions & 0 deletions pkg/analyzer/test/src/dart/resolution/resolution.dart
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ mixin ResolutionTest implements ResourceProviderMixin {
return node.staticElement;
} else if (node is Declaration) {
return node.declaredElement;
} else if (node is ExtensionOverride) {
return node.staticElement;
} else if (node is FormalParameter) {
return node.declaredElement;
} else if (node is FunctionExpressionInvocation) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// 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/analysis/features.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../driver_resolution.dart';

main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExtensionMethodsTest);
});
}

@reflectiveTest
class ExtensionMethodsTest extends DriverResolutionTest {
@override
AnalysisOptionsImpl get analysisOptions => AnalysisOptionsImpl()
..contextFeatures = new FeatureSet.forTesting(
sdkVersion: '2.3.0', additionalFeatures: [Feature.extension_methods]);

test_override_hasTypeArguments_getter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
List<T> get foo => <T>[];
}
void f(A<int> a) {
E<num>(a).foo;
}
''');
var override = findNode.extensionOverride('E<num>(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['num']);
assertElementTypeString(override.extendedType, 'A<num>');

var propertyAccess = findNode.propertyAccess('.foo');
assertMember(
propertyAccess,
findElement.getter('foo', of: 'E'),
{'T': 'num'},
);
assertType(propertyAccess, 'List<num>');
}

test_override_hasTypeArguments_method() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
E<num>(a).foo(1.0);
}
''');
var override = findNode.extensionOverride('E<num>(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['num']);
assertElementTypeString(override.extendedType, 'A<num>');

// TODO(scheglov) We need to instantiate "foo" fully.
var invocation = findNode.methodInvocation('foo(1.0)');
assertMember(
invocation,
findElement.method('foo', of: 'E'),
{'T': 'num'},
);
// assertMember(
// invocation,
// findElement.method('foo', of: 'E'),
// {'T': 'int', 'U': 'double'},
// );
assertInvokeType(invocation, 'Map<num, double> Function(double)');
}

test_override_inferTypeArguments_getter() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
List<T> get foo => <T>[];
}
void f(A<int> a) {
E(a).foo;
}
''');
var override = findNode.extensionOverride('E(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['int']);
assertElementTypeString(override.extendedType, 'A<int>');

var propertyAccess = findNode.propertyAccess('.foo');
assertMember(
propertyAccess,
findElement.getter('foo', of: 'E'),
{'T': 'int'},
);
assertType(propertyAccess, 'List<int>');
}

test_override_inferTypeArguments_method() async {
await assertNoErrorsInCode('''
class A<T> {}
extension E<T> on A<T> {
Map<T, U> foo<U>(U u) => <T, U>{};
}
void f(A<int> a) {
E(a).foo(1.0);
}
''');
var override = findNode.extensionOverride('E(a)');
assertElement(override, findElement.extension_('E'));
assertElementTypeStrings(override.typeArgumentTypes, ['int']);
assertElementTypeString(override.extendedType, 'A<int>');

// TODO(scheglov) We need to instantiate "foo" fully.
var invocation = findNode.methodInvocation('foo(1.0)');
assertMember(
invocation,
findElement.method('foo', of: 'E'),
{'T': 'int'},
);
// assertMember(
// invocation,
// findElement.method('foo', of: 'E'),
// {'T': 'int', 'U': 'double'},
// );
assertInvokeType(invocation, 'Map<int, double> Function(double)');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'collection_elements_test.dart' as collection_elements;
import 'conditional_expression_test.dart' as conditional_expression;
import 'equality_expressions_test.dart' as equality_expressions;
import 'extension_methods_test.dart' as extension_methods;
import 'list_literal_test.dart' as list_literal;
import 'logical_boolean_expressions_test.dart' as logical_boolean_expressions;
import 'map_literal_test.dart' as map_literal;
Expand All @@ -22,6 +23,7 @@ main() {
collection_elements.main();
conditional_expression.main();
equality_expressions.main();
extension_methods.main();
list_literal.main();
logical_boolean_expressions.main();
map_literal.main();
Expand Down

0 comments on commit 4bebfeb

Please sign in to comment.