From 936b348b96cfc45fce6f06774d9b012c72aed456 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Tue, 20 Oct 2026 18:46:14 +0000 Subject: [PATCH] Parse a switch guard that calls a function named async Why: The async arrow lookahead also ran in guards, where the case's `=>` follows the guard, so `if async(x) =>` was read as an async arrow. Async arrows now skip the lookahead in the positions where other arrows already do: guards and conditions. Fixes #8933. AST modified (switch guard): Example: let async = x => x > 0 let f = v => switch v { | Some(x) if async(x) => 1 | _ => 0 } Before (diagnostic): Did you forget a `=>` here? After (expanded): let f [arity:1]v = match v with | Some x when async x -> 1 | _ -> 0 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF Signed-off-by: Christoph Knittel --- changes/async-guard-lookahead.fix.md | 1 + compiler/syntax/src/res_core.ml | 11 +++++++---- .../data/parsing/grammar/expressions/async.res | 7 +++++++ .../grammar/expressions/expected/async.res.txt | 6 +++++- 4 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 changes/async-guard-lookahead.fix.md diff --git a/changes/async-guard-lookahead.fix.md b/changes/async-guard-lookahead.fix.md new file mode 100644 index 00000000000..ae06bb70d63 --- /dev/null +++ b/changes/async-guard-lookahead.fix.md @@ -0,0 +1 @@ +- Parse a switch guard that calls a function named `async`. diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 222cffc834d..8ffd5d1cad0 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -2321,11 +2321,14 @@ and parse_operand_expr ~context p = (* we need to be careful when we're in a ternary true branch: `condition ? ternary-true-branch : false-branch` Arrow expressions could be of the form: `async (): int => stuff()` - But if we're in a ternary, the `:` of the ternary takes precedence + But if we're in a ternary, the `:` of the ternary takes precedence. + Like other arrows, none starts in a guard or a condition, where + `async(x) =>` is a call followed by the case's `=>`. *) - when is_es6_arrow_expression - ~in_ternary:(context = TernaryTrueBranchExpr) - p -> + when context != WhenExpr + && is_es6_arrow_expression + ~in_ternary:(context = TernaryTrueBranchExpr) + p -> let arrow_attrs = !attrs in let () = attrs := [] in parse_async_arrow_expression ~arrow_attrs p diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/async.res b/tests/syntax_tests/data/parsing/grammar/expressions/async.res index 8828e7d05e3..da7205342ef 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/async.res +++ b/tests/syntax_tests/data/parsing/grammar/expressions/async.res @@ -42,3 +42,10 @@ let attr2 = @a async (type a) => (type b c, x) => 3 let attr3 = @a (type a) => async (type b c, x) => 3 let attr4 = @a (type a) => @b async (type b c, x) => 3 let attr5 : int = @a @b async (type a, type b c) => (x:a) => x + +// A guard calling a function named async is a call, not an async arrow +let guarded = v => + switch v { + | Some(x) if async(x) => async () => x + | _ => async () => 0 + } diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt index c34d7be652f..65a9cfdbbab 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt @@ -36,4 +36,8 @@ let attr4 = let (attr5 : int) = ((async fun (type a) (type b) (type c) [arity:1]() -> fun [arity:1](x : a) -> x) - [@a ][@b ]) \ No newline at end of file + [@a ][@b ]) +let guarded [arity:1]v = + match v with + | Some x when async x -> (async fun [arity:1]() -> x) + | _ -> (async fun [arity:1]() -> 0) \ No newline at end of file