Issue 1182. Use the initial type for extension instantiation.

Bug: https://github.com/dart-lang/sdk/issues/43590
Bug: https://github.com/dart-lang/language/issues/1182
Change-Id: I80366ad4f777eec299143a002bbdc9d92ba61a5b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/160883
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Konstantin Shcheglov 2020-10-02 23:44:41 +00:00 committed by commit-bot@chromium.org
parent d63d5d93a5
commit ba9355624c
9 changed files with 544 additions and 46 deletions

View file

@ -755,6 +755,56 @@ class TypeSystemImpl implements TypeSystem {
return false;
}
/// A dynamic bounded type is either `dynamic` itself, or a type variable
/// whose bound is dynamic bounded, or an intersection (promoted type
/// parameter type) whose second operand is dynamic bounded.
bool isDynamicBounded(DartType type) {
if (identical(type, DynamicTypeImpl.instance)) {
return true;
}
if (type is TypeParameterTypeImpl) {
var bound = type.element.bound;
if (bound != null && isDynamicBounded(bound)) {
return true;
}
var promotedBound = type.promotedBound;
if (promotedBound != null && isDynamicBounded(promotedBound)) {
return true;
}
}
return false;
}
/// A function bounded type is either `Function` itself, or a type variable
/// whose bound is function bounded, or an intersection (promoted type
/// parameter type) whose second operand is function bounded.
bool isFunctionBounded(DartType type) {
if (type is FunctionType) {
return type.nullabilitySuffix != NullabilitySuffix.question;
}
if (type is InterfaceType && type.isDartCoreFunction) {
return type.nullabilitySuffix != NullabilitySuffix.question;
}
if (type is TypeParameterTypeImpl) {
var bound = type.element.bound;
if (bound != null && isFunctionBounded(bound)) {
return true;
}
var promotedBound = type.promotedBound;
if (promotedBound != null && isFunctionBounded(promotedBound)) {
return true;
}
}
return false;
}
/// Defines an (almost) total order on bottom and `Null` types. This does not
/// currently consistently order two different type variables with the same
/// bound.

View file

@ -136,27 +136,14 @@ class MethodInvocationResolver {
}
DartType receiverType = receiver.staticType;
receiverType = _resolveTypeParameter(receiverType);
if (_migratableAstInfoProvider.isMethodInvocationNullAware(node) &&
_typeSystem.isNonNullableByDefault) {
receiverType = _typeSystem.promoteToNonNull(receiverType);
}
if (receiverType is InterfaceType) {
_resolveReceiverInterfaceType(
node, receiver, receiverType, nameNode, name);
if (_typeSystem.isDynamicBounded(receiverType)) {
_resolveReceiverDynamicBounded(node);
return;
}
if (receiverType is DynamicTypeImpl) {
_resolveReceiverDynamic(node);
return;
}
if (receiverType is FunctionType) {
_resolveReceiverFunctionType(
node, receiver, receiverType, nameNode, name);
if (receiverType is NeverTypeImpl) {
_resolveReceiverNever(node, receiver, receiverType);
return;
}
@ -165,10 +152,25 @@ class MethodInvocationResolver {
return;
}
if (receiverType is NeverTypeImpl) {
_resolveReceiverNever(node, receiver, receiverType);
if (_migratableAstInfoProvider.isMethodInvocationNullAware(node) &&
_typeSystem.isNonNullableByDefault) {
receiverType = _typeSystem.promoteToNonNull(receiverType);
}
if (_typeSystem.isFunctionBounded(receiverType)) {
_resolveReceiverFunctionBounded(
node, receiver, receiverType, nameNode, name);
return;
}
_resolveReceiverType(
node: node,
receiver: receiver,
receiverType: receiverType,
nameNode: nameNode,
name: name,
receiverErrorNode: receiver,
);
}
bool _isCoreFunction(DartType type) {
@ -382,7 +384,7 @@ class MethodInvocationResolver {
_setResolution(node, member.type);
}
void _resolveReceiverDynamic(MethodInvocationImpl node) {
void _resolveReceiverDynamicBounded(MethodInvocation node) {
var nameNode = node.methodName;
var objectElement = _typeSystem.typeProvider.objectElement;
@ -411,8 +413,13 @@ class MethodInvocationResolver {
node.argumentList.accept(_resolver);
}
void _resolveReceiverFunctionType(MethodInvocation node, Expression receiver,
FunctionType receiverType, SimpleIdentifier nameNode, String name) {
void _resolveReceiverFunctionBounded(
MethodInvocation node,
Expression receiver,
DartType receiverType,
SimpleIdentifier nameNode,
String name,
) {
if (name == FunctionElement.CALL_METHOD_NAME) {
_setResolution(node, receiverType);
// TODO(scheglov) Replace this with using FunctionType directly.
@ -432,18 +439,6 @@ class MethodInvocationResolver {
);
}
void _resolveReceiverInterfaceType(MethodInvocation node, Expression receiver,
InterfaceType receiverType, SimpleIdentifier nameNode, String name) {
_resolveReceiverType(
node: node,
receiver: receiver,
receiverType: receiverType,
nameNode: nameNode,
name: name,
receiverErrorNode: receiver,
);
}
void _resolveReceiverNever(
MethodInvocation node,
Expression receiver,

View file

@ -328,7 +328,6 @@ class PropertyElementResolver {
}
var targetType = target.staticType;
targetType = _resolveTypeParameter(targetType);
if (targetType.isVoid) {
_errorReporter.reportErrorForNode(

View file

@ -8,7 +8,6 @@ import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_provider.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/resolver/extension_member_resolver.dart';
@ -71,9 +70,9 @@ class TypePropertyResolver {
_nameErrorEntity = nameErrorEntity;
_resetResult();
receiverType = _resolveTypeParameter(receiverType);
receiverType = _resolveTypeParameter(receiverType, ifLegacy: true);
if (receiverType is DynamicTypeImpl) {
if (_typeSystem.isDynamicBounded(receiverType)) {
_lookupInterfaceType(_typeProvider.objectType);
_needsGetterError = false;
_needsSetterError = false;
@ -100,29 +99,33 @@ class TypePropertyResolver {
_reportedSetterError = true;
// Recovery, get some resolution.
receiverType = _resolveTypeParameter(receiverType, ifNullSafe: true);
if (receiverType is InterfaceType) {
_lookupInterfaceType(receiverType);
}
return _toResult();
} else {
if (receiverType is InterfaceType) {
_lookupInterfaceType(receiverType);
var receiverTypeResolved =
_resolveTypeParameter(receiverType, ifNullSafe: true);
if (receiverTypeResolved is InterfaceType) {
_lookupInterfaceType(receiverTypeResolved);
if (_hasGetterOrSetter) {
return _toResult();
}
if (receiverType.isDartCoreFunction && _name == 'call') {
if (receiverTypeResolved.isDartCoreFunction && _name == 'call') {
_needsGetterError = false;
_needsSetterError = false;
return _toResult();
}
}
if (receiverType is FunctionType && _name == 'call') {
if (receiverTypeResolved is FunctionType && _name == 'call') {
return _toResult();
}
if (receiverType is NeverType) {
if (receiverTypeResolved is NeverType) {
_lookupInterfaceType(_typeProvider.objectType);
_needsGetterError = false;
_needsSetterError = false;
@ -200,8 +203,22 @@ class TypePropertyResolver {
/// If the given [type] is a type parameter, replace it with its bound.
/// Otherwise, return the original type.
DartType _resolveTypeParameter(DartType type) {
return type?.resolveToBound(_typeProvider.objectType);
///
/// See https://github.com/dart-lang/language/issues/1182
/// There was a bug in the analyzer (and CFE) - we were always resolving
/// types to bounds before searching for a property. But extensions should
/// be applied to original types. Fixing this would be a breaking change,
/// so we fix it together with null safety.
DartType _resolveTypeParameter(
DartType type, {
bool ifLegacy = false,
bool ifNullSafe = false,
}) {
if (_typeSystem.isNonNullableByDefault ? ifNullSafe : ifLegacy) {
return type?.resolveToBound(_typeProvider.objectType);
} else {
return type;
}
}
ResolutionResult _toResult() {

View file

@ -22,6 +22,7 @@ import 'runtime_type_equality_test.dart' as runtime_type_equality;
import 'subtype_test.dart' as subtype;
import 'top_merge_test.dart' as top_merge;
import 'type_algebra_test.dart' as type_algebra;
import 'type_bounded_test.dart' as type_bounded;
import 'type_constraint_gatherer_test.dart' as type_constraint_gatherer;
import 'type_parameter_element_test.dart' as type_parameter_element;
import 'type_visitor_test.dart' as type_visitor;
@ -48,6 +49,7 @@ main() {
subtype.main();
top_merge.main();
type_algebra.main();
type_bounded.main();
type_constraint_gatherer.main();
type_parameter_element.main();
type_visitor.main();

View file

@ -0,0 +1,240 @@
// Copyright (c) 2020, 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/element/type.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../generated/type_system_test.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(DynamicBoundedTest);
defineReflectiveTests(FunctionBoundedTest);
});
}
@reflectiveTest
class DynamicBoundedTest extends AbstractTypeSystemNullSafetyTest {
test_dynamic() {
_assertDynamicBounded(dynamicNone);
}
test_dynamic_typeParameter_hasBound_dynamic() {
var T = typeParameter('T', bound: dynamicNone);
_assertDynamicBounded(
typeParameterTypeNone(T),
);
}
test_dynamic_typeParameter_hasBound_notDynamic() {
var T = typeParameter('T', bound: intNone);
_assertNotDynamicBounded(
typeParameterTypeNone(T),
);
}
test_dynamic_typeParameter_hasPromotedBound_dynamic() {
var T = typeParameter('T');
_assertDynamicBounded(
typeParameterTypeNone(T, promotedBound: dynamicNone),
);
}
test_dynamic_typeParameter_hasPromotedBound_notDynamic() {
var T = typeParameter('T');
_assertNotDynamicBounded(
typeParameterTypeNone(T, promotedBound: intNone),
);
}
test_dynamic_typeParameter_noBound() {
var T = typeParameter('T');
_assertNotDynamicBounded(
typeParameterTypeNone(T),
);
}
test_functionType() {
_assertNotDynamicBounded(
functionTypeNone(returnType: voidNone),
);
_assertNotDynamicBounded(
functionTypeNone(returnType: dynamicNone),
);
}
test_interfaceType() {
_assertNotDynamicBounded(intNone);
_assertNotDynamicBounded(intQuestion);
_assertNotDynamicBounded(intStar);
}
test_never() {
_assertNotDynamicBounded(neverNone);
_assertNotDynamicBounded(neverQuestion);
_assertNotDynamicBounded(neverStar);
}
test_void() {
_assertNotDynamicBounded(voidNone);
}
void _assertDynamicBounded(DartType type) {
expect(typeSystem.isDynamicBounded(type), isTrue);
}
void _assertNotDynamicBounded(DartType type) {
expect(typeSystem.isDynamicBounded(type), isFalse);
}
}
@reflectiveTest
class FunctionBoundedTest extends AbstractTypeSystemNullSafetyTest {
test_dynamic() {
_assertNotFunctionBounded(dynamicNone);
}
test_dynamic_typeParameter_hasBound_functionType_none() {
var T = typeParameter(
'T',
bound: functionTypeNone(returnType: voidNone),
);
_assertFunctionBounded(
typeParameterTypeNone(T),
);
}
test_dynamic_typeParameter_hasBound_functionType_question() {
var T = typeParameter(
'T',
bound: functionTypeQuestion(returnType: voidNone),
);
_assertNotFunctionBounded(
typeParameterTypeNone(T),
);
}
test_dynamic_typeParameter_hasBound_functionType_star() {
var T = typeParameter(
'T',
bound: functionTypeStar(returnType: voidNone),
);
_assertFunctionBounded(
typeParameterTypeStar(T),
);
}
test_dynamic_typeParameter_hasBound_notFunction() {
var T = typeParameter('T', bound: intNone);
_assertNotFunctionBounded(
typeParameterTypeNone(T),
);
}
test_dynamic_typeParameter_hasPromotedBound_functionType_none() {
var T = typeParameter('T');
_assertFunctionBounded(
typeParameterTypeNone(
T,
promotedBound: functionTypeNone(
returnType: voidNone,
),
),
);
}
test_dynamic_typeParameter_hasPromotedBound_functionType_question() {
var T = typeParameter('T');
_assertNotFunctionBounded(
typeParameterTypeStar(
T,
promotedBound: functionTypeQuestion(
returnType: voidNone,
),
),
);
}
test_dynamic_typeParameter_hasPromotedBound_functionType_star() {
var T = typeParameter('T');
_assertFunctionBounded(
typeParameterTypeStar(
T,
promotedBound: functionTypeStar(
returnType: voidNone,
),
),
);
}
test_dynamic_typeParameter_hasPromotedBound_notFunction() {
var T = typeParameter('T');
_assertNotFunctionBounded(
typeParameterTypeNone(T, promotedBound: intNone),
);
}
test_dynamic_typeParameter_noBound() {
var T = typeParameter('T');
_assertNotFunctionBounded(
typeParameterTypeNone(T),
);
}
test_functionType() {
_assertFunctionBounded(
functionTypeNone(returnType: voidNone),
);
_assertNotFunctionBounded(
functionTypeQuestion(returnType: voidNone),
);
_assertFunctionBounded(
functionTypeStar(returnType: voidNone),
);
_assertFunctionBounded(
functionTypeNone(returnType: dynamicNone),
);
}
test_interfaceType() {
_assertNotFunctionBounded(intNone);
_assertNotFunctionBounded(intQuestion);
_assertNotFunctionBounded(intStar);
}
test_never() {
_assertNotFunctionBounded(neverNone);
_assertNotFunctionBounded(neverQuestion);
_assertNotFunctionBounded(neverStar);
}
test_void() {
_assertNotFunctionBounded(voidNone);
}
void _assertFunctionBounded(DartType type) {
expect(typeSystem.isFunctionBounded(type), isTrue);
}
void _assertNotFunctionBounded(DartType type) {
expect(typeSystem.isFunctionBounded(type), isFalse);
}
}

View file

@ -2554,6 +2554,24 @@ main(A? a) {
);
}
test_hasReceiver_typeParameter_promotedToNonNullable() async {
await assertNoErrorsInCode('''
void f<T>(T? t) {
if (t is int) {
t.abs();
}
}
''');
assertMethodInvocation2(
findNode.methodInvocation('t.abs()'),
element: intElement.getMethod('abs'),
typeArgumentTypes: [],
invokeType: 'int Function()',
type: 'int',
);
}
test_nullShorting_cascade_firstMethodInvocation() async {
await assertNoErrorsInCode(r'''
class A {

View file

@ -440,6 +440,62 @@ class B extends A {
);
}
test_targetTypeParameter_dynamicBounded() async {
await assertNoErrorsInCode('''
class A<T extends dynamic> {
void f(T t) {
(t).foo;
}
}
''');
var propertyAccess = findNode.propertyAccess('.foo');
assertPropertyAccess2(
propertyAccess,
element: null,
type: 'dynamic',
);
assertSimpleIdentifier(
propertyAccess.propertyName,
readElement: null,
writeElement: null,
type: 'dynamic',
);
}
test_targetTypeParameter_noBound() async {
await resolveTestCode('''
class C<T> {
void f(T t) {
(t).foo;
}
}
''');
assertErrorsInResult(expectedErrorsByNullability(
nullable: [
error(CompileTimeErrorCode.UNCHECKED_USE_OF_NULLABLE_VALUE, 33, 3),
],
legacy: [
error(CompileTimeErrorCode.UNDEFINED_GETTER, 37, 3),
],
));
var propertyAccess = findNode.propertyAccess('.foo');
assertPropertyAccess2(
propertyAccess,
element: null,
type: 'dynamic',
);
assertSimpleIdentifier(
propertyAccess.propertyName,
readElement: null,
writeElement: null,
type: 'dynamic',
);
}
test_tearOff_method() async {
await assertNoErrorsInCode('''
class A {

View file

@ -6,15 +6,20 @@ import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../context_collection_resolution.dart';
import '../resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExtensionMethodsTest);
defineReflectiveTests(ExtensionMethodsWithNullSafetyTest);
});
}
@reflectiveTest
class ExtensionMethodsTest extends PubPackageResolutionTest {
class ExtensionMethodsTest extends PubPackageResolutionTest
with ExtensionMethodsTestCases {}
mixin ExtensionMethodsTestCases on ResolutionTest {
test_implicit_getter() async {
await assertNoErrorsInCode('''
class A<T> {}
@ -150,6 +155,118 @@ void f(A<int> a) {
);
}
test_implicit_targetTypeParameter_hasBound_methodInvocation() async {
await assertNoErrorsInCode('''
extension Test<T> on T {
T Function(T) test() => throw 0;
}
void f<S extends num>(S x) {
x.test();
}
''');
if (result.libraryElement.isNonNullableByDefault) {
assertMethodInvocation2(
findNode.methodInvocation('test();'),
element: elementMatcher(
findElement.method('test'),
substitution: {'T': 'S'},
),
typeArgumentTypes: [],
invokeType: 'S Function(S) Function()',
type: 'S Function(S)',
);
} else {
assertMethodInvocation2(
findNode.methodInvocation('test();'),
element: elementMatcher(
findElement.method('test'),
substitution: {'T': 'num'},
),
typeArgumentTypes: [],
invokeType: 'num Function(num) Function()',
type: 'num Function(num)',
);
}
}
test_implicit_targetTypeParameter_hasBound_propertyAccess_getter() async {
await assertNoErrorsInCode('''
extension Test<T> on T {
T Function(T) get test => throw 0;
}
void f<S extends num>(S x) {
(x).test;
}
''');
if (result.libraryElement.isNonNullableByDefault) {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.getter('test'),
substitution: {'T': 'S'},
),
type: 'S Function(S)',
);
} else {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.getter('test'),
substitution: {'T': 'num'},
),
type: 'num Function(num)',
);
}
}
test_implicit_targetTypeParameter_hasBound_propertyAccess_setter() async {
await assertNoErrorsInCode('''
extension Test<T> on T {
void set test(T _) {}
}
T g<T>() => throw 0;
void f<S extends num>(S x) {
(x).test = g();
}
''');
if (result.libraryElement.isNonNullableByDefault) {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.setter('test'),
substitution: {'T': 'S'},
),
type: 'S',
);
assertTypeArgumentTypes(
findNode.methodInvocation('g()'),
['S'],
);
} else {
assertPropertyAccess2(
findNode.propertyAccess('.test'),
element: elementMatcher(
findElement.setter('test'),
substitution: {'T': 'num'},
),
type: 'num',
);
assertTypeArgumentTypes(
findNode.methodInvocation('g()'),
['num'],
);
}
}
test_override_downward_hasTypeArguments() async {
await assertNoErrorsInCode('''
extension E<T> on Set<T> {
@ -425,3 +542,7 @@ void f(A<int> a) {
);
}
}
@reflectiveTest
class ExtensionMethodsWithNullSafetyTest extends PubPackageResolutionTest
with WithNullSafetyMixin, ExtensionMethodsTestCases {}