Skip to content

Harden semantic validation and preserve source diagnostics - #332

Merged
LunaStev merged 2 commits into
wavefnd:masterfrom
LunaStev:patch/semantic-validation
Aug 8, 2026
Merged

Harden semantic validation and preserve source diagnostics#332
LunaStev merged 2 commits into
wavefnd:masterfrom
LunaStev:patch/semantic-validation

Conversation

@LunaStev

@LunaStev LunaStev commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

This change replaces Wave's minimal string-based semantic verifier with a program-aware validation pass that rejects invalid programs before LLVM code generation. It also introduces structured semantic diagnostics with source provenance so errors point to the actual declaration, expression, or imported file that caused the failure.

The work is the prerequisite semantic-validation gate for the v0.2.1-pre-beta RISC-V effort. RISC-V backend failures cannot be diagnosed reliably while invalid source programs are still accepted by wavec check, lowered differently by wavec build, or reported as backend panics.

Semantic validation

  • Collect functions, externs, globals, aliases, structs, enums, methods, and generic declarations before validating bodies.
  • Reject duplicate top-level symbols, local bindings, parameters, fields, variants, methods, aliases, and same-scope declarations while preserving nested-scope shadowing.
  • Validate unknown, void, pointer, array, named, alias, struct, enum, and generic types in declarations, signatures, fields, casts, and expressions.
  • Enforce void and non-void return contracts, missing-return detection, and function call argument compatibility.
  • Reject break and continue outside loops.
  • Support default parameters and generic function, struct, field, and method type substitution during pre-monomorphization validation.
  • Diagnose incorrect generic arity before monomorphization, including generic errors originating in imported modules.
  • Require mutable lvalues for input and mutation operations.
  • Validate assignment, compound assignment, increment, decrement, explicit casts, contextual array literals, and addressed array literals.
  • Reject integer literals that would be silently truncated.
  • Reject invalid implicit pointer/integer conversions and require explicit casts where the language contract requires them.
  • Enforce deterministic mixed-width floating-point rules.
  • Detect duplicate match cases by evaluated numeric value while continuing to allow enum aliases.
  • Reject aggregate formatting until Wave defines an explicit aggregate formatter contract.

Structured diagnostics and source provenance

  • Add SemanticDiagnostic with an error code, message, top-level node index, primary span hint, label, note, and help text.
  • Preserve the existing string-returning parser API as a compatibility wrapper around detailed diagnostics.
  • Preserve imported source text and map each expanded top-level AST node back to its source unit.
  • Validate the expanded AST before generic monomorphization so user-authored failures retain their original source location.
  • Remove the previous error-message parsing and substring-based diagnostic locator.
  • Resolve structured declaration, keyword, and identifier hints within the relevant top-level source scope.
  • Correctly locate repeated identifiers, repeated return statements, duplicate declarations, and other repeated syntax.
  • Report imported semantic errors using the imported file path, line, column, and highlighted span instead of the entry file at 1:1.

LLVM and control-flow fixes

  • Stop generating statements after the current LLVM basic block already has a terminator.
  • Mark unreachable conditional merge blocks as unreachable when every branch terminates.
  • Mark exits from statically infinite, non-breaking loops as unreachable.
  • Lower returns with implicit coercion only after semantic validation has established type compatibility.
  • Add LLVM floating-point widening and narrowing casts for valid explicit float conversions.

Regression coverage and corpus updates

  • Add regression coverage for invalid returns, missing returns, loop control, function calls, casts, lvalues, duplicate declarations, unknown and void types, array contexts, integer truncation, match duplicates, formatting, mixed float widths, generics, and diagnostic source locations.
  • Verify that wavec check and wavec build reject the same invalid source before a backend panic or E9001 failure can occur.
  • Cover repeated local declarations and returns, duplicate top-level types, imported unknown types, and imported generic arity failures.
  • Update TCP and overflow samples to express narrowing conversions explicitly.
  • Avoid aggregate formatting in the formatting regression corpus until the formatter contract is implemented.
  • Ignore the repository-local .tmp planning directory.

User and developer impact

Invalid Wave programs now fail earlier with actionable semantic diagnostics instead of reaching target-specific LLVM lowering. Diagnostics remain attached to the original imported source, and valid programs using defaults or generics continue to pass validation. This establishes a stable frontend baseline for distinguishing language errors from upcoming RISC-V ABI, code generation, linking, and runtime issues.

Validation

  • cargo fmt --all -- --check
  • cargo test --locked --all-targets — 15 tests passed
  • cargo clippy --locked --all-targets -- -D warnings
  • cargo build --locked --release
  • Wave end-to-end suite — 96 passed, 12 environment/architecture skips, 0 failed
  • All 13 examples passed wavec check
  • All 79 standard-library modules passed wavec check
  • git diff --check

Replace the minimal string-based verifier with a program-aware semantic validation pass that rejects invalid Wave programs before they reach LLVM code generation.

Semantic and type-system changes:
- collect functions, externs, globals, aliases, structs, enums, methods, and generic declarations before validating bodies
- reject duplicate top-level symbols, local bindings, fields, variants, methods, aliases, parameters, and declarations in the same scope while preserving nested shadowing
- validate unknown, void, pointer, array, named, alias, struct, enum, and generic types in declarations, signatures, casts, fields, and expressions
- enforce function return contracts, missing-return detection, void-value restrictions, loop-only break and continue, and call argument compatibility
- support default parameters and generic function, struct, field, and method type substitution without regressing pre-monomorphization validation
- reject incorrect generic arity early, including errors originating in imported source files
- require mutable lvalues for input and mutation, validate assignment and compound-assignment operands, and reject unsupported increment and decrement operations
- validate explicit casts, contextual array literals, addressed arrays, integer literal ranges, pointer and integer conversions, and float width conversions
- reject duplicate match constants by their evaluated numeric value and enforce consistent mixed-width floating-point semantics
- reject aggregate formatting until a defined formatter contract exists while retaining scalar, string, pointer, and null formatting

Structured diagnostic and import provenance changes:
- add SemanticDiagnostic with a stable error code, message, top-level node index, primary span hint, label, note, and help text
- retain the legacy string-returning parser API as a compatibility wrapper around detailed diagnostics
- preserve imported source text and associate every expanded top-level AST node with its original source unit
- validate the expanded AST before generic monomorphization so user-facing semantic failures retain their original source location
- replace message parsing and heuristic substring lookup with structured declaration, keyword, and identifier span hints
- scope occurrence resolution to the relevant top-level node so repeated identifiers, repeated returns, and duplicate declarations point at the actual failing occurrence
- report semantic failures in imported modules against the imported path, line, column, and highlighted source span instead of the entry file at 1:1

LLVM and control-flow fixes:
- stop emitting statements after a basic block already has a terminator
- mark unreachable merge blocks and provably non-breaking infinite-loop exits as unreachable
- use implicit coercion rules for return lowering after semantic validation has established compatibility
- add explicit floating-point widening and narrowing emission with LLVM float casts

Regression and corpus updates:
- add integration coverage for invalid returns, missing returns, loop control, calls, casts, lvalues, duplicate symbols, unknown and void types, array contexts, integer truncation, match duplicates, formatting, float widths, generics, and diagnostic source locations
- verify check and build reject the same invalid programs before backend panics or E9001 failures
- cover repeated local and return locations, duplicate top-level declarations, imported unknown types, and imported generic arity diagnostics
- update TCP and overflow examples to use explicit narrowing casts and avoid formatting an aggregate without a formatter
- ignore the local .tmp planning directory

Validation completed:
- cargo fmt --all -- --check
- cargo test --locked --all-targets: 15 tests passed
- cargo clippy --locked --all-targets -- -D warnings
- cargo build --locked --release
- Wave end-to-end suite: 96 passed, 12 environment or architecture skips, 0 failed
- all 13 examples passed wavec check
- all 79 standard-library modules passed wavec check
- git diff --check
Make compiler integration tests deterministic when GitHub Actions enables colored Cargo output. All test helpers now launch wavec with NO_COLOR=1, preventing ANSI styling from splitting diagnostic tokens such as error[E3001] and causing valid semantic-diagnostic assertions to fail on Linux and macOS runners.

Expose the tested host architecture directly in each workflow check name:
- rename build-ubuntu to build-linux-amd64
- rename build-macos to build-macos-arm64
- rename build-windows to build-windows-amd64

Repair Windows toolchain provisioning after the current MSYS2 package database stopped resolving the versioned llvm-21 and lld-21 package names through pacman -S. Keep the regular runtime dependencies in setup-msys2 and install the official LLVM 21.1.8-5 and LLD 21.1.8-5 packages from pinned MSYS2 mirror URLs so the llvm-sys 21 contract and /mingw64/opt/llvm-21 layout remain stable.

Validation:
- reproduced the GitHub runner environment with NO_COLOR unset and CARGO_TERM_COLOR=always
- cargo test --locked --all-targets: 15 tests passed
- cargo fmt --all -- --check
- cargo clippy --locked --all-targets -- -D warnings
- parsed .github/workflows/rust.yml successfully
- verified both pinned MSYS2 package URLs return HTTP 200
- git diff --check
@LunaStev
LunaStev marked this pull request as ready for review August 8, 2026 10:33
@LunaStev
LunaStev merged commit 6501937 into wavefnd:master Aug 8, 2026
3 checks passed
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