Remove assert as a reserved keyword#8399
Conversation
`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 <aspeddro@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I should remove |
rescript
@rescript/darwin-arm64
@rescript/darwin-x64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
Yes, if it is unused now, it should be removed from |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c716366e2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| | 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 -> |
There was a problem hiding this comment.
Handle
%assert when used via aliases or higher-order values
This special-case only rewrites direct assert(cond) calls, so %assert can still escape as a first-class value (e.g. let f = assert; f(true)). In that path the backend no longer sees Texp_assert, and %assert falls through primitive lowering paths that are not implemented for it, which can surface as an internal primitive-conversion failure instead of normal assert semantics. Please either reject non-direct %assert usage in typing or add full primitive lowering support for %assert as a value.
Useful? React with 👍 / 👎.
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 <aspeddro@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| (* let module M = ME in E *) | ||
| | Pexp_letexception of extension_constructor * expression | ||
| (* let exception C in E *) | ||
| | Pexp_assert of expression |
There was a problem hiding this comment.
Parsetree0 represents the old AST and needs to remain unchanged for PPX to continue to work.
So we can only remove Pexp_assert from Parsetree, not Parsetree0.
There was a problem hiding this comment.
I tried removing it from parsetree, but Pexp_assert is used in parsetree0, ast_mapper_to0.ml and other modules.
I undid the commit.
1ce54a0 to
adbb76c
Compare
|
@aspeddro I pushed a commit that removes In local review, Codex found another issue though: The direct-call typechecker special case gives As soon as For example: let f = assert
let x: int = f(true)This can typecheck but produces a unit/undefined value at runtime. Consider exposing |
|
@cknitt changes done. Ready for review!! |
|
@codex review once more |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0cbe0e833f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 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)); | ||
| } |
There was a problem hiding this comment.
Preserve
-noassert behavior for first-class assert
When %assert is translated as a normal one-argument function here, calls through an alias still evaluate the argument even under -noassert (e.g. let f = assert; f(sideEffect())), because function arguments are evaluated before the call. That diverges from assertion-disabled semantics used elsewhere in this commit (direct assert(...) is lowered to () without evaluating the condition), so enabling -noassert no longer reliably removes assertion-condition side effects once assert is passed around as a value.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
To preserve -noassert behavior %assert should be a non-first-class again.
To make first-class assert correct, we would need a much deeper special representation: track aliases of %assert, recognize applications through those aliases, possibly through records/modules/higher-order arguments, and lower the entire application before evaluating the condition.
We can add some limitation, assert can only be used as a direct call like assert(condition). It cannot be aliased or passed as a function.
What you think @cknitt @fhammerschmidt ?
There was a problem hiding this comment.
@aspeddro Sounds like a good compromise - can you give it a try?
Signed-off-by: Pedro Castro <aspeddro@gmail.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Asserttoken from the scanner/tokenizer, its parse case from the parser (res_core.ml), and theAssertentries from grammar predicates (is_expr_start,is_block_expr_start)external assert: bool => 'a = "%assert"toPervasives.resso the function is available everywhere without an explicit import (Pervasives is auto-opened for all runtime files)%assertspecially in the type checker (typecore.ml) to produceTexp_assertwith the same type rules as the formerPexp_assertnode:unitin statement position, polymorphic'awhen the argument isfalse— preventing warning 21 from firing onassert(expr)used as a statementtranslcore.mlso the existingassert_failedhelper (which uses the call-site location) generates the correct JS output with source location in the thrownAssert_failureexceptionassertusage, such aslet f = assert, because it cannot preserve-noassertsemantics without evaluating arguments first.Generated JS output
Test plan
make test-syntax— syntax parser and printer tests passmake lib— all runtime modules compile (181 modules)make test— full test suite🤖 Generated with Claude Code and Codex