Semantic Pass
What does the semantic pass do?
The semantic pass analyzes the AST (Abstract Syntax Tree) generated by the parser; it checks for semantic errors (type checking, scope resolution and variable shadowing, …) at compile time and resolves the symbols for the nodes in the AST.
Orn currently has four main phases in the semantic pass:
This code will help to ilustrate each phase:
type Num = int;
const MAX: Num = 100;
struct Point {
x: Num;
y: Num;
}
fn dist(p: Point) -> int {
ret p.x + p.y;
}
fn main() -> int {
let p = Point { x: 3, y: 4 };
ret dist(p);
}
All phases work with the information that the previous phases collected.
Top hoisting
Global scope signatures symbols are collected first, therefore making the declaration order at the global scope irrelevant. It only looks for signatures due to that an object member or function params can be type of another object, and that object can be not collected yet.
What this phase sees is:
type Num
const MAX
struct Point
fn dist
fn main
Type resolution
At the type resolution phase, the aliases and the objects symbols (structs and enums) are resolved. Each object symbol gets their members symbols resolved and so does the type alias symbol. Any unknown type at this point means that it has not been declared, as we had collected all the signatures at the previous phase.
What this phase sees is:
type Num = int;
struct Point {
x: Num;
y: Num;
}
Function signature and top level declaration resolution
You’ll be asking why is function signature resolution a different phase than
the type resolution? Functions can be members of objects thanks to impl blocks
and these functions needs to be appended to the object’s members, but the object
needs to be resolved first.
Consts and global scope variables are also resolved at the third phase, this declarations depend on the types that have to be previously resolved.
What this phase sees is:
const MAX: Num = 100;
fn dist(p: Point) -> int
fn main() -> int
Bodies check
Now that all the information that we could need is gathered, we can dive into the bodies and check; types, mutability, returns, name space access, etc. all of this is done at this phase.
What this phase sees is:
fn dist(p: Point) -> int {
ret p.x + p.y;
}
fn main() -> int {
let p = Point { x: 3, y: 4 };
ret dist(p);
}