Skip to content

An assert message can be known only at run time; output before a failing assert is kept - #393

Merged
ASDAlexander77 merged 3 commits into
mainfrom
assert-runtime-message
Sep 28, 2026
Merged

ASDAlexander77 merged 3 commits into
mainfrom
assert-runtime-message

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

An assert whose message is known only at run time failed to compile:

function check(ok: boolean, what: string) {
    assert(ok, what + ": failed");   // error: operation's operand is unlinked
}

Cause: mlirGenAssert erased the op behind every message argument, as if each were a constant string, while other ops still used it.

Fix:

  • A constant string is still kept as the op's text.
  • Any other message becomes an optional string operand of ts.Assert (message). A message that isn't a string is cast to one.
  • AssertLogic passes that operand to _assert (Windows) or __assert_fail (Unix) when the assertion fails. A null message shows the constant text instead.
assertion failed: n is 7

Tests:

  • 00assert_message.ts, registered for compile, jit and the corpus: passing asserts with a concatenated message, a template literal, a null string and a number. It is clean under -mm=rc --verify-ownership.
  • test-jit-assert-message-failing: a failing assert must print assertion failed: n is 7.

Second commit: output printed before a failing assert was lost. _assert and __assert_fail abort, which flushes no stream, and the JIT's _assert handler ends with _exit. So whenever stdout wasn't a console (a pipe, a file, the test runner), everything printed before the failure was lost:

print("before"); assert(false, "boom");   // piped: only "Assertion failed: boom"

Fix:

  • The lowered assert now calls fflush(NULL) before _assert / __assert_fail.
  • The JIT handler flushes stdout.
  • The JIT binds fflush to its own CRT, as it already does puts.

test-jit-assert-message-failing now requires the output printed before the failure, then the message.

Third commit (CI): the failing-assert test. It failed on both CI platforms with Symbols not found: GC_init, GC_malloc. Run directly, tslang --emit=jit finds the GC only through the GC_LIB_PATH/TSLANG_LIB_PATH environment, which CI doesn't set. Locally the environment hid this. The test now passes --shared-libs=$<TARGET_FILE:TypeScriptRuntime>, as the other direct JIT tests do. I reproduced the failure and the fix with those variables unset.

The test is now Windows only, like the uncaught-exception JIT tests. On Linux the output and the message are both printed:

printed before
tslang: :0: Assertion `n is 7' failed.

But abort() then reaches LLVM's crash handler (Stack dump), and ctest fails the test on the signal. Catching an abort in a Linux JIT run is a separate gap.

Results (after rebasing onto main, with #389/#391/#392):

  • Windows suite: 3043/3043.
  • Linux suite (WSL): 3029/3029.

🤖 Generated with Claude Code

@ASDAlexander77 ASDAlexander77 changed the title An assert message can be known only at run time An assert message can be known only at run time; output before a failing assert is kept Sep 28, 2026
ASDAlexander77 and others added 3 commits September 28, 2026 15:46
`assert(ok, what + ": failed")` failed to compile with "operation's
operand is unlinked": mlirGenAssert erased the op behind every message
argument, as if each were a constant string, while other ops still used
it. A constant string is still kept as the op's text; anything else is
now a string operand of ts.Assert, shown by _assert/__assert_fail when
the assertion fails (a null string shows the constant text).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_assert and __assert_fail abort, which flushes no stream, and the JIT's
_assert handler ends with _exit: whenever stdout was not a console - a
pipe, a file, the test runner - everything the program printed before the
failure was lost. The lowered assert now calls fflush(NULL) first, the
JIT handler flushes stdout, and the JIT binds fflush to its own CRT like
puts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run on its own, tslang --emit=jit finds the GC only through the
GC_LIB_PATH/TSLANG_LIB_PATH environment, which CI does not set, so the
test failed with "Symbols not found: GC_init, GC_malloc". It now passes
--shared-libs=TypeScriptRuntime, as the other direct JIT tests do.

It is Windows only, like the uncaught-exception JIT tests: on Linux the
message and the earlier output are printed, but abort() then reaches
LLVM's crash handler ("Stack dump"), and ctest fails the signal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 675573b into main Sep 28, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the assert-runtime-message branch September 28, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant