Skip to content

✨ Add libhal_add_compile_checks() for must-not-compile tests - #63

Merged
kammce merged 1 commit into
mainfrom
add-compile-checks
Oct 14, 2026
Merged

kammce merged 1 commit into
mainfrom
add-compile-checks

Conversation

@kammce

@kammce kammce commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Adds libhal_add_compile_checks(), for testing code that must fail to compile: misuse rejected by the type system, and dangling references reported by Clang's lifetime safety analysis.

libhal_add_compile_checks(libhal
    FILES
        tests/compile_checks/lifetime_rejects.cpp
        tests/compile_checks/lifetime_analysis.cpp
)

Each file is compiled with Clang's -verify mode and marks the diagnostics it must produce:

// expected-error@+1 {{is returned}}
return sensor(bus);

The build fails if an expected diagnostic is missing or an unexpected one appears. Known gaps can stay in a file without annotations; once Clang starts catching them, the build fails and they become checked cases.

How

  • The function adds an object library, <target>_compile_checks, built with LIBHAL_CXX_FLAGS, linked to the target, with clang-tidy off.
  • -verify is added by a compiler launcher, LibhalVerifyLauncher.cmake (run with cmake -P), because a launcher wraps only the compile step. As a compile option it would also reach the module dependency scanner, which doesn't see the expected-* comments and fails.
  • Clang 23+ only; other compilers print a status line and skip the checks.
  • Clang writes no object file when the expected diagnostics include errors, so after a passing check the launcher creates an empty stand-in object (never linked). A passing check is rebuilt only when the file, a header it includes (Clang still writes the dependency file) or an imported module changes. A failing check gets no stand-in, so it keeps failing on later builds.

Testing

macOS 27, LLVM 23.1.2: libhal v5's 36 compile-check cases, converted to four files (libhal#245), pass. Removing an expectation, or expecting an error on a known gap, fails the build with Clang's message. The launcher passes arguments through unchanged and returns the compiler's exit code. Incremental builds: no change rebuilds no checks; editing fixtures.hpp or lifetime.cppm rebuilds all four; editing one check file rebuilds only that one; a broken check fails again on the next build until fixed.

Compiles each file with Clang's -verify mode, where the file marks the
diagnostics it must produce:

  // expected-error@+1 {{is returned}}
  return sensor(bus);

The build fails when an expected diagnostic is missing or an unexpected
one appears. This lets libraries test that misuse is rejected by the type
system or reported by Clang's lifetime safety analysis.

-verify is added by a compiler launcher (LibhalVerifyLauncher.cmake)
rather than a compile option: the launcher wraps only the compile step,
while a compile option would also reach the module dependency scanner,
which doesn't see the expected-* comments and fails.

Clang 23+ only; other compilers skip the checks.
@kammce
kammce force-pushed the add-compile-checks branch from e6b0ec4 to 1565a31 Compare October 14, 2026 19:25
@kammce
kammce merged commit 9dfc79e into main Oct 14, 2026
1 check passed
@kammce
kammce deleted the add-compile-checks branch October 14, 2026 19:36
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