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:
Konstantin Shcheglov 2024-02-09 22:11:21 +00:00 committed by Commit Queue
parent e7f79cad7c
commit adee0a2399
13 changed files with 300 additions and 60 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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