From 8cc75fe23f67fcbabfe675d90044440950f5c8b0 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 29 Apr 2026 11:38:46 -0300 Subject: [PATCH 1/6] Remove `assert` as a reserved keyword MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `assert` is now a regular function call `assert(expr)` rather than a keyword expression. This removes the `Assert` token from the scanner, its parse case from the parser, and the corresponding grammar predicates. To preserve correct semantics and source-location information in thrown `Assert_failure` exceptions, the `%assert` primitive is handled specially in the type checker (`typecore.ml`): it produces `Texp_assert` with the same type rules as the former `Pexp_assert` node (`unit` in statement position, `'a` when the argument is `false`). `translcore.ml` has a matching special case so the existing `assert_failed` helper — which uses the call-site location — generates the correct JS output. `Pervasives.res` now declares `external assert: bool => 'a = "%assert"` so the function is available everywhere without an explicit import. Signed-Off-By: Pedro Castro Co-Authored-By: Claude Sonnet 4.6 --- compiler/ml/translcore.ml | 17 +++++ compiler/ml/typecore.ml | 23 ++++++ compiler/syntax/src/res_core.ml | 6 -- compiler/syntax/src/res_grammar.ml | 10 +-- compiler/syntax/src/res_token.ml | 5 +- compiler/syntax/src/res_token_debugger.ml | 1 - packages/@rescript/runtime/Pervasives.res | 2 + .../termination/src/TestCyberTruck.res | 4 +- tests/analysis_tests/tests/src/Objects.res | 2 +- tests/analysis_tests/tests/src/Patterns.res | 2 +- .../parsing/grammar/expressions/binary.res | 6 +- .../expressions/expected/binary.res.txt | 2 - .../comments/expected/blockExpr.res.txt | 10 +-- .../syntax_tests/data/printer/expr/assert.res | 70 +++++++++---------- .../data/printer/expr/expected/assert.res.txt | 56 +++++++++------ .../printer/expr/expected/asyncAwait.res.txt | 2 +- .../data/printer/expr/expected/binary.res.txt | 2 +- .../data/printer/expr/expected/braced.res.txt | 4 +- .../data/printer/expr/expected/field.res.txt | 2 +- .../data/printer/expr/expected/unary.res.txt | 2 +- 20 files changed, 133 insertions(+), 95 deletions(-) diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index ecf3a73528d..a81e30d4f28 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -718,6 +718,23 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = [lambda], loc ) | None -> lambda) + | Texp_apply + { + funct = + { + exp_desc = + Texp_ident (_, _, {val_kind = Val_prim {prim_name = "%assert"}}); + }; + args = [(_, Some cond)]; + } -> ( + (* assert(cond) — same semantics as the old assert keyword *) + match cond.exp_desc with + | Texp_construct (_, {cstr_name = "false"}, _) -> + if !Clflags.no_assert_false then Lambda.lambda_assert_false + else assert_failed e + | _ -> + if !Clflags.noassert then lambda_unit + else Lifthenelse (transl_exp cond, lambda_unit, assert_failed e)) | Texp_apply { funct = diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 4f0b3be38e4..f883063b8ae 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -2503,6 +2503,29 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) type_function ?in_function ~arity ~async loc sexp.pexp_attributes env ty_expected l [Ast_helper.Exp.case spat sbody] + | Pexp_apply {funct = {pexp_desc = Pexp_ident lid}; args = [(Nolabel, scond)]} + when match Env.lookup_value lid.txt env with + | _, {val_kind = Val_prim {Primitive.prim_name = "%assert"}} -> true + | _ -> false + | exception Not_found -> false -> + (* assert(cond) via the %assert primitive — same semantics as the keyword form *) + let cond = + type_expect ~context:(Some AssertCondition) env scond Predef.type_bool + in + let exp_type = + match cond.exp_desc with + | Texp_construct (_, {cstr_name = "false"}, _) -> instance env ty_expected + | _ -> instance_def Predef.type_unit + in + rue + { + exp_desc = Texp_assert cond; + exp_loc = loc; + exp_extra = []; + exp_type; + exp_attributes = sexp.pexp_attributes; + exp_env = env; + } | Pexp_apply {funct = sfunct; args = sargs; partial; transformed_jsx} -> assert (sargs <> []); begin_def (); diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 619f71e6929..cc19f1249f0 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -2374,15 +2374,9 @@ and parse_unary_expr p = * If you have `a + b`, `a` and `b` both represent * the operands of the binary expression with opeartor `+` *) and parse_operand_expr ~context p = - let start_pos = p.Parser.start_pos in let attrs = ref (parse_attributes p) in let expr = match p.Parser.token with - | Assert -> - Parser.next p; - let expr = parse_expr p in - let loc = mk_loc start_pos p.prev_end_pos in - Ast_helper.Exp.assert_ ~loc expr | Lident "async" (* we need to be careful when we're in a ternary true branch: `condition ? ternary-true-branch : false-branch` diff --git a/compiler/syntax/src/res_grammar.ml b/compiler/syntax/src/res_grammar.ml index de4a9578064..c3710832741 100644 --- a/compiler/syntax/src/res_grammar.ml +++ b/compiler/syntax/src/res_grammar.ml @@ -149,10 +149,10 @@ let is_atomic_typ_expr_start = function | _ -> false let is_expr_start = function - | Token.Assert | At | Await | Backtick | Bang | Codepoint _ | False | Float _ - | For | Hash | If | Int _ | Lbrace | Lbracket | LessThan | Lident _ | List - | Lparen | Minus | MinusDot | Module | Percent | Plus | PlusDot | Bnot | Bor - | Bxor | Band | String _ | Switch | True | Try | Uident _ + | Token.At | Await | Backtick | Bang | Codepoint _ | False | Float _ | For + | Hash | If | Int _ | Lbrace | Lbracket | LessThan | Lident _ | List | Lparen + | Minus | MinusDot | Module | Percent | Plus | PlusDot | Bnot | Bor | Bxor + | Band | String _ | Switch | True | Try | Uident _ | Underscore (* _ => doThings() *) | While | Forwardslash | ForwardslashDot | Dict -> true @@ -264,7 +264,7 @@ let is_attribute_start = function let is_jsx_child_start = is_atomic_expr_start let is_block_expr_start = function - | Token.Assert | At | Await | Backtick | Bang | Break | Codepoint _ | Continue + | Token.At | Await | Backtick | Bang | Break | Codepoint _ | Continue | Exception | False | Float _ | For | Forwardslash | ForwardslashDot | Hash | If | Int _ | Lbrace | Lbracket | LessThan | Let _ | Lident _ | List | Lparen | Minus | MinusDot | Module | Open | Percent | Plus | PlusDot | String _ diff --git a/compiler/syntax/src/res_token.ml b/compiler/syntax/src/res_token.ml index 92e02afcbf3..32f53e3c9bb 100644 --- a/compiler/syntax/src/res_token.ml +++ b/compiler/syntax/src/res_token.ml @@ -57,7 +57,6 @@ type t = | LessThan | Hash | HashEqual - | Assert | Tilde | Question | If @@ -182,7 +181,6 @@ let to_string = function | Asterisk -> "*" | AsteriskDot -> "*." | Exponentiation -> "**" - | Assert -> "assert" | Tilde -> "~" | Question -> "?" | If -> "if" @@ -234,7 +232,6 @@ let to_string = function let keyword_table = function | "and" -> And | "as" -> As - | "assert" -> Assert | "await" -> Await | "break" -> Break | "constraint" -> Constraint @@ -267,7 +264,7 @@ let keyword_table = function [@@raises Not_found] let is_keyword = function - | Await | Break | Continue | And | As | Assert | Constraint | Else | Exception + | Await | Break | Continue | And | As | Constraint | Else | Exception | External | False | For | If | In | Include | Land | Let _ | List | Lor | Module | Mutable | Of | Open | Private | Rec | Switch | True | Try | Typ | When | While | Dict -> diff --git a/compiler/syntax/src/res_token_debugger.ml b/compiler/syntax/src/res_token_debugger.ml index e745308dd4f..d887363041f 100644 --- a/compiler/syntax/src/res_token_debugger.ml +++ b/compiler/syntax/src/res_token_debugger.ml @@ -74,7 +74,6 @@ let dump_tokens filename = | Res_token.LessThan -> "LessThan" | Res_token.Hash -> "Hash" | Res_token.HashEqual -> "HashEqual" - | Res_token.Assert -> "Assert" | Res_token.Tilde -> "Tilde" | Res_token.Question -> "Question" | Res_token.If -> "If" diff --git a/packages/@rescript/runtime/Pervasives.res b/packages/@rescript/runtime/Pervasives.res index dcd2a29780a..bb1168f250f 100644 --- a/packages/@rescript/runtime/Pervasives.res +++ b/packages/@rescript/runtime/Pervasives.res @@ -22,6 +22,8 @@ result == "Caught exception: Out of milk" */ external throw: exn => 'a = "%raise" +external assert: bool => 'a = "%assert" + @deprecated({ reason: "`raise` has been renamed to `throw` to align with JavaScript vocabulary. Please use `throw` instead", migrate: throw(), diff --git a/tests/analysis_tests/tests-reanalyze/termination/src/TestCyberTruck.res b/tests/analysis_tests/tests-reanalyze/termination/src/TestCyberTruck.res index 413179ad533..f3fd73b3e41 100644 --- a/tests/analysis_tests/tests-reanalyze/termination/src/TestCyberTruck.res +++ b/tests/analysis_tests/tests-reanalyze/termination/src/TestCyberTruck.res @@ -5,7 +5,7 @@ let progress = { let counter = ref((100)) () => { if counter.contents < 0 { - assert false + assert(false) } counter := counter.contents - 1 } @@ -247,7 +247,7 @@ module UITermination = { let nothing: onClick = () => () type div = (~text: string, ~onClick: onClick) => dom - let div: div = (~text, ~onClick) => assert false + let div: div = (~text, ~onClick) => assert(false) let initState = n => n == 0 ? Some(42) : None let increment = n => Some(n + 1) diff --git a/tests/analysis_tests/tests/src/Objects.res b/tests/analysis_tests/tests/src/Objects.res index 4718f37464d..3b32ca8b9fe 100644 --- a/tests/analysis_tests/tests/src/Objects.res +++ b/tests/analysis_tests/tests/src/Objects.res @@ -5,7 +5,7 @@ type nestedObjT = {"y": objT} module Rec = { type recordt = {xx: int, ss: string} - let recordVal: recordt = assert false + let recordVal: recordt = assert(false) } let object: objT = {"name": "abc", "age": 4} diff --git a/tests/analysis_tests/tests/src/Patterns.res b/tests/analysis_tests/tests/src/Patterns.res index 8d6dc6520bb..7617cb67b36 100644 --- a/tests/analysis_tests/tests/src/Patterns.res +++ b/tests/analysis_tests/tests/src/Patterns.res @@ -13,7 +13,7 @@ module A = { type rec arr = A(array) - let A([v1, _, _]) | _ as v1 = assert false + let A([v1, _, _]) | _ as v1 = assert(false) } diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/binary.res b/tests/syntax_tests/data/parsing/grammar/expressions/binary.res index 5620a38958a..51c603a6c4f 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/binary.res +++ b/tests/syntax_tests/data/parsing/grammar/expressions/binary.res @@ -14,10 +14,8 @@ node := node } -let x = z->switch z {| _ => false} -let x = z->@attr switch z {| _ => false} -let x = z->assert(z) -let x = z->@attr assert(z) +let x = z->switch z {| _ => false} +let x = z->@attr switch z {| _ => false} let x = z->try sideEffect() catch { | _ => f() } let x = z->@attr try sideEffect() catch { | _ => f() } let x = z->for i in 0 to 10 { () } diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt index 21148778bc1..5f3b1f22605 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/binary.res.txt @@ -3,8 +3,6 @@ [@attr ]) let x = z -> (match z with | _ -> false) let x = z -> ((match z with | _ -> false)[@attr ]) -let x = z -> (assert z) -let x = z -> ((assert z)[@attr ]) let x = z -> (try sideEffect () with | _ -> f ()) let x = z -> ((try sideEffect () with | _ -> f ())[@attr ]) let x = z -> for i = 0 to 10 do () done diff --git a/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt b/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt index 9c08337639b..10bc8a4684a 100644 --- a/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt +++ b/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt @@ -354,17 +354,17 @@ if { true } // trailing -assert({ - // here +assert(// here +{ open /* inside */ Matrix // c // c2 compare(m1, m2) - // after - - // test }) +// after + +// test user.name = { // here diff --git a/tests/syntax_tests/data/printer/expr/assert.res b/tests/syntax_tests/data/printer/expr/assert.res index 84f12aa0853..ddd57356ef2 100644 --- a/tests/syntax_tests/data/printer/expr/assert.res +++ b/tests/syntax_tests/data/printer/expr/assert.res @@ -1,56 +1,56 @@ -assert false - -assert truth - -let x = assert true -let x = assert 12 -let x = assert (12: int) -let x = assert 12 -let x = assert list{1, 2, ...x} -let x = assert module(Foo: Bar) -let x = assert module(Foo) -let x = assert Rgb(1, 2, 3) -let x = assert [a, b, c] -let x = assert {x: 1, y: 3} -let x = assert (1, 2, 3) -let x = assert %extension -let x = assert user.name -let x = assert streets[0] -let x = assert apply(arg1, arg2) -let x = assert apply(arg1, arg2) -let x = assert -1 -let x = assert !true -let x = assert (x => print(x)) -let x = assert (switch x { +assert(false) + +assert(truth) + +let x = assert(true) +let x = assert(12) +let x = assert(12: int) +let x = assert(12) +let x = assert(list{1, 2, ...x}) +let x = assert(module(Foo: Bar)) +let x = assert(module(Foo)) +let x = assert(Rgb(1, 2, 3)) +let x = assert([a, b, c]) +let x = assert({x: 1, y: 3}) +let x = assert((1, 2, 3)) +let x = assert(%extension) +let x = assert(user.name) +let x = assert(streets[0]) +let x = assert(apply(arg1, arg2)) +let x = assert(apply(arg1, arg2)) +let x = assert(-1) +let x = assert(!true) +let x = assert(x => print(x)) +let x = assert(switch x { | Blue => () | Yello => () }) -let x = assert (for i in 0 to 10 { +let x = assert(for i in 0 to 10 { print_int(i) }) -let x = assert (if i < 10 { +let x = assert(if i < 10 { print_int(i) } else { print_int(1000) }) -let x = assert (while i < 10 { +let x = assert(while i < 10 { print_int(i) }) -let x = assert (try sideEffect() catch {| Exit => ()}) +let x = assert(try sideEffect() catch {| Exit => ()}) -let x = assert (@attr expr) +let x = assert(@attr expr) -let x = assert (a + b) +let x = assert(a + b) -let x = @attr assert false +let x = @attr assert(false) -assert invariant["fatal"] -assert invariants[0] +assert(invariant["fatal"]) +assert(invariants[0]) -assert address["street"] = "Brusselsestraat" +assert(address["street"] = "Brusselsestraat") -assert (true ? 0 : 1) +assert(true ? 0 : 1) diff --git a/tests/syntax_tests/data/printer/expr/expected/assert.res.txt b/tests/syntax_tests/data/printer/expr/expected/assert.res.txt index 93d7d60d5ca..6fc1dc92dac 100644 --- a/tests/syntax_tests/data/printer/expr/expected/assert.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/assert.res.txt @@ -4,7 +4,7 @@ assert(truth) let x = assert(true) let x = assert(12) -let x = assert(12: int) +let x = assert((12: int)) let x = assert(12) let x = assert(list{1, 2, ...x}) let x = assert(module(Foo: Bar)) @@ -21,28 +21,38 @@ let x = assert(apply(arg1, arg2)) let x = assert(-1) let x = assert(!true) let x = assert(x => print(x)) -let x = assert(switch x { -| Blue => () -| Yello => () -}) - -let x = assert(for i in 0 to 10 { - print_int(i) -}) - -let x = assert(if i < 10 { - print_int(i) -} else { - print_int(1000) -}) - -let x = assert(while i < 10 { - print_int(i) -}) - -let x = assert(try sideEffect() catch { -| Exit => () -}) +let x = assert( + switch x { + | Blue => () + | Yello => () + }, +) + +let x = assert( + for i in 0 to 10 { + print_int(i) + }, +) + +let x = assert( + if i < 10 { + print_int(i) + } else { + print_int(1000) + }, +) + +let x = assert( + while i < 10 { + print_int(i) + }, +) + +let x = assert( + try sideEffect() catch { + | Exit => () + }, +) let x = assert(@attr expr) diff --git a/tests/syntax_tests/data/printer/expr/expected/asyncAwait.res.txt b/tests/syntax_tests/data/printer/expr/expected/asyncAwait.res.txt index cb61de0fa22..f47d946897b 100644 --- a/tests/syntax_tests/data/printer/expr/expected/asyncAwait.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/asyncAwait.res.txt @@ -84,7 +84,7 @@ let _ = await ( sideEffect() } ) -let _ = await (assert(x)) +let _ = await assert(x) let _ = await promises[0] let _ = await promises["resolved"] let _ = await promises["resolved"] = sideEffect() diff --git a/tests/syntax_tests/data/printer/expr/expected/binary.res.txt b/tests/syntax_tests/data/printer/expr/expected/binary.res.txt index fdea02c9273..17b19891f65 100644 --- a/tests/syntax_tests/data/printer/expr/expected/binary.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/binary.res.txt @@ -377,7 +377,7 @@ let x = } let x = a && assert(false) -let x = a && assert(false && assert(true)) +let x = a && assert(false) && assert(true) let x = a && { diff --git a/tests/syntax_tests/data/printer/expr/expected/braced.res.txt b/tests/syntax_tests/data/printer/expr/expected/braced.res.txt index 80ea8243896..95fcc8a08a3 100644 --- a/tests/syntax_tests/data/printer/expr/expected/braced.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/braced.res.txt @@ -206,8 +206,8 @@ map((arr, i): coordinate => { x +. (Environment.width /. 2. -. centering.x) }) -let _ = assert(true) -let _ = {assert(true)} +let _ = assert({true}) +let _ = {assert({true})} let _ = {%extension} let _ = {module(ME)} diff --git a/tests/syntax_tests/data/printer/expr/expected/field.res.txt b/tests/syntax_tests/data/printer/expr/expected/field.res.txt index f3e2fed8960..218ecac0bc5 100644 --- a/tests/syntax_tests/data/printer/expr/expected/field.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/field.res.txt @@ -48,7 +48,7 @@ let x = ( } ).x -let x = (assert(false)).x +let x = assert(false).x let x = ( try sideEffect() catch { | Exit => () diff --git a/tests/syntax_tests/data/printer/expr/expected/unary.res.txt b/tests/syntax_tests/data/printer/expr/expected/unary.res.txt index e3c7fd69176..08250fdc31e 100644 --- a/tests/syntax_tests/data/printer/expr/expected/unary.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/unary.res.txt @@ -30,7 +30,7 @@ let x = -a.bar // same as above let x = -a.bar -!(assert(x)) +!assert(x) assert(!x) !(@attr expr) //!(arg => doStuffWith(arg)) From 2c716366e254688456efd6158d843c9ffd0123dd Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Wed, 29 Apr 2026 12:50:31 -0300 Subject: [PATCH 2/6] update syntax_tests --- tests/syntax_tests/data/printer/comments/blockExpr.res | 5 +---- .../data/printer/comments/expected/blockExpr.res.txt | 7 ++----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/syntax_tests/data/printer/comments/blockExpr.res b/tests/syntax_tests/data/printer/comments/blockExpr.res index e571446a2ea..089658707db 100644 --- a/tests/syntax_tests/data/printer/comments/blockExpr.res +++ b/tests/syntax_tests/data/printer/comments/blockExpr.res @@ -357,16 +357,13 @@ if { true } // trailing +// here assert({ - // here open /* inside */ Matrix // c // c2 compare(m1, m2) - // after - - // test }) user.name = { diff --git a/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt b/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt index 10bc8a4684a..9c1e76d9ab3 100644 --- a/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt +++ b/tests/syntax_tests/data/printer/comments/expected/blockExpr.res.txt @@ -354,17 +354,14 @@ if { true } // trailing -assert(// here -{ +// here +assert({ open /* inside */ Matrix // c // c2 compare(m1, m2) }) -// after - -// test user.name = { // here From adbb76cd3ba90b12132eb7d86e072682bc80c658 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Thu, 30 Apr 2026 09:47:04 -0300 Subject: [PATCH 3/6] Support %assert as a first-class value When assert is used as a value (e.g. let f = assert), transl_primitive now generates a proper wrapper function instead of falling through to Pccall, which would produce a call to a non-existent runtime function. The generated lambda is: fun assert_cond -> if assert_cond then () else raise Assert_failure(file, line, col), using the location where assert appears in source. This is the best possible location for an alias since the call-site location is not available at primitive translation time. assert_failed_at is extracted as a location-based helper alongside the existing assert_failed (which takes a typed expression). Signed-Off-By: Pedro Castro Co-Authored-By: Claude Sonnet 4.6 --- compiler/ml/translcore.ml | 115 +++++++++++++++++++++++++------------- 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index a81e30d4f28..363d88048c8 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -453,52 +453,87 @@ let warn_polymorphic_comparison loc prim args = Location.prerr_warning loc Warnings.Bs_polymorphic_comparison | _ -> () +let assert_failed_at loc = + let fname, line, char = Location.get_pos_info loc.Location.loc_start in + let fname = Filename.basename fname in + Lprim + ( Praise Raise_regular, + [ + Lprim + ( Pmakeblock Blk_extension, + [ + transl_normal_path Predef.path_assert_failure; + Lconst + (Const_block + ( Blk_tuple, + [ + Const_base (Const_string (fname, None)); + Const_base (Const_int line); + Const_base (Const_int char); + ] )); + ], + loc ); + ], + loc ) + (* Eta-expand a primitive *) let transl_primitive loc p env ty = (* Printf.eprintf "----transl_primitive %s----\n" p.prim_name; *) - let prim = - try specialize_primitive p env ty (* ~has_constant_constructor:false *) - with Not_found -> Pccall p - in - warn_polymorphic_comparison loc prim []; - match prim with - | Ploc kind -> ( - let lam = lam_of_loc kind loc in - match p.prim_arity with - | 0 -> lam - | 1 -> - (* TODO: we should issue a warning ? *) - let param = Ident.create "prim" in - Lfunction - { - params = [param]; - attr = default_function_attribute; - loc; - body = Lprim (Pmakeblock Blk_tuple, [lam; Lvar param], loc); - } - | _ -> assert false) - | _ -> - let rec make_params n total = - if n <= 0 then [] - else - Ident.create ("prim" ^ string_of_int (total - n)) - :: make_params (n - 1) total + if p.Primitive.prim_name = "%assert" then + let param = Ident.create "assert_cond" in + Lfunction + { + params = [param]; + attr = default_function_attribute; + loc; + body = + (if !Clflags.noassert then lambda_unit + else Lifthenelse (Lvar param, lambda_unit, assert_failed_at loc)); + } + else + let prim = + try specialize_primitive p env ty (* ~has_constant_constructor:false *) + with Not_found -> Pccall p in - let prim_arity = p.prim_arity in - if p.prim_from_constructor || prim_arity = 0 then Lprim (prim, [], loc) - else - let params = - if prim_arity = 1 then [Ident.create "prim"] - else make_params prim_arity prim_arity + warn_polymorphic_comparison loc prim []; + match prim with + | Ploc kind -> ( + let lam = lam_of_loc kind loc in + match p.prim_arity with + | 0 -> lam + | 1 -> + (* TODO: we should issue a warning ? *) + let param = Ident.create "prim" in + Lfunction + { + params = [param]; + attr = default_function_attribute; + loc; + body = Lprim (Pmakeblock Blk_tuple, [lam; Lvar param], loc); + } + | _ -> assert false) + | _ -> + let rec make_params n total = + if n <= 0 then [] + else + Ident.create ("prim" ^ string_of_int (total - n)) + :: make_params (n - 1) total in - Lfunction - { - params; - attr = default_function_attribute; - loc; - body = Lprim (prim, List.map (fun id -> Lvar id) params, loc); - } + let prim_arity = p.prim_arity in + if p.prim_from_constructor || prim_arity = 0 then Lprim (prim, [], loc) + else + let params = + if prim_arity = 1 then [Ident.create "prim"] + else make_params prim_arity prim_arity + in + Lfunction + { + params; + attr = default_function_attribute; + loc; + body = Lprim (prim, List.map (fun id -> Lvar id) params, loc); + } let transl_primitive_application loc prim env ty args = let prim_name = prim.prim_name in From 3548f361f0fac63a4cc4d1269fc426f9be0a7034 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Fri, 1 May 2026 13:42:18 -0300 Subject: [PATCH 4/6] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d42ca81578..42530539d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ #### :boom: Breaking Change - Make Jsx.component abstract. https://github.com/rescript-lang/rescript/pull/8390 +- Remove `assert` as a reserved keyword. Since v12 `assert` is parsed as regular function. If you ran the formatter on your codebase using v12, this change is not a breaking change. https://github.com/rescript-lang/rescript/pull/8399 #### :eyeglasses: Spec Compliance From d22712c97b7522672f61edacedcff90489fe8b69 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 3 May 2026 13:33:33 +0200 Subject: [PATCH 5/6] Remove Pexp_assert from Parsetree --- analysis/src/DumpAst.ml | 2 -- analysis/src/Utils.ml | 1 - compiler/frontend/bs_ast_mapper.ml | 1 - compiler/ml/ast_helper.ml | 1 - compiler/ml/ast_helper.mli | 1 - compiler/ml/ast_iterator.ml | 1 - compiler/ml/ast_mapper.ml | 1 - compiler/ml/ast_mapper_from0.ml | 6 ++++- compiler/ml/ast_mapper_to0.ml | 1 - compiler/ml/depend.ml | 1 - compiler/ml/parsetree.ml | 4 ---- compiler/ml/pprintast.ml | 1 - compiler/ml/printast.ml | 3 --- compiler/ml/typecore.ml | 19 ---------------- compiler/syntax/src/res_ast_debugger.ml | 1 - compiler/syntax/src/res_comments_table.ml | 7 ------ compiler/syntax/src/res_parens.ml | 27 +++++++++++------------ compiler/syntax/src/res_printer.ml | 3 --- 18 files changed, 18 insertions(+), 63 deletions(-) diff --git a/analysis/src/DumpAst.ml b/analysis/src/DumpAst.ml index c21cff6b66d..89819113af0 100644 --- a/analysis/src/DumpAst.ml +++ b/analysis/src/DumpAst.ml @@ -234,8 +234,6 @@ and printExprItem expr ~pos ~indentation = ^ "\n" ^ addIndentation indentation ^ ")" | Pexp_extension (({txt} as loc), _) -> "Pexp_extension(%" ^ (loc |> printLocDenominatorLoc ~pos) ^ txt ^ ")" - | Pexp_assert expr -> - "Pexp_assert(" ^ printExprItem expr ~pos ~indentation ^ ")" | Pexp_field (exp, loc) -> "Pexp_field(" ^ (loc |> printLocDenominatorLoc ~pos) diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index abc286af599..30a1afdbadc 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -109,7 +109,6 @@ let identifyPexp pexp = | Pexp_send _ -> "Pexp_send" | Pexp_letmodule _ -> "Pexp_letmodule" | Pexp_letexception _ -> "Pexp_letexception" - | Pexp_assert _ -> "Pexp_assert" | Pexp_newtype _ -> "Pexp_newtype" | Pexp_pack _ -> "Pexp_pack" | Pexp_extension _ -> "Pexp_extension" diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml index 7144cc776a5..25aaf2ce944 100644 --- a/compiler/frontend/bs_ast_mapper.ml +++ b/compiler/frontend/bs_ast_mapper.ml @@ -375,7 +375,6 @@ module E = struct letexception ~loc ~attrs (sub.extension_constructor sub cd) (sub.expr sub e) - | Pexp_assert e -> assert_ ~loc ~attrs (sub.expr sub e) | Pexp_newtype (s, e) -> newtype ~loc ~attrs (map_loc sub s) (sub.expr sub e) | Pexp_pack me -> pack ~loc ~attrs (sub.module_expr sub me) diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index d8d3b350cb4..9945d66f892 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -189,7 +189,6 @@ module Exp = struct let send ?loc ?attrs a b = mk ?loc ?attrs (Pexp_send (a, b)) let letmodule ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_letmodule (a, b, c)) let letexception ?loc ?attrs a b = mk ?loc ?attrs (Pexp_letexception (a, b)) - let assert_ ?loc ?attrs a = mk ?loc ?attrs (Pexp_assert a) let newtype ?loc ?attrs a b = mk ?loc ?attrs (Pexp_newtype (a, b)) let pack ?loc ?attrs a = mk ?loc ?attrs (Pexp_pack a) let open_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_open (a, b, c)) diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index 6538c50419f..7cee97a9b4b 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -212,7 +212,6 @@ module Exp : sig extension_constructor -> expression -> expression - val assert_ : ?loc:loc -> ?attrs:attrs -> expression -> expression val newtype : ?loc:loc -> ?attrs:attrs -> str -> expression -> expression val pack : ?loc:loc -> ?attrs:attrs -> module_expr -> expression val open_ : diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index 474fec12d68..404e2c871e6 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -360,7 +360,6 @@ module E = struct | Pexp_letexception (cd, e) -> sub.extension_constructor sub cd; sub.expr sub e - | Pexp_assert e -> sub.expr sub e | Pexp_newtype (_s, e) -> sub.expr sub e | Pexp_pack me -> sub.module_expr sub me | Pexp_open (_ovf, lid, e) -> diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index 0970b4b3ee6..e4b40ba87e6 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -344,7 +344,6 @@ module E = struct letexception ~loc ~attrs (sub.extension_constructor sub cd) (sub.expr sub e) - | Pexp_assert e -> assert_ ~loc ~attrs (sub.expr sub e) | Pexp_newtype (s, e) -> newtype ~loc ~attrs (map_loc sub s) (sub.expr sub e) | Pexp_pack me -> pack ~loc ~attrs (sub.module_expr sub me) diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index c4e8f80bb35..e951023fae4 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -624,7 +624,11 @@ module E = struct letexception ~loc ~attrs (sub.extension_constructor sub cd) (sub.expr sub e) - | Pexp_assert e -> assert_ ~loc ~attrs (sub.expr sub e) + | Pexp_assert e -> + apply ~loc ~attrs + (ident ~loc + {txt = Longident.Ldot (Longident.Lident "Pervasives", "assert"); loc}) + [(Asttypes.Nolabel, sub.expr sub e)] | Pexp_lazy _ -> failwith "Pexp_lazy is no longer present in ReScript" | Pexp_poly _ -> failwith "Pexp_poly is no longer present in ReScript" | Pexp_object () -> assert false diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index c204651070e..bc102a9ab38 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -504,7 +504,6 @@ module E = struct letexception ~loc ~attrs (sub.extension_constructor sub cd) (sub.expr sub e) - | Pexp_assert e -> assert_ ~loc ~attrs (sub.expr sub e) | Pexp_newtype (s, e) -> newtype ~loc ~attrs (map_loc sub s) (sub.expr sub e) | Pexp_pack me -> pack ~loc ~attrs (sub.module_expr sub me) diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml index 43a625160be..8e39c2aa0e0 100644 --- a/compiler/ml/depend.ml +++ b/compiler/ml/depend.ml @@ -283,7 +283,6 @@ let rec add_expr bv exp = let b = add_module_binding bv m in add_expr (StringMap.add id.txt b bv) e | Pexp_letexception (_, e) -> add_expr bv e - | Pexp_assert e -> add_expr bv e | Pexp_newtype (_, e) -> add_expr bv e | Pexp_pack m -> add_module bv m | Pexp_open (_ovf, m, e) -> diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 29207d0150b..4ab2b1ceec4 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -303,10 +303,6 @@ and expression_desc = (* let module M = ME in E *) | Pexp_letexception of extension_constructor * expression (* let exception C in E *) - | Pexp_assert of expression - (* assert E - Note: "assert false" is treated in a special way by the - type-checker. *) | Pexp_newtype of string loc * expression (* fun (type t) -> E *) | Pexp_pack of module_expr (* (module ME) diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index a2f0fd45305..0c0ec2f01da 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -718,7 +718,6 @@ and expression ctxt f x = pp f "@[let@ exception@ %a@ in@ %a@]" (extension_constructor ctxt) cd (expression ctxt) e - | Pexp_assert e -> pp f "@[assert@ %a@]" (simple_expr ctxt) e | Pexp_open (ovf, lid, e) -> pp f "@[<2>let open%s %a in@;%a@]" (override ovf) longident_loc lid (expression ctxt) e diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index f583acef641..c569fc7b057 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -347,9 +347,6 @@ and expression i ppf x = line i ppf "Pexp_letexception\n"; extension_constructor i ppf cd; expression i ppf e - | Pexp_assert e -> - line i ppf "Pexp_assert\n"; - expression i ppf e | Pexp_newtype (s, e) -> line i ppf "Pexp_newtype \"%s\"\n" s.txt; expression i ppf e diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index f883063b8ae..44f9e148aaa 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -186,7 +186,6 @@ let iter_expression f e = List.iter (fun {x = e} -> expr e) iel | Pexp_open (_, _, e) | Pexp_newtype (_, e) - | Pexp_assert e | Pexp_send (e, _) | Pexp_constraint (e, _) | Pexp_coerce (e, _, _) @@ -3360,24 +3359,6 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) exp_attributes = sexp.pexp_attributes; exp_env = env; } - | Pexp_assert e -> - let cond = - type_expect ~context:(Some AssertCondition) env e Predef.type_bool - in - let exp_type = - match cond.exp_desc with - | Texp_construct (_, {cstr_name = "false"}, _) -> instance env ty_expected - | _ -> instance_def Predef.type_unit - in - rue - { - exp_desc = Texp_assert cond; - exp_loc = loc; - exp_extra = []; - exp_type; - exp_attributes = sexp.pexp_attributes; - exp_env = env; - } | Pexp_newtype ({txt = name}, sbody) -> let ty = newvar () in (* remember original level *) diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index 5b3e5ecf01e..7a59586b5f3 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -703,7 +703,6 @@ module SexpAst = struct extension_constructor ext_constr; expression expr; ] - | Pexp_assert expr -> Sexp.list [Sexp.atom "Pexp_assert"; expression expr] | Pexp_newtype (lbl, expr) -> Sexp.list [Sexp.atom "Pexp_newtype"; string lbl.Asttypes.txt; expression expr] diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index a1cfd1c05a1..71536973b55 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -1191,13 +1191,6 @@ and walk_expression expr t comments = attach t.leading expr2.pexp_loc leading; walk_expression expr2 t inside; attach t.trailing expr2.pexp_loc trailing - | Pexp_assert expr -> - if is_block_expr expr then walk_expression expr t comments - else - let leading, inside, trailing = partition_by_loc comments expr.pexp_loc in - attach t.leading expr.pexp_loc leading; - walk_expression expr t inside; - attach t.trailing expr.pexp_loc trailing | Pexp_coerce (expr, (), typexpr) -> let leading, inside, trailing = partition_by_loc comments expr.pexp_loc in attach t.leading expr.pexp_loc leading; diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml index 09bfa4196c5..6061a18fe7b 100644 --- a/compiler/syntax/src/res_parens.ml +++ b/compiler/syntax/src/res_parens.ml @@ -50,9 +50,9 @@ let call_expr expr = Nothing | { pexp_desc = - ( Pexp_assert _ | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ - | Pexp_setfield _ | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ - | Pexp_for_of _ | Pexp_for_await_of _ | Pexp_ifthenelse _ ); + ( Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ + | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when Ast_uncurried.expr_is_uncurried_fun expr -> Parenthesized @@ -101,16 +101,16 @@ let unary_expr_operand expr = Nothing | { pexp_desc = - ( Pexp_assert _ | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ - | Pexp_setfield _ | Pexp_extension _ (* readability? maybe remove *) - | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + ( Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ + | Pexp_extension _ (* readability? maybe remove *) | Pexp_match _ + | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when ParsetreeViewer.expr_is_await expr -> Parenthesized | _ -> Nothing) -let binary_expr_operand ~is_lhs expr = +let binary_expr_operand ~is_lhs:_ expr = let opt_braces, _ = ParsetreeViewer.process_braces_attr expr in match opt_braces with | Some ({Location.loc = braces_loc}, _) -> Braced braces_loc @@ -129,7 +129,6 @@ let binary_expr_operand ~is_lhs expr = | _ when Ast_uncurried.expr_is_uncurried_fun expr -> Parenthesized | expr when ParsetreeViewer.is_binary_expression expr -> Parenthesized | expr when ParsetreeViewer.is_ternary_expr expr -> Parenthesized - | {pexp_desc = Pexp_assert _} when is_lhs -> Parenthesized | _ when ParsetreeViewer.expr_is_await expr -> Parenthesized | {Parsetree.pexp_attributes = attrs} -> if ParsetreeViewer.has_printable_attributes attrs then Parenthesized @@ -220,9 +219,9 @@ let assert_or_await_expr_rhs ?(in_await = false) expr = Nothing | { pexp_desc = - ( Pexp_assert _ | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ - | Pexp_setfield _ | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ - | Pexp_for_of _ | Pexp_for_await_of _ | Pexp_ifthenelse _ ); + ( Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ + | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when (not in_await) && ParsetreeViewer.expr_is_await expr -> @@ -265,9 +264,9 @@ let field_expr expr = Nothing | { pexp_desc = - ( Pexp_assert _ | Pexp_extension _ (* %extension.x vs (%extension).x *) - | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ - | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + ( Pexp_extension _ (* %extension.x vs (%extension).x *) | Pexp_fun _ + | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ | Pexp_match _ + | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 063ccd50051..ff93ca4a30f 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -3753,9 +3753,6 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = print_expression_block ~state ~braces:true e cmt_tbl | Pexp_letexception (_extensionConstructor, _expr) -> print_expression_block ~state ~braces:true e cmt_tbl - | Pexp_assert expr -> - let expr = print_expression_with_comments ~state expr cmt_tbl in - Doc.concat [Doc.text "assert("; expr; Doc.text ")"] | Pexp_open (_overrideFlag, _longidentLoc, _expr) -> print_expression_block ~state ~braces:true e cmt_tbl | Pexp_pack mod_expr -> From 842e05dc3bec7a7c66ae1666504b5b67bb54302b Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Sun, 3 May 2026 18:17:49 -0300 Subject: [PATCH 6/6] Change return type to unit and add docstrings --- packages/@rescript/runtime/Pervasives.res | 26 ++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/@rescript/runtime/Pervasives.res b/packages/@rescript/runtime/Pervasives.res index bb1168f250f..dd23f46dd7f 100644 --- a/packages/@rescript/runtime/Pervasives.res +++ b/packages/@rescript/runtime/Pervasives.res @@ -22,7 +22,31 @@ result == "Caught exception: Out of milk" */ external throw: exn => 'a = "%raise" -external assert: bool => 'a = "%assert" +/** +Asserts that the given condition is `true`. If the condition is `false`, raises +an `Assert_failure` exception, terminating execution unless caught by a +surrounding try/catch block. + +## Examples + +```rescript +// Passes silently when the condition is true +assert(1 + 1 == 2) + +// Raises Assert_failure when the condition is false +let result = try { + assert(1 + 1 == 3) + "no failure" +} catch { +| Assert_failure(loc) => + Console.log(loc) + "assertion failed" +} + +result == "assertion failed" +``` +*/ +external assert: bool => unit = "%assert" @deprecated({ reason: "`raise` has been renamed to `throw` to align with JavaScript vocabulary. Please use `throw` instead",