Skip to content

🔍 feat(analyser) new analyser - #226

Draft
timfennis wants to merge 19 commits into
masterfrom
feat/new-analyser
Draft

timfennis wants to merge 19 commits into
masterfrom
feat/new-analyser

Conversation

@timfennis

Copy link
Copy Markdown
Owner

No description provided.

@timfennis timfennis changed the title Feat/new analyser 🔍 Feat/new analyser Sep 10, 2026
@timfennis timfennis changed the title 🔍 Feat/new analyser 🔍 feat(analyser) new analyser Oct 13, 2026
timfennis and others added 17 commits October 13, 2026 16:14
…ment targets 🧩

Key declarations and references by NodeId now that patterns and targets
carry their own node identities, replacing the span-keyed lvalue list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Function declarations join overload sets while plain bindings shadow, so
each symbol now records which of the two it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An identifier now records every same-named symbol it can see, nearest
scope first, so the type pass can choose between overloads and plain
bindings. Assignment targets still resolve to the innermost symbol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Natives now live in a root scope above the program's top level, so a
top-level binding shadows a native instead of sharing its scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Named functions bind before their body so recursion resolves, and
parameters live in a scope that ends with the function. Duplicate
parameters keep the current analyser's error message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scope 🚧

A scope holds a name either as plain bindings or as an overload set, so
a function declared after a same-named variable in one scope is an
error. Across scopes, ordinary lexical shadowing applies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scopes now know whether they hold builtins, the program's top level, a
function's parameters, or a block, so later lookups can tell when they
cross a function boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each function records the outer symbols it captures, including ones it
only passes through to an inner function, and the resolver collects
every captured symbol so the compiler can keep them alive past their
scope (#227). Every overload candidate is captured until the type pass
narrows them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`x op= y` records candidates for both the in-place `op=` and the plain
`op` under the assignment's node, leaving the choice to the type pass.
Sequence targets keep the current analyser's error message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ccess 🔑

`p.x` records candidates for the getter `x`, member and index targets
record their setter (`x=` or `[]=`), and augmented assignment to them
also records the getter it reads through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Type names live in their own namespace beside values, so `let Int = 5`
doesn't hide the type `Int`. Every name in a `let`, parameter, return
or cast annotation resolves through lexical scopes to a type symbol, or
reports the current analyser's unknown type message. Built-in type
names are declared in the builtins scope.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A struct binds its name as a type, a constructor, and a getter and
setter per field. Field types resolve before the name is bound, so
self and forward references stay unknown types. Builtin shadowing,
same-scope redefinitions and duplicate fields keep the current
analyser's messages. The constructor follows the same-scope rule for
functions, while field accessors are exempt so field names can match
local variables. With structs done, the resolver has no unsupported
expressions left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`None` is a literal since #229, so the resolver treats it like `true`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stacked on #226 so these changes can be reviewed apart from the resolver
work itself. The resolver on `feat/new-analyser` produced its results
but nothing used them. This PR runs it in front of the existing
analyser, so it handles every program and test, as phase 1 of replacing
the old analyser. Phase 2 will turn the old analyser into the type pass.

- **Keep going after errors:** the resolver records one error per
failing statement and carries on, including inside blocks, so `a; b;`
reports both names like the current analyser does.
- **Resolve names before analysis:** `Analyser::analyse` runs the
resolver on each top-level statement first, with the registered natives
as builtins. A statement with name errors isn't analysed further, so the
same error isn't reported twice. The resolver's state is part of the
analyser checkpoint, so a failed REPL line rolls it back. The compiler
is unchanged.

**Worth a look:**
- **Behaviour change:** a `fn` after a `let` of the same name in one
scope is now an error, as decided for the resolver. `005_functions/034`
became an error test, and the function page in the manual documents the
rule.
- **Test fixtures:** four analyser unit tests now declare the `[]`/`[]=`
natives their index assignments use, because the resolver rejects index
access without them.
- **Unused fields:** the `AugmentedCandidates` and `StructSymbols`
fields only the type pass will read are marked `expect(dead_code)`, so
the marker becomes an error once they're used.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Phase 2, step 1, stacked on #226. Later steps will stack on this branch.

The layout pass in phase 2 will turn native symbols into the VM's global
slots, so it needs each native's position in the function registry. That
position happened to equal the native's `SymbolId`, because natives are
declared first, but nothing guaranteed it. This makes the mapping
explicit.

- `NameResolver` records `native_slots`, from each native symbol to its
registry index. Overloads of the same name keep separate slots.
- Nothing reads it yet, so the field is marked `expect(dead_code)`
outside tests, like the other fields waiting for later passes.

No behaviour change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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