# Semantic Analysis

### Semantic Analysis

Semantic analysis is the compiler stage that checks what a program means.

Parsing checks the shape of the code. Semantic analysis checks whether that shape is valid according to the rules of Zig.

For example, this code has a valid shape:

```zig
const x: u8 = 300;
```

The parser can understand it. It sees a constant declaration named `x`, with type `u8`, initialized with the integer literal `300`.

But the program is wrong.

A `u8` can only hold values from `0` to `255`. The number `300` is too large. The parser does not decide that. Semantic analysis does.

So the basic distinction is:

```text
parsing = structure
semantic analysis = meaning
```

#### Names Must Be Resolved

When you write a name, the compiler must find what that name refers to.

Example:

```zig
const answer = 42;

pub fn main() void {
    const x = answer;
    _ = x;
}
```

Inside `main`, the name `answer` refers to the top-level constant.

Semantic analysis connects the use of `answer` to its declaration.

If the name does not exist, the compiler reports an error:

```zig
pub fn main() void {
    const x = missing_name;
    _ = x;
}
```

The parser can parse this. The code has a valid shape.

But semantic analysis cannot resolve `missing_name`.

The compiler must know what every name means before it can produce correct code.

#### Types Must Match

Zig is statically typed.

That means types are checked before the program runs.

Example:

```zig
fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn main() void {
    const result = add(10, 20);
    _ = result;
}
```

This is valid. The function expects two `i32` values and returns an `i32`.

Now compare:

```zig
fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn main() void {
    const result = add("hello", "world");
    _ = result;
}
```

The parser accepts the structure. It sees a function call with two arguments.

Semantic analysis rejects it because the arguments are string literals, not `i32` values.

This is one of semantic analysis’s main jobs:

```text
find the type of each expression
check that each type is allowed where it appears
```

#### Type Inference Happens Here

Zig often lets the compiler infer a type.

Example:

```zig
const x = 123;
```

You did not write the type of `x`. The compiler must infer it from the initializer.

Another example:

```zig
const name = "zig";
```

The compiler infers a string-literal type.

Type inference does not mean Zig is dynamically typed. The type is still known at compile time. You simply did not write it explicitly.

Semantic analysis is where these decisions happen.

#### Integer Literals Are Special

Integer literals in Zig are not immediately fixed to one machine type.

Example:

```zig
const x: u8 = 42;
```

The literal `42` can fit in `u8`, so this works.

But:

```zig
const x: u8 = 300;
```

does not work.

The compiler checks whether the literal can fit into the destination type.

This is why integer literals often feel flexible but still safe. They can adapt to context, but they cannot silently overflow into a smaller type.

#### Function Calls Are Checked

When you call a function, semantic analysis checks several things.

Example:

```zig
fn repeat(value: u8, count: usize) void {
    _ = value;
    _ = count;
}

pub fn main() void {
    repeat(7, 3);
}
```

The compiler checks:

```text
Does repeat exist?
Is repeat callable?
How many parameters does it have?
Do the argument types match?
Can each argument be coerced safely?
What is the return type?
```

If you pass too few arguments:

```zig
repeat(7);
```

the compiler rejects the call.

If you pass too many:

```zig
repeat(7, 3, 9);
```

the compiler rejects that too.

The syntax of both calls is valid. The meaning is invalid.

#### Return Values Are Checked

Semantic analysis also checks function return values.

Example:

```zig
fn getNumber() i32 {
    return 123;
}
```

This is valid.

But:

```zig
fn getNumber() i32 {
    return "hello";
}
```

is invalid because the function promises to return `i32`, but it returns a string.

The compiler also checks that a function returns when it must.

Example:

```zig
fn getNumber(flag: bool) i32 {
    if (flag) {
        return 1;
    }
}
```

If `flag` is false, the function reaches the end without returning an `i32`. Semantic analysis rejects this.

#### Control Flow Is Checked

Semantic analysis understands control flow.

It checks whether branches, loops, and blocks are valid.

Example:

```zig
const value = if (true) 10 else 20;
```

Both branches produce integer values, so this can be used as an expression.

But:

```zig
const value = if (true) 10 else "no";
```

The two branches do not have a compatible result type. Semantic analysis must reject or require a clear type resolution depending on context.

Control flow also matters for unreachable code.

Example:

```zig
fn crash() noreturn {
    @panic("stop");
}
```

A function returning `noreturn` never returns normally. Semantic analysis uses this information when checking code paths.

#### Errors Are Part of Types

In Zig, errors are values, and possible errors are represented in types.

Example:

```zig
fn readNumber() !u32 {
    return 10;
}
```

The return type `!u32` means the function returns either an error or a `u32`.

When you call it:

```zig
const n = try readNumber();
```

semantic analysis checks that `try` is used in a place where errors can be returned.

For example:

```zig
fn readNumber() !u32 {
    return 10;
}

pub fn main() void {
    const n = try readNumber();
    _ = n;
}
```

This is invalid because `main` returns `void`, not an error union. There is nowhere for `try` to return the error.

One fix is:

```zig
pub fn main() !void {
    const n = try readNumber();
    _ = n;
}
```

Now `main` can return an error.

This is semantic analysis at work. It checks that error flow is valid.

#### `comptime` Is Checked

Zig has code that can run at compile time.

Example:

```zig
fn double(comptime x: u32) u32 {
    return x * 2;
}

const value = double(21);
```

The parameter `x` must be known at compile time.

If you try to pass a runtime value where a compile-time value is required, semantic analysis rejects it.

Example:

```zig
fn double(comptime x: u32) u32 {
    return x * 2;
}

pub fn main() void {
    var n: u32 = 21;
    const value = double(n);
    _ = value;
}
```

Here, `n` is a runtime variable. It cannot be used as a `comptime` argument.

Semantic analysis enforces the boundary between compile-time and runtime.

#### Imports Are Resolved

This line is common:

```zig
const std = @import("std");
```

The parser sees a declaration and a builtin call.

Semantic analysis resolves what `"std"` means, loads the imported module, and connects the name `std` to that module.

Then this works:

```zig
std.debug.print("hello\n", .{});
```

The compiler resolves:

```text
std
std.debug
std.debug.print
```

Each step must refer to a valid declaration.

If you misspell something:

```zig
std.debig.print("hello\n", .{});
```

semantic analysis rejects it because `debig` does not exist.

#### Semantic Analysis Produces Better Internal Code

After semantic analysis, the compiler knows much more than it knew after parsing.

Before semantic analysis:

```text
this is a function call
this is a declaration
this is a block
this is an identifier
```

After semantic analysis:

```text
this identifier refers to this declaration
this expression has this type
this function can return these errors
this branch is unreachable
this call requires these argument types
this value is known at compile time
```

This information is needed before code generation.

The backend should not have to guess what a name means or what type an expression has. Semantic analysis resolves those questions.

#### Why Semantic Analysis Is Hard

Semantic analysis is one of the largest parts of a compiler because language meaning is complicated.

It must handle:

```text
types
pointers
slices
arrays
structs
unions
enums
optionals
errors
generics
comptime
imports
control flow
visibility
alignment
calling conventions
target differences
```

Each feature interacts with the others.

For example, a generic function may depend on a compile-time type, return an error union, allocate memory through an allocator, and behave differently on different targets.

The compiler must still check it precisely.

#### A Useful Mental Model

Use this model:

```text
Parser: builds the tree.
Semantic analyzer: proves the tree makes sense.
```

Semantic analysis is where Zig becomes strict.

It catches mistakes before runtime. It resolves types before code generation. It enforces explicit error handling. It separates compile-time values from runtime values.

When Zig gives you a type error, name error, return error, `comptime` error, or invalid function call error, you are usually seeing semantic analysis doing its job.

