Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions compiler/syntax/src/res_parsetree_viewer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,55 @@ let rewrite_underscore_apply expr =
}
| _ -> expr

(* For pipe RHS: (__x) => f(__x, a, b) -----> f(a, b)
Omits the first __x argument only if it's the sole occurrence.
If multiple __x exist (e.g., f(__x, __x, b)), keeps all to preserve semantics. *)
let rewrite_underscore_apply_in_pipe expr =
let is_underscore_arg = function
| _, {pexp_desc = Pexp_ident {txt = Longident.Lident "__x"}} -> true
| _ -> false
in
let convert_underscore_to_placeholder arg =
match arg with
| ( lbl,
({pexp_desc = Pexp_ident ({txt = Longident.Lident "__x"} as lid)} as
arg_expr) ) ->
( lbl,
{
arg_expr with
pexp_desc = Pexp_ident {lid with txt = Longident.Lident "_"};
} )
| arg -> arg
in
match expr.pexp_desc with
| Pexp_fun
{
arg_label = Nolabel;
default = None;
lhs = {ppat_desc = Ppat_var {txt = "__x"}};
rhs = {pexp_desc = Pexp_apply {funct; args}} as e;
} -> (
match args with
| first_arg :: rest_args when is_underscore_arg first_arg ->
if List.exists is_underscore_arg rest_args then
(* Multiple __x - keep all to preserve semantics *)
rewrite_underscore_apply expr
else
(* Single __x in first position - safe to omit *)
{
e with
pexp_desc =
Pexp_apply
{
funct;
args = List.map convert_underscore_to_placeholder rest_args;
partial = false;
transformed_jsx = false;
};
}
| _ -> rewrite_underscore_apply expr)
| _ -> expr

type fun_param_kind =
| Parameter of {
attrs: Parsetree.attributes;
Expand Down
3 changes: 3 additions & 0 deletions compiler/syntax/src/res_parsetree_viewer.mli
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ val is_single_pipe_expr : Parsetree.expression -> bool
(* (__x) => f(a, __x, c) -----> f(a, _, c) *)
val rewrite_underscore_apply : Parsetree.expression -> Parsetree.expression

val rewrite_underscore_apply_in_pipe :
Parsetree.expression -> Parsetree.expression

(* (__x) => f(a, __x, c) -----> f(a, _, c) *)
val is_underscore_apply_sugar : Parsetree.expression -> bool

Expand Down
5 changes: 5 additions & 0 deletions compiler/syntax/src/res_printer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3985,6 +3985,11 @@ and print_binary_expression ~state (expr : Parsetree.expression) cmt_tbl =
->
let lhs_has_comment_below = has_comment_below cmt_tbl lhs.pexp_loc in
let lhs_doc = print_operand ~is_lhs:true ~is_multiline:false lhs op in
(* For pipe RHS, use pipe-specific rewrite to omit redundant first underscore *)
let rhs =
if op = "->" then ParsetreeViewer.rewrite_underscore_apply_in_pipe rhs
else rhs
in
let rhs_doc = print_operand ~is_lhs:false ~is_multiline:false rhs op in
Doc.group
(Doc.concat
Expand Down
10 changes: 9 additions & 1 deletion scripts/test_syntax.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ if [[ $ROUNDTRIP_TEST = 1 ]]; then
mkdir -p temp/$(dirname $file)
sexpAst1=temp/$file.sexp
sexpAst2=temp/$file.2.sexp
sexpAst3=temp/$file.3.sexp
rescript1=temp/$file.res
rescript2=temp/$file.2.res

Expand All @@ -89,14 +90,21 @@ if [[ $ROUNDTRIP_TEST = 1 ]]; then
*.resi ) resIntf=-interface ;;
esac

# First pass: original file -> AST1 and text1
$DUNE_BIN_DIR/res_parser $resIntf -print sexp $file > $sexpAst1
$DUNE_BIN_DIR/res_parser $resIntf -print res $file > $rescript1

# Second pass: text1 -> AST2 and text2
$DUNE_BIN_DIR/res_parser $resIntf -print sexp $rescript1 > $sexpAst2
$DUNE_BIN_DIR/res_parser $resIntf -print res $rescript1 > $rescript2

diff --unified $sexpAst1 $sexpAst2
# Third pass: text2 -> AST3 (to check idempotency after normalization)
$DUNE_BIN_DIR/res_parser $resIntf -print sexp $rescript2 > $sexpAst3

# Check AST idempotency: AST2 should equal AST3 (allows AST1 != AST2 for canonicalization)
diff --unified $sexpAst2 $sexpAst3
[[ "$?" = 1 ]] && echo 1 > $roundtripTestsResult
# Check text idempotency: text1 should equal text2
diff --unified $rescript1 $rescript2
[[ "$?" = 1 ]] && echo 1 > $roundtripTestsResult
} & maybeWait
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ let nested2 = (x, y, z) => List.length(_)

let l = [1, 2, 3]->List.map(i => i + 1, _)->List.filter(i => i > 0, _)

let l = (i => i + 1)->List.map(_, [1, 2, 3])
let l = (i => i + 1)->List.map([1, 2, 3])

let x = List.length(_)

Expand Down Expand Up @@ -52,8 +52,8 @@ f(a, b, _)[ix] = 2

getDirector(a, b, _).name = "Steve"

filterNone->Array.get(_, 0)
filterNone->Array.get(_, 0)
filterNone->Array.get(0)
filterNone->Array.get(0)
Array.get(_, 0)
1 + Array.get(_, 0)
Array.get(_, 1) + Array.get(_, 0)
Expand All @@ -71,3 +71,9 @@ let status =
json
->optional(field("status", string, _), _)
->Option.mapOr(Status.Active, Status.fromString)

a->map2(fn)

a->map2(fn)

a->f(_, _, b)
6 changes: 6 additions & 0 deletions tests/syntax_tests/data/printer/expr/underscoreApply.res
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,9 @@ let status =
json
->optional(field("status", string, _), _)
->Option.mapOr(Status.Active, Status.fromString)

a->map2(_, fn)

a->map2(fn)

a->f(_, _, b)
Loading