This document describes how HQL detects, reports, and maps errors back to your original source code.
HQL reports errors from parsing, import resolution, transformation, type checking, code generation, and runtime execution. When something goes wrong, HQL reports the available:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Example Error Output β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β error[HQL1003] : Unexpected ')' - Check for a missing opening '(' ... β
β --> src/math.hql:5:24 β
β β β
β 5 β (fn add [a, b] (+ a b))) β
β β ^ unmatched `)` β
β β β
β help: Check for missing opening parenthesis '(' earlier in the code. β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
HQL has four main categories of errors, each occurring at different stages:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ERROR CATEGORIES β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β PARSE β β TRANSFORM β β TYPE β β RUNTIME β β
β β ERRORS ββββΆβ ERRORS ββββΆβ ERRORS ββββΆβ ERRORS β β
β β β β β β β β β β
β β HQL1xxx β β HQL4xxx β β TSxxxx β β HQL5xxx β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β β
β βΌ βΌ βΌ βΌ β
β Syntax issues IR generation TypeScript Execution β
β Invalid tokens Macro expansion type checker exceptions β
β Mismatched ( ) Unknown forms Type mismatch Undefined vars β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Occur when HQL cannot understand your code's structure.
// Missing closing parenthesis
(fn add [a, b] (+ a b) // β HQL1001: Unclosed list
// Unterminated string literal
(print "oops) // β HQL1002: Unclosed string
// Stray closing parenthesis
(+ 1 2)) // β HQL1003: Unexpected ')'
Occur when an imported module cannot be resolved.
// File does not exist
(import [helper] from "./missing.hql")
(helper) // β error[HQL2002]: Module not found
Occur when the type checker finds type mismatches. HQL uses TypeScript's checker
as the backend but rewrites diagnostics into HQL-native error codes such as
HQL6101 and HQL6104 under error[HQL6004]: Type checking failed. The raw
TSxxxx code is not shown to you. Current hql run and hql compile both fail
closed when type checking fails; JavaScript is not emitted or executed.
// Type mismatch
(fn add [a:number, b:number] -> number (+ a b))
(add "hello" "world") // β error[HQL6101]: Expected number but got string
Occur when code executes but encounters a problem.
// Undefined variable (hyphens normalize to underscores in the message)
(print unknown-var) // β HQL5001: unknown_var is not defined
Understanding the pipeline helps you understand where errors come from:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HQL COMPILATION PIPELINE β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
YOUR CODE OUTPUT
β β²
βΌ β
βββββββββββββββββββ β
β β β
β math.hql β Your HQL source file β
β β β
ββββββββββ¬βββββββββ β
β β
β PHASE 1: PARSING β
β βββββββββββββββββ β
β β’ Tokenize source code β
β β’ Build Abstract Syntax Tree (AST) β
β β’ Track line:column for each token β
β β’ ERRORS: HQL1xxx (syntax errors) β
βΌ β
βββββββββββββββββββ β
β β β
β AST β Tree structure of your code β
β (with pos) β Each node has position info β
β β β
ββββββββββ¬βββββββββ β
β β
β PHASE 2: TRANSFORMATION β
β βββββββββββββββββββββββ β
β β’ Expand macros β
β β’ Convert to IR (Intermediate Representation) β
β β’ Preserve positions through transforms β
β β’ ERRORS: HQL4xxx (transform errors) β
βΌ β
βββββββββββββββββββ β
β β β
β IR β Intermediate Representation β
β (with pos) β Normalized code structure β
β β β
ββββββββββ¬βββββββββ β
β β
β PHASE 3: TYPESCRIPT GENERATION β
β ββββββββββββββββββββββββββββββ β
β β’ Generate TypeScript code β
β β’ Include type annotations β
β β’ Create SOURCE MAP 1 (HQL β TS) β
βΌ β
βββββββββββββββββββ β
β β β
β generated.ts β TypeScript code β
β + source map β Maps back to HQL positions β
β β β
ββββββββββ¬βββββββββ β
β β
β PHASE 4: TYPE CHECKING β
β ββββββββββββββββββββββ β
β β’ TypeScript compiler analyzes types β
β β’ Reports type mismatches β
β β’ We map positions back to HQL β
β β’ ERRORS: TSxxxx (type errors) β
βΌ β
βββββββββββββββββββ β
β β β
β Type Errors β Mapped to HQL positions β
β (fatal) β Displayed to user β
β β β
ββββββββββ¬βββββββββ β
β β
β PHASE 5: JAVASCRIPT COMPILATION β
β βββββββββββββββββββββββββββββββ β
β β’ TypeScript compiles to JavaScript β
β β’ Creates SOURCE MAP 2 (TS β JS) β
β β’ We chain: HQL β TS β JS = HQL β JS β
βΌ β
βββββββββββββββββββ β
β β β
β output.js β Final JavaScript β
β + source map β Chained map points to HQL β
β β β
ββββββββββ¬βββββββββ β
β β
β PHASE 6: EXECUTION β
β ββββββββββββββββββ β
β β’ JavaScript runs β
β β’ Runtime errors occur here β
β β’ Stack traces mapped to HQL β
β β’ ERRORS: HQL5xxx (runtime errors) β
βΌ β
βββββββββββββββββββ β
β β β
β Result or ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Runtime Error β
β β
βββββββββββββββββββ
HQL tracks the original position (line and column) of every piece of code through all transformations.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β POSITION TRACKING FLOW β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Original HQL:
βββββββββββββ
Line 3: (print (add "wrong" 5))
β²
βββ Position: line=3, column=18
After Parsing (AST):
ββββββββββββββββββββ
CallExpression {
callee: "add",
arguments: [
StringLiteral {
value: "wrong",
position: { line: 3, column: 18 } β Position preserved!
},
NumberLiteral { value: 5, position: { line: 3, column: 26 } }
],
position: { line: 3, column: 9 }
}
After Transform (IR):
βββββββββββββββββββββ
IRCallExpression {
callee: IRIdentifier { name: "add" },
arguments: [
IRStringLiteral {
value: "wrong",
position: { line: 3, column: 18 } β Still preserved!
},
...
]
}
Generated TypeScript:
βββββββββββββββββββββ
console.log(add("wrong", 5))//
β²
βββ Generated at: line=7, column=17
Maps to HQL: line=3, column=18
Source Map Entry:
βββββββββββββββββ
{
generated: { line: 7, column: 17 },
original: { line: 3, column: 18 },
source: "math.hql"
}
Position tracking happens in the TypeScript generator:
// In ir-to-typescript.ts
private emit(text: string, irPosition?: IR.SourcePosition): void {
// Record mapping if we have original position
if (irPosition && irPosition.line !== undefined) {
this.mappings.push({
generated: {
line: this.currentLine,
column: this.currentColumn
},
original: {
line: irPosition.line,
column: irPosition.column
},
source: irPosition.filePath
})//
}
// Update current position in generated code
this.code += text//
// ... update currentLine/currentColumn
}
HQL creates a chain of source maps to trace from final JavaScript back to original HQL:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SOURCE MAP CHAIN β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
math.hql generated.ts output.js
ββββββββ ββββββββββββ βββββββββ
Line 3 Line 7 Line 5
Column 18 Column 17 Column 12
β β β
β β β
β SOURCE MAP 1 β SOURCE MAP 2 β
β (HQL β TS) β (TS β JS) β
β β β
βΌ βΌ βΌ
βββββββββββ βββββββββββ βββββββββββ
β HQL βββββββββββββββΆβ TS ββββββββββββββββΆβ JS β
β Source β β Source β β Output β
βββββββββββ βββββββββββ βββββββββββ
β² β
β β
β CHAINED SOURCE MAP β
β (HQL β JS) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
When error at JS 5:12
We look up: JS β TS β HQL
Result: HQL 3:18
// In source-map-chain.ts
export async function chainSourceMaps(
hqlToTsMappings: SourceMapping[], // Map 1: HQL β TS
tsToJsMapJson: string, // Map 2: TS β JS (from tsc)
hqlSourcePath: string,
): Promise<ChainedSourceMap> {
// Build lookup: TS position β HQL position
const tsToHqlMap = new Map<string, Position>(); //
for (let mapping of hqlToTsMappings) {
const key = `${mapping.generated.line}:${mapping.generated.column}`; //
tsToHqlMap.set(key, mapping.original); //
}
// For each JS β TS mapping, look up TS β HQL
// Result: JS β HQL (chained)
}
HQL leverages TypeScript's type checker as a "free" type system:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TYPE ERROR FLOW β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1. YOUR HQL CODE
βββββββββββββββββ
(fn add [a:number, b:number] -> number (+ a b))
(print (add "wrong" 5))
β²
βββ You made a type error here!
2. GENERATED TYPESCRIPT
βββββββββββββββββββββββ
function add(a: number, b: number): number {
return a + b//
}
console.log(add("wrong", 5))//
β²
βββ TypeScript sees this
3. TYPESCRIPT TYPE CHECKER
ββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TypeScript Compiler (tsc) β
β β
β "I found an error!" β
β β
β { β
β message: "Argument of type 'string' is not assignable β
β to parameter of type 'number'", β
β code: 2345, β
β file: "generated.ts", β
β line: 4, β
β column: 17 β
β } β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
4. TRANSLATE + MAP BACK TO HQL
ββββββββββββββββββββββββββββββ
Source map lookup: TS 4:17 β HQL 2:13
TS2345 message rewritten into an HQL-native `HQL6101` diagnostic
(the raw `TSxxxx` code is not shown to the user).
5. DISPLAY TO USER
ββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β error[HQL6101] at math.hql:2:13: Expected number but got string β
β β
β 2 β (print (add "wrong" 5)) β
β β β²β²β²β²β²β²β² β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
On current hql run, type errors fail before JavaScript execution:
$ deno run -A src/hql/cli.ts run math.hql
error[HQL6004]: Type checking failed:
math.hql:2:13 - error[HQL6101]: Expected number but got string
On hql compile, the same type errors fail the build and no JavaScript is
emitted:
$ deno run -A src/hql/cli.ts compile math.hql -o out.js
error[HQL6004]: Type checking failed:
math.hql:2:13 - error[HQL6101]: Expected number but got string
All HQL errors follow a consistent format:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ERROR MESSAGE ANATOMY β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
error[HQL5001] : variable_name is not defined β error code + message
--> src/app.hql:5:9 β file:line:column
β
5 β (print variable-name) β source context
β ^ not found in this scope β caret + label
β
help: Check that `variable_name` is spelled ... β suggestion (help:)
Some errors also append a note: line with extra context (for example, parse
errors note that every ( must have a matching )). HQL diagnostics stay
HQL-native and do not link out to upstream TypeScript documentation.
| Component | Description |
|---|---|
| Error code | error[HQL5001] β unique identifier plus message |
Location (-->) | Full path with line:column |
| Source context | The line(s) of code, with a caret under the span |
help: | How to fix it (a "Did you mean β¦?" suggestion when a |
| close match is found) | |
note: | Extra context shown for some errors |
The code prefix indicates the owning HQL boundary:
HQL errors come from language parsing, compilation, and runtime semantics.The numeric values below are the single source of truth in
src/common/error-codes.ts(enum HQLErrorCode). Keep this table in sync.
| Code | Description |
|---|---|
| HQL1001 | Unclosed list |
| HQL1002 | Unclosed string |
| HQL1003 | Unexpected token |
| HQL1004 | Unexpected EOF |
| HQL1005 | Invalid syntax |
| HQL1006 | Unclosed comment |
| HQL1007 | Invalid character |
| Code | Description |
|---|---|
| HQL2001 | Invalid import syntax |
| HQL2002 | Module not found |
| HQL2003 | Circular import |
| HQL2004 | Invalid import path |
| HQL2005 | Import resolution failed |
| HQL2006 | Export not found |
| Code | Description |
|---|---|
| HQL3001 | Invalid function syntax |
| HQL3002 | Invalid class syntax |
| HQL3003 | Missing required argument |
| HQL3004 | Too many arguments |
| HQL3005 | Invalid parameter |
| HQL3006 | Invalid variable name |
| HQL3007 | Duplicate parameter |
| HQL3008 | Invalid expression |
| HQL3009 | Pure effect violation |
| Code | Description |
|---|---|
| HQL4001 | Transformation failed |
| HQL4002 | Unsupported feature |
| HQL4003 | Invalid AST node |
| HQL4004 | Transform type mismatch |
| Code | Description |
|---|---|
| HQL5001 | Undefined variable |
| HQL5002 | Type mismatch |
| HQL5003 | Division by zero |
| HQL5004 | Null or undefined reference |
| HQL5005 | Function not found |
| Code | Description |
|---|---|
| HQL6001 | Code generation failed |
| HQL6002 | Invalid codegen target |
| HQL6003 | Source map generation failed |
| Code | Description |
|---|---|
| HQL7001 | Invalid macro definition |
| HQL7002 | Macro expansion failed |
| HQL7003 | Invalid macro syntax |
| HQL7004 | Macro not found |
| HQL7005 | Macro recursion limit |
Type errors are detected by the TypeScript checker backend, then rewritten into
HQL-native messages under error[HQL6004]: Type checking failed; individual
diagnostics use HQL610x codes. The raw TSxxxx code is not surfaced to you.
The most common backend codes and the HQL-native message they translate to:
| Backend code | HQL-native message |
|---|---|
| TS2345 | Expected <type> but got <type> |
| TS2322 | Cannot use <type> where <type> is expected |
| TS2304 | Undefined binding: <name> |
| TS2339 | Property '<x>' does not exist on type '<y>' |
| TS2554 | Function expected N argument(s), got M |
If an error points to the wrong location:
If a type error isn't being caught:
...rest:Array<T> so the variadic tail
is typedname:Type// Annotate the parameters you want checked
(fn add [a:number, b:number] -> number (+ a b))
// Commas between parameters are optional
(fn add [a:number b:number] -> number (+ a b))
If runtime errors show JavaScript line numbers:
This section documents the verified behavior and known limitations of HQL's error reporting system.
These checks are intentionally scoped to behavior proven by the current test suite. Do not describe the system as "100% accurate" unless every compiler, runtime, bundled-output, and host-integration path has fresh black-box proof.
| Surface | Verified behavior |
|---|---|
hql run runtime errors | Maps real-file runtime failures back to .hql source with highlighted context. |
| Imported HQL runtime errors | Maps failures in imported .hql modules to the imported source file. |
| Legacy HQL CLI parse errors | Prints error[HQL1001], source file, line/column, caret context, help, and note. |
| Run-path type diagnostics | Fails closed before execution, displays HQL-native type diagnostics, and uses 1-based HQL columns. |
| Compile-path type diagnostics | Fails closed, does not emit the target JS, displays 1-based HQL columns, and avoids duplicate run-warning output. |
| Compiled JS source maps | Final bundled inline maps include original .hql sources, and direct deno run compiled.js stack traces point back to .hql rather than .hql-cache/*.ts. |
| Runtime fallback precision | Runtime location resolution no longer invents exact HQL source locations through keyword-search fallback when no source map or metadata can prove the position. |
The system now has meaningful black-box coverage for the main CLI and compiled JavaScript paths, but it is still not a Rust/Swift-grade diagnostic system. Remaining maturity work includes multi-span type diagnostics, richer type-error source context, and broader host-level verification outside the focused HQL CLI and Deno execution paths.
Status: β Fixed (December 2024)
When a user-defined macro and its call site are in the same file, type
errors in macro-expanded code may point to the macro definition instead of the
call site.
This bug has been fixed. The updateMetaRecursively function in
src/hql/s-exp/macro.ts now correctly updates positions when:
// Example - now correctly reports line 5
(macro my-add [a, b]
`(+ ~a ~b)) // Line 2 - macro definition
(fn check [x:number] -> number x)
(check (my-add "x" 5)) // Line 5 - call site
// Diagnostic correctly reports line 5 (the call site), e.g.
// "error[HQL6101] at test.hql:5:16: Expected number but got string"
Status: By design (gradual typing)
Accessing a missing property never throws β at runtime it returns undefined.
Whether the checker flags the access depends on whether it can infer a type:
// The literal 42 is inferred as `number`, so the missing `.length` is flagged
// before execution.
(let x 42)
(print x.length) // β error[HQL6104]: Property 'length' does not
// exist on type 'number'
// A typed parameter is checked the same way.
(fn f [x:number] -> number
(.length x)) // β error[HQL6104]: Property 'length' does not
// exist on type 'number'
Workaround: Annotate values/parameters so the checker can verify property access; truly dynamic values are not checked.
For developers working on HQL itself:
| Component | File |
|---|---|
| Parser | src/hql/transpiler/pipeline/parser.ts |
| IR Generator | src/hql/transpiler/pipeline/syntax-transformer.ts |
| TS Generator | src/hql/transpiler/pipeline/ir-to-typescript.ts |
| Type Checker | src/hql/transpiler/pipeline/ts-compiler.ts |
| Source Maps | src/hql/transpiler/pipeline/source-map-chain.ts |
| Error Formatter | src/common/error.ts |
| Error Codes | src/common/error-codes.ts |