Analysis works out what every name means. The parser knows total is a name; analysis knows it's the function total in pricing/price.rfn, and that order two lines down is the let on line 8. It's the first step that looks at the whole program at once, since a name can mean something defined in another file.
Given every file's syntax tree. Gives each file's links - every name used in it, and what it leads to: something bound nearby, or a definition, by its file and name - and whatever's wrong with the program's names. In bootstrapping/3-analyze; --analyze prints the links. The tree itself isn't changed: links sit beside it, found by the span of the name.
First, every file's top-level definitions are collected into one table - each with its name, the file and folder it's in, and whether it's local, global or neither. A method isn't found by its name alone: method area for Circle is listed as area for Circle, which no bare name can mean, since it's reached through a value. A destructuring const defines each name it binds.
This is where the rules of Files and Folders are checked: a name is defined once in a folder - once in a file, for a local one - and a macro once in the whole project, since it's seen from everywhere. An implement block for an interface and a type can be written once. A folder has at most one entry point - one @main or @app.
Then each file is walked, and every name in it linked. Inside a definition, analysis keeps the scopes the code is in: a frame for the function or lambda, holding its parameters and generics, and one for each block, holding what's bound there. A name is looked for from the innermost frame out, then among the definitions:
A block's definitions are seen by all of it - a function defined at the bottom of a block can be called at the top - but its lets only from where they're written. A let can reuse a let's name in the same block, shadowing it; nothing else can be written twice in one block. A loop's variables are also the loop's name: break row is linked to the loop binding row, and a break inside a lambda can't reach a loop outside it.
A backtick name starting with /, ./ or ../ is a path, read from the root or from the file's folder, and reaches only a global definition - that's what global means. Any other backtick name is just a name, quoted.
A builtin macro's #{ } placeholders are code where the macro is used, so their names are linked like the code around them - order inside the #format above is the let. A program's own macro gets its text as written, so its placeholders aren't linked.
A context value - &database, inject database, reject database - is linked to its context database: Type declaration: the file's local one, else the folder's, else a global one. Context names are a table of their own, so context database and a const database don't clash. Without a declaration, an @inject, @injectLocally or @injectGlobally const of that name reaching the file stands in for one. A builtin - &stdout - has no link; type checking knows it.
Each line is a name's position, the name, and where it leads - local at 8:7 for something bound in the function, Order in main.rfn for a definition. Builtin names - String, U64 - have no link: they're not defined in the program, and type checking knows them.
A combined type gets the implementations of the types it's combined from (see Combined types), and 3-analyze/copies.rfn writes them, as source - the way a derive macro gives its result. Once the program's analyzed, and its macros on definitions have run, each implement block and method written for a struct that another is combined from is written again: its text, with its for - and each parameter and result typed as the old struct - naming the new one. A combined enum gets one that calls the part each case came from instead. What a part derives isn't written: type checking and lowering derive it again for the combined type.
The pipeline lexes and parses each copy into a region past the end of the file the original is in, so its names mean what the original's do, and analyzes the program again. The combined type needn't be visible from that file, so the copy names it by a placeholder - `copied Sign` - that's linked before analysis starts (Source.preLinks). Anything wrong in a copy is reported on the combine, saying what it's a copy of.
local one twice in a file@main or @app functions in a folderimplement blocks for the same interface and typelocal to another fileglobal, or one that climbs out of the project/ in backticks that don't start a path, or in any definition's or context value's own name - only a reference is a pathbreak or continue naming no loop it's in_ as a name@testHelper used outside test code A name that leads nowhere isn't reported here. It might be a builtin, and analysis doesn't know the builtins - type checking does, and reports it as Unknown name if it isn't one.
src/3-analyzer collects definitions (analyzeGlobalDefinitions.ts) and links names (linkNames.ts), leaving each link on the name's node. It also checks that implement blocks provide their interface's functions, and resolves combined structs and enums (&&, ||). copyImplementations.ts copies what a combined type gets as syntax trees rather than source: a struct's copies before names are linked, a combined enum's once they are, kept beside each file's own definitions (analyzeTreeData.copies) where an editor doesn't see them.
Next: Type Checking.