Skip to content

Commit

Permalink
[cfe] Report errors on awaiting types incompatible with await
Browse files Browse the repository at this point in the history
Closes #54649

Change-Id: I66497f9251710723b983b9f29f3f22eff2c83d96
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/348640
Reviewed-by: Johnni Winther <[email protected]>
Commit-Queue: Chloe Stefantsova <[email protected]>
  • Loading branch information
chloestefantsova authored and Commit Queue committed Jan 29, 2024
1 parent a2ee738 commit 1672177
Show file tree
Hide file tree
Showing 21 changed files with 1,173 additions and 6 deletions.
109 changes: 103 additions & 6 deletions pkg/front_end/lib/src/fasta/type_inference/inference_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,108 @@ class InferenceVisitorImpl extends InferenceVisitorBase
return const StatementInferenceResult();
}

bool _derivesFutureType(DartType type) {
// TODO(cstefantsova): Update this method when
// https://github.com/dart-lang/language/pull/3574 is merged.
if (isNullableTypeConstructorApplication(type)) {
return false;
} else {
switch (type) {
case InterfaceType():
return typeSchemaEnvironment.hierarchy
.getInterfaceTypeAsInstanceOfClass(
type, coreTypes.futureClass,
isNonNullableByDefault: isNonNullableByDefault) !=
null;
case ExtensionType():
return typeSchemaEnvironment.hierarchy
.getExtensionTypeAsInstanceOfClass(
type, coreTypes.futureClass,
isNonNullableByDefault: isNonNullableByDefault) !=
null;
case TypeParameterType():
DartType boundedBy = type;
while (boundedBy is TypeParameterType &&
!isNullableTypeConstructorApplication(boundedBy)) {
boundedBy = boundedBy.parameter.bound;
}
return boundedBy is FutureOrType ||
boundedBy is InterfaceType &&
boundedBy.classNode == coreTypes.futureClass &&
boundedBy.nullability == Nullability.nullable;
case StructuralParameterType():
DartType boundedBy = type;
while (boundedBy is TypeParameterType &&
!isNullableTypeConstructorApplication(boundedBy)) {
boundedBy = boundedBy.parameter.bound;
}
return boundedBy is FutureOrType ||
boundedBy is InterfaceType &&
boundedBy.classNode == coreTypes.futureClass &&
boundedBy.nullability == Nullability.nullable;
case IntersectionType():
DartType boundedBy = type.right;
while (boundedBy is TypeParameterType &&
!isNullableTypeConstructorApplication(boundedBy)) {
boundedBy = boundedBy.parameter.bound;
}
return boundedBy is FutureOrType ||
boundedBy is InterfaceType &&
boundedBy.classNode == coreTypes.futureClass &&
boundedBy.nullability == Nullability.nullable;
case DynamicType():
case VoidType():
case FutureOrType():
case TypedefType():
case FunctionType():
case RecordType():
case NullType():
case NeverType():
case AuxiliaryType():
case InvalidType():
return false;
}
}
}

bool _isIncompatibleWithAwait(DartType type) {
if (isNullableTypeConstructorApplication(type)) {
return _isIncompatibleWithAwait(computeTypeWithoutNullabilityMarker(
(type),
isNonNullableByDefault: isNonNullableByDefault));
} else {
switch (type) {
case ExtensionType():
return typeSchemaEnvironment.hierarchy
.getExtensionTypeAsInstanceOfClass(
type, coreTypes.futureClass,
isNonNullableByDefault:
libraryBuilder.isNonNullableByDefault) ==
null;
case TypeParameterType():
return _isIncompatibleWithAwait(type.parameter.bound);
case StructuralParameterType():
return _isIncompatibleWithAwait(type.parameter.bound);
case IntersectionType():
return _isIncompatibleWithAwait(type.right) ||
!_derivesFutureType(type.right) &&
_isIncompatibleWithAwait(type.left);
case DynamicType():
case VoidType():
case FutureOrType():
case InterfaceType():
case TypedefType():
case FunctionType():
case RecordType():
case NullType():
case NeverType():
case AuxiliaryType():
case InvalidType():
return false;
}
}
}

@override
ExpressionInferenceResult visitAwaitExpression(
AwaitExpression node, DartType typeContext) {
Expand All @@ -818,12 +920,7 @@ class InferenceVisitorImpl extends InferenceVisitorBase
isVoidAllowed: !isNonNullableByDefault);
DartType operandType = operandResult.inferredType;
DartType flattenType = typeSchemaEnvironment.flatten(operandType);
if (operandType is ExtensionType &&
typeSchemaEnvironment.hierarchy.getExtensionTypeAsInstanceOfClass(
operandType, coreTypes.futureClass,
isNonNullableByDefault:
libraryBuilder.isNonNullableByDefault) ==
null) {
if (_isIncompatibleWithAwait(operandType)) {
Expression wrapped = operandResult.expression;
node.operand = helper.wrapInProblem(
wrapped, messageAwaitOfExtensionTypeNotFuture, wrapped.fileOffset, 1);
Expand Down
1 change: 1 addition & 0 deletions pkg/front_end/test/spell_checking_list_common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,7 @@ dereferenced
derivation
derive
derived
derives
descendant
descendants
describe
Expand Down
5 changes: 5 additions & 0 deletions pkg/front_end/test/spell_checking_list_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ forty
fortytwo
foundation
fox
fq
frequency
frozen
fruits
Expand Down Expand Up @@ -525,6 +526,7 @@ nonexisting
noo
noted
nottest
nq
null'ed
numerator
nums
Expand Down Expand Up @@ -584,6 +586,7 @@ pooled
population
portions
pp
pr
preliminary
prematurely
prerequisite
Expand Down Expand Up @@ -836,7 +839,9 @@ wrongly
ws
x's
xf
xfq
xlate
xnq
xrequired
xxx
xxxxxxxx
Expand Down
27 changes: 27 additions & 0 deletions pkg/front_end/testcases/extension_types/issue54649.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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.

extension type N(Future<int> _) {}
extension type F(Future<int> _) implements Future<int> {}

void test<X, XN extends N, XF extends F>(
N n, F f, X x, XN xn, XF xf, N? nq, F? fq, XN? xnq, XF? xfq) async {
await n; // Error.
await f; // OK, type `int`.
await x; // OK, type `X`.
await xn; // Error.
await xf; // OK, type `int`.
await nq; // Error.
await fq; // OK, type `int?`.
await xnq; // Error.
await xfq; // OK, type `int?`.
if (x is N) await x; // Error.
if (x is N?) await x; // Error.
if (x is XN) await x; // Error.
if (x is XN?) await x; // Error.
if (x is F) await x; // OK, type `int`.
if (x is F?) await x; // OK, type `int?`.
if (x is XF) await x; // OK, type `int`.
if (x is XF?) await x; // OK, type `int?`.
}
105 changes: 105 additions & 0 deletions pkg/front_end/testcases/extension_types/issue54649.dart.strong.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
library;
//
// Problems in library:
//
// pkg/front_end/testcases/extension_types/issue54649.dart:10:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// await n; // Error.
// ^
//
// pkg/front_end/testcases/extension_types/issue54649.dart:13:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// await xn; // Error.
// ^
//
// pkg/front_end/testcases/extension_types/issue54649.dart:15:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// await nq; // Error.
// ^
//
// pkg/front_end/testcases/extension_types/issue54649.dart:17:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// await xnq; // Error.
// ^
//
// pkg/front_end/testcases/extension_types/issue54649.dart:19:21: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// if (x is N) await x; // Error.
// ^
//
// pkg/front_end/testcases/extension_types/issue54649.dart:20:22: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// if (x is N?) await x; // Error.
// ^
//
// pkg/front_end/testcases/extension_types/issue54649.dart:21:22: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// if (x is XN) await x; // Error.
// ^
//
// pkg/front_end/testcases/extension_types/issue54649.dart:22:23: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
// if (x is XN?) await x; // Error.
// ^
//
import self as self;
import "dart:async" as asy;
import "dart:core" as core;

extension type N(asy::Future<core::int> _) {
abstract extension-type-member representation-field get _() → asy::Future<core::int>;
constructor • = self::N|constructor#;
constructor tearoff • = self::N|constructor#_#new#tearOff;
}
extension type F(asy::Future<core::int> _) implements asy::Future<core::int> {
abstract extension-type-member representation-field get _() → asy::Future<core::int>;
constructor • = self::F|constructor#;
constructor tearoff • = self::F|constructor#_#new#tearOff;
}
static extension-type-member method N|constructor#(asy::Future<core::int> _) → self::N /* = asy::Future<core::int> */ {
lowered final self::N /* = asy::Future<core::int> */ #this = _;
return #this;
}
static extension-type-member method N|constructor#_#new#tearOff(asy::Future<core::int> _) → self::N /* = asy::Future<core::int> */
return self::N|constructor#(_);
static extension-type-member method F|constructor#(asy::Future<core::int> _) → self::F /* = asy::Future<core::int> */ {
lowered final self::F /* = asy::Future<core::int> */ #this = _;
return #this;
}
static extension-type-member method F|constructor#_#new#tearOff(asy::Future<core::int> _) → self::F /* = asy::Future<core::int> */
return self::F|constructor#(_);
static method test<X extends core::Object? = dynamic, XN extends self::N /* = asy::Future<core::int> */, XF extends self::F /* = asy::Future<core::int> */>(self::N /* = asy::Future<core::int> */ n, self::F /* = asy::Future<core::int> */ f, self::test::X% x, self::test::XN% xn, self::test::XF xf, self::N? /* = asy::Future<core::int>? */ nq, self::F? /* = asy::Future<core::int>? */ fq, self::test::XN? xnq, self::test::XF? xfq) → void async /* emittedValueType= void */ {
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:10:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
await n; // Error.
^" in n /* runtimeCheckType= asy::Future<self::N /* = asy::Future<core::int> */> */ ;
await f;
await x /* runtimeCheckType= asy::Future<self::test::X%> */ ;
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:13:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
await xn; // Error.
^" in xn /* runtimeCheckType= asy::Future<self::test::XN%> */ ;
await xf;
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:15:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
await nq; // Error.
^" in nq /* runtimeCheckType= asy::Future<self::N? /* = asy::Future<core::int>? */> */ ;
await fq /* runtimeCheckType= asy::Future<core::int?> */ ;
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:17:9: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
await xnq; // Error.
^" in xnq /* runtimeCheckType= asy::Future<self::test::XN?> */ ;
await xfq /* runtimeCheckType= asy::Future<core::int?> */ ;
if(x is self::N /* = asy::Future<core::int> */)
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:19:21: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
if (x is N) await x; // Error.
^" in x{self::test::X% & self::N /* = asy::Future<core::int> */ /* '%' & '%' = '%' */} /* runtimeCheckType= asy::Future<self::test::X%> */ ;
if(x is self::N? /* = asy::Future<core::int>? */)
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:20:22: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
if (x is N?) await x; // Error.
^" in x{self::test::X% & self::N? /* = asy::Future<core::int>? */ /* '%' & '?' = '%' */} /* runtimeCheckType= asy::Future<self::test::X%> */ ;
if(x is self::test::XN%)
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:21:22: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
if (x is XN) await x; // Error.
^" in x{self::test::X% & self::test::XN% /* '%' & '%' = '%' */} /* runtimeCheckType= asy::Future<self::test::X%> */ ;
if(x is self::test::XN?)
await invalid-expression "pkg/front_end/testcases/extension_types/issue54649.dart:22:23: Error: The 'await' expression can't be used for an expression with an extension type that is not a subtype of 'Future'.
if (x is XN?) await x; // Error.
^" in x{self::test::X% & self::test::XN? /* '%' & '?' = '%' */} /* runtimeCheckType= asy::Future<self::test::X%> */ ;
if(x is self::F /* = asy::Future<core::int> */)
await x{self::test::X% & self::F /* = asy::Future<core::int> */ /* '%' & '!' = '!' */};
if(x is self::F? /* = asy::Future<core::int>? */)
await x{self::test::X% & self::F? /* = asy::Future<core::int>? */ /* '%' & '?' = '%' */} /* runtimeCheckType= asy::Future<core::int?> */ ;
if(x is self::test::XF)
await x{self::test::X% & self::test::XF /* '%' & '!' = '!' */};
if(x is self::test::XF?)
await x{self::test::X% & self::test::XF? /* '%' & '?' = '%' */} /* runtimeCheckType= asy::Future<core::int?> */ ;
}
Loading

0 comments on commit 1672177

Please sign in to comment.