Files
bux-lang/docs/superpowers/specs/2026-06-10-source-location-errors-design.md
T

240 lines
7.5 KiB
Markdown

# Bux Source Location Tracking in Error Messages — Design Document
> **Date:** 2026-06-10
> **Status:** Design Approved
> **Scope:** MVP Rust-style error messages with code snippets for parser and sema diagnostics
---
## 1. Overview
The Bux self-hosted compiler already tracks source locations (lexer tokens have line/column, AST nodes have line/column, parser and sema have diagnostic structs with line/column). However, the CLI prints errors as plain strings or raw `line X col Y` text, without code snippets or consistent formatting.
This feature unifies and improves error display to match the Rust-style format:
```
error: type mismatch
--> src/Main.bux:42:15
|
42 | let x: int = "hello";
| ^
```
### Goals
- Unified Rust-style error formatting for all parser and sema diagnostics
- Code snippets showing the exact line with an underline
- Consistent severity prefixes (`error:`, `warning:`, `note:`)
### Non-Goals (for MVP)
- Source maps (mapping merged-source line numbers back to original files)
- Multi-line error spans
- Multi-character underlines (`^^^^` for entire tokens)
- Colorized output
- Error codes (e.g., `E0001`)
- HIR lower / C backend diagnostics (these phases do not currently emit errors)
---
## 2. Architecture
```
┌─────────────────────────────────────────┐
│ Parser/Sema Diagnostics │
│ ParserDiag { line, col, message } │
│ SemaDiag { line, col, message } │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Unified Diagnostic Formatter │
│ (inline in cli.bux or lib/Diagnostic) │
│ • Reads source line from file │
│ • Formats snippet with underline │
│ • Prints Rust-style error block │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ CLI Output │
│ error: type mismatch │
│ --> src/Main.bux:42:15 │
│ | │
│ 42 | let x: int = "hello"; │
│ | ^ │
└─────────────────────────────────────────┘
```
### Components
1. **Source Line Loader** — reads a specific line from a source file
2. **Snippet Renderer** — formats `line_num | code` + underline
3. **Diagnostic Printer** — assembles the full Rust-style error block
**Note:** Bux compiles a merged source file (all `.bux` files merged before compilation). For MVP, snippets show lines from the merged source. Source map tracking (which original file a line came from) is future work.
---
## 3. Data Structures
```bux
struct Diagnostic {
message: String;
line: uint32; /* 1-based */
column: uint32; /* 1-based */
severity: int; /* 0=error, 1=warning, 2=note */
}
```
**Severity values:**
- `0``error:`
- `1``warning:`
- `2``note:`
For MVP, parser and sema diagnostics are always severity `0` (error). Warning/note severity is reserved for future use.
---
## 4. API Design
```bux
/* Print a single diagnostic in Rust-style format */
func Diagnostic_Print(diag: *Diagnostic, sourcePath: String);
/* Helper: read line N from a file (1-based) */
func Diagnostic_GetLine(path: String, lineNum: uint32) -> String;
```
### Output Format
```
error: <message>
--> <path>:<line>:<col>
|
<ln> | <source_line>
| <spaces>^
```
Where:
- `<ln>` is the line number, right-aligned
- `<spaces>` is `column - 1` spaces to position the `^`
- If `source_line` is empty (file not found), only the header is printed
---
## 5. Snippet Rendering Algorithm
### `Diagnostic_GetLine(path, lineNum)`
1. Open the file at `path`
2. Read character by character, counting newlines
3. When reaching line `lineNum`, accumulate characters until `\n` or EOF
4. Return the accumulated string (without trailing `\n`)
5. If file cannot be opened or `lineNum` exceeds file length, return `""`
### `Diagnostic_Print(diag, sourcePath)`
```
Print severity prefix + message
Print " --> " + sourcePath + ":" + line + ":" + column + "\n"
Print " |\n"
Print line number + " | " + source line + "\n"
Print " | " + (column-1 spaces) + "^\n"
```
**Underline:** Single `^` at the column position. Multi-character spans are future work.
---
## 6. Integration Points
### Parser Diagnostics
**Current code** (`src/cli.bux`, lexer error printing):
```bux
PrintLine(lex.diags[i].message);
```
**New code:**
```bux
let diag: Diagnostic = Diagnostic {
message: lex.diags[i].message,
line: lex.diags[i].line,
column: lex.diags[i].column,
severity: 0,
};
Diagnostic_Print(&diag, sourcePath);
```
### Sema Diagnostics
**Current code** (`src/cli.bux`, `Cli_Compile` path):
```bux
PrintLine(sema.diags[i].message);
```
**Current code** (`src/cli.bux`, `Cli_BuildProject` path):
```bux
Print(" line "); PrintInt(sema.diags[i].line as int64);
Print(" col "); PrintInt(sema.diags[i].column as int64);
Print(": "); PrintLine(sema.diags[i].message);
```
**New code (both paths):**
```bux
let diag: Diagnostic = Diagnostic {
message: sema.diags[i].message,
line: sema.diags[i].line,
column: sema.diags[i].column,
severity: 0,
};
Diagnostic_Print(&diag, sourcePath);
```
### Unified Helper
Add to `src/cli.bux`:
```bux
func Cli_ReportSemaErrors(sema: *Sema, sourcePath: String) {
let i: int = 0;
while i < Sema_DiagCount(sema) {
let d: SemaDiag = sema.diags[i];
let diag: Diagnostic = Diagnostic {
message: d.message, line: d.line,
column: d.column, severity: 0,
};
Diagnostic_Print(&diag, sourcePath);
i = i + 1;
}
}
```
---
## 7. Files to Modify
| File | Change |
|------|--------|
| `src/cli.bux` | Replace all manual error printing with `Diagnostic_Print`. Add `Diagnostic` struct and helper functions. |
**No changes needed to:**
- `src/lexer.bux` — already emits `LexerDiag` with line/col
- `src/parser.bux` — already emits `ParserDiag` with line/col
- `src/sema.bux` — already emits `SemaDiag` with line/col via `Sema_EmitError`
- `src/ast.bux` — already stores line/col on every node
- `src/hir.bux` — already stores line/col on HIR nodes
---
## 8. Testing Strategy
1. **Type error test:** Create a `.bux` file with `let x: int = "hello";` → build → verify Rust-style output with snippet
2. **Syntax error test:** Create a `.bux` file with missing `}` → build → verify parser error shows snippet
3. **Selfhost loop:** Verify compiler C output remains identical (CLI changes do not affect codegen)
---
## 9. Future Work
- **Source maps:** Track which original file each merged line came from, so errors show original paths
- **Multi-character underlines:** Underline entire token/span, not just a single `^`
- **Colorized output:** ANSI color codes for error/warning/note
- **Error codes:** Assign stable codes like `E0001` for each error type
- **Notes and help text:** Attach explanatory notes to errors (Rust's `help:` and `note:` blocks)