Skip to content

Implement error handling on the LLVM diagnostic system#84

Merged
pmatos merged 4 commits into
mainfrom
issue-57
Jul 1, 2026
Merged

Implement error handling on the LLVM diagnostic system#84
pmatos merged 4 commits into
mainfrom
issue-57

Conversation

@pmatos

@pmatos pmatos commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces norac's ad-hoc error reporting (bare Parsing failed! /
Interpretation failed! strings and nullptr sentinels with no source
location) with a diagnostic layer built on llvm::SourceMgr, rendering
errors in the familiar file:line:col: error: … form with the source line
and a caret.

  • nora::DiagnosticEngine (src/Diagnostics.{h,cpp}) wraps llvm::SourceMgr,
    tracks error/warning counts, and emits located and location-less diagnostics.
    Uses the already-linked Support component — no CMake/link changes needed.
  • Source locations: SourceStream registers its buffer with the engine and
    maps byte offsets to SMLocs; every ASTNode carries an SMRange threaded
    through the parser (and preserved across clone()), so diagnostics point at
    the offending expression.
  • Parser: emits precise located errors at keyword-form commit points
    (lambda, if, set!, begin, define-values, let-values, quote,
    values, vector) and across the top-level linklet structure, with
    hadError guards so a single error unwinds cleanly instead of cascading.
    Pure backtracking on valid input is unchanged — no diagnostic ever fires on a
    well-formed program.
  • Interpreter: routes unbound-identifier, arity, non-procedure, set! on
    unbound, let-values/define-values count, and runtime errors through the
    engine at the relevant node's location, and propagates evaluation failures via
    a null result (replacing assert(Result) sites that would abort on error).
  • main owns the engine, passes it to SourceStream and Interpreter, and
    exits non-zero whenever any diagnostic is reported.

Out of scope (left as internal, non-user-facing): the Values::write and
AnalysisFreeVars invariant guards, and lexer-level INVALID-token recovery.

Closes #57

Test plan

  • ctest --preset debug — 16/16 pass (unit + lit)
  • 8 new negative integration tests (not norac … | FileCheck): unbound id,
    arity, non-callable, malformed lambda, + type error, non-linklet,
    missing if else, set! unbound
  • New DiagnosticEngine/getLoc Catch2 unit tests
  • All 48 pre-existing integration tests still pass
  • Warning-clean on GCC and Clang with -DCMAKE_COMPILE_WARNING_AS_ERROR=ON
  • clang-format-22 clean

norac previously reported failures as bare "Parsing failed!" /
"Interpretation failed!" strings (or nullptr sentinels) with no source
location. Introduce a DiagnosticEngine wrapping llvm::SourceMgr that
renders errors in the familiar file:line:col form with the source line
and a caret.

SourceStream now registers its buffer with the engine and maps byte
offsets to SMLocs; AST nodes carry an SMRange threaded through the
parser so diagnostics can point at the offending expression. The parser
emits located errors at keyword-form commit points and at the top-level
linklet structure, guarding against cascades once an error is reported.
The interpreter routes unbound-identifier, arity, non-procedure, set!,
let-values and runtime errors through the engine and propagates
evaluation failures via a null result instead of asserting.

main exits non-zero when the engine reports any error. Adds negative
integration tests (not + FileCheck) and DiagnosticEngine unit tests.
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.92593% with 92 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.47%. Comparing base (29cbfd1) to head (31421b9).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/Parse.cpp 56.58% 56 Missing ⚠️
src/Interpreter.cpp 59.64% 23 Missing ⚠️
src/Diagnostics.cpp 66.66% 5 Missing ⚠️
src/SourceStream.cpp 69.23% 4 Missing ⚠️
src/AST.cpp 0.00% 2 Missing ⚠️
src/include/AST.h 88.88% 1 Missing ⚠️
src/main.cpp 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #84      +/-   ##
==========================================
+ Coverage   75.61%   76.47%   +0.85%     
==========================================
  Files          24       26       +2     
  Lines        2391     2567     +176     
  Branches      332      371      +39     
==========================================
+ Hits         1808     1963     +155     
- Misses        583      604      +21     
Flag Coverage Δ
integration 76.47% <65.92%> (+0.85%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 000c9a0224

ℹ️ 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".

Comment thread src/Interpreter.cpp
@claude

claude Bot commented Jul 1, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

pmatos added 3 commits July 1, 2026 19:50
Identifier's copy constructor, copy assignment and move assignment
re-initialised the ASTNode base via ClonableNode(Kind), dropping the
SMRange. ClonableNode::clone() restores the range, but SetBang's copy
constructor copies its nested Identifier via the copy constructor
(new Identifier(*SB.Id)), not clone() — so a set! target inside a lambda
body lost its location once the body was cloned into the closure, and
the unbound-set! diagnostic printed <unknown>:0 instead of pointing at
the identifier.

Carry the range across all three Identifier copy/move operations so it
survives copy-construction wherever identifiers are stored by value
(set! targets, define-values/let-values id lists, lambda formals).
Adds a regression test for set! inside a lambda.

Addresses review feedback on PR #84.
Resolve conflicts in the interpreter with main's case-lambda /
letrec-values refactor: adopt applyFormals()/bindClause() and the
Closure/CaseLambdaClosure dispatch, and route their error sites
(arity mismatch, case-lambda no-match, let-values value-count) through
the DiagnosticEngine with source locations, keeping the diagnostics
consistent. Convert the new letrec-values assert(Result) into error
propagation.
Keep both the new #%variable-reference parser test and the
DiagnosticEngine unit tests; no source conflicts this round.
@pmatos pmatos merged commit d950d13 into main Jul 1, 2026
13 checks passed
@pmatos pmatos deleted the issue-57 branch July 1, 2026 20:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31421b9cc1

ℹ️ 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".

Comment thread src/Interpreter.cpp
Comment on lines +312 to +313
Diag.error(A[0].getLoc(),
"application: expected a procedure in operator position");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve operator locations for variable references

When the operator expression is a #%variable-reference, parseVariableReference never assigns a source range to the AST node, so this new A[0].getLoc() path receives an invalid location and reports <unknown>:0 for inputs like (linklet () () ((#%variable-reference) 1)). Either give VariableReference nodes a range when parsing them or fall back to the application range here so the diagnostic remains located.

Useful? React with 👍 / 👎.

pmatos added a commit that referenced this pull request Jul 2, 2026
Reconcile the CEK abstract machine (with-continuation-mark, #11) with the
features main gained since this branch was opened: the DiagnosticEngine
error system (#57/#84) and the case-lambda (#3), letrec-values (#8), and
#%variable-reference (#13) / keyword forms.

- Closures now capture the live lexical environment chain instead of a copy
  of their free variables. This gives correct lexical scoping and makes
  letrec-values forward/mutual references resolve, and it subsumes the
  earlier dynamic-scope fix. The interpreter tracks every scope it creates
  and clears their bindings at teardown to break the closure/scope shared_ptr
  cycles this introduces (verified clean under LeakSanitizer).
- All interpreter error paths now report through the DiagnosticEngine at the
  offending node's source location, matching main's messages; main() decides
  the exit status from Diag.hadError().
- case-lambda / case-lambda closures, #%variable-reference, and keyword are
  evaluated by the machine (case-lambda selects the first clause whose formals
  accept the argument count); letrec-values fills a recursive scope in place.

All presets green: ctest 21/21, nora-lit 88/88 under debug, asan, and ubsan;
warning-free build; clang-format clean.
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.

Implement error handling based on the LLVM diagnostic system

1 participant