Skip to content

Commit

Permalink
[stable] Implement error about awaiting a type that is incompatible w…
Browse files Browse the repository at this point in the history
…ith await

This is a cherry-pick of the following 5 commits from the main branch:

- Add test about types being incompatible with await
  (https://dart-review.googlesource.com/c/sdk/+/351143)

- [cfe] Report errors on awaiting types incompatible with await
  (https://dart-review.googlesource.com/c/sdk/+/348640)

- [cfe] Implement the update of "incompatible with await"
  (https://dart-review.googlesource.com/c/sdk/+/350986)

- Extension type. Implement isIncompatibleWithAwait() predicate,
  report AWAIT_OF_INCOMPATIBLE_TYPE.
  (https://dart-review.googlesource.com/c/sdk/+/350986)

- Extension type. Issue 54648. Fix 'incompatible with await'
  predicate. (https://dart-review.googlesource.com/c/sdk/+/355500)

Collectively, these 5 commits implement the new "incompatible with
await" error that was specified in
dart-lang/language#3560 and then refined in
dart-lang/language#3598.

Cherry-pick: https://dart-review.googlesource.com/c/sdk/+/348640
Cherry-pick-request: #55095
Change-Id: I84a79ffccce89c4d91c99a09ab7f5107a96e9844
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/355542
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
  • Loading branch information
stereotype441 authored and Commit Queue committed Mar 5, 2024
1 parent 6dd9806 commit 662d6e1
Show file tree
Hide file tree
Showing 36 changed files with 1,638 additions and 66 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ This is a patch release that:
- Fixes an issue in dart2js where object literal constructors in interop
extension types would fail to compile without an `@JS` annotation on the
library (issue [#55057][]).
- Disallows certain types involving extension types from being used as the
operand of an `await` expression, unless the extension type itself implements
`Future` (issue [#54647][]).

[#55057]: https://github.com/dart-lang/sdk/issues/55057
[#54647]: https://github.com/dart-lang/sdk/issues/54647

## 3.3.0 - 2024-02-15

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions pkg/analyzer/lib/src/dart/element/type_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,40 @@ 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?) {
return isIncompatibleWithAwait(B);
}
// `T` is a type variable with bound `S`, and `S` is incompatible
// with await.
if (T.element.bound case var S?) {
return isIncompatibleWithAwait(S);
}
}

return false;
}

/// Either [InvalidType] itself, or an intersection with it.
bool isInvalidBounded(DartType type) {
if (identical(type, InvalidTypeImpl.instance)) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/analyzer/lib/src/error/codes.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pkg/analyzer/lib/src/error/error_code_values.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 6 additions & 11 deletions pkg/analyzer/lib/src/generated/error_verifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ class ErrorVerifier extends RecursiveAstVisitor<void>
}

_checkForAwaitInLateLocalVariableInitializer(node);
_checkForAwaitOfExtensionTypeNotFuture(node);
_checkForAwaitOfIncompatibleType(node);
super.visitAwaitExpression(node);
}

Expand Down Expand Up @@ -1802,19 +1802,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.reportErrorForToken(
CompileTimeErrorCode.AWAIT_OF_INCOMPATIBLE_TYPE,
node.awaitKeyword,
);
if (!typeSystem.isSubtypeOf(expressionType, anyFuture)) {
errorReporter.reportErrorForToken(
CompileTimeErrorCode.AWAIT_OF_EXTENSION_TYPE_NOT_FUTURE,
node.awaitKeyword,
);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/analyzer/messages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,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
Expand Down
168 changes: 168 additions & 0 deletions pkg/analyzer/test/src/dart/element/incompatible_with_await_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// 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);

// Incompatible with `await`, used as a bound.
// Does not matter, `T` is promoted to not incompatible.
var N = extensionType(
'N',
representationType: futureOfIntNone,
);

var F = extensionType(
'F',
representationType: futureOfIntNone,
interfaces: [
futureOfIntNone,
],
);

isNotIncompatible(
typeParameterTypeNone(
typeParameter(
'T',
bound: interfaceTypeNone(N),
),
promotedBound: interfaceTypeNone(F),
),
);
}

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,
),
);
}
}
2 changes: 2 additions & 0 deletions pkg/analyzer/test/src/dart/element/test_all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,6 +56,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();
Expand Down

This file was deleted.

Loading

0 comments on commit 662d6e1

Please sign in to comment.