mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 02:47:36 +00:00
Extension type. Implement isIncompatibleWithAwait() predicate, report AWAIT_OF_INCOMPATIBLE_TYPE.
Change-Id: I8f562db3fb3399704369725fdfba49c52bb14ab8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/350986 Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Keerti Parthasarathy <keertip@google.com>
This commit is contained in:
parent
e7f79cad7c
commit
adee0a2399
|
@ -221,7 +221,7 @@ CompileTimeErrorCode.AWAIT_IN_LATE_LOCAL_VARIABLE_INITIALIZER:
|
|||
The fix is to remove the `late` modifier.
|
||||
CompileTimeErrorCode.AWAIT_IN_WRONG_CONTEXT:
|
||||
status: hasFix
|
||||
CompileTimeErrorCode.AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE:
|
||||
CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE:
|
||||
status: noFix
|
||||
CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY:
|
||||
status: noFix
|
||||
|
|
|
@ -997,6 +997,44 @@ class TypeSystemImpl implements TypeSystem {
|
|||
return false;
|
||||
}
|
||||
|
||||
/// We say that a type `T` is _incompatible with await_ if at least
|
||||
/// one of the following criteria holds:
|
||||
bool isIncompatibleWithAwait(DartType T) {
|
||||
T as TypeImpl;
|
||||
|
||||
// `T` is `S?`, and `S` is incompatible with await.
|
||||
if (T.nullabilitySuffix == NullabilitySuffix.question) {
|
||||
var T_none = T.withNullability(NullabilitySuffix.none);
|
||||
return isIncompatibleWithAwait(T_none);
|
||||
}
|
||||
|
||||
// `T` is an extension type that does not implement `Future`.
|
||||
if (T.element is ExtensionTypeElement) {
|
||||
var anyFuture = typeProvider.futureType(objectQuestion);
|
||||
if (!isSubtypeOf(T, anyFuture)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (T is TypeParameterTypeImpl) {
|
||||
// `T` is `X & B`, and `B` is incompatible with await.
|
||||
if (T.promotedBound case var B?) {
|
||||
if (isIncompatibleWithAwait(B)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// `T` is a type variable with bound `S`, and `S` is incompatible
|
||||
// with await.
|
||||
if (T.element.bound case var S?) {
|
||||
if (isIncompatibleWithAwait(S)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Either [InvalidType] itself, or an intersection with it.
|
||||
bool isInvalidBounded(DartType type) {
|
||||
if (identical(type, InvalidTypeImpl.instance)) {
|
||||
|
|
|
@ -253,9 +253,9 @@ class CompileTimeErrorCode extends AnalyzerErrorCode {
|
|||
"Try marking the function body with either 'async' or 'async*'.",
|
||||
);
|
||||
|
||||
static const CompileTimeErrorCode AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE =
|
||||
static const CompileTimeErrorCode AWAIT_OF_INCOMPATIBLE_TYPE =
|
||||
CompileTimeErrorCode(
|
||||
'AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE',
|
||||
'AWAIT_OF_INCOMPATIBLE_TYPE',
|
||||
"The 'await' expression can't be used for an expression with an extension "
|
||||
"type that is not a subtype of 'Future'.",
|
||||
correctionMessage:
|
||||
|
|
|
@ -68,7 +68,7 @@ const List<ErrorCode> errorCodeValues = [
|
|||
CompileTimeErrorCode.AUGMENTATION_WITHOUT_LIBRARY,
|
||||
CompileTimeErrorCode.AWAIT_IN_LATE_LOCAL_VARIABLE_INITIALIZER,
|
||||
CompileTimeErrorCode.AWAIT_IN_WRONG_CONTEXT,
|
||||
CompileTimeErrorCode.AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE,
|
||||
CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE,
|
||||
CompileTimeErrorCode.BASE_CLASS_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
|
||||
CompileTimeErrorCode.BASE_MIXIN_IMPLEMENTED_OUTSIDE_OF_LIBRARY,
|
||||
CompileTimeErrorCode.BODY_MIGHT_COMPLETE_NORMALLY,
|
||||
|
|
|
@ -367,7 +367,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
|
|||
}
|
||||
checkForUseOfVoidResult(node.expression);
|
||||
_checkForAwaitInLateLocalVariableInitializer(node);
|
||||
_checkForAwaitOfExtensionTypeNotFuture(node);
|
||||
_checkForAwaitOfIncompatibleType(node);
|
||||
super.visitAwaitExpression(node);
|
||||
}
|
||||
|
||||
|
@ -1836,19 +1836,14 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
|
|||
}
|
||||
}
|
||||
|
||||
void _checkForAwaitOfExtensionTypeNotFuture(AwaitExpression node) {
|
||||
void _checkForAwaitOfIncompatibleType(AwaitExpression node) {
|
||||
final expression = node.expression;
|
||||
final expressionType = expression.typeOrThrow;
|
||||
if (expressionType.element is ExtensionTypeElement) {
|
||||
final anyFuture = typeSystem.typeProvider.futureType(
|
||||
typeSystem.objectQuestion,
|
||||
if (typeSystem.isIncompatibleWithAwait(expressionType)) {
|
||||
errorReporter.atToken(
|
||||
node.awaitKeyword,
|
||||
CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE,
|
||||
);
|
||||
if (!typeSystem.isSubtypeOf(expressionType, anyFuture)) {
|
||||
errorReporter.atToken(
|
||||
node.awaitKeyword,
|
||||
CompileTimeErrorCode.AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1164,7 +1164,7 @@ CompileTimeErrorCode:
|
|||
16.30 Await Expressions: It is a compile-time error if the function
|
||||
immediately enclosing _a_ is not declared asynchronous. (Where _a_ is the
|
||||
await expression.)
|
||||
AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE:
|
||||
AWAIT_OF_INCOMPATIBLE_TYPE:
|
||||
problemMessage: "The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'."
|
||||
correctionMessage: Try removing the `await`, or updating the extension type to implement 'Future'.
|
||||
hasPublishedDocs: true
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright (c) 2024, 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_base.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(IsIncompatibleWithAwaitTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class IsIncompatibleWithAwaitTest extends AbstractTypeSystemTest {
|
||||
void isIncompatible(DartType type) {
|
||||
expect(typeSystem.isIncompatibleWithAwait(type), isTrue);
|
||||
}
|
||||
|
||||
void isNotIncompatible(DartType type) {
|
||||
expect(typeSystem.isIncompatibleWithAwait(type), isFalse);
|
||||
}
|
||||
|
||||
test_class_int() {
|
||||
isNotIncompatible(intNone);
|
||||
isNotIncompatible(intQuestion);
|
||||
}
|
||||
|
||||
test_extensionType_implementsFuture() {
|
||||
var futureOfIntNone = futureNone(intNone);
|
||||
isNotIncompatible(
|
||||
interfaceTypeNone(
|
||||
extensionType(
|
||||
'A',
|
||||
representationType: futureOfIntNone,
|
||||
interfaces: [futureOfIntNone],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test_extensionType_notImplementsFuture() {
|
||||
var futureOfIntNone = futureNone(intNone);
|
||||
|
||||
isIncompatible(
|
||||
interfaceTypeNone(
|
||||
extensionType(
|
||||
'A',
|
||||
representationType: futureOfIntNone,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test_futureInt() {
|
||||
isNotIncompatible(
|
||||
futureNone(intNone),
|
||||
);
|
||||
}
|
||||
|
||||
test_futureOrInt() {
|
||||
isNotIncompatible(
|
||||
futureOrNone(intNone),
|
||||
);
|
||||
}
|
||||
|
||||
test_typeParameter_bound_extensionType_implementsFuture() {
|
||||
var futureOfIntNone = futureNone(intNone);
|
||||
|
||||
var A = extensionType(
|
||||
'A',
|
||||
representationType: futureOfIntNone,
|
||||
interfaces: [futureOfIntNone],
|
||||
);
|
||||
|
||||
isNotIncompatible(
|
||||
typeParameterTypeNone(
|
||||
typeParameter(
|
||||
'T',
|
||||
bound: interfaceTypeNone(A),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test_typeParameter_bound_extensionType_notImplementsFuture() {
|
||||
var futureOfIntNone = futureNone(intNone);
|
||||
|
||||
var A = extensionType(
|
||||
'A',
|
||||
representationType: futureOfIntNone,
|
||||
);
|
||||
|
||||
isIncompatible(
|
||||
typeParameterTypeNone(
|
||||
typeParameter(
|
||||
'T',
|
||||
bound: interfaceTypeNone(A),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test_typeParameter_bound_numNone() {
|
||||
isNotIncompatible(
|
||||
typeParameterTypeNone(
|
||||
typeParameter('T', bound: numNone),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test_typeParameter_promotedBound_extensionType_implementsFuture() {
|
||||
var futureOfIntNone = futureNone(intNone);
|
||||
|
||||
var A = extensionType(
|
||||
'A',
|
||||
representationType: futureOfIntNone,
|
||||
interfaces: [
|
||||
futureOfIntNone,
|
||||
],
|
||||
);
|
||||
|
||||
isNotIncompatible(
|
||||
typeParameterTypeNone(
|
||||
typeParameter('T'),
|
||||
promotedBound: interfaceTypeNone(A),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test_typeParameter_promotedBound_extensionType_notImplementsFuture() {
|
||||
var futureOfIntNone = futureNone(intNone);
|
||||
|
||||
var A = extensionType(
|
||||
'A',
|
||||
representationType: futureOfIntNone,
|
||||
);
|
||||
|
||||
isIncompatible(
|
||||
typeParameterTypeNone(
|
||||
typeParameter('T'),
|
||||
promotedBound: interfaceTypeNone(A),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test_typeParameter_promotedBound_intNone() {
|
||||
isNotIncompatible(
|
||||
typeParameterTypeNone(
|
||||
typeParameter('T'),
|
||||
promotedBound: intNone,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import 'function_type_test.dart' as function_type;
|
|||
import 'future_or_base_test.dart' as future_or_base;
|
||||
import 'future_value_type_test.dart' as future_value_type;
|
||||
import 'generic_inferrer_test.dart' as generic_inferrer;
|
||||
import 'incompatible_with_await_test.dart' as incompatible_with_await;
|
||||
import 'inheritance_manager3_test.dart' as inheritance_manager3;
|
||||
import 'is_known_test.dart' as is_known_test;
|
||||
import 'least_greatest_closure_test.dart' as least_greatest_closure_test;
|
||||
|
@ -54,6 +55,7 @@ main() {
|
|||
future_or_base.main();
|
||||
future_value_type.main();
|
||||
generic_inferrer.main();
|
||||
incompatible_with_await.main();
|
||||
inheritance_manager3.main();
|
||||
is_known_test.main();
|
||||
least_greatest_closure_test.main();
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright (c) 2023, 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/src/error/codes.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import '../dart/resolution/context_collection_resolution.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(AwaitOfExtensionTypeNotFutureTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class AwaitOfExtensionTypeNotFutureTest extends PubPackageResolutionTest {
|
||||
test_hasError() async {
|
||||
await assertErrorsInCode(r'''
|
||||
extension type A(int it) {}
|
||||
|
||||
void f(A a) async {
|
||||
await a;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE, 51, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
test_noError() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
extension type A(Future<int> it) implements Future<int> {}
|
||||
|
||||
void f(A a) async {
|
||||
await a;
|
||||
}
|
||||
''');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) 2023, 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/src/error/codes.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import '../dart/resolution/context_collection_resolution.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(AwaitOfExtensionTypeNotFutureTest);
|
||||
});
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class AwaitOfExtensionTypeNotFutureTest extends PubPackageResolutionTest {
|
||||
test_extensionType_implementsFuture() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
extension type A(Future<int> it) implements Future<int> {}
|
||||
|
||||
void f(A a) async {
|
||||
await a;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_extensionType_notImplementsFuture() async {
|
||||
await assertErrorsInCode(r'''
|
||||
extension type A(int it) {}
|
||||
|
||||
void f(A a) async {
|
||||
await a;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE, 51, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
test_typeParameter_bound_extensionType_implementsFuture() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
extension type A(Future<int> it) implements Future<int> {}
|
||||
|
||||
void f<T extends A>(T a) async {
|
||||
await a;
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_typeParameter_bound_extensionType_notImplementsFuture() async {
|
||||
await assertErrorsInCode(r'''
|
||||
extension type A(Future<int> it) {}
|
||||
|
||||
void f<T extends A>(T a) async {
|
||||
await a;
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE, 72, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
test_typeParameter_promotedBound_extensionType_implementsFuture() async {
|
||||
await assertNoErrorsInCode(r'''
|
||||
extension type A(Future<int> it) implements Future<int> {}
|
||||
|
||||
void f<T>(T a) async {
|
||||
if (T is A) {
|
||||
await a;
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
test_typeParameter_promotedBound_extensionType_notImplementsFuture() async {
|
||||
await assertErrorsInCode(r'''
|
||||
extension type A(Future<int> it) {}
|
||||
|
||||
void f<T>(T a) async {
|
||||
if (a is A) {
|
||||
await a;
|
||||
}
|
||||
}
|
||||
''', [
|
||||
error(CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE, 80, 5),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -41,8 +41,7 @@ import 'async_keyword_used_as_identifier_test.dart'
|
|||
import 'await_in_late_local_variable_initializer_test.dart'
|
||||
as await_in_late_local_variable_initializer;
|
||||
import 'await_in_wrong_context_test.dart' as await_in_wrong_context;
|
||||
import 'await_of_extension_type_not_future_test.dart'
|
||||
as await_of_extension_type_not_future;
|
||||
import 'await_of_incompatible_type_test.dart' as await_of_incompatible_type;
|
||||
import 'base_class_implemented_outside_of_library_test.dart'
|
||||
as base_class_implemented_outside_of_library;
|
||||
import 'base_mixin_implemented_outside_of_library_test.dart'
|
||||
|
@ -932,7 +931,7 @@ main() {
|
|||
async_keyword_used_as_identifier.main();
|
||||
await_in_late_local_variable_initializer.main();
|
||||
await_in_wrong_context.main();
|
||||
await_of_extension_type_not_future.main();
|
||||
await_of_incompatible_type.main();
|
||||
base_class_implemented_outside_of_library.main();
|
||||
base_mixin_implemented_outside_of_library.main();
|
||||
binary_operator_written_out.main();
|
||||
|
|
|
@ -1592,7 +1592,7 @@ Future<int> f() async {
|
|||
}
|
||||
```
|
||||
|
||||
### await_of_extension_type_not_future
|
||||
### await_of_incompatible_type
|
||||
|
||||
_The 'await' expression can't be used for an expression with an extension type
|
||||
that is not a subtype of 'Future'._
|
||||
|
|
|
@ -36,7 +36,7 @@ void f() async {
|
|||
}
|
||||
''', [
|
||||
// No lint
|
||||
error(CompileTimeErrorCode.AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE, 48, 5),
|
||||
error(CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE, 48, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue