error handling kickoff
This commit is contained in:
@@ -552,11 +552,46 @@
|
|||||||
inside arm bodies. As a side effect the rvalue pointer-capture guard from 22.5 is now reachable
|
inside arm bodies. As a side effect the rvalue pointer-capture guard from 22.5 is now reachable
|
||||||
(`match make_box() { .v |@p|: … }` correctly errors "requires an addressable subject")
|
(`match make_box() { .v |@p|: … }` correctly errors "requires an addressable subject")
|
||||||
- checker-only; no parser/AST/HIR/IR/lowering/codegen change
|
- checker-only; no parser/AST/HIR/IR/lowering/codegen change
|
||||||
- deferred: payload-carrying contextual construction (`.variant{…}`, milestone 23); a
|
- deferred: payload-carrying contextual construction (`.variant{…}`) remains outside milestone
|
||||||
`yield call()` inside a value-block/value-match arm still misses its spec (`infer_statements`
|
23 v1. The separate `yield call()` inference gap is fixed in milestone 23.
|
||||||
has no `.Yield` case either — a separate pre-existing gap)
|
|
||||||
|
|
||||||
23. error types (see below)
|
23. sum-type composition, fallible channels, and yield inference (implemented; v1)
|
||||||
|
- `.Yield` is now visited by specialization-demand inference, so `yield call()` inside value
|
||||||
|
blocks, value-if/value-match branches, and match arms requests the needed call specialization
|
||||||
|
- native unbacked enums and native `union(enum)` / `union(SomeEnum)` variants are registered in a
|
||||||
|
program-global variant table keyed by `(name, payload-type)`. Runtime tags are fixed `u16`
|
||||||
|
global ids; `0` is reserved for fallible success/no-error, and overflow is diagnosed
|
||||||
|
- tagged-union construction, tag reads, matching, and LLVM layout use those global ids rather
|
||||||
|
than per-type dense tags. Component-to-composite widening preserves the tag and copies only the
|
||||||
|
active payload carrier bytes from the source carrier offset to the destination carrier offset
|
||||||
|
- `A | B` composes native unbacked enums and native tagged unions: matching `(name, payload-type)`
|
||||||
|
variants merge, same-name/different-payload conflicts are diagnosed, and backed enums, C enums,
|
||||||
|
and untagged unions are rejected for v1
|
||||||
|
- `T ! E` is represented as a real synthetic fallible channel type in `types.Store`. A fallible
|
||||||
|
function returns success with code `0` or an error variant's global id; `return expr` dispatches
|
||||||
|
based on whether `expr` coerces to the success type `T` or the error type `E`
|
||||||
|
- `try expr` unwraps success and propagates the enclosing function's exact error channel;
|
||||||
|
`expr catch fallback` unwraps success or evaluates the fallback value
|
||||||
|
- runtime coverage lives in `examples/programs/errors`; compiler coverage includes global-id sum
|
||||||
|
merge/conflict/widening/rejections and the `.Yield` inference regression
|
||||||
|
- deferred follow-ups are split below; 23.5 keeps the next user-visible slice small
|
||||||
|
|
||||||
|
23.5. fallible ergonomics: catch blocks + composable try widening
|
||||||
|
- implement `expr catch |e| { ... }` with `e : E`
|
||||||
|
- allow `try` to widen `T ! E1` into an enclosing `T ! (E1 | E2)` channel
|
||||||
|
- add focused tests/examples for both
|
||||||
|
- leave ABI/layout/lint/design polish for later milestones
|
||||||
|
|
||||||
|
23.6. contextual payload construction + inline error types
|
||||||
|
- implement contextual payload `.variant{...}` construction
|
||||||
|
- allow named fallible signatures to use inline enum / tagged-union error types
|
||||||
|
- settle qualified same-name disambiguation only if contextual construction needs it
|
||||||
|
|
||||||
|
23.7. sum-type ABI/layout polish
|
||||||
|
- consider dynamic tag-width shrinking after the fixed-`u16` ABI has real pressure
|
||||||
|
- consider all-void channel collapse after fallible channels are otherwise stable
|
||||||
|
- define cross-module/global-id ABI determinism before multi-module builds depend on it
|
||||||
|
- keep backed/C enum composition and must-consume fallible linting as later policy work
|
||||||
|
|
||||||
24. dynamic heap allocation
|
24. dynamic heap allocation
|
||||||
- see below for direction
|
- see below for direction
|
||||||
@@ -572,7 +607,7 @@ name: ?[]u8 = get_name()
|
|||||||
age: ?u8 = get_age()
|
age: ?u8 = get_age()
|
||||||
if name and age |n, a| {
|
if name and age |n, a| {
|
||||||
# both n and a are guaranteed non-none here
|
# both n and a are guaranteed non-none here
|
||||||
print("{s} is {d} years old", {n, a})
|
print("{s} is {d} years old", n, a)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -580,7 +615,7 @@ if name and age |n, a| {
|
|||||||
|
|
||||||
```
|
```
|
||||||
if name and hat |n, h : n == "Huginn" and h.brand == .gucci| {
|
if name and hat |n, h : n == "Huginn" and h.brand == .gucci| {
|
||||||
print("{s}'s got that drip\n", {n})
|
print("{s}'s got that drip\n", n)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -765,7 +800,7 @@ message = "Header:\t" ++
|
|||||||
|
|
||||||
The `yield` keyword provides a value from a block to its enclosing expression and **exits the block immediately** — just as `return` exits a function, `yield` exits the enclosing scope. Code after a `yield` is unreachable, and the compiler flags it. This makes `yield` part of a consistent set of scope-exiting control flow: `return` exits a function, `yield` exits a block, `break` exits a loop, and `continue` skips to the next iteration.
|
The `yield` keyword provides a value from a block to its enclosing expression and **exits the block immediately** — just as `return` exits a function, `yield` exits the enclosing scope. Code after a `yield` is unreachable, and the compiler flags it. This makes `yield` part of a consistent set of scope-exiting control flow: `return` exits a function, `yield` exits a block, `break` exits a loop, and `continue` skips to the next iteration.
|
||||||
|
|
||||||
It is used in scoped blocks, match arms, and catch handlers.
|
It is used in scoped blocks and match arms today; catch-handler blocks are planned.
|
||||||
|
|
||||||
**General rule:** When a block needs to produce a value, single expressions yield implicitly while multi-statement blocks require explicit `yield`. This rule applies uniformly across the language:
|
**General rule:** When a block needs to produce a value, single expressions yield implicitly while multi-statement blocks require explicit `yield`. This rule applies uniformly across the language:
|
||||||
|
|
||||||
@@ -785,8 +820,7 @@ label []u8 = match p {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# catch handlers
|
# catch handlers (planned; block form deferred in milestone 23 v1)
|
||||||
data []u8 = read(path) catch default_data # single expression: implicit
|
|
||||||
data []u8 = read(path) catch |e| {
|
data []u8 = read(path) catch |e| {
|
||||||
log(e)
|
log(e)
|
||||||
yield fallback_data # block: explicit yield
|
yield fallback_data # block: explicit yield
|
||||||
@@ -895,7 +929,7 @@ match result {
|
|||||||
process(data)
|
process(data)
|
||||||
}
|
}
|
||||||
.failure |info|: {
|
.failure |info|: {
|
||||||
print("error {d}: {s}", { info.code, info.msg })
|
print("error {d}: {s}", info.code, info.msg)
|
||||||
}
|
}
|
||||||
.pending: {
|
.pending: {
|
||||||
# void payload - no capture needed
|
# void payload - no capture needed
|
||||||
@@ -938,14 +972,16 @@ message :: match code {
|
|||||||
|
|
||||||
## A word on error handling
|
## A word on error handling
|
||||||
|
|
||||||
Honey handles errors as values. There is no hidden control flow — a function that can fail declares this in its signature, and the caller must explicitly handle the possibility of failure.
|
Brolang handles errors as values. There is no hidden control flow — a function that can fail declares this in its signature, and the caller must explicitly handle the possibility of failure.
|
||||||
|
|
||||||
|
Milestone 23 v1 implements named error channels, native sum composition, `return`-based error dispatch, exact-channel `try`, and fallback `catch`. It intentionally defers the `error` keyword shorthand, inline error types, `catch |e|` blocks, and `try` widening across different-but-composable error channels.
|
||||||
|
|
||||||
### Fallible Functions
|
### Fallible Functions
|
||||||
|
|
||||||
Functions that can fail declare their error type after `!`:
|
Functions that can fail declare their error type after `!`:
|
||||||
|
|
||||||
```honey
|
```
|
||||||
read_file :: func(path: []u8) []u8 ! IoError { ... }
|
read_file :: func(path []u8) []u8 ! IoError { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
This reads as: "returns `[]u8` or fails with `IoError`." The space around `!` is idiomatic but not required.
|
This reads as: "returns `[]u8` or fails with `IoError`." The space around `!` is idiomatic but not required.
|
||||||
@@ -956,7 +992,7 @@ Errors can be enums (when you only need to identify what went wrong) or tagged u
|
|||||||
|
|
||||||
**Simple errors (enum):**
|
**Simple errors (enum):**
|
||||||
|
|
||||||
```honey
|
```
|
||||||
SimpleError :: enum {
|
SimpleError :: enum {
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
InvalidSize,
|
InvalidSize,
|
||||||
@@ -966,12 +1002,12 @@ SimpleError :: enum {
|
|||||||
|
|
||||||
**Rich errors (tagged union):**
|
**Rich errors (tagged union):**
|
||||||
|
|
||||||
```honey
|
```
|
||||||
IoError :: union(enum) {
|
IoError :: union(enum) {
|
||||||
NotFound: struct { path: []u8 },
|
NotFound struct { path: []u8 },
|
||||||
PermissionDenied: struct { path: []u8, operation: []u8 },
|
PermissionDenied struct { path []u8, operation []u8 },
|
||||||
Timeout: struct { after_ms: u64 },
|
Timeout struct { after_ms u64 },
|
||||||
ConnectionReset: void, # no additional data needed
|
ConnectionReset void, # no additional data needed
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -979,7 +1015,7 @@ IoError :: union(enum) {
|
|||||||
|
|
||||||
When you have a predefined set of error kinds, you can constrain the union:
|
When you have a predefined set of error kinds, you can constrain the union:
|
||||||
|
|
||||||
```honey
|
```
|
||||||
IoErrorKind :: enum {
|
IoErrorKind :: enum {
|
||||||
not_found,
|
not_found,
|
||||||
permission_denied,
|
permission_denied,
|
||||||
@@ -987,64 +1023,43 @@ IoErrorKind :: enum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IoError :: union(IoErrorKind) {
|
IoError :: union(IoErrorKind) {
|
||||||
not_found: struct { path: []u8 },
|
not_found struct { path []u8 },
|
||||||
permission_denied: struct { path: []u8 },
|
permission_denied struct { path []u8 },
|
||||||
timeout: struct { after_ms: u64 },
|
timeout struct { after_ms u64 },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Composition
|
### Error Composition
|
||||||
|
|
||||||
Functions that can fail with multiple error types use `|` to compose them:
|
Functions that can fail with multiple error types use `|` to compose a named error type:
|
||||||
|
|
||||||
```honey
|
```
|
||||||
process :: func(path: []u8) Ast ! IoError | ParseError { ... }
|
ProcessError :: alias IoError | ParseError
|
||||||
|
process :: func(path []u8) Ast ! ProcessError { ... }
|
||||||
```
|
```
|
||||||
|
|
||||||
Parentheses are optional but can aid readability:
|
Parentheses are optional in the composed type and can aid readability:
|
||||||
|
|
||||||
```honey
|
|
||||||
process :: func(path: []u8) Ast ! (IoError | ParseError) { ... }
|
|
||||||
```
|
```
|
||||||
|
ProcessError :: alias (IoError | ParseError)
|
||||||
Composed error types can also be defined standalone:
|
|
||||||
|
|
||||||
```honey
|
|
||||||
ProcessError :: IoError | ParseError | ValidationError
|
|
||||||
process :: func(path: []u8) Ast ! ProcessError { ... }
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Inline Error Types
|
### Inline Error Types
|
||||||
|
|
||||||
For simple one-off cases, you can inline the error type:
|
Inline error types are planned, but not part of milestone 23 v1. Use named enums, named tagged unions, or named aliases for now.
|
||||||
|
|
||||||
```honey
|
|
||||||
# Inline enum
|
|
||||||
simple :: func() void ! enum{Failed, Timeout} { ... }
|
|
||||||
|
|
||||||
# Inline tagged union (when payloads are needed)
|
|
||||||
complex :: func() Data ! union(enum) {
|
|
||||||
OutOfMemory: struct { requested: usize },
|
|
||||||
InvalidInput: void,
|
|
||||||
} { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
For anything non-trivial, prefer named error types for clarity.
|
|
||||||
|
|
||||||
### Returning Errors
|
### Returning Errors
|
||||||
|
|
||||||
The `error` keyword is a control flow statement that exits the function via the error channel — symmetric to return for success values:
|
Fallible functions use ordinary `return` for both channels. If the returned expression coerces to the success type `T`, the function returns success with channel code `0`. If it coerces to the error type `E`, the function returns the error with that variant's global tag id:
|
||||||
|
|
||||||
```honey
|
```
|
||||||
parse_section :: func(p: @mut Parser) void ! ParseError {
|
parse_section :: func(p: @mut Parser) void ! ParseError {
|
||||||
start_line := p.line
|
start_line Line = p.line
|
||||||
p.advance()
|
p.advance()
|
||||||
|
|
||||||
# ... parsing logic ...
|
# ... parsing logic ...
|
||||||
|
|
||||||
if p.pos >= p.input.len or p.input[p.pos] != ']' {
|
if p.pos >= p.input.len or p.input[p.pos] != ']' return ParseError{ unclosed_section = start_line }
|
||||||
error .unclosed_section{.line = start_line}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ... continue on success ...
|
# ... continue on success ...
|
||||||
}
|
}
|
||||||
@@ -1052,48 +1067,49 @@ parse_section :: func(p: @mut Parser) void ! ParseError {
|
|||||||
|
|
||||||
Since errors are just union values, you can also construct them separately:
|
Since errors are just union values, you can also construct them separately:
|
||||||
|
|
||||||
```honey
|
```
|
||||||
# Construct error value (it's just a union)
|
# Construct error value (it's just a union)
|
||||||
e: ParseError = .timeout{.ms = 500}
|
e ParseError = ParseError{ timeout = 500 }
|
||||||
|
|
||||||
# Return it via error channel later
|
# Return it via error channel later
|
||||||
error e
|
return e
|
||||||
```
|
```
|
||||||
|
|
||||||
The symmetry:
|
The symmetry:
|
||||||
|
|
||||||
* `return x` — exit function with success value
|
* `return x` — exits with success when `x` is the success type
|
||||||
* `error e` — exit function with error value
|
* `return e` — exits with error when `e` is the error type
|
||||||
|
|
||||||
### Propagation with `try`
|
### Propagation with `try`
|
||||||
|
|
||||||
The `try` keyword unwraps a successful result or returns early with the error:
|
The `try` keyword unwraps a successful result or returns early with the error:
|
||||||
|
|
||||||
```honey
|
```
|
||||||
process :: func(path: []u8) Ast ! IoError | ParseError {
|
ProcessError :: alias IoError | ParseError
|
||||||
data := try read_file(path) # returns early if IoError
|
process :: func(path []u8) Ast ! ProcessError {
|
||||||
ast := try parse(data) # returns early if ParseError
|
data :: try read_file(path) # read_file also returns []u8 ! ProcessError in v1
|
||||||
|
ast :: try parse(data) # parse also returns Ast ! ProcessError in v1
|
||||||
return ast
|
return ast
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
When the function's error type is a composition, `try` automatically widens narrower error types to match the return type.
|
In v1, `try` propagates only when the callee's error channel exactly matches the enclosing function's error channel. Widening narrower error types into a composed return channel is a planned follow-up.
|
||||||
|
|
||||||
### Handling with `catch`
|
### Handling with `catch`
|
||||||
|
|
||||||
The `catch` keyword handles errors and provides a value to continue with.
|
The `catch` keyword handles errors and provides a value to continue with. Milestone 23 v1 implements the fallback-value form.
|
||||||
|
|
||||||
**Provide a fallback value:**
|
**Provide a fallback value:**
|
||||||
|
|
||||||
```honey
|
```
|
||||||
data := read_file(path) catch default_data
|
data :: read_file(path) catch default_data
|
||||||
```
|
```
|
||||||
|
|
||||||
**Handle with a block using** `yield`:
|
**Planned block form using** `yield` (deferred in v1):
|
||||||
|
|
||||||
```honey
|
```
|
||||||
data := read_file(path) catch |e| {
|
data :: read_file(path) catch |e| {
|
||||||
log("read failed: {}", {e})
|
log("read failed: {}", e)
|
||||||
yield empty_data
|
yield empty_data
|
||||||
}
|
}
|
||||||
use(data) # continues with data = empty_data
|
use(data) # continues with data = empty_data
|
||||||
@@ -1101,11 +1117,11 @@ use(data) # continues with data = empty_data
|
|||||||
|
|
||||||
The `yield` keyword provides a value from a block to the enclosing expression. Execution continues after the statement. For single expressions, yield is implicit (e.g., `catch default_data`). For blocks, explicit `yield` is required. See the Yield section under Control Flow for the full rule.
|
The `yield` keyword provides a value from a block to the enclosing expression. Execution continues after the statement. For single expressions, yield is implicit (e.g., `catch default_data`). For blocks, explicit `yield` is required. See the Yield section under Control Flow for the full rule.
|
||||||
|
|
||||||
**Return from the function instead:**
|
**Planned return-from-handler form** (deferred in v1):
|
||||||
|
|
||||||
```honey
|
```
|
||||||
data := read_file(path) catch |e| {
|
data :: read_file(path) catch |e| {
|
||||||
log("read failed: {}", {e})
|
log("read failed: {}", e)
|
||||||
return # exits the enclosing function
|
return # exits the enclosing function
|
||||||
}
|
}
|
||||||
use(data) # never reached if error occurred
|
use(data) # never reached if error occurred
|
||||||
@@ -1113,16 +1129,16 @@ use(data) # never reached if error occurred
|
|||||||
|
|
||||||
Use `return` when the error is unrecoverable at this level.
|
Use `return` when the error is unrecoverable at this level.
|
||||||
|
|
||||||
**Match on specific error variants:**
|
**Planned match-on-error form** (deferred in v1):
|
||||||
|
|
||||||
```honey
|
```
|
||||||
data := read_file(path) catch |e| match e {
|
data :: read_file(path) catch |e| match e {
|
||||||
.NotFound |info|: {
|
.NotFound |info|: {
|
||||||
print("file not found: {s}", {info.path})
|
print("file not found: {s}", info.path)
|
||||||
yield create_default(info.path)
|
yield create_default(info.path)
|
||||||
},
|
},
|
||||||
.Timeout |info|: {
|
.Timeout |info|: {
|
||||||
print("timed out after {d}ms", {info.after_ms})
|
print("timed out after {d}ms", info.after_ms)
|
||||||
yield retry(path)
|
yield retry(path)
|
||||||
},
|
},
|
||||||
.PermissionDenied: panic("cannot recover from permission error"),
|
.PermissionDenied: panic("cannot recover from permission error"),
|
||||||
@@ -1135,16 +1151,17 @@ data := read_file(path) catch |e| match e {
|
|||||||
| Syntax | Meaning |
|
| Syntax | Meaning |
|
||||||
| -- | -- |
|
| -- | -- |
|
||||||
| `T ! E` | Function returns `T` or fails with `E` |
|
| `T ! E` | Function returns `T` or fails with `E` |
|
||||||
| `T ! E1 | E2` | Function can fail with either error type |
|
| `T ! E1 | E2` | Planned direct spelling; use a named alias in v1 |
|
||||||
| `T ! (E1 | E2)` | Same, with optional parentheses |
|
| `T ! (E1 | E2)` | Planned direct spelling with parentheses; use a named alias in v1 |
|
||||||
| `error e` | Exit function via error channel |
|
| `return e` | Exit function via error channel when `e : E` |
|
||||||
| `error .variant{...}` | Construct and return error in one step |
|
| `error e` | Planned shorthand, not v1 |
|
||||||
| `try expr` | Unwrap success or propagate error |
|
| `error .variant{...}` | Planned shorthand, not v1 |
|
||||||
|
| `try expr` | Unwrap success or propagate an exact matching error channel |
|
||||||
| `expr catch fallback` | Provide fallback value on error |
|
| `expr catch fallback` | Provide fallback value on error |
|
||||||
| `expr catch |e| { ... }` | Handle error with block |
|
| `expr catch |e| { ... }` | Planned block handler, not v1 |
|
||||||
| `yield value` | Provide value from innermost block |
|
| `yield value` | Provide value from innermost block |
|
||||||
| `yield :label value` | Provide value from labeled block |
|
| `yield :label value` | Provide value from labeled block |
|
||||||
| `return` / `return value` | Exit function from within catch block |
|
| `return` / `return value` | Exit the current function; in fallible functions, return value dispatches by type |
|
||||||
|
|
||||||
## A word on memory allocation
|
## A word on memory allocation
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ Expr_Kind :: enum u8 {
|
|||||||
Or,
|
Or,
|
||||||
Range,
|
Range,
|
||||||
Call,
|
Call,
|
||||||
|
Try,
|
||||||
|
Catch,
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr :: struct {
|
Expr :: struct {
|
||||||
@@ -109,6 +111,7 @@ Expr :: struct {
|
|||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
left: Expr_Id,
|
left: Expr_Id,
|
||||||
right: Expr_Id,
|
right: Expr_Id,
|
||||||
|
body: []Stmt_Id,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
parenthesized: bool,
|
parenthesized: bool,
|
||||||
kind: Expr_Kind,
|
kind: Expr_Kind,
|
||||||
@@ -204,6 +207,7 @@ Function :: struct {
|
|||||||
variadic: bool,
|
variadic: bool,
|
||||||
params: []Param,
|
params: []Param,
|
||||||
result: Type_Syntax,
|
result: Type_Syntax,
|
||||||
|
error: Type_Syntax,
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
link_name: string,
|
link_name: string,
|
||||||
unsupported_reason: string,
|
unsupported_reason: string,
|
||||||
@@ -304,6 +308,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
|||||||
destroy_module :: proc(module: ^Module) {
|
destroy_module :: proc(module: ^Module) {
|
||||||
for expr in module.exprs {
|
for expr in module.exprs {
|
||||||
delete(expr.args, module.allocator)
|
delete(expr.args, module.allocator)
|
||||||
|
delete(expr.body, module.allocator)
|
||||||
}
|
}
|
||||||
for statement in module.statements {
|
for statement in module.statements {
|
||||||
delete(statement.captures, module.allocator)
|
delete(statement.captures, module.allocator)
|
||||||
|
|||||||
+241
-18
@@ -167,6 +167,7 @@ Checker :: struct {
|
|||||||
cycle_stack: [dynamic]Cycle_Frame,
|
cycle_stack: [dynamic]Cycle_Frame,
|
||||||
main_symbol: symbol.Id,
|
main_symbol: symbol.Id,
|
||||||
sink_symbol: symbol.Id,
|
sink_symbol: symbol.Id,
|
||||||
|
current_result: types.Type,
|
||||||
target: target.Target,
|
target: target.Target,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -322,6 +323,14 @@ type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function_channel_type :: proc(checker: ^Checker, function: ast.Function) -> types.Type {
|
||||||
|
result := type_from_syntax(function.result)
|
||||||
|
if types.is_valid(function.error) {
|
||||||
|
return types.fallible(&checker.module.types, result, type_from_syntax(function.error))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
||||||
return types.is_runtime_value(value, &checker.module.types)
|
return types.is_runtime_value(value, &checker.module.types)
|
||||||
}
|
}
|
||||||
@@ -609,11 +618,15 @@ add_unsupported_type_diagnostic :: proc(
|
|||||||
if types.is_valid(item.child) {
|
if types.is_valid(item.child) {
|
||||||
return add_unsupported_type_diagnostic(checker, span, item.child, depth+1)
|
return add_unsupported_type_diagnostic(checker, span, item.child, depth+1)
|
||||||
}
|
}
|
||||||
|
if types.is_valid(item.extra) {
|
||||||
|
return add_unsupported_type_diagnostic(checker, span, item.extra, depth+1)
|
||||||
|
}
|
||||||
return source.INVALID_DIAGNOSTIC
|
return source.INVALID_DIAGNOSTIC
|
||||||
}
|
}
|
||||||
|
|
||||||
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
|
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
|
||||||
if left.result != right.result || left.variadic != right.variadic || len(left.params) != len(right.params) {
|
if left.result != right.result || left.error != right.error ||
|
||||||
|
left.variadic != right.variadic || len(left.params) != len(right.params) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for param, index in left.params {
|
for param, index in left.params {
|
||||||
@@ -665,7 +678,7 @@ function_value_signature :: proc(
|
|||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
}
|
}
|
||||||
function := checker.ast_module.functions[template]
|
function := checker.ast_module.functions[template]
|
||||||
if !function.c_abi {
|
if !function.c_abi || types.is_valid(function.error) {
|
||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
}
|
}
|
||||||
result = type_from_syntax(function.result)
|
result = type_from_syntax(function.result)
|
||||||
@@ -744,8 +757,14 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
}
|
}
|
||||||
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed:
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
|
case .Catch:
|
||||||
|
append(&stack, expr.left)
|
||||||
|
if expr.right != ast.INVALID_EXPR {
|
||||||
|
append(&stack, expr.right)
|
||||||
|
}
|
||||||
|
mark_block_imports_used(checker, expr.body, file)
|
||||||
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
append(&stack, expr.left, expr.right)
|
append(&stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name, .Enum_Literal:
|
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Name, .Enum_Literal:
|
||||||
@@ -916,6 +935,24 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
symbol_text(checker, function.name),
|
symbol_text(checker, function.name),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if types.is_valid(function.error) {
|
||||||
|
error_type := type_from_syntax(function.error)
|
||||||
|
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
||||||
|
types.is_tagged_union(error_type, &checker.module.types)
|
||||||
|
if function.c_abi {
|
||||||
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
function.span,
|
||||||
|
"fallible functions must use 'func', not 'c_func'",
|
||||||
|
)
|
||||||
|
} else if !error_sum {
|
||||||
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
function.span,
|
||||||
|
"fallible function error type must be a native enum or tagged union",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
if !function.has_body && !function.c_abi {
|
if !function.has_body && !function.c_abi {
|
||||||
checker.template_diagnostics[function_id] = source.addf(
|
checker.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -1072,15 +1109,15 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A tagged union (`item.child` set) must tag with an enum, and each variant
|
// A tagged union stores a hidden runtime tag enum keyed by global variant IDs.
|
||||||
// must name a member of it. The synthesized `union(enum)` tag satisfies this
|
// An explicit `union(Enum)` keeps that declared enum only for validation.
|
||||||
// by construction; the check guards the explicit `union(Enum)` form.
|
|
||||||
if item.kind == .Union && types.is_valid(item.child) {
|
if item.kind == .Union && types.is_valid(item.child) {
|
||||||
if !types.is_enum(item.child, &checker.module.types) {
|
declared_tag := types.union_declared_tag_enum(id, &checker.module.types)
|
||||||
|
if !types.is_enum(declared_tag, &checker.module.types) {
|
||||||
source.add(checker.diagnostics, source.Span{}, "a tagged union's tag must be an enum")
|
source.add(checker.diagnostics, source.Span{}, "a tagged union's tag must be an enum")
|
||||||
} else {
|
} else {
|
||||||
for field in types.fields_for(&checker.module.types, id) {
|
for field in types.fields_for(&checker.module.types, id) {
|
||||||
if _, ok := find_enum_member(checker, item.child, symbol.Id(field.name)); !ok {
|
if _, ok := find_enum_member(checker, declared_tag, symbol.Id(field.name)); !ok {
|
||||||
source.addf(
|
source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
source.Span{},
|
source.Span{},
|
||||||
@@ -1187,8 +1224,9 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
|
|||||||
}
|
}
|
||||||
append(&signature, specialized_param_type(checker, param.type, actual))
|
append(&signature, specialized_param_type(checker, param.type, actual))
|
||||||
}
|
}
|
||||||
result := type_from_syntax(function.result)
|
result := function_channel_type(checker, function)
|
||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT &&
|
||||||
|
!types.is_valid(function.error) {
|
||||||
result = types.I32
|
result = types.I32
|
||||||
}
|
}
|
||||||
index := spec_id(len(checker.specs))
|
index := spec_id(len(checker.specs))
|
||||||
@@ -1346,6 +1384,31 @@ infer_compound_expr :: proc(
|
|||||||
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||||
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
|
return types.child_type(value, store) if types.is_optional(value, store) else types.INVALID
|
||||||
|
case .Try:
|
||||||
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
|
return types.fallible_success(value, store) if types.kind(value, store) == .Fallible else types.INVALID
|
||||||
|
case .Catch:
|
||||||
|
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
|
success := types.fallible_success(value, store)
|
||||||
|
error_type := types.fallible_error(value, store)
|
||||||
|
if expr.right != ast.INVALID_EXPR {
|
||||||
|
fallback := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||||
|
if types.is_valid(success) && types.is_valid(fallback) && !types.equal(success, fallback) {
|
||||||
|
return types.widest(success, fallback)
|
||||||
|
}
|
||||||
|
return success if types.is_valid(success) else fallback
|
||||||
|
}
|
||||||
|
block_locals: [dynamic]Infer_Local
|
||||||
|
block_locals.allocator = checker.allocator
|
||||||
|
defer delete(block_locals)
|
||||||
|
append(&block_locals, ..locals)
|
||||||
|
capture_start := len(block_locals)
|
||||||
|
if symbol.is_valid(expr.name) && expr.name != checker.sink_symbol && types.is_valid(error_type) {
|
||||||
|
append(&block_locals, Infer_Local{name=expr.name, type=error_type, declared=error_type, statement=ast.INVALID_STMT})
|
||||||
|
}
|
||||||
|
infer_statements(checker, expr.body, &block_locals, local_types, pkg, file, demanded, &success, success)
|
||||||
|
resize(&block_locals, capture_start)
|
||||||
|
return success
|
||||||
case .Struct_Literal:
|
case .Struct_Literal:
|
||||||
for keyed in expr.args {
|
for keyed in expr.args {
|
||||||
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
|
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded, local_types)
|
||||||
@@ -1417,7 +1480,7 @@ infer_expr :: proc(
|
|||||||
last = types.F64
|
last = types.F64
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Enum_Literal,
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal,
|
||||||
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
@@ -1556,7 +1619,7 @@ infer_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
|
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
|
||||||
declared := type_from_syntax(checker.ast_module.functions[template].result)
|
declared := function_channel_type(checker, checker.ast_module.functions[template])
|
||||||
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
@@ -1656,12 +1719,13 @@ infer_expr :: proc(
|
|||||||
if spec != INVALID_SPEC {
|
if spec != INVALID_SPEC {
|
||||||
last = checker.specs[spec].result
|
last = checker.specs[spec].result
|
||||||
} else {
|
} else {
|
||||||
declared := type_from_syntax(function.result)
|
declared := function_channel_type(checker, function)
|
||||||
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
declared := type_from_syntax(function.result)
|
declared := function_channel_type(checker, function)
|
||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT &&
|
||||||
|
!types.is_valid(function.error) {
|
||||||
last = types.I32
|
last = types.I32
|
||||||
} else {
|
} else {
|
||||||
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
||||||
@@ -1889,6 +1953,10 @@ infer_statements :: proc(
|
|||||||
}
|
}
|
||||||
case .Expression:
|
case .Expression:
|
||||||
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||||
|
case .Yield:
|
||||||
|
if statement.expr != ast.INVALID_EXPR {
|
||||||
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||||
|
}
|
||||||
case .Return:
|
case .Return:
|
||||||
if statement.expr != ast.INVALID_EXPR {
|
if statement.expr != ast.INVALID_EXPR {
|
||||||
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||||
@@ -2581,6 +2649,23 @@ find_build_local :: proc(locals: []Build_Local, name: symbol.Id) -> (Build_Local
|
|||||||
return Build_Local{}, false
|
return Build_Local{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
can_implicitly_convert_type :: proc(checker: ^Checker, actual, expected: types.Type) -> bool {
|
||||||
|
store := &checker.module.types
|
||||||
|
if types.equal(actual, expected) ||
|
||||||
|
types.can_widen(actual, expected) ||
|
||||||
|
types.can_coerce_c_integer(actual, expected) ||
|
||||||
|
types.can_weaken_pointer(actual, expected, store) ||
|
||||||
|
types.can_weaken_slice(actual, expected, store) ||
|
||||||
|
types.can_decay_array_pointer(actual, expected, store) ||
|
||||||
|
types.can_sum_widen(actual, expected, store) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if types.is_optional(expected, store) {
|
||||||
|
return can_implicitly_convert_type(checker, actual, types.child_type(expected, store))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
coerce_expr :: proc(
|
coerce_expr :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr_id: hir.Expr_Id,
|
expr_id: hir.Expr_Id,
|
||||||
@@ -2627,6 +2712,17 @@ coerce_expr :: proc(
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if types.can_sum_widen(actual, expected, &checker.module.types) {
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Sum_Widen,
|
||||||
|
span=span,
|
||||||
|
type=expected,
|
||||||
|
left=expr_id,
|
||||||
|
target=hir.INVALID_REF,
|
||||||
|
right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
if types.is_optional(expected, &checker.module.types) {
|
if types.is_optional(expected, &checker.module.types) {
|
||||||
child := types.child_type(expected, &checker.module.types)
|
child := types.child_type(expected, &checker.module.types)
|
||||||
if types.equal(actual, child) ||
|
if types.equal(actual, child) ||
|
||||||
@@ -3049,6 +3145,28 @@ build_nested_expr :: proc(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fallible_aggregate :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
span: source.Span,
|
||||||
|
channel: types.Type,
|
||||||
|
value: hir.Expr_Id,
|
||||||
|
error_path: bool,
|
||||||
|
) -> hir.Expr_Id {
|
||||||
|
values := make([]hir.Expr_Id, 1, checker.allocator)
|
||||||
|
values[0] = value
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Struct,
|
||||||
|
span=span,
|
||||||
|
type=channel,
|
||||||
|
integer=1 if error_path else 0,
|
||||||
|
args=values,
|
||||||
|
target=hir.INVALID_REF,
|
||||||
|
left=hir.INVALID_EXPR,
|
||||||
|
right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
build_compound_expr :: proc(
|
build_compound_expr :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr: ast.Expr,
|
expr: ast.Expr,
|
||||||
@@ -3303,6 +3421,52 @@ build_compound_expr :: proc(
|
|||||||
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
|
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
|
||||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Try:
|
||||||
|
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
channel_type := checker.module.exprs[channel].type
|
||||||
|
success := types.fallible_success(channel_type, store)
|
||||||
|
if !types.is_valid(success) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'try' requires a fallible expression")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
if !types.equal(channel_type, checker.current_result) {
|
||||||
|
// ponytail: exact channel propagation; add fallible-error widening when cross-error-set try matters.
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'try' can only propagate the enclosing function's exact error channel in v1")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Try,
|
||||||
|
span=expr.span,
|
||||||
|
type=success,
|
||||||
|
left=channel,
|
||||||
|
target=hir.INVALID_REF,
|
||||||
|
right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .Catch:
|
||||||
|
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
channel_type := checker.module.exprs[channel].type
|
||||||
|
success := types.fallible_success(channel_type, store)
|
||||||
|
if !types.is_valid(success) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'catch' requires a fallible expression")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
if expr.right == ast.INVALID_EXPR {
|
||||||
|
// ponytail: catch blocks need Build_Ctx threading through expression build; fallback catch covers v1.
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "catch block form is not implemented in v1")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
|
}
|
||||||
|
fallback := build_nested_expr(checker, expr.right, locals, global_reads, calls, success, pkg, file)
|
||||||
|
fallback = coerce_expr(checker, fallback, success, checker.module.exprs[fallback].span)
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Catch,
|
||||||
|
span=expr.span,
|
||||||
|
type=success,
|
||||||
|
left=channel,
|
||||||
|
right=fallback,
|
||||||
|
target=hir.INVALID_REF,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Range:
|
case .Range:
|
||||||
expected_child := types.INVALID
|
expected_child := types.INVALID
|
||||||
if types.is_range(expected, store) {
|
if types.is_range(expected, store) {
|
||||||
@@ -3611,7 +3775,7 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
switch expr.kind {
|
switch expr.kind {
|
||||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
||||||
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
||||||
.Enum_Literal:
|
.Enum_Literal:
|
||||||
last = build_compound_expr(
|
last = build_compound_expr(
|
||||||
@@ -4169,6 +4333,7 @@ build_block :: proc(
|
|||||||
close := true,
|
close := true,
|
||||||
) -> []hir.Stmt_Id {
|
) -> []hir.Stmt_Id {
|
||||||
checker := ctx.checker
|
checker := ctx.checker
|
||||||
|
store := &checker.module.types
|
||||||
body: [dynamic]hir.Stmt_Id
|
body: [dynamic]hir.Stmt_Id
|
||||||
body.allocator = checker.allocator
|
body.allocator = checker.allocator
|
||||||
scope_start := len(ctx.locals^)
|
scope_start := len(ctx.locals^)
|
||||||
@@ -4488,7 +4653,16 @@ build_block :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
if !types.is_void(ctx.result) {
|
if types.kind(ctx.result, store) == .Fallible &&
|
||||||
|
types.is_void(types.fallible_success(ctx.result, store)) {
|
||||||
|
flush_defers(ctx, &body, 0)
|
||||||
|
value := fallible_aggregate(checker, statement.span, ctx.result, hir.INVALID_EXPR, false)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Return, span = statement.span, expr = value,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else if !types.is_void(ctx.result) {
|
||||||
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
|
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
@@ -4516,11 +4690,57 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value := build_expr(
|
value := hir.INVALID_EXPR
|
||||||
|
if types.kind(ctx.result, store) == .Fallible {
|
||||||
|
success := types.fallible_success(ctx.result, store)
|
||||||
|
error_type := types.fallible_error(ctx.result, store)
|
||||||
|
error_path := false
|
||||||
|
expr_ast := checker.ast_module.exprs[statement.expr]
|
||||||
|
if expr_ast.kind == .Enum_Literal {
|
||||||
|
success_has := types.sum_has_name(store, success, u32(expr_ast.name))
|
||||||
|
error_has := types.sum_has_name(store, error_type, u32(expr_ast.name))
|
||||||
|
if error_has && !success_has {
|
||||||
|
error_path = true
|
||||||
|
} else if error_has && success_has {
|
||||||
|
id := source.add(checker.diagnostics, expr_ast.span, "ambiguous fallible return member")
|
||||||
|
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
|
||||||
|
}
|
||||||
|
} else if expr_ast.kind == .Struct_Literal {
|
||||||
|
target_pkg, available := expr_package(checker, expr_ast, ctx.pkg, ctx.file, true)
|
||||||
|
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name)) if available else types.INVALID
|
||||||
|
named = types.resolve_alias(named, store)
|
||||||
|
error_path = can_implicitly_convert_type(checker, named, error_type)
|
||||||
|
} else {
|
||||||
|
probe := build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
types.INVALID, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
probe_type := checker.module.exprs[probe].type
|
||||||
|
if can_implicitly_convert_type(checker, probe_type, error_type) &&
|
||||||
|
!can_implicitly_convert_type(checker, probe_type, success) {
|
||||||
|
error_path = true
|
||||||
|
value = probe
|
||||||
|
} else if can_implicitly_convert_type(checker, probe_type, success) {
|
||||||
|
value = probe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if value == hir.INVALID_EXPR {
|
||||||
|
expected := error_type if error_path else success
|
||||||
|
value = build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
expected, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
expected := error_type if error_path else success
|
||||||
|
value = coerce_expr(checker, value, expected, statement.span)
|
||||||
|
value = fallible_aggregate(checker, statement.span, ctx.result, value, error_path)
|
||||||
|
} else {
|
||||||
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
ctx.result, ctx.pkg, ctx.file,
|
ctx.result, ctx.pkg, ctx.file,
|
||||||
)
|
)
|
||||||
value = coerce_expr(checker, value, ctx.result, statement.span)
|
value = coerce_expr(checker, value, ctx.result, statement.span)
|
||||||
|
}
|
||||||
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||||
// Run deferred statements before returning, but capture the return value
|
// Run deferred statements before returning, but capture the return value
|
||||||
// first (spill it to a temp) so a defer that mutates the returned local
|
// first (spill it to a temp) so a defer that mutates the returned local
|
||||||
@@ -6506,7 +6726,10 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
loop_is_loop = &loop_is_loop,
|
loop_is_loop = &loop_is_loop,
|
||||||
yield_targets = &yield_targets,
|
yield_targets = &yield_targets,
|
||||||
}
|
}
|
||||||
|
previous_result := checker.current_result
|
||||||
|
checker.current_result = spec.result
|
||||||
block := build_block(&ctx, function.body)
|
block := build_block(&ctx, function.body)
|
||||||
|
checker.current_result = previous_result
|
||||||
returns := all_paths_return(&checker.module, block)
|
returns := all_paths_return(&checker.module, block)
|
||||||
for block_stmt in block {
|
for block_stmt in block {
|
||||||
append(&body, block_stmt)
|
append(&body, block_stmt)
|
||||||
|
|||||||
@@ -95,7 +95,10 @@ Expr_Kind :: enum u8 {
|
|||||||
Slice_Ptr,
|
Slice_Ptr,
|
||||||
Unwrap,
|
Unwrap,
|
||||||
Orelse,
|
Orelse,
|
||||||
|
Try,
|
||||||
|
Catch,
|
||||||
Widen,
|
Widen,
|
||||||
|
Sum_Widen,
|
||||||
C_Coerce,
|
C_Coerce,
|
||||||
C_Vararg_Promote,
|
C_Vararg_Promote,
|
||||||
Retype,
|
Retype,
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ Opcode :: enum u8 {
|
|||||||
Orelse_Begin,
|
Orelse_Begin,
|
||||||
Orelse,
|
Orelse,
|
||||||
Widen,
|
Widen,
|
||||||
|
Sum_Widen,
|
||||||
C_Coerce,
|
C_Coerce,
|
||||||
C_Vararg_Promote,
|
C_Vararg_Promote,
|
||||||
Retype,
|
Retype,
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "alias": return .Keyword_Alias
|
case "alias": return .Keyword_Alias
|
||||||
case "import": return .Keyword_Import
|
case "import": return .Keyword_Import
|
||||||
case "return": return .Keyword_Return
|
case "return": return .Keyword_Return
|
||||||
|
case "try": return .Keyword_Try
|
||||||
|
case "catch": return .Keyword_Catch
|
||||||
case "mut": return .Keyword_Mut
|
case "mut": return .Keyword_Mut
|
||||||
case "none": return .Keyword_None
|
case "none": return .Keyword_None
|
||||||
case "undefined": return .Keyword_Undefined
|
case "undefined": return .Keyword_Undefined
|
||||||
|
|||||||
+117
-4
@@ -163,7 +163,7 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
|
|||||||
return "ptr"
|
return "ptr"
|
||||||
}
|
}
|
||||||
return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store))
|
return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store))
|
||||||
case .Struct, .Union:
|
case .Struct, .Union, .Fallible:
|
||||||
return fmt.tprintf("%%bro.type.%d", resolved)
|
return fmt.tprintf("%%bro.type.%d", resolved)
|
||||||
}
|
}
|
||||||
selected := store.selected if store != nil else target.DEFAULT
|
selected := store.selected if store != nil else target.DEFAULT
|
||||||
@@ -256,7 +256,7 @@ valid_value :: proc(
|
|||||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
||||||
.Extract, .Select, .Unwrap,
|
.Extract, .Select, .Unwrap,
|
||||||
.Optional_Is_Some, .Optional_Value, .Orelse,
|
.Optional_Is_Some, .Optional_Value, .Orelse,
|
||||||
.Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||||
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
||||||
return true
|
return true
|
||||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||||
@@ -690,6 +690,8 @@ emit_instruction_stream :: proc(
|
|||||||
expected_count = int(item.field_count)
|
expected_count = int(item.field_count)
|
||||||
} else if ok && item.kind == .Union {
|
} else if ok && item.kind == .Union {
|
||||||
expected_count = 1
|
expected_count = 1
|
||||||
|
} else if ok && item.kind == .Fallible {
|
||||||
|
expected_count = 1
|
||||||
} else if ok && item.kind == .Range {
|
} else if ok && item.kind == .Range {
|
||||||
expected_count = 3
|
expected_count = 3
|
||||||
} else {
|
} else {
|
||||||
@@ -701,6 +703,56 @@ emit_instruction_stream :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
if item.kind == .Fallible {
|
||||||
|
success := item.child
|
||||||
|
error_type := item.extra
|
||||||
|
error_path := instruction.integer != 0
|
||||||
|
payload_offset := types.fallible_payload_offset(instruction.type, &emitter.module.types, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_slot%d = alloca %s, align %d\n", instruction_index, type_name, types.alignment_of(instruction.type, &emitter.module.types, emitter.module.target))
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%fallible_slot%d\n", type_name, instruction_index)
|
||||||
|
if !error_path {
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i16 0, ptr %%fallible_slot%d\n", instruction_index)
|
||||||
|
if !types.is_void(success) {
|
||||||
|
if !valid_value(instructions, instruction.args[0], success, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid fallible success operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_payload%d = getelementptr i8, ptr %%fallible_slot%d, i64 %d\n", instruction_index, instruction_index, payload_offset)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(success, &emitter.module.types))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.args[0], success, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr %%fallible_payload%d\n", instruction_index)
|
||||||
|
}
|
||||||
|
} else if types.is_enum(error_type, &emitter.module.types) {
|
||||||
|
if !valid_value(instructions, instruction.args[0], error_type, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid fallible enum error operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i16 ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.args[0], error_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr %%fallible_slot%d\n", instruction_index)
|
||||||
|
} else if types.is_tagged_union(error_type, &emitter.module.types) {
|
||||||
|
if !valid_value(instructions, instruction.args[0], error_type, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid fallible union error operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
error_slot_align := types.alignment_of(error_type, &emitter.module.types, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_error_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(error_type, &emitter.module.types), error_slot_align)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(error_type, &emitter.module.types))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.args[0], error_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr %%fallible_error_slot%d\n", instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_error_code%d = load i16, ptr %%fallible_error_slot%d\n", instruction_index, instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i16 %%fallible_error_code%d, ptr %%fallible_slot%d\n", instruction_index, instruction_index)
|
||||||
|
error_payload_offset := types.union_payload_offset(error_type, &emitter.module.types, emitter.module.target)
|
||||||
|
error_payload_size := types.sum_payload_size(error_type, &emitter.module.types, emitter.module.target)
|
||||||
|
if error_payload_size > 0 {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_error_payload%d = getelementptr i8, ptr %%fallible_error_slot%d, i64 %d\n", instruction_index, instruction_index, error_payload_offset)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%fallible_payload%d = getelementptr i8, ptr %%fallible_slot%d, i64 %d\n", instruction_index, instruction_index, payload_offset)
|
||||||
|
fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%fallible_payload%d, ptr %%fallible_error_payload%d, i64 %d, i1 false)\n", instruction_index, instruction_index, error_payload_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%fallible_slot%d\n", instruction_index, type_name, instruction_index)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if item.kind == .Union {
|
if item.kind == .Union {
|
||||||
fields := types.fields_for(&emitter.module.types, instruction.type)
|
fields := types.fields_for(&emitter.module.types, instruction.type)
|
||||||
field_index := int(instruction.integer)
|
field_index := int(instruction.integer)
|
||||||
@@ -958,6 +1010,10 @@ emit_instruction_stream :: proc(
|
|||||||
if types.is_pointer(base_type, &emitter.module.types) {
|
if types.is_pointer(base_type, &emitter.module.types) {
|
||||||
base_type = types.child_type(base_type, &emitter.module.types)
|
base_type = types.child_type(base_type, &emitter.module.types)
|
||||||
}
|
}
|
||||||
|
if types.kind(base_type, &emitter.module.types) == .Fallible {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 %d\n", instruction_index, instruction.a, types.fallible_payload_offset(base_type, &emitter.module.types, emitter.module.target))
|
||||||
|
continue
|
||||||
|
}
|
||||||
fields := types.fields_for(&emitter.module.types, base_type)
|
fields := types.fields_for(&emitter.module.types, base_type)
|
||||||
field_index := int(instruction.integer)
|
field_index := int(instruction.integer)
|
||||||
if field_index < 0 || field_index >= len(fields) ||
|
if field_index < 0 || field_index >= len(fields) ||
|
||||||
@@ -1254,6 +1310,45 @@ emit_instruction_stream :: proc(
|
|||||||
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
|
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
|
||||||
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
||||||
|
case .Sum_Widen:
|
||||||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
|
!types.can_sum_widen(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid sum widening operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
from_type := instructions[instruction.a].type
|
||||||
|
if types.is_enum(from_type, &emitter.module.types) && types.is_enum(instruction.type, &emitter.module.types) {
|
||||||
|
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if types.is_tagged_union(from_type, &emitter.module.types) && types.is_tagged_union(instruction.type, &emitter.module.types) {
|
||||||
|
from_name := llvm_type(from_type, &emitter.module.types)
|
||||||
|
to_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
from_align := types.alignment_of(from_type, &emitter.module.types, emitter.module.target)
|
||||||
|
to_align := types.alignment_of(instruction.type, &emitter.module.types, emitter.module.target)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%sum_from_slot%d = alloca %s, align %d\n", instruction_index, from_name, from_align)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s ", from_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", ptr %%sum_from_slot%d\n", instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%sum_to_slot%d = alloca %s, align %d\n", instruction_index, to_name, to_align)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%sum_to_slot%d\n", to_name, instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%sum_tag%d = load i16, ptr %%sum_from_slot%d\n", instruction_index, instruction_index)
|
||||||
|
fmt.sbprintf(&emitter.builder, " store i16 %%sum_tag%d, ptr %%sum_to_slot%d\n", instruction_index, instruction_index)
|
||||||
|
from_payload_offset := types.union_payload_offset(from_type, &emitter.module.types, emitter.module.target)
|
||||||
|
to_payload_offset := types.union_payload_offset(instruction.type, &emitter.module.types, emitter.module.target)
|
||||||
|
payload_size := types.sum_payload_size(from_type, &emitter.module.types, emitter.module.target)
|
||||||
|
if payload_size > 0 {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%sum_from_payload%d = getelementptr i8, ptr %%sum_from_slot%d, i64 %d\n", instruction_index, instruction_index, from_payload_offset)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%sum_to_payload%d = getelementptr i8, ptr %%sum_to_slot%d, i64 %d\n", instruction_index, instruction_index, to_payload_offset)
|
||||||
|
fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%sum_to_payload%d, ptr %%sum_from_payload%d, i64 %d, i1 false)\n", instruction_index, instruction_index, payload_size)
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%sum_to_slot%d\n", instruction_index, to_name, instruction_index)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "unsupported sum widening operand")
|
||||||
case .C_Coerce:
|
case .C_Coerce:
|
||||||
if !valid_instruction(instructions, instruction.a) ||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
!types.can_coerce_c_integer(instructions[instruction.a].type, instruction.type) {
|
!types.can_coerce_c_integer(instructions[instruction.a].type, instruction.type) {
|
||||||
@@ -1814,7 +1909,7 @@ emit_globals :: proc(emitter: ^Emitter) {
|
|||||||
|
|
||||||
emit_types :: proc(emitter: ^Emitter) {
|
emit_types :: proc(emitter: ^Emitter) {
|
||||||
for item, index in emitter.module.types.nodes {
|
for item, index in emitter.module.types.nodes {
|
||||||
if item.kind != .Struct && item.kind != .Union {
|
if item.kind != .Struct && item.kind != .Union && item.kind != .Fallible {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
id := types.DYNAMIC_START+types.Type(index)
|
id := types.DYNAMIC_START+types.Type(index)
|
||||||
@@ -1823,6 +1918,24 @@ emit_types :: proc(emitter: ^Emitter) {
|
|||||||
strings.write_string(&emitter.builder, "opaque\n")
|
strings.write_string(&emitter.builder, "opaque\n")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if item.kind == .Fallible {
|
||||||
|
payload_offset := types.fallible_payload_offset(id, &emitter.module.types, emitter.module.target)
|
||||||
|
payload_size := types.fallible_payload_size(id, &emitter.module.types, emitter.module.target)
|
||||||
|
total_size := types.size(id, &emitter.module.types, emitter.module.target)
|
||||||
|
strings.write_string(&emitter.builder, "{ i16")
|
||||||
|
if payload_offset > 2 {
|
||||||
|
fmt.sbprintf(&emitter.builder, ", [%d x i8]", payload_offset-2)
|
||||||
|
}
|
||||||
|
if payload_size > 0 {
|
||||||
|
fmt.sbprintf(&emitter.builder, ", [%d x i8]", payload_size)
|
||||||
|
}
|
||||||
|
used := payload_offset + payload_size
|
||||||
|
if used < total_size {
|
||||||
|
fmt.sbprintf(&emitter.builder, ", [%d x i8]", total_size-used)
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, " }\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
if item.kind == .Union {
|
if item.kind == .Union {
|
||||||
fields := types.fields_for(&emitter.module.types, id)
|
fields := types.fields_for(&emitter.module.types, id)
|
||||||
carrier := types.INVALID
|
carrier := types.INVALID
|
||||||
@@ -1839,7 +1952,7 @@ emit_types :: proc(emitter: ^Emitter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
total_size := types.size(id, &emitter.module.types, emitter.module.target)
|
total_size := types.size(id, &emitter.module.types, emitter.module.target)
|
||||||
if !types.is_valid(carrier) {
|
if !types.is_valid(carrier) || types.is_void(carrier) {
|
||||||
fmt.sbprintf(&emitter.builder, "[%d x i8]\n", total_size)
|
fmt.sbprintf(&emitter.builder, "[%d x i8]\n", total_size)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ translate_c_type :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type, variadic: bool) -> bool {
|
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type, variadic: bool) -> bool {
|
||||||
if left.result != result || left.variadic != variadic || len(left.params) != len(params) {
|
if left.result != result || types.is_valid(left.error) || left.variadic != variadic || len(left.params) != len(params) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for param, index in params {
|
for param, index in params {
|
||||||
@@ -1333,12 +1333,20 @@ canonical_type :: proc(
|
|||||||
}
|
}
|
||||||
if item.kind == .Struct || item.kind == .Union {
|
if item.kind == .Struct || item.kind == .Union {
|
||||||
mapping[index] = value
|
mapping[index] = value
|
||||||
|
module.type_store.nodes[index].extra = canonical_type(module, item.extra, mapping, visiting)
|
||||||
fields := types.fields_for(&module.type_store, value)
|
fields := types.fields_for(&module.type_store, value)
|
||||||
for &field in fields {
|
for &field in fields {
|
||||||
field.type = canonical_type(module, field.type, mapping, visiting)
|
field.type = canonical_type(module, field.type, mapping, visiting)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
if item.kind == .Fallible {
|
||||||
|
item.child = canonical_type(module, item.child, mapping, visiting)
|
||||||
|
item.extra = canonical_type(module, item.extra, mapping, visiting)
|
||||||
|
resolved := types.fallible(&module.type_store, item.child, item.extra)
|
||||||
|
mapping[index] = resolved
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
if item.kind == .Function {
|
if item.kind == .Function {
|
||||||
params := make([]types.Type, int(item.field_count), context.temp_allocator)
|
params := make([]types.Type, int(item.field_count), context.temp_allocator)
|
||||||
for param, param_index in types.params_for(&module.type_store, value) {
|
for param, param_index in types.params_for(&module.type_store, value) {
|
||||||
@@ -1352,6 +1360,9 @@ canonical_type :: proc(
|
|||||||
if types.is_valid(item.child) {
|
if types.is_valid(item.child) {
|
||||||
item.child = canonical_type(module, item.child, mapping, visiting)
|
item.child = canonical_type(module, item.child, mapping, visiting)
|
||||||
}
|
}
|
||||||
|
if types.is_valid(item.extra) {
|
||||||
|
item.extra = canonical_type(module, item.extra, mapping, visiting)
|
||||||
|
}
|
||||||
resolved := types.intern(&module.type_store, item)
|
resolved := types.intern(&module.type_store, item)
|
||||||
mapping[index] = resolved
|
mapping[index] = resolved
|
||||||
return resolved
|
return resolved
|
||||||
@@ -1368,6 +1379,7 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
|||||||
param.type = canonical_type(module, param.type, mapping, visiting)
|
param.type = canonical_type(module, param.type, mapping, visiting)
|
||||||
}
|
}
|
||||||
function.result = canonical_type(module, function.result, mapping, visiting)
|
function.result = canonical_type(module, function.result, mapping, visiting)
|
||||||
|
function.error = canonical_type(module, function.error, mapping, visiting)
|
||||||
}
|
}
|
||||||
for &global in module.globals {
|
for &global in module.globals {
|
||||||
global.type = canonical_type(module, global.type, mapping, visiting)
|
global.type = canonical_type(module, global.type, mapping, visiting)
|
||||||
@@ -1378,6 +1390,9 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
|||||||
for index := 0; index < original_count; index += 1 {
|
for index := 0; index < original_count; index += 1 {
|
||||||
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
|
_ = canonical_type(module, types.DYNAMIC_START+types.Type(index), mapping, visiting)
|
||||||
}
|
}
|
||||||
|
for &variant in module.type_store.variants {
|
||||||
|
variant.payload = canonical_type(module, variant.payload, mapping, visiting)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load :: proc(
|
load :: proc(
|
||||||
|
|||||||
+102
-2
@@ -366,6 +366,105 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
target=ir.INVALID_REF, a=begin, b=fallback,
|
target=ir.INVALID_REF, a=begin, b=fallback,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Try, .Catch:
|
||||||
|
channel := lower_nested_expr(state, expr.left)
|
||||||
|
channel_type := state.hir_module.exprs[expr.left].type
|
||||||
|
success := types.fallible_success(channel_type, &state.hir_module.types)
|
||||||
|
channel_slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca, span=expr.span, type=channel_type,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=channel_type,
|
||||||
|
target=ir.INVALID_REF, a=channel_slot, b=channel, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
code := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Union_Tag, span=expr.span, type=types.U16,
|
||||||
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
zero := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Const, span=expr.span, type=types.U16, integer=0,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
ok := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Compare, span=expr.span, type=types.BOOL, integer=i64(ir.Compare_Predicate.Eq),
|
||||||
|
target=ir.INVALID_REF, a=code, b=zero, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
success_lbl := fresh_label(state)
|
||||||
|
error_lbl := fresh_label(state)
|
||||||
|
merge_lbl := fresh_label(state)
|
||||||
|
slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca, span=expr.span, type=success,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Cond_Br, span=expr.span, type=types.VOID,
|
||||||
|
integer=success_lbl, target=ir.Ref(u32(error_lbl)), a=ok,
|
||||||
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=expr.span, type=types.VOID, integer=error_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
if expr.kind == .Try {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Return, span=expr.span, type=state.func_result,
|
||||||
|
target=ir.INVALID_REF, a=channel, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fallback := lower_nested_expr(state, expr.right)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=success,
|
||||||
|
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=expr.span, type=types.VOID, integer=success_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
if !types.is_void(success) {
|
||||||
|
payload := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Field_Address, span=expr.span, type=success, integer=0,
|
||||||
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
loaded := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Load, span=expr.span, type=success,
|
||||||
|
target=ir.INVALID_REF, a=payload, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=success,
|
||||||
|
target=ir.INVALID_REF, a=slot, b=loaded, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=expr.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Load, span=expr.span, type=success,
|
||||||
|
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Not:
|
case .Not:
|
||||||
value := lower_nested_expr(state, expr.left)
|
value := lower_nested_expr(state, expr.left)
|
||||||
return append_instruction(state, ir.Instruction{
|
return append_instruction(state, ir.Instruction{
|
||||||
@@ -475,7 +574,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .String, .Array, .Struct, .Range, .None, .Optional_Some, .Address, .Deref,
|
case .String, .Array, .Struct, .Range, .None, .Optional_Some, .Address, .Deref,
|
||||||
.Index, .Slice, .Field, .Union_Tag, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
.Index, .Slice, .Field, .Union_Tag, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
||||||
.Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
.Try, .Catch, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
last = lower_compound_expr(state, frame.expr)
|
last = lower_compound_expr(state, frame.expr)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Local:
|
case .Local:
|
||||||
@@ -518,7 +617,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
|
case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
|
||||||
stack[frame_index].stage = 1
|
stack[frame_index].stage = 1
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Negate:
|
case .Negate:
|
||||||
@@ -572,6 +671,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
if frame.stage == 1 {
|
if frame.stage == 1 {
|
||||||
op := ir.Opcode.Widen
|
op := ir.Opcode.Widen
|
||||||
#partial switch expr.kind {
|
#partial switch expr.kind {
|
||||||
|
case .Sum_Widen: op = .Sum_Widen
|
||||||
case .Weaken_Pointer: op = .Weaken_Pointer
|
case .Weaken_Pointer: op = .Weaken_Pointer
|
||||||
case .Weaken_Slice: op = .Weaken_Slice
|
case .Weaken_Slice: op = .Weaken_Slice
|
||||||
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
|
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
|
||||||
|
|||||||
+84
-30
@@ -165,7 +165,7 @@ parse_type_constant :: proc(parser: ^Parser) -> (u64, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||||
tok := current(parser)
|
tok := current(parser)
|
||||||
if tok.kind == .Question {
|
if tok.kind == .Question {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
@@ -366,6 +366,25 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||||
|
left := parse_type_atom(parser)
|
||||||
|
for current(parser).kind == .Pipe {
|
||||||
|
operator := advance(parser)
|
||||||
|
right := parse_type_atom(parser)
|
||||||
|
composed, compose_error := types.compose_sum(&parser.module.type_store, left, right)
|
||||||
|
if compose_error == .Unsupported {
|
||||||
|
source.add(parser.diagnostics, operator.span, "only native unbacked enums and tagged unions can be composed with '|'")
|
||||||
|
left = types.INVALID
|
||||||
|
} else if compose_error == .Conflict {
|
||||||
|
source.add(parser.diagnostics, operator.span, "sum composition contains the same variant name with different payload types")
|
||||||
|
left = types.INVALID
|
||||||
|
} else {
|
||||||
|
left = composed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
|
skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
|
||||||
start := current(parser)
|
start := current(parser)
|
||||||
depth := 0
|
depth := 0
|
||||||
@@ -706,7 +725,7 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
|||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Range, .Range_Inclusive:
|
case .Range, .Range_Inclusive:
|
||||||
return 0, 1, true
|
return 0, 1, true
|
||||||
case .Keyword_Orelse:
|
case .Keyword_Orelse, .Keyword_Catch:
|
||||||
return 2, 3, true
|
return 2, 3, true
|
||||||
case .Keyword_Or:
|
case .Keyword_Or:
|
||||||
return 4, 5, true
|
return 4, 5, true
|
||||||
@@ -726,6 +745,7 @@ infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
|
|||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Range, .Range_Inclusive: return .Range
|
case .Range, .Range_Inclusive: return .Range
|
||||||
case .Keyword_Orelse: return .Orelse
|
case .Keyword_Orelse: return .Orelse
|
||||||
|
case .Keyword_Catch: return .Catch
|
||||||
case .Keyword_Or: return .Or
|
case .Keyword_Or: return .Or
|
||||||
case .Keyword_And: return .And
|
case .Keyword_And: return .And
|
||||||
case .Equal_Equal: return .Eq
|
case .Equal_Equal: return .Eq
|
||||||
@@ -766,7 +786,7 @@ is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
|
|||||||
|
|
||||||
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
|
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
|
||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Minus, .Ampersand, .Bang:
|
case .Minus, .Ampersand, .Bang, .Keyword_Try:
|
||||||
return 20, true
|
return 20, true
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
@@ -792,6 +812,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
#partial switch operator.kind {
|
#partial switch operator.kind {
|
||||||
case .Ampersand: prefix_kind = .Address
|
case .Ampersand: prefix_kind = .Address
|
||||||
case .Bang: prefix_kind = .Not
|
case .Bang: prefix_kind = .Not
|
||||||
|
case .Keyword_Try: prefix_kind = .Try
|
||||||
}
|
}
|
||||||
left = add_expr(parser, ast.Expr{
|
left = add_expr(parser, ast.Expr{
|
||||||
kind=prefix_kind,
|
kind=prefix_kind,
|
||||||
@@ -921,6 +942,42 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
}
|
}
|
||||||
operator := advance(parser)
|
operator := advance(parser)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
if operator.kind == .Keyword_Catch {
|
||||||
|
left_expr := parser.module.exprs[left]
|
||||||
|
if _, pipe_ok := allow(parser, .Pipe); pipe_ok {
|
||||||
|
capture := current(parser)
|
||||||
|
if capture.kind != .Identifier && capture.kind != .Underscore {
|
||||||
|
source.add(parser.diagnostics, capture.span, "expected a catch capture name")
|
||||||
|
} else {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
if _, close_ok := allow(parser, .Pipe); !close_ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '|' after catch capture")
|
||||||
|
}
|
||||||
|
body := parse_block(parser)
|
||||||
|
end := previous(parser)
|
||||||
|
left = add_expr(parser, ast.Expr{
|
||||||
|
kind=.Catch,
|
||||||
|
span=span_from(left_expr.span, end.span),
|
||||||
|
name=capture.symbol,
|
||||||
|
left=left,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
body=body,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
right := parse_expression_bp(parser, right_power, nesting+1)
|
||||||
|
right_expr := parser.module.exprs[right]
|
||||||
|
left = add_expr(parser, ast.Expr{
|
||||||
|
kind=.Catch,
|
||||||
|
span=span_from(left_expr.span, right_expr.span),
|
||||||
|
left=left,
|
||||||
|
right=right,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
right := parse_expression_bp(parser, right_power, nesting+1)
|
right := parse_expression_bp(parser, right_power, nesting+1)
|
||||||
left_expr := parser.module.exprs[left]
|
left_expr := parser.module.exprs[left]
|
||||||
right_expr := parser.module.exprs[right]
|
right_expr := parser.module.exprs[right]
|
||||||
@@ -1812,6 +1869,10 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
}
|
}
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
result := parse_type(parser)
|
result := parse_type(parser)
|
||||||
|
error_type := types.INVALID
|
||||||
|
if _, ok := allow(parser, .Bang); ok {
|
||||||
|
error_type = parse_type(parser)
|
||||||
|
}
|
||||||
end := previous(parser)
|
end := previous(parser)
|
||||||
ended_by_newline := current(parser).kind == .Newline
|
ended_by_newline := current(parser).kind == .Newline
|
||||||
if current(parser).kind == .Newline {
|
if current(parser).kind == .Newline {
|
||||||
@@ -1832,6 +1893,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
variadic=variadic,
|
variadic=variadic,
|
||||||
params=params,
|
params=params,
|
||||||
result=result,
|
result=result,
|
||||||
|
error=error_type,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -1849,6 +1911,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
variadic=variadic,
|
variadic=variadic,
|
||||||
params=params,
|
params=params,
|
||||||
result=result,
|
result=result,
|
||||||
|
error=error_type,
|
||||||
body=body,
|
body=body,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
@@ -1860,13 +1923,14 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
|
|||||||
// A tagged union spells its discriminant in parens: `union(Enum)` reuses an existing
|
// A tagged union spells its discriminant in parens: `union(Enum)` reuses an existing
|
||||||
// enum; `union(enum)` synthesizes one from the variant names after the body is parsed.
|
// enum; `union(enum)` synthesizes one from the variant names after the body is parsed.
|
||||||
tag := types.INVALID
|
tag := types.INVALID
|
||||||
|
declared_tag := types.INVALID
|
||||||
inferred_tag := false
|
inferred_tag := false
|
||||||
if is_union {
|
if is_union {
|
||||||
if _, ok := allow(parser, .Left_Paren); ok {
|
if _, ok := allow(parser, .Left_Paren); ok {
|
||||||
if _, enum_ok := allow(parser, .Keyword_Enum); enum_ok {
|
if _, enum_ok := allow(parser, .Keyword_Enum); enum_ok {
|
||||||
inferred_tag = true
|
inferred_tag = true
|
||||||
} else {
|
} else {
|
||||||
tag = parse_type(parser)
|
declared_tag = parse_type(parser)
|
||||||
}
|
}
|
||||||
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
|
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected ')' after union tag")
|
source.add(parser.diagnostics, current(parser).span, "expected ')' after union tag")
|
||||||
@@ -1882,7 +1946,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
|
|||||||
"native union declarations require a body" if is_union else "native struct declarations require a body",
|
"native union declarations require a body" if is_union else "native struct declarations require a body",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !types.define_record(&parser.module.type_store, id, nil, c_layout, true, is_union, tag=tag) {
|
if !types.define_record(&parser.module.type_store, id, nil, c_layout, true, is_union, tag=tag, declared_tag=declared_tag) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
if !ended_by_newline {
|
if !ended_by_newline {
|
||||||
@@ -1918,36 +1982,29 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
|
|||||||
if _, ok := allow(parser, .Right_Brace); !ok {
|
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
|
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
|
||||||
}
|
}
|
||||||
if inferred_tag {
|
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
|
||||||
tag = synthesize_union_tag(parser, fields[:])
|
tag = synthesize_union_tag(parser, fields[:])
|
||||||
}
|
}
|
||||||
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag) {
|
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag) {
|
||||||
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
|
||||||
}
|
}
|
||||||
_ = finish_statement(parser)
|
_ = finish_statement(parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// synthesize_union_tag builds the anonymous discriminant enum for a `union(enum)`:
|
// synthesize_union_tag builds the anonymous runtime discriminant enum for a tagged
|
||||||
// one member per variant, dense 0-based, in the smallest fitting unsigned backing
|
// union: one member per variant, valued by the program-global (name, payload-type)
|
||||||
// (mirroring `parse_enum`'s unbacked pick).
|
// ID. The declared tag enum, if any, remains only the validation surface.
|
||||||
synthesize_union_tag :: proc(parser: ^Parser, fields: []types.Field) -> types.Type {
|
synthesize_union_tag :: proc(parser: ^Parser, fields: []types.Field) -> types.Type {
|
||||||
members := make([]types.Enum_Member, len(fields), parser.module.allocator)
|
members := make([]types.Enum_Member, len(fields), parser.module.allocator)
|
||||||
defer delete(members, parser.module.allocator)
|
defer delete(members, parser.module.allocator)
|
||||||
for field, index in fields {
|
for field, index in fields {
|
||||||
members[index] = types.Enum_Member{name=field.name, value=i128(index)}
|
id, ok := types.variant_id(&parser.module.type_store, field.name, field.type)
|
||||||
|
if !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "too many global sum variants for u16 tags")
|
||||||
}
|
}
|
||||||
max_value := u64(max(len(fields)-1, 0))
|
members[index] = types.Enum_Member{name=field.name, value=i128(id)}
|
||||||
backing := types.U8
|
|
||||||
if max_value > 0xff {
|
|
||||||
backing = types.U16
|
|
||||||
}
|
}
|
||||||
if max_value > 0xffff {
|
return types.enum_anonymous(&parser.module.type_store, members, types.U16)
|
||||||
backing = types.U32
|
|
||||||
}
|
|
||||||
if max_value > 0xffff_ffff {
|
|
||||||
backing = types.U64
|
|
||||||
}
|
|
||||||
return types.enum_anonymous(&parser.module.type_store, members, backing)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_distinct :: proc(parser: ^Parser, name: token.Token) {
|
parse_distinct :: proc(parser: ^Parser, name: token.Token) {
|
||||||
@@ -2069,16 +2126,13 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
|
|||||||
source.add(parser.diagnostics, start.span, "enum declarations require at least one member")
|
source.add(parser.diagnostics, start.span, "enum declarations require at least one member")
|
||||||
}
|
}
|
||||||
if !explicit_backing {
|
if !explicit_backing {
|
||||||
max_value := u64(max(len(members)-1, 0))
|
|
||||||
backing = types.U8
|
|
||||||
if max_value > 0xff {
|
|
||||||
backing = types.U16
|
backing = types.U16
|
||||||
|
for &member in members {
|
||||||
|
id, ok := types.variant_id(&parser.module.type_store, member.name, types.VOID)
|
||||||
|
if !ok {
|
||||||
|
source.add(parser.diagnostics, start.span, "too many global sum variants for u16 tags")
|
||||||
}
|
}
|
||||||
if max_value > 0xffff {
|
member.value = i128(id)
|
||||||
backing = types.U32
|
|
||||||
}
|
|
||||||
if max_value > 0xffff_ffff {
|
|
||||||
backing = types.U64
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
|
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ Kind :: enum u8 {
|
|||||||
Keyword_Alias,
|
Keyword_Alias,
|
||||||
Keyword_Import,
|
Keyword_Import,
|
||||||
Keyword_Return,
|
Keyword_Return,
|
||||||
|
Keyword_Try,
|
||||||
|
Keyword_Catch,
|
||||||
Keyword_Mut,
|
Keyword_Mut,
|
||||||
Keyword_None,
|
Keyword_None,
|
||||||
Keyword_Undefined,
|
Keyword_Undefined,
|
||||||
|
|||||||
+299
-3
@@ -72,11 +72,13 @@ Kind :: enum u8 {
|
|||||||
Enum,
|
Enum,
|
||||||
Struct,
|
Struct,
|
||||||
Union,
|
Union,
|
||||||
|
Fallible,
|
||||||
}
|
}
|
||||||
|
|
||||||
Node :: struct {
|
Node :: struct {
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
child: Type,
|
child: Type,
|
||||||
|
extra: Type,
|
||||||
count: u64,
|
count: u64,
|
||||||
sentinel: u64,
|
sentinel: u64,
|
||||||
explicit_size: u64,
|
explicit_size: u64,
|
||||||
@@ -110,10 +112,29 @@ Enum_Member :: struct {
|
|||||||
value: i128,
|
value: i128,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Variant :: struct {
|
||||||
|
name: u32,
|
||||||
|
payload: Type,
|
||||||
|
id: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
Sum_Variant :: struct {
|
||||||
|
name: u32,
|
||||||
|
payload: Type,
|
||||||
|
id: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
Compose_Error :: enum u8 {
|
||||||
|
None,
|
||||||
|
Unsupported,
|
||||||
|
Conflict,
|
||||||
|
}
|
||||||
|
|
||||||
Store :: struct {
|
Store :: struct {
|
||||||
nodes: [dynamic]Node,
|
nodes: [dynamic]Node,
|
||||||
fields: [dynamic]Field,
|
fields: [dynamic]Field,
|
||||||
enum_members: [dynamic]Enum_Member,
|
enum_members: [dynamic]Enum_Member,
|
||||||
|
variants: [dynamic]Variant,
|
||||||
selected: target.Target,
|
selected: target.Target,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -123,6 +144,7 @@ init_store :: proc(allocator := context.allocator) -> Store {
|
|||||||
store.nodes.allocator = allocator
|
store.nodes.allocator = allocator
|
||||||
store.fields.allocator = allocator
|
store.fields.allocator = allocator
|
||||||
store.enum_members.allocator = allocator
|
store.enum_members.allocator = allocator
|
||||||
|
store.variants.allocator = allocator
|
||||||
store.selected = target.DEFAULT
|
store.selected = target.DEFAULT
|
||||||
store.allocator = allocator
|
store.allocator = allocator
|
||||||
return store
|
return store
|
||||||
@@ -132,6 +154,7 @@ destroy_store :: proc(store: ^Store) {
|
|||||||
delete(store.nodes)
|
delete(store.nodes)
|
||||||
delete(store.fields)
|
delete(store.fields)
|
||||||
delete(store.enum_members)
|
delete(store.enum_members)
|
||||||
|
delete(store.variants)
|
||||||
}
|
}
|
||||||
|
|
||||||
clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
|
clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
|
||||||
@@ -139,6 +162,7 @@ clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
|
|||||||
append(&store.nodes, ..source.nodes[:])
|
append(&store.nodes, ..source.nodes[:])
|
||||||
append(&store.fields, ..source.fields[:])
|
append(&store.fields, ..source.fields[:])
|
||||||
append(&store.enum_members, ..source.enum_members[:])
|
append(&store.enum_members, ..source.enum_members[:])
|
||||||
|
append(&store.variants, ..source.variants[:])
|
||||||
store.selected = source.selected
|
store.selected = source.selected
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
@@ -230,6 +254,7 @@ define_record :: proc(
|
|||||||
explicit_size: u64 = 0,
|
explicit_size: u64 = 0,
|
||||||
explicit_alignment: u32 = 0,
|
explicit_alignment: u32 = 0,
|
||||||
tag: Type = INVALID,
|
tag: Type = INVALID,
|
||||||
|
declared_tag: Type = INVALID,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
existing, ok := node(store, id)
|
existing, ok := node(store, id)
|
||||||
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
|
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
|
||||||
@@ -247,6 +272,7 @@ define_record :: proc(
|
|||||||
// structs leave it INVALID); the per-variant tag value is derived from the enum
|
// structs leave it INVALID); the per-variant tag value is derived from the enum
|
||||||
// member whose name matches the variant, so no extra storage is needed.
|
// member whose name matches the variant, so no extra storage is needed.
|
||||||
store.nodes[index].child = tag
|
store.nodes[index].child = tag
|
||||||
|
store.nodes[index].extra = declared_tag
|
||||||
store.nodes[index].field_start = u32(len(store.fields))
|
store.nodes[index].field_start = u32(len(store.fields))
|
||||||
store.nodes[index].field_count = u32(len(fields))
|
store.nodes[index].field_count = u32(len(fields))
|
||||||
append(&store.fields, ..fields)
|
append(&store.fields, ..fields)
|
||||||
@@ -268,6 +294,180 @@ enum_anonymous :: proc(store: ^Store, members: []Enum_Member, backing: Type) ->
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
union_anonymous :: proc(store: ^Store, fields: []Field, tag: Type) -> Type {
|
||||||
|
start := u32(len(store.fields))
|
||||||
|
append(&store.fields, ..fields)
|
||||||
|
return intern(store, Node{
|
||||||
|
kind=.Union,
|
||||||
|
child=tag,
|
||||||
|
field_start=start,
|
||||||
|
field_count=u32(len(fields)),
|
||||||
|
declared=true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
variant_id :: proc(store: ^Store, name: u32, payload: Type) -> (u16, bool) {
|
||||||
|
for variant in store.variants {
|
||||||
|
if variant.name == name && variant.payload == payload {
|
||||||
|
return variant.id, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next := len(store.variants)+1
|
||||||
|
if next > 0xffff {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
id := u16(next)
|
||||||
|
append(&store.variants, Variant{name=name, payload=payload, id=id})
|
||||||
|
return id, true
|
||||||
|
}
|
||||||
|
|
||||||
|
fallible :: proc(store: ^Store, success, error: Type) -> Type {
|
||||||
|
return intern(store, Node{kind=.Fallible, child=success, extra=error, declared=true})
|
||||||
|
}
|
||||||
|
|
||||||
|
fallible_success :: proc(value: Type, store: ^Store) -> Type {
|
||||||
|
item, ok := node(store, value)
|
||||||
|
return item.child if ok && item.kind == .Fallible else INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
fallible_error :: proc(value: Type, store: ^Store) -> Type {
|
||||||
|
item, ok := node(store, value)
|
||||||
|
return item.extra if ok && item.kind == .Fallible else INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Variant) -> bool {
|
||||||
|
item, ok := node(store, value)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if item.kind == .Enum {
|
||||||
|
if item.explicit_backing {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for member in enum_members_for(store, value) {
|
||||||
|
if member.value <= 0 || member.value > 0xffff {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
append(out, Sum_Variant{name=member.name, payload=VOID, id=u16(member.value)})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if item.kind != .Union || !is_enum(item.child, store) || item.c_layout {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tag_members := enum_members_for(store, item.child)
|
||||||
|
for field in fields_for(store, value) {
|
||||||
|
found := false
|
||||||
|
for member in tag_members {
|
||||||
|
if member.name == field.name {
|
||||||
|
if member.value <= 0 || member.value > 0xffff {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
append(out, Sum_Variant{name=field.name, payload=field.type, id=u16(member.value)})
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
compose_sum :: proc(store: ^Store, left, right: Type) -> (Type, Compose_Error) {
|
||||||
|
variants: [dynamic]Sum_Variant
|
||||||
|
variants.allocator = store.allocator
|
||||||
|
defer delete(variants)
|
||||||
|
if !append_sum_variants(store, left, &variants) {
|
||||||
|
return INVALID, .Unsupported
|
||||||
|
}
|
||||||
|
right_variants: [dynamic]Sum_Variant
|
||||||
|
right_variants.allocator = store.allocator
|
||||||
|
defer delete(right_variants)
|
||||||
|
if !append_sum_variants(store, right, &right_variants) {
|
||||||
|
return INVALID, .Unsupported
|
||||||
|
}
|
||||||
|
for candidate in right_variants {
|
||||||
|
merged := false
|
||||||
|
for existing in variants {
|
||||||
|
if existing.id == candidate.id {
|
||||||
|
merged = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if existing.name == candidate.name && existing.payload != candidate.payload {
|
||||||
|
return INVALID, .Conflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !merged {
|
||||||
|
append(&variants, candidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
all_void := true
|
||||||
|
for variant in variants {
|
||||||
|
all_void = all_void && variant.payload == VOID
|
||||||
|
}
|
||||||
|
members := make([]Enum_Member, len(variants), store.allocator)
|
||||||
|
defer delete(members, store.allocator)
|
||||||
|
for variant, index in variants {
|
||||||
|
members[index] = Enum_Member{name=variant.name, value=i128(variant.id)}
|
||||||
|
}
|
||||||
|
if all_void {
|
||||||
|
return enum_anonymous(store, members, U16), .None
|
||||||
|
}
|
||||||
|
fields := make([]Field, len(variants), store.allocator)
|
||||||
|
defer delete(fields, store.allocator)
|
||||||
|
for variant, index in variants {
|
||||||
|
fields[index] = Field{name=variant.name, type=variant.payload}
|
||||||
|
}
|
||||||
|
tag := enum_anonymous(store, members, U16)
|
||||||
|
return union_anonymous(store, fields, tag), .None
|
||||||
|
}
|
||||||
|
|
||||||
|
sum_has_name :: proc(store: ^Store, value: Type, name: u32) -> bool {
|
||||||
|
variants: [dynamic]Sum_Variant
|
||||||
|
variants.allocator = store.allocator
|
||||||
|
defer delete(variants)
|
||||||
|
if !append_sum_variants(store, value, &variants) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for variant in variants {
|
||||||
|
if variant.name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
can_sum_widen :: proc(from, to: Type, store: ^Store) -> bool {
|
||||||
|
if from == to {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
from_variants: [dynamic]Sum_Variant
|
||||||
|
from_variants.allocator = store.allocator
|
||||||
|
defer delete(from_variants)
|
||||||
|
to_variants: [dynamic]Sum_Variant
|
||||||
|
to_variants.allocator = store.allocator
|
||||||
|
defer delete(to_variants)
|
||||||
|
if !append_sum_variants(store, from, &from_variants) ||
|
||||||
|
!append_sum_variants(store, to, &to_variants) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for needed in from_variants {
|
||||||
|
found := false
|
||||||
|
for available in to_variants {
|
||||||
|
if available.id == needed.id && available.payload == needed.payload {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
|
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
|
||||||
return define_record(store, id, fields, c_layout, opaque)
|
return define_record(store, id, fields, c_layout, opaque)
|
||||||
}
|
}
|
||||||
@@ -514,7 +714,7 @@ is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
|
|||||||
value_kind := kind(value, store)
|
value_kind := kind(value, store)
|
||||||
if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer ||
|
if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer ||
|
||||||
value_kind == .Slice || value_kind == .Range || value_kind == .Optional ||
|
value_kind == .Slice || value_kind == .Range || value_kind == .Optional ||
|
||||||
value_kind == .Enum {
|
value_kind == .Enum || value_kind == .Fallible {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if value_kind == .Struct || value_kind == .Union {
|
if value_kind == .Struct || value_kind == .Union {
|
||||||
@@ -575,6 +775,14 @@ union_tag_enum :: proc(value: Type, store: ^Store) -> Type {
|
|||||||
return item.child
|
return item.child
|
||||||
}
|
}
|
||||||
|
|
||||||
|
union_declared_tag_enum :: proc(value: Type, store: ^Store) -> Type {
|
||||||
|
if !is_tagged_union(value, store) {
|
||||||
|
return INVALID
|
||||||
|
}
|
||||||
|
item, _ := node(store, value)
|
||||||
|
return item.extra if is_valid(item.extra) else item.child
|
||||||
|
}
|
||||||
|
|
||||||
// union_payload_offset is the byte offset of a tagged union's payload carrier (after
|
// union_payload_offset is the byte offset of a tagged union's payload carrier (after
|
||||||
// the discriminant), shared by `size` and the LLVM emitter so construction, field
|
// the discriminant), shared by `size` and the LLVM emitter so construction, field
|
||||||
// access, and layout agree. Zero for untagged unions.
|
// access, and layout agree. Zero for untagged unions.
|
||||||
@@ -591,6 +799,16 @@ union_payload_offset :: proc(value: Type, store: ^Store, selected := target.DEFA
|
|||||||
return (tag_size+payload_align-1)/payload_align*payload_align
|
return (tag_size+payload_align-1)/payload_align*payload_align
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sum_tag_type :: proc(value: Type, store: ^Store) -> Type {
|
||||||
|
if is_enum(value, store) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if is_tagged_union(value, store) {
|
||||||
|
return union_tag_enum(value, store)
|
||||||
|
}
|
||||||
|
return INVALID
|
||||||
|
}
|
||||||
|
|
||||||
is_distinct :: proc(value: Type, store: ^Store) -> bool {
|
is_distinct :: proc(value: Type, store: ^Store) -> bool {
|
||||||
return kind(value, store) == .Distinct
|
return kind(value, store) == .Distinct
|
||||||
}
|
}
|
||||||
@@ -666,6 +884,13 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
|||||||
item, ok := node(store, value)
|
item, ok := node(store, value)
|
||||||
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0)
|
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0)
|
||||||
}
|
}
|
||||||
|
if value_kind == .Fallible {
|
||||||
|
item, ok := node(store, value)
|
||||||
|
if !ok || !is_runtime_value(item.extra, store, depth+1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return is_void(item.child) || is_runtime_value(item.child, store, depth+1)
|
||||||
|
}
|
||||||
if value_kind == .Distinct || value_kind == .Enum {
|
if value_kind == .Distinct || value_kind == .Enum {
|
||||||
item, ok := node(store, value)
|
item, ok := node(store, value)
|
||||||
return ok && item.declared && is_runtime_value(item.child, store, depth+1)
|
return ok && item.declared && is_runtime_value(item.child, store, depth+1)
|
||||||
@@ -689,6 +914,59 @@ runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type {
|
|||||||
return runtime_representation(item.child, store, depth+1)
|
return runtime_representation(item.child, store, depth+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sum_payload_size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||||
|
if is_enum(value, store) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if !is_tagged_union(value, store) {
|
||||||
|
return size(value, store, selected)
|
||||||
|
}
|
||||||
|
result: u64
|
||||||
|
for field in fields_for(store, value) {
|
||||||
|
result = max(result, size(field.type, store, selected))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
sum_payload_alignment :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||||
|
if is_enum(value, store) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if !is_tagged_union(value, store) {
|
||||||
|
return u64(alignment_of(value, store, selected))
|
||||||
|
}
|
||||||
|
result: u64 = 1
|
||||||
|
for field in fields_for(store, value) {
|
||||||
|
result = max(result, u64(alignment_of(field.type, store, selected)))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fallible_payload_offset :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||||
|
item, ok := node(store, value)
|
||||||
|
if !ok || item.kind != .Fallible {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
payload_align := max(
|
||||||
|
u64(1),
|
||||||
|
max(
|
||||||
|
u64(alignment_of(item.child, store, selected)) if !is_void(item.child) else u64(1),
|
||||||
|
sum_payload_alignment(item.extra, store, selected),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
tag_size := size(U16, store, selected)
|
||||||
|
return (tag_size+payload_align-1)/payload_align*payload_align
|
||||||
|
}
|
||||||
|
|
||||||
|
fallible_payload_size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
||||||
|
item, ok := node(store, value)
|
||||||
|
if !ok || item.kind != .Fallible {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
success_size := u64(0) if is_void(item.child) else size(item.child, store, selected)
|
||||||
|
return max(success_size, sum_payload_size(item.extra, store, selected))
|
||||||
|
}
|
||||||
|
|
||||||
contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
|
||||||
if depth > 256 {
|
if depth > 256 {
|
||||||
return true
|
return true
|
||||||
@@ -717,6 +995,10 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo
|
|||||||
if item.kind == .Array || item.kind == .Slice || item.kind == .Range || item.kind == .Optional {
|
if item.kind == .Array || item.kind == .Slice || item.kind == .Range || item.kind == .Optional {
|
||||||
return contains_c_struct_by_value(item.child, store, depth+1)
|
return contains_c_struct_by_value(item.child, store, depth+1)
|
||||||
}
|
}
|
||||||
|
if item.kind == .Fallible {
|
||||||
|
return contains_c_struct_by_value(item.child, store, depth+1) ||
|
||||||
|
contains_c_struct_by_value(item.extra, store, depth+1)
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,7 +1056,8 @@ contains_distinct_seen :: proc(value: Type, store: ^Store, seen: ^[dynamic]Type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return is_valid(item.child) && contains_distinct_seen(item.child, store, seen)
|
return (is_valid(item.child) && contains_distinct_seen(item.child, store, seen)) ||
|
||||||
|
(is_valid(item.extra) && contains_distinct_seen(item.extra, store, seen))
|
||||||
}
|
}
|
||||||
|
|
||||||
is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
|
is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
|
||||||
@@ -1110,6 +1393,11 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
|
|||||||
return (payload_offset+carrier_size+total_align-1)/total_align*total_align
|
return (payload_offset+carrier_size+total_align-1)/total_align*total_align
|
||||||
}
|
}
|
||||||
return (carrier_size+max_align-1)/max_align*max_align
|
return (carrier_size+max_align-1)/max_align*max_align
|
||||||
|
case .Fallible:
|
||||||
|
payload_offset := fallible_payload_offset(value, store, selected)
|
||||||
|
payload_size := fallible_payload_size(value, store, selected)
|
||||||
|
total_align := u64(alignment_of(value, store, selected))
|
||||||
|
return (payload_offset+payload_size+total_align-1)/total_align*total_align
|
||||||
case:
|
case:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -1150,6 +1438,14 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) ->
|
|||||||
result = max(result, alignment_of(item.child, store, selected))
|
result = max(result, alignment_of(item.child, store, selected))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
case .Fallible:
|
||||||
|
item, _ := node(store, value)
|
||||||
|
result := alignment_of(U16, store, selected)
|
||||||
|
if !is_void(item.child) {
|
||||||
|
result = max(result, alignment_of(item.child, store, selected))
|
||||||
|
}
|
||||||
|
result = max(result, int(sum_payload_alignment(item.extra, store, selected)))
|
||||||
|
return result
|
||||||
case:
|
case:
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -1271,6 +1567,6 @@ name :: proc(value: Type) -> string {
|
|||||||
case C_DOUBLE: return "c_double"
|
case C_DOUBLE: return "c_double"
|
||||||
case C_LONGDOUBLE: return "c_longdouble"
|
case C_LONGDOUBLE: return "c_longdouble"
|
||||||
case:
|
case:
|
||||||
return fmt.tprintf("<type %d>", value)
|
return "fallible" if kind(value) == .Fallible else fmt.tprintf("<type %d>", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+162
-19
@@ -90,7 +90,7 @@ main :: func() void { _ = value }
|
|||||||
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
|
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
|
||||||
testing.expect_value(t, size_of(source.Span), 12)
|
testing.expect_value(t, size_of(source.Span), 12)
|
||||||
testing.expect_value(t, size_of(token.Token), 24)
|
testing.expect_value(t, size_of(token.Token), 24)
|
||||||
testing.expect(t, size_of(ast.Expr) <= 64)
|
testing.expect(t, size_of(ast.Expr) <= 88)
|
||||||
testing.expect(t, size_of(hir.Expr) <= 88)
|
testing.expect(t, size_of(hir.Expr) <= 88)
|
||||||
testing.expect(t, size_of(ir.Instruction) <= 88)
|
testing.expect(t, size_of(ir.Instruction) <= 88)
|
||||||
testing.expect_value(t, size_of(types.Type), 4)
|
testing.expect_value(t, size_of(types.Type), 4)
|
||||||
@@ -2169,9 +2169,9 @@ tagged_union_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
@(test)
|
@(test)
|
||||||
tagged_union_stores_the_discriminant :: proc(t: ^testing.T) {
|
tagged_union_stores_the_discriminant :: proc(t: ^testing.T) {
|
||||||
// The runtime test observes only the payload; this one checks the *tag* is written.
|
// The runtime test observes only the payload; this one checks the *tag* is written.
|
||||||
// `Animal` is unbacked/dense (dog=0, cat=1, bird=2) in a u8 backing, so `Data{ bird = 99 }`
|
// Native sum tags are global u16 IDs. `Animal` contributes dog/cat/bird (1..3),
|
||||||
// lays out as `{ i8, [3 x i8], i32 }` (tag at 0, i32 payload at offset 4) and writes the
|
// then `Data` contributes dog:i32/bird:i32 (4..5), so `Data{ bird = 99 }`
|
||||||
// discriminant `store i8 2` beside the payload `store i32 99`.
|
// writes discriminant `store i16 5` beside the payload.
|
||||||
text := `Animal :: enum {
|
text := `Animal :: enum {
|
||||||
dog
|
dog
|
||||||
cat
|
cat
|
||||||
@@ -2203,8 +2203,8 @@ main :: func() i32 {
|
|||||||
defer delete(llvm_text)
|
defer delete(llvm_text)
|
||||||
|
|
||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
testing.expect(t, strings.contains(llvm_text, "{ i8, [3 x i8], i32 }"))
|
testing.expect(t, strings.contains(llvm_text, "{ i16, [2 x i8], i32 }"))
|
||||||
testing.expect(t, strings.contains(llvm_text, "store i8 2,"))
|
testing.expect(t, strings.contains(llvm_text, "store i16 5,"))
|
||||||
testing.expect(t, strings.contains(llvm_text, "store i32 99,"))
|
testing.expect(t, strings.contains(llvm_text, "store i32 99,"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2251,6 +2251,151 @@ main :: func() i32 {
|
|||||||
testing.expect(t, found_variant)
|
testing.expect(t, found_variant)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
sum_composition_merges_widens_and_rejects_conflicts :: proc(t: ^testing.T) {
|
||||||
|
text := `A :: enum {
|
||||||
|
same
|
||||||
|
left
|
||||||
|
}
|
||||||
|
B :: enum {
|
||||||
|
same
|
||||||
|
right
|
||||||
|
}
|
||||||
|
Both :: alias A | B
|
||||||
|
UA :: union(enum) {
|
||||||
|
item i32
|
||||||
|
}
|
||||||
|
UB :: union(enum) {
|
||||||
|
empty void
|
||||||
|
}
|
||||||
|
UBoth :: alias UA | UB
|
||||||
|
pick :: func(value Both) i32 {
|
||||||
|
match value {
|
||||||
|
.same: return 1
|
||||||
|
.left: return 2
|
||||||
|
.right: return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payload :: func(value UBoth) i32 {
|
||||||
|
match value {
|
||||||
|
.item |n|: return n
|
||||||
|
.empty: return 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main :: func() i32 {
|
||||||
|
a A = .left
|
||||||
|
b B = .right
|
||||||
|
u UA = UA{ item = 4 }
|
||||||
|
v UB = .empty
|
||||||
|
return pick(.same) + pick(a) + pick(b) + payload(u) + payload(v)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64"))
|
||||||
|
|
||||||
|
conflict := `A :: union(enum) {
|
||||||
|
dup i32
|
||||||
|
}
|
||||||
|
B :: union(enum) {
|
||||||
|
dup i64
|
||||||
|
}
|
||||||
|
Bad :: alias A | B
|
||||||
|
main :: func() void {}
|
||||||
|
`
|
||||||
|
conflict_source := source.Source{path="conflict.bro", text=conflict}
|
||||||
|
conflict_diagnostics := source.init_diagnostics(&conflict_source)
|
||||||
|
defer source.destroy_diagnostics(&conflict_diagnostics)
|
||||||
|
conflict_symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&conflict_symbols)
|
||||||
|
conflict_stream := lexer.lex(&conflict_source, &conflict_diagnostics, &conflict_symbols)
|
||||||
|
defer delete(conflict_stream.items)
|
||||||
|
conflict_module := parser.parse(&conflict_stream, &conflict_source, &conflict_diagnostics)
|
||||||
|
defer ast.destroy_module(&conflict_module)
|
||||||
|
found_conflict := false
|
||||||
|
for diagnostic in conflict_diagnostics.items {
|
||||||
|
found_conflict = found_conflict || strings.contains(diagnostic.message, "same variant name")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_conflict)
|
||||||
|
|
||||||
|
backed := `A :: enum(u8) {
|
||||||
|
a = 1
|
||||||
|
}
|
||||||
|
B :: enum {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
Bad :: alias A | B
|
||||||
|
main :: func() void {}
|
||||||
|
`
|
||||||
|
backed_source := source.Source{path="backed.bro", text=backed}
|
||||||
|
backed_diagnostics := source.init_diagnostics(&backed_source)
|
||||||
|
defer source.destroy_diagnostics(&backed_diagnostics)
|
||||||
|
backed_symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&backed_symbols)
|
||||||
|
backed_stream := lexer.lex(&backed_source, &backed_diagnostics, &backed_symbols)
|
||||||
|
defer delete(backed_stream.items)
|
||||||
|
backed_module := parser.parse(&backed_stream, &backed_source, &backed_diagnostics)
|
||||||
|
defer ast.destroy_module(&backed_module)
|
||||||
|
found_backed := false
|
||||||
|
for diagnostic in backed_diagnostics.items {
|
||||||
|
found_backed = found_backed || strings.contains(diagnostic.message, "native unbacked")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_backed)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
yield_inference_visits_yielded_calls :: proc(t: ^testing.T) {
|
||||||
|
text := `identity :: func(value int) int {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main :: func() i32 {
|
||||||
|
value :: {
|
||||||
|
yield identity(41)
|
||||||
|
}
|
||||||
|
return value - 41
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="yield_call.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, len(hir_module.functions) > 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
errors_example_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-errors"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/errors", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
match_compiles_and_runs :: proc(t: ^testing.T) {
|
match_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-match"
|
output := "/tmp/brolang-test-match"
|
||||||
@@ -2268,8 +2413,7 @@ match_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
match_dispatches_on_the_tag :: proc(t: ^testing.T) {
|
match_dispatches_on_the_tag :: proc(t: ^testing.T) {
|
||||||
// A tagged-union match desugars to: read the discriminant once (a load of the tag
|
// A tagged-union match desugars to: read the discriminant once (a load of the tag
|
||||||
// enum at the union's offset 0), then compare it against each variant's tag value.
|
// enum at the union's offset 0), then compare it against each variant's tag value.
|
||||||
// `Animal` is dense in a u8 backing (dog=0, bird=2), so the dog arm compares the
|
// Data's runtime tag is the hidden global u16 variant ID.
|
||||||
// loaded i8 tag against 0.
|
|
||||||
text := `Animal :: enum {
|
text := `Animal :: enum {
|
||||||
dog
|
dog
|
||||||
cat
|
cat
|
||||||
@@ -2306,9 +2450,8 @@ main :: func() i32 {
|
|||||||
defer delete(llvm_text)
|
defer delete(llvm_text)
|
||||||
|
|
||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
// The tag is loaded as an i8 and compared (icmp eq) against the dog variant's tag (0).
|
testing.expect(t, strings.contains(llvm_text, "load i16"))
|
||||||
testing.expect(t, strings.contains(llvm_text, "load i8"))
|
testing.expect(t, strings.contains(llvm_text, "icmp eq i16"))
|
||||||
testing.expect(t, strings.contains(llvm_text, "icmp eq i8"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -7038,21 +7181,21 @@ main :: func() i32 {
|
|||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
testing.expect(t, animal_ok && nat_ok)
|
testing.expect(t, animal_ok && nat_ok)
|
||||||
testing.expect(t, types.is_enum(animal, &ast_module.type_store))
|
testing.expect(t, types.is_enum(animal, &ast_module.type_store))
|
||||||
testing.expect_value(t, animal_node.child, types.U8)
|
testing.expect_value(t, animal_node.child, types.U16)
|
||||||
testing.expect(t, !animal_node.explicit_backing)
|
testing.expect(t, !animal_node.explicit_backing)
|
||||||
testing.expect_value(t, nat_node.child, types.U16)
|
testing.expect_value(t, nat_node.child, types.U16)
|
||||||
testing.expect(t, nat_node.explicit_backing)
|
testing.expect(t, nat_node.explicit_backing)
|
||||||
testing.expect_value(t, len(animal_members), 3)
|
testing.expect_value(t, len(animal_members), 3)
|
||||||
testing.expect_value(t, animal_members[0].value, i128(0))
|
testing.expect_value(t, animal_members[0].value, i128(1))
|
||||||
testing.expect_value(t, animal_members[2].value, i128(2))
|
testing.expect_value(t, animal_members[2].value, i128(3))
|
||||||
testing.expect_value(t, nat_members[0].value, i128(1))
|
testing.expect_value(t, nat_members[0].value, i128(1))
|
||||||
testing.expect_value(t, nat_members[1].value, i128(2))
|
testing.expect_value(t, nat_members[1].value, i128(2))
|
||||||
testing.expect_value(t, nat_members[2].value, i128(5))
|
testing.expect_value(t, nat_members[2].value, i128(5))
|
||||||
testing.expect_value(t, types.runtime_representation(animal, &ast_module.type_store), types.U8)
|
testing.expect_value(t, types.runtime_representation(animal, &ast_module.type_store), types.U16)
|
||||||
testing.expect_value(t, types.size(animal, &ast_module.type_store), u64(1))
|
testing.expect_value(t, types.size(animal, &ast_module.type_store), u64(2))
|
||||||
testing.expect(t, hir_module.globals[0].is_static)
|
testing.expect(t, hir_module.globals[0].is_static)
|
||||||
testing.expect_value(t, hir_module.globals[0].static_value, i64(0))
|
testing.expect_value(t, hir_module.globals[0].static_value, i64(1))
|
||||||
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i8 0"))
|
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i16 1"))
|
||||||
found_promotion := false
|
found_promotion := false
|
||||||
for function in ir_module.functions {
|
for function in ir_module.functions {
|
||||||
for instruction in function.instructions {
|
for instruction in function.instructions {
|
||||||
@@ -7063,7 +7206,7 @@ main :: func() i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
unbacked_enum_selects_the_smallest_fitting_unsigned_backing :: proc(t: ^testing.T) {
|
unbacked_enum_uses_global_u16_backing :: proc(t: ^testing.T) {
|
||||||
builder := strings.builder_make()
|
builder := strings.builder_make()
|
||||||
defer strings.builder_destroy(&builder)
|
defer strings.builder_destroy(&builder)
|
||||||
strings.write_string(&builder, "Large :: enum {\n")
|
strings.write_string(&builder, "Large :: enum {\n")
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
BasicError :: enum {
|
||||||
|
bad
|
||||||
|
worse
|
||||||
|
}
|
||||||
|
|
||||||
|
DetailError :: union(enum) {
|
||||||
|
code i32
|
||||||
|
empty void
|
||||||
|
}
|
||||||
|
|
||||||
|
Left :: enum {
|
||||||
|
left
|
||||||
|
}
|
||||||
|
|
||||||
|
Right :: enum {
|
||||||
|
right
|
||||||
|
}
|
||||||
|
|
||||||
|
Both :: alias Left | Right
|
||||||
|
|
||||||
|
BoxA :: union(enum) {
|
||||||
|
a i32
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxB :: union(enum) {
|
||||||
|
b void
|
||||||
|
}
|
||||||
|
|
||||||
|
Box :: alias BoxA | BoxB
|
||||||
|
|
||||||
|
maybe :: func(value i32) i32 ! BasicError {
|
||||||
|
if (value == 0) return .bad
|
||||||
|
return value + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
via_try :: func(value i32) i32 ! BasicError {
|
||||||
|
unwrapped :: try maybe(value)
|
||||||
|
return unwrapped + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
with_detail :: func(value i32) i32 ! DetailError {
|
||||||
|
if (value == 0) return DetailError{ code = 5 }
|
||||||
|
if (value == 1) return .empty
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
pick :: func(value Both) i32 {
|
||||||
|
match value {
|
||||||
|
.left: return 10
|
||||||
|
.right: return 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payload :: func(value Box) i32 {
|
||||||
|
match value {
|
||||||
|
.a |n|: return n
|
||||||
|
.b: return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
acc i32 = 0
|
||||||
|
|
||||||
|
a :: maybe(0) catch 7
|
||||||
|
b :: maybe(4) catch 99
|
||||||
|
c :: via_try(3) catch 99
|
||||||
|
d :: via_try(0) catch 11
|
||||||
|
e :: with_detail(0) catch 13
|
||||||
|
f :: with_detail(2) catch 99
|
||||||
|
|
||||||
|
acc = acc + a + b + c + d + e + f
|
||||||
|
acc = acc + pick(.left)
|
||||||
|
r Right = .right
|
||||||
|
acc = acc + pick(r)
|
||||||
|
box BoxA = BoxA{ a = 8 }
|
||||||
|
acc = acc + payload(box)
|
||||||
|
empty BoxB = .b
|
||||||
|
acc = acc + payload(empty)
|
||||||
|
|
||||||
|
# 7 + 5 + 5 + 11 + 13 + 2 + 10 + 20 + 8 + 3 = 84
|
||||||
|
return acc - 84
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user