Skip to content

Commit 5872d7e

Browse files
jtwarrenmeta-codesync[bot]
authored andcommitted
Add support for delay to signal intention to leave awaitables un-awaited
Summary: Add `delay` keyword that signals intentions to leave an awaitable unawaited (typically an anti-pattern, but valid in certain cases). Similar to `await`, `delay` is only permitted in async functions, and its operand must be an Awaitable<T>. Unlike await, which unwraps to T, delay preserves the Awaitable<T> type and is a runtime no-op. This change doesn't impose any restrictions on unawaited awaitables without `delay`, yet. Will build on top of this to typecheck unawaited awaitables (D93179385) after addressing rollout concerns. TODO: RFC: I still need to add a check that `delay` is only used in assignment (e.g. `$x = delay foo()`, and not `foo(delay $bar)`. The latter isn't dangerous, but it is useless. Should I add an explicit check for this, or just let it be innocuous? Reviewed By: vassilmladenov Differential Revision: D93162973 fbshipit-source-id: 6424b937b1f41954863c04a2e4c96b0740461039
1 parent 404b156 commit 5872d7e

66 files changed

Lines changed: 882 additions & 576 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

hphp/hack/src/annotated_ast/aast_defs.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,11 @@ and ('ex, 'en) expr_ =
588588
(** Await expression.
589589
*
590590
* await $foo *)
591+
| Delay of ('ex, 'en) expr
592+
(** Delay expression. Runtime no-op that signals intent not to immediately
593+
* await an awaitable.
594+
*
595+
* delay $foo *)
591596
| ReadonlyExpr of ('ex, 'en) expr
592597
(** Readonly expression.
593598
*

hphp/hack/src/annotated_ast/aast_utils.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ let rec can_be_captured = function
4646
| Yield _
4747
| Clone _
4848
| Await _
49+
| Delay _
4950
| ReadonlyExpr _
5051
| Cast _
5152
| Unop _
@@ -166,6 +167,7 @@ let rec is_const_expr (_, _, expr_) =
166167
| Call _
167168
| New _
168169
| Await _
170+
| Delay _
169171
| Assign _
170172
| ExpressionTree _
171173
| ET_Splice _

hphp/hack/src/decl/direct_decl_smart_constructors.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,14 +1472,15 @@ impl<'o, 't> DirectDeclSmartConstructors<'o, 't> {
14721472
Unop(box (_, expr)) => expr_to_ty(expr),
14731473
Hole(box (expr, _, _, _)) => expr_to_ty(expr),
14741474

1475-
ArrayGet(_) | As(_) | Await(_) | Binop(_) | Assign(_) | Call(_)
1476-
| Cast(_) | ClassConst(_) | ClassGet(_) | Clone(_) | Collection(_)
1477-
| Dollardollar(_) | Efun(_) | Eif(_) | EnumClassLabel(_) | ETSplice(_)
1478-
| ExpressionTree(_) | FunctionPointer(_) | Id(_) | Import(_) | Is(_)
1479-
| KeyValCollection(_) | Lfun(_) | List(_) | Lplaceholder(_) | Lvar(_)
1480-
| MethodCaller(_) | New(_) | ObjGet(_) | Omitted | Pair(_) | Pipe(_)
1481-
| ReadonlyExpr(_) | Shape(_) | Tuple(_) | Upcast(_) | ValCollection(_)
1482-
| Xml(_) | Yield(_) | Invalid(_) | Package(_) | Nameof(_) => None,
1475+
ArrayGet(_) | As(_) | Await(_) | Delay(_) | Binop(_) | Assign(_)
1476+
| Call(_) | Cast(_) | ClassConst(_) | ClassGet(_) | Clone(_)
1477+
| Collection(_) | Dollardollar(_) | Efun(_) | Eif(_)
1478+
| EnumClassLabel(_) | ETSplice(_) | ExpressionTree(_)
1479+
| FunctionPointer(_) | Id(_) | Import(_) | Is(_) | KeyValCollection(_)
1480+
| Lfun(_) | List(_) | Lplaceholder(_) | Lvar(_) | MethodCaller(_)
1481+
| New(_) | ObjGet(_) | Omitted | Pair(_) | Pipe(_) | ReadonlyExpr(_)
1482+
| Shape(_) | Tuple(_) | Upcast(_) | ValCollection(_) | Xml(_)
1483+
| Yield(_) | Invalid(_) | Package(_) | Nameof(_) => None,
14831484
}
14841485
}
14851486
Some(Ty(
@@ -3034,6 +3035,7 @@ impl<'o, 't> FlattenSmartConstructors for DirectDeclSmartConstructors<'o, 't> {
30343035
| TokenKind::Concurrent
30353036
| TokenKind::Continue
30363037
| TokenKind::Default
3038+
| TokenKind::Delay
30373039
| TokenKind::Do
30383040
| TokenKind::Echo
30393041
| TokenKind::Else

hphp/hack/src/elab/lift_await.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ fn check_await_usage(expr: &Expr) -> AwaitUsage {
140140
| Expr_::Clone(box expr)
141141
| Expr_::PrefixedString(box (_, expr))
142142
| Expr_::ReadonlyExpr(box expr)
143+
| Expr_::Delay(box expr)
143144
| Expr_::Cast(box (_, expr))
144145
| Expr_::Is(box (expr, _))
145146
| Expr_::As(box nast::As_ { expr, .. })
@@ -345,6 +346,7 @@ fn extract_subexprs(expr: &mut nast::Expr) -> Vec<&mut nast::Expr> {
345346
| Expr_::Clone(_)
346347
| Expr_::PrefixedString(_)
347348
| Expr_::ReadonlyExpr(_)
349+
| Expr_::Delay(_)
348350
| Expr_::Cast(_)
349351
| Expr_::Is(_)
350352
| Expr_::As(_)
@@ -911,6 +913,7 @@ impl LiftAwait {
911913
| Expr_::Clone(box expr)
912914
| Expr_::PrefixedString(box (_, expr))
913915
| Expr_::ReadonlyExpr(box expr)
916+
| Expr_::Delay(box expr)
914917
| Expr_::Cast(box (_, expr))
915918
| Expr_::Is(box (expr, _))
916919
| Expr_::As(box nast::As_ { expr, .. })

hphp/hack/src/elab/passes/elab_const_expr.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ impl Pass for ElabConstExprPass {
183183
| Expr_::Lplaceholder(..)
184184
| Expr_::ArrayGet(..)
185185
| Expr_::Await(..)
186+
| Expr_::Delay(..)
186187
| Expr_::Cast(..)
187188
| Expr_::ClassGet(..)
188189
| Expr_::Clone(..)

hphp/hack/src/elab/passes/elab_dynamic_class_name.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ fn is_dynamic(class_id: &ClassId) -> bool {
8787
| ClassId_::CIexpr(Expr(
8888
_,
8989
_,
90-
Expr_::Lvar(..) | Expr_::This | Expr_::Dollardollar(..) | Expr_::Await(..),
90+
Expr_::Lvar(..)
91+
| Expr_::This
92+
| Expr_::Dollardollar(..)
93+
| Expr_::Await(..)
94+
| Expr_::Delay(..),
9195
)) => false,
9296
ClassId_::CIexpr(_) => true,
9397
}

hphp/hack/src/elab/passes/validate_coroutine.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ impl Pass for ValidateCoroutinePass {
6161
env.emit_error(NastCheckError::AwaitInSyncFunction {
6262
pos: exprs.0.clone(),
6363
func_pos: None,
64+
keyword: "await".into(),
6465
})
6566
}
6667
Stmt_::Foreach(box (_, AsExpr::AwaitAsV(pos, _) | AsExpr::AwaitAsKv(pos, _, _), _))
@@ -69,12 +70,14 @@ impl Pass for ValidateCoroutinePass {
6970
env.emit_error(NastCheckError::AwaitInSyncFunction {
7071
pos: pos.clone(),
7172
func_pos: None,
73+
keyword: "await".into(),
7274
})
7375
}
7476
Stmt_::Awaitall(..) if self.is_sync() => {
7577
env.emit_error(NastCheckError::AwaitInSyncFunction {
7678
pos: elem.0.clone(),
7779
func_pos: None,
80+
keyword: "await".into(),
7881
})
7982
}
8083
Stmt_::Return(box Some(_)) if self.is_generator() => {
@@ -91,6 +94,14 @@ impl Pass for ValidateCoroutinePass {
9194
env.emit_error(NastCheckError::AwaitInSyncFunction {
9295
pos: elem.1.clone(),
9396
func_pos: self.func_pos.clone(),
97+
keyword: "await".into(),
98+
})
99+
}
100+
Expr_::Delay(..) if self.is_sync() => {
101+
env.emit_error(NastCheckError::AwaitInSyncFunction {
102+
pos: elem.1.clone(),
103+
func_pos: self.func_pos.clone(),
104+
keyword: "delay".into(),
94105
})
95106
}
96107
_ => (),

hphp/hack/src/elab/transform.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the "hack" directory of this source tree.
55
//
6-
// @generated SignedSource<<025b1d8e377255344caf6a4eb3584dad>>
6+
// @generated SignedSource<<9bf0fd63ae6ce7d846018264a93cb872>>
77
//
88
// To regenerate this file, run:
99
// buck run @fbcode//mode/dev-nosan-lg fbcode//hphp/hack/src:oxidized_regen
@@ -748,6 +748,7 @@ impl Transform for Expr_ {
748748
}
749749
Expr_::Yield(ref mut __binding_0) => __binding_0.transform(env, &mut pass.clone()),
750750
Expr_::Await(ref mut __binding_0) => __binding_0.transform(env, &mut pass.clone()),
751+
Expr_::Delay(ref mut __binding_0) => __binding_0.transform(env, &mut pass.clone()),
751752
Expr_::ReadonlyExpr(ref mut __binding_0) => {
752753
__binding_0.transform(env, &mut pass.clone())
753754
}

hphp/hack/src/hackc/compile/closure_convert.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,10 @@ impl<'ast, 'a: 'b, 'b> VisitorMut<'ast> for ClosureVisitor<'a, 'b> {
11201120
x.recurse(scope, self)?;
11211121
Expr_::Await(x)
11221122
}
1123+
Expr_::Delay(mut x) => {
1124+
x.recurse(scope, self)?;
1125+
Expr_::Delay(x)
1126+
}
11231127
Expr_::ReadonlyExpr(mut x) => {
11241128
x.recurse(scope, self)?;
11251129
Expr_::ReadonlyExpr(x)

hphp/hack/src/hackc/emitter/emit_expression.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ pub fn emit_expr<'a>(
507507
Expr_::Clone(e) => Ok(emit_pos_then(pos, emit_clone(emitter, env, e)?)),
508508
Expr_::Shape(e) => Ok(emit_pos_then(pos, emit_shape(emitter, env, expression, e)?)),
509509
Expr_::Await(e) => emit_await(emitter, env, pos, e),
510+
Expr_::Delay(e) => emit_expr(emitter, env, e),
510511
Expr_::ReadonlyExpr(e) => emit_readonly_expr(emitter, env, pos, e),
511512
Expr_::Yield(e) => emit_yield(emitter, env, pos, e),
512513
Expr_::Efun(e) => Ok(emit_pos_then(
@@ -6148,6 +6149,7 @@ fn can_use_as_rhs_in_list_assignment(expr: &ast::Expr_) -> Result<bool> {
61486149
| Expr_::As(_)
61496150
| Expr_::Upcast(_)
61506151
| Expr_::Await(_)
6152+
| Expr_::Delay(_)
61516153
| Expr_::ReadonlyExpr(_)
61526154
| Expr_::ClassConst(_) => true,
61536155
Expr_::Pipe(p) => can_use_as_rhs_in_list_assignment(&(p.2).2)?,

0 commit comments

Comments
 (0)