runtime mutable global state
This commit is contained in:
+3
-3
@@ -8,8 +8,8 @@ roadmap and milestone history.
|
|||||||
### source, declarations, and packages
|
### source, declarations, and packages
|
||||||
|
|
||||||
- newline-terminated statements and `#` comments
|
- newline-terminated statements and `#` comments
|
||||||
- immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
- immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
|
||||||
- immutable package globals, function-local mutable locals, and mutable local declarations initialized with `undefined`
|
- immutable package globals, mutable runtime globals, function-local mutable locals, and mutable local declarations initialized with `undefined`
|
||||||
- package-level functions, globals, native type declarations, and `Name :: alias T`
|
- package-level functions, globals, native type declarations, and `Name :: alias T`
|
||||||
- directory packages with merged declarations
|
- directory packages with merged declarations
|
||||||
- file-local relative imports, import aliases, and qualified member access
|
- file-local relative imports, import aliases, and qualified member access
|
||||||
@@ -76,7 +76,7 @@ roadmap and milestone history.
|
|||||||
|
|
||||||
- error-tolerant compilation with diagnostics and runtime traps where recovery is possible
|
- error-tolerant compilation with diagnostics and runtime traps where recovery is possible
|
||||||
- lazy semantic checking of demanded function specializations
|
- lazy semantic checking of demanded function specializations
|
||||||
- static, eager runtime, and deferred problematic globals with cycle diagnostics
|
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
|
||||||
- demand-driven LLVM declarations for referenced foreign functions
|
- demand-driven LLVM declarations for referenced foreign functions
|
||||||
- replaceable dynamically loaded libclang C-import backend
|
- replaceable dynamically loaded libclang C-import backend
|
||||||
- C-header import caching by canonical path, target, include paths, and defines
|
- C-header import caching by canonical path, target, include paths, and defines
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ Current prototype features:
|
|||||||
|
|
||||||
- Newline-terminated, multiline statements; `}` may terminate a block's final statement
|
- Newline-terminated, multiline statements; `}` may terminate a block's final statement
|
||||||
- `#` comments
|
- `#` comments
|
||||||
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
|
- Immutable `::` bindings, typed mutable `=` locals/globals, and `_` sinks
|
||||||
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int`
|
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int`
|
||||||
- Target-dependent atomic `c_*` primitive types, `c_func`, and defined or opaque `c_struct`
|
- Target-dependent atomic `c_*` primitive types, `c_func`, and defined or opaque `c_struct`
|
||||||
- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
|
- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
|
||||||
@@ -143,7 +143,7 @@ Current prototype features:
|
|||||||
- Bodyless manual and imported C variadic declarations with default argument promotions
|
- Bodyless manual and imported C variadic declarations with default argument promotions
|
||||||
- Ordered linking of additional C sources, objects, archives, and libraries
|
- Ordered linking of additional C sources, objects, archives, and libraries
|
||||||
- Checked signed addition and unary negation
|
- Checked signed addition and unary negation
|
||||||
- Static, eager runtime, and deferred problematic globals
|
- Static, eager runtime, mutable runtime, and deferred problematic globals
|
||||||
- Runtime diagnostics followed by `llvm.trap`
|
- Runtime diagnostics followed by `llvm.trap`
|
||||||
|
|
||||||
See [LANGUAGE.md](LANGUAGE.md) for the concise implemented and planned language
|
See [LANGUAGE.md](LANGUAGE.md) for the concise implemented and planned language
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
- operators: `and`, `or`, `!`
|
- operators: `and`, `or`, `!`
|
||||||
- lazy evaluation / short-circuit evaluation
|
- lazy evaluation / short-circuit evaluation
|
||||||
- if statements (implemented). example: `if condition { ... } else if { ... } else { ... }`
|
- if statements (implemented). example: `if condition { ... } else if { ... } else { ... }`
|
||||||
- conditions must be `bool`; block-scoped locals with shadowing across blocks
|
- conditions must be `bool`; block-scoped locals do not escape their blocks
|
||||||
- lowered through new `Label` / `Br` / `Cond_Br` IR opcodes (alloca-backed locals, no phi nodes)
|
- lowered through new `Label` / `Br` / `Cond_Br` IR opcodes (alloca-backed locals, no phi nodes)
|
||||||
- conditional unwrapping for optionals (`?T`) (implemented): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
- conditional unwrapping for optionals (`?T`) (implemented): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
||||||
- single immutable binding scoped to the then-block; `v` not visible in `else` or after the `if`
|
- single immutable binding scoped to the then-block; `v` not visible in `else` or after the `if`
|
||||||
@@ -403,8 +403,7 @@
|
|||||||
statements first (a throwaway probe), so a first concrete `yield :blk` that references a
|
statements first (a throwaway probe), so a first concrete `yield :blk` that references a
|
||||||
block local still resolves the result to `?T`
|
block local still resolves the result to `?T`
|
||||||
- deferred (`// ponytail:`): the same `none`-before-concrete typing in an untyped block (or
|
- deferred (`// ponytail:`): the same `none`-before-concrete typing in an untyped block (or
|
||||||
loop) whose concrete yield references a local declared *past* the first yield (annotate);
|
loop) whose concrete yield references a local declared *past* the first yield (annotate)
|
||||||
same-label loop/block shadowing resolves innermost-wins
|
|
||||||
|
|
||||||
21. unions and tagged unions (implemented; first pass — native untagged unions only; see below)
|
21. unions and tagged unions (implemented; first pass — native untagged unions only; see below)
|
||||||
- inspired by zig
|
- inspired by zig
|
||||||
@@ -683,6 +682,32 @@
|
|||||||
stable aggregate serialization, comptime pointers/slices, and calls through
|
stable aggregate serialization, comptime pointers/slices, and calls through
|
||||||
comptime-known function values/function pointers are deferred
|
comptime-known function values/function pointers are deferred
|
||||||
|
|
||||||
|
27.8 source-defined mutable runtime globals (implemented)
|
||||||
|
- allow mutable global declarations in Brolang source for process-global runtime
|
||||||
|
state, matching the writable-global support already needed for imported C globals
|
||||||
|
- require source type syntax and an initializer; constraints (`int`/`float`/`range`)
|
||||||
|
and inferred array counts may resolve through the existing inference fixpoint, but
|
||||||
|
the final type must be concrete runtime storage
|
||||||
|
- emit source-defined mutable globals as writable globals, not constants
|
||||||
|
- allow assignment, address-taking, field/index mutation, and pointer passing under
|
||||||
|
the same mutability rules as other writable locations
|
||||||
|
- keep mutable globals invalid in comptime evaluation; `$global_var` and writes from
|
||||||
|
comptime execution must remain runtime-dependent errors
|
||||||
|
- define initialization order and cycle behavior by reusing the existing global
|
||||||
|
initializer dependency/cycle system where possible
|
||||||
|
- reject user-visible name shadowing across imports, named types, globals, functions,
|
||||||
|
params, locals, comptime params, captures, and labels; `_` remains reusable
|
||||||
|
|
||||||
|
27.9 comptime storage and function values
|
||||||
|
- add a comptime pointer/storage model for pointers, slices, address/deref,
|
||||||
|
pointer captures, lifetimes, aliasing, mutability, and escape rules
|
||||||
|
- define what `$&value` and other comptime addresses can legally materialize into,
|
||||||
|
without exposing compiler-owned memory as runtime memory
|
||||||
|
- add first-class comptime function values and calls through comptime-known function
|
||||||
|
pointers
|
||||||
|
- resolve function-pointer targets during comptime execution, apply ABI/runtime
|
||||||
|
restrictions, and reliably reject imported/runtime callbacks
|
||||||
|
|
||||||
28. brolang build system (requires comptime execution)
|
28. brolang build system (requires comptime execution)
|
||||||
|
|
||||||
## A word on multi-unwrap
|
## A word on multi-unwrap
|
||||||
|
|||||||
+337
-15
@@ -534,6 +534,90 @@ find_import :: proc(checker: ^Checker, file: ast.File_Id, alias: symbol.Id, mark
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declared_type_named :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
||||||
|
id := types.find_named(&checker.module.types, u32(pkg), u32(name))
|
||||||
|
item, ok := types.node(&checker.module.types, id)
|
||||||
|
return ok && item.declared
|
||||||
|
}
|
||||||
|
|
||||||
|
visible_name_kind :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
name: symbol.Id,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
locals: []Build_Local = nil,
|
||||||
|
labels: []symbol.Id = nil,
|
||||||
|
yield_targets: []Yield_Target = nil,
|
||||||
|
) -> string {
|
||||||
|
if !symbol.is_valid(name) || name == checker.sink_symbol {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if _, ok := current_comptime_value(checker, name); ok {
|
||||||
|
return "comptime parameter"
|
||||||
|
}
|
||||||
|
if _, ok := find_build_local(locals, name); ok {
|
||||||
|
return "local"
|
||||||
|
}
|
||||||
|
for label in labels {
|
||||||
|
if label == name {
|
||||||
|
return "label"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for target in yield_targets {
|
||||||
|
if target.label == name {
|
||||||
|
return "label"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if find_import(checker, file, name) != ast.INVALID_IMPORT {
|
||||||
|
return "import"
|
||||||
|
}
|
||||||
|
if find_global(checker, name, pkg) != ast.INVALID_GLOBAL {
|
||||||
|
return "global"
|
||||||
|
}
|
||||||
|
if find_template(checker, name, pkg) != ast.INVALID_FUNCTION {
|
||||||
|
return "function"
|
||||||
|
}
|
||||||
|
if declared_type_named(checker, pkg, name) {
|
||||||
|
return "type"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
add_shadow_diagnostic :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
span: source.Span,
|
||||||
|
name: symbol.Id,
|
||||||
|
decl_kind: string,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
locals: []Build_Local = nil,
|
||||||
|
labels: []symbol.Id = nil,
|
||||||
|
yield_targets: []Yield_Target = nil,
|
||||||
|
) -> source.Diagnostic_Id {
|
||||||
|
kind := visible_name_kind(checker, name, pkg, file, locals, labels, yield_targets)
|
||||||
|
if len(kind) == 0 {
|
||||||
|
return source.INVALID_DIAGNOSTIC
|
||||||
|
}
|
||||||
|
return source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
span,
|
||||||
|
"%s '%s' shadows visible %s",
|
||||||
|
decl_kind,
|
||||||
|
symbol_text(checker, name),
|
||||||
|
kind,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_label_shadow_diagnostic :: proc(ctx: ^Build_Ctx, span: source.Span, label: symbol.Id) -> source.Diagnostic_Id {
|
||||||
|
if !symbol.is_valid(label) {
|
||||||
|
return source.INVALID_DIAGNOSTIC
|
||||||
|
}
|
||||||
|
return add_shadow_diagnostic(
|
||||||
|
ctx.checker, span, label, "label",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, mark_used := false) -> (ast.Package_Id, bool) {
|
expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, mark_used := false) -> (ast.Package_Id, bool) {
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
if !symbol.is_valid(expr.qualifier) {
|
||||||
return pkg, true
|
return pkg, true
|
||||||
@@ -1081,6 +1165,11 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
"duplicate parameter '%s'",
|
"duplicate parameter '%s'",
|
||||||
symbol_text(checker, param.name),
|
symbol_text(checker, param.name),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
_ = add_shadow_diagnostic(
|
||||||
|
checker, param.span, param.name, "parameter",
|
||||||
|
function.pkg, function.file,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
append(&locals, param.name)
|
append(&locals, param.name)
|
||||||
if !has_comptime && types.contains_c_struct_by_value(param_type, &checker.module.types) {
|
if !has_comptime && types.contains_c_struct_by_value(param_type, &checker.module.types) {
|
||||||
@@ -1748,6 +1837,31 @@ infer_expr :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !types.is_valid(last) && symbol.is_valid(expr.qualifier) &&
|
||||||
|
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
||||||
|
if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL {
|
||||||
|
base_type := checker.global_types[global]
|
||||||
|
item, has_item := types.container(base_type, &checker.module.types)
|
||||||
|
field_name := symbol_text(checker, expr.name)
|
||||||
|
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
||||||
|
if field_name == "len" {
|
||||||
|
last = types.USIZE
|
||||||
|
} else if field_name == "ptr" &&
|
||||||
|
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
|
||||||
|
last = container_pointer_type(&checker.module.types, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if types.is_pointer(base_type, &checker.module.types) {
|
||||||
|
base_type = types.child_type(base_type, &checker.module.types)
|
||||||
|
}
|
||||||
|
if !types.is_valid(last) {
|
||||||
|
_, field, ok := find_struct_field(checker, base_type, expr.name)
|
||||||
|
if ok {
|
||||||
|
last = field.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if !types.is_valid(last) {
|
if !types.is_valid(last) {
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
if !symbol.is_valid(expr.qualifier) {
|
||||||
if value, ok := current_comptime_value(checker, expr.name); ok {
|
if value, ok := current_comptime_value(checker, expr.name); ok {
|
||||||
@@ -2200,6 +2314,11 @@ infer_statements :: proc(
|
|||||||
if !rhs_is_arith {
|
if !rhs_is_arith {
|
||||||
_ = record_demand(checker, statement.expr, locals^[local_index].type, locals^[:], local_types, pkg, file)
|
_ = record_demand(checker, statement.expr, locals^[local_index].type, locals^[:], local_types, pkg, file)
|
||||||
}
|
}
|
||||||
|
} else if global := find_global(checker, statement.name, pkg); global != ast.INVALID_GLOBAL {
|
||||||
|
_ = merge_global_demand(checker, global, value_type)
|
||||||
|
if !rhs_is_arith {
|
||||||
|
_ = record_demand(checker, statement.expr, checker.global_types[global], locals^[:], local_types, pkg, file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .Expression:
|
case .Expression:
|
||||||
@@ -2462,6 +2581,11 @@ merge_global_demand :: proc(checker: ^Checker, global: ast.Global_Id, demand: ty
|
|||||||
if index < 0 || index >= len(checker.global_demands) {
|
if index < 0 || index >= len(checker.global_demands) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
ast_global := checker.ast_module.globals[global]
|
||||||
|
declared := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
||||||
|
if types.is_constraint(declared) && !types.constraint_accepts(declared, demand, &checker.module.types) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
changed: bool
|
changed: bool
|
||||||
if checker.global_open_const[index] || checker.global_open_float[index] {
|
if checker.global_open_const[index] || checker.global_open_float[index] {
|
||||||
changed = merge_open_const_demand(
|
changed = merge_open_const_demand(
|
||||||
@@ -2766,6 +2890,20 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
if is_runtime_type(checker, declared) && !has_inferred_array_count(checker, declared) {
|
if is_runtime_type(checker, declared) && !has_inferred_array_count(checker, declared) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if types.is_constraint(declared) {
|
||||||
|
resolved := types.INVALID
|
||||||
|
if is_runtime_type(checker, checker.global_demands[index]) &&
|
||||||
|
types.constraint_accepts(declared, checker.global_demands[index], &checker.module.types) {
|
||||||
|
resolved = checker.global_demands[index]
|
||||||
|
} else {
|
||||||
|
resolved = types.constraint_target(declared, inferred, &checker.module.types)
|
||||||
|
}
|
||||||
|
if is_runtime_type(checker, resolved) && !types.equal(checker.global_types[index], resolved) {
|
||||||
|
checker.global_types[index] = resolved
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if is_runtime_type(checker, checker.global_demands[index]) {
|
if is_runtime_type(checker, checker.global_demands[index]) {
|
||||||
// A backward demand is authoritative; assign directly (it may cross the
|
// A backward demand is authoritative; assign directly (it may cross the
|
||||||
// signed/unsigned family that widening would reject).
|
// signed/unsigned family that widening would reject).
|
||||||
@@ -4229,6 +4367,43 @@ build_expr :: proc(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) &&
|
||||||
|
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
||||||
|
if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL {
|
||||||
|
base := build_global_reference(checker, global, expr.span, global_reads)
|
||||||
|
base_type := checker.global_types[global]
|
||||||
|
item, has_item := types.container(base_type, &checker.module.types)
|
||||||
|
field_name := symbol_text(checker, expr.name)
|
||||||
|
if has_item && (item.kind == .Array || item.kind == .Slice) {
|
||||||
|
if field_name == "len" {
|
||||||
|
last = add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Length, span=expr.span, type=types.USIZE, left=base,
|
||||||
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else if field_name == "ptr" &&
|
||||||
|
(item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) {
|
||||||
|
last = add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Slice_Ptr, span=expr.span,
|
||||||
|
type=container_pointer_type(&checker.module.types, item), left=base,
|
||||||
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else if field_name == "ptr" && item.kind == .Array {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first")
|
||||||
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if types.is_pointer(base_type, &checker.module.types) {
|
||||||
|
base_type = types.child_type(base_type, &checker.module.types)
|
||||||
|
}
|
||||||
|
index, field, found := find_struct_field(checker, base_type, expr.name)
|
||||||
|
if last == hir.INVALID_EXPR && found {
|
||||||
|
last = add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Field, span=expr.span, type=field.type, integer=i64(index), left=base,
|
||||||
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if last == hir.INVALID_EXPR {
|
if last == hir.INVALID_EXPR {
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
if !symbol.is_valid(expr.qualifier) {
|
||||||
if value, ok := current_comptime_value(checker, expr.name); ok {
|
if value, ok := current_comptime_value(checker, expr.name); ok {
|
||||||
@@ -4839,6 +5014,18 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if id := add_shadow_diagnostic(
|
||||||
|
checker, statement.span, statement.name, "local",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
); id != source.INVALID_DIAGNOSTIC {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
local_id := hir.local_id(len(ctx.hir_locals^))
|
local_id := hir.local_id(len(ctx.hir_locals^))
|
||||||
append(ctx.hir_locals, hir.Local{
|
append(ctx.hir_locals, hir.Local{
|
||||||
name = statement.name, type = value_type, mutable = !statement.immutable,
|
name = statement.name, type = value_type, mutable = !statement.immutable,
|
||||||
@@ -4954,6 +5141,18 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if id := add_shadow_diagnostic(
|
||||||
|
checker, statement.span, statement.name, "local",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
); id != source.INVALID_DIAGNOSTIC {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
local_id := hir.local_id(len(ctx.hir_locals^))
|
local_id := hir.local_id(len(ctx.hir_locals^))
|
||||||
append(ctx.hir_locals, hir.Local{
|
append(ctx.hir_locals, hir.Local{
|
||||||
name = statement.name, type = value_type, mutable = !statement.immutable,
|
name = statement.name, type = value_type, mutable = !statement.immutable,
|
||||||
@@ -5094,13 +5293,46 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
local, found := find_build_local(ctx.locals^[:], statement.name)
|
local, found := find_build_local(ctx.locals^[:], statement.name)
|
||||||
if !found {
|
if !found {
|
||||||
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
|
global := find_global(checker, statement.name, ctx.pkg)
|
||||||
|
if global == ast.INVALID_GLOBAL {
|
||||||
|
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
target_expr := build_global_reference(checker, global, statement.span, ctx.global_reads)
|
||||||
|
target_type := checker.module.exprs[target_expr].type
|
||||||
|
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
||||||
|
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value: hir.Expr_Id
|
||||||
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
|
value, _ = build_value_source(ctx, &body, statement.body, target_type, statement.span, statement.label, statement.value_control_flow)
|
||||||
|
} else {
|
||||||
|
value = build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
target_type, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
value = coerce_expr(checker, value, target_type, statement.span)
|
||||||
|
}
|
||||||
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{
|
||||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
|
||||||
local = hir.INVALID_LOCAL, diagnostic = id,
|
assignment_op=.Set,
|
||||||
|
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
ctx.problematic^ = true
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !local.mutable {
|
if !local.mutable {
|
||||||
@@ -5343,6 +5575,12 @@ build_block :: proc(
|
|||||||
"'if' unwrap captures must have distinct names",
|
"'if' unwrap captures must have distinct names",
|
||||||
)
|
)
|
||||||
valid_unwrap = false
|
valid_unwrap = false
|
||||||
|
} else if id := add_shadow_diagnostic(
|
||||||
|
checker, statement.span, capture, "capture",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
); id != source.INVALID_DIAGNOSTIC {
|
||||||
|
diagnostic = id
|
||||||
|
valid_unwrap = false
|
||||||
}
|
}
|
||||||
local = hir.local_id(len(ctx.hir_locals^))
|
local = hir.local_id(len(ctx.hir_locals^))
|
||||||
append(ctx.hir_locals, hir.Local{name=capture, type=child, mutable=false})
|
append(ctx.hir_locals, hir.Local{name=capture, type=child, mutable=false})
|
||||||
@@ -5445,6 +5683,15 @@ build_block :: proc(
|
|||||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
}
|
}
|
||||||
|
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
||||||
|
id != source.INVALID_DIAGNOSTIC {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
}
|
||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
append(ctx.loop_labels, statement.label)
|
append(ctx.loop_labels, statement.label)
|
||||||
append(ctx.loop_is_loop, true)
|
append(ctx.loop_is_loop, true)
|
||||||
@@ -5530,6 +5777,15 @@ build_block :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
capture_start := len(ctx.locals^)
|
capture_start := len(ctx.locals^)
|
||||||
|
if statement.name != checker.sink_symbol {
|
||||||
|
if id := add_shadow_diagnostic(
|
||||||
|
checker, statement.span, statement.name, "capture",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
); id != source.INVALID_DIAGNOSTIC {
|
||||||
|
diagnostic = id
|
||||||
|
valid_loop = false
|
||||||
|
}
|
||||||
|
}
|
||||||
item_local := hir.local_id(len(ctx.hir_locals^))
|
item_local := hir.local_id(len(ctx.hir_locals^))
|
||||||
append(ctx.hir_locals, hir.Local{name=statement.name, type=capture_type, mutable=false})
|
append(ctx.hir_locals, hir.Local{name=statement.name, type=capture_type, mutable=false})
|
||||||
append(ctx.locals, Build_Local{name=statement.name, type=capture_type, mutable=false, id=item_local})
|
append(ctx.locals, Build_Local{name=statement.name, type=capture_type, mutable=false, id=item_local})
|
||||||
@@ -5538,12 +5794,27 @@ build_block :: proc(
|
|||||||
if statement.index_name == statement.name {
|
if statement.index_name == statement.name {
|
||||||
diagnostic = source.add(checker.diagnostics, statement.span, "for-loop captures must have distinct names")
|
diagnostic = source.add(checker.diagnostics, statement.span, "for-loop captures must have distinct names")
|
||||||
valid_loop = false
|
valid_loop = false
|
||||||
|
} else if id := add_shadow_diagnostic(
|
||||||
|
checker, statement.span, statement.index_name, "capture",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
); id != source.INVALID_DIAGNOSTIC {
|
||||||
|
diagnostic = id
|
||||||
|
valid_loop = false
|
||||||
} else {
|
} else {
|
||||||
index_local = hir.local_id(len(ctx.hir_locals^))
|
index_local = hir.local_id(len(ctx.hir_locals^))
|
||||||
append(ctx.hir_locals, hir.Local{name=statement.index_name, type=types.USIZE, mutable=false})
|
append(ctx.hir_locals, hir.Local{name=statement.index_name, type=types.USIZE, mutable=false})
|
||||||
append(ctx.locals, Build_Local{name=statement.index_name, type=types.USIZE, mutable=false, id=index_local})
|
append(ctx.locals, Build_Local{name=statement.index_name, type=types.USIZE, mutable=false, id=index_local})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
||||||
|
id != source.INVALID_DIAGNOSTIC {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
}
|
||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
append(ctx.loop_labels, statement.label)
|
append(ctx.loop_labels, statement.label)
|
||||||
append(ctx.loop_is_loop, true)
|
append(ctx.loop_is_loop, true)
|
||||||
@@ -5636,6 +5907,15 @@ build_block :: proc(
|
|||||||
// A labeled block statement (`blk: { … break :blk … }`): a break target
|
// A labeled block statement (`blk: { … break :blk … }`): a break target
|
||||||
// with an exit-label boundary, built as a HIR `.Block`. Not a loop, so
|
// with an exit-label boundary, built as a HIR `.Block`. Not a loop, so
|
||||||
// unlabeled `break`/`continue` and `continue :blk` skip it.
|
// unlabeled `break`/`continue` and `continue :blk` skip it.
|
||||||
|
if id := add_label_shadow_diagnostic(ctx, statement.span, statement.label);
|
||||||
|
id != source.INVALID_DIAGNOSTIC {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
}
|
||||||
append(ctx.loop_defer_starts, len(ctx.defers^))
|
append(ctx.loop_defer_starts, len(ctx.defers^))
|
||||||
append(ctx.loop_labels, statement.label)
|
append(ctx.loop_labels, statement.label)
|
||||||
append(ctx.loop_is_loop, false)
|
append(ctx.loop_is_loop, false)
|
||||||
@@ -6061,6 +6341,11 @@ emit_value_if :: proc(
|
|||||||
if _, dup := find_build_local(ctx.locals^[capture_start:], capture); dup {
|
if _, dup := find_build_local(ctx.locals^[capture_start:], capture); dup {
|
||||||
source.add(checker.diagnostics, if_stmt.span, "'if' unwrap captures must have distinct names")
|
source.add(checker.diagnostics, if_stmt.span, "'if' unwrap captures must have distinct names")
|
||||||
ok = false
|
ok = false
|
||||||
|
} else if add_shadow_diagnostic(
|
||||||
|
checker, if_stmt.span, capture, "capture",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:capture_start], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
) != source.INVALID_DIAGNOSTIC {
|
||||||
|
ok = false
|
||||||
}
|
}
|
||||||
local = hir.local_id(len(ctx.hir_locals^))
|
local = hir.local_id(len(ctx.hir_locals^))
|
||||||
append(ctx.hir_locals, hir.Local{name=capture, type=child, mutable=false})
|
append(ctx.hir_locals, hir.Local{name=capture, type=child, mutable=false})
|
||||||
@@ -6594,10 +6879,22 @@ build_match_arm_body :: proc(
|
|||||||
result: [dynamic]hir.Stmt_Id
|
result: [dynamic]hir.Stmt_Id
|
||||||
result.allocator = checker.allocator
|
result.allocator = checker.allocator
|
||||||
capture_start := len(ctx.locals^)
|
capture_start := len(ctx.locals^)
|
||||||
|
capture_ok := true
|
||||||
|
|
||||||
if len(arm.captures) > 0 && field_index >= 0 {
|
if len(arm.captures) > 0 && field_index >= 0 {
|
||||||
capture := arm.captures[0]
|
capture := arm.captures[0]
|
||||||
if capture != checker.sink_symbol {
|
if capture != checker.sink_symbol {
|
||||||
|
if id := add_shadow_diagnostic(
|
||||||
|
checker, span, capture, "capture",
|
||||||
|
ctx.pkg, ctx.file, ctx.locals^[:], ctx.loop_labels^[:], ctx.yield_targets^[:],
|
||||||
|
); id != source.INVALID_DIAGNOSTIC {
|
||||||
|
append(&result, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
capture_ok = false
|
||||||
|
}
|
||||||
// The payload sits at the subject's shared carrier offset. A value capture
|
// The payload sits at the subject's shared carrier offset. A value capture
|
||||||
// loads it; a `|@cap|` capture binds a pointer to it (mutability follows the
|
// loads it; a `|@cap|` capture binds a pointer to it (mutability follows the
|
||||||
// subject), aliasing the original storage via the subject location.
|
// subject), aliasing the original storage via the subject location.
|
||||||
@@ -6626,7 +6923,7 @@ build_match_arm_body :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body_ok := true
|
body_ok := capture_ok
|
||||||
if !as_value {
|
if !as_value {
|
||||||
built := build_block(ctx, arm.body)
|
built := build_block(ctx, arm.body)
|
||||||
for s in built {
|
for s in built {
|
||||||
@@ -6634,7 +6931,7 @@ build_match_arm_body :: proc(
|
|||||||
}
|
}
|
||||||
delete(built, checker.allocator)
|
delete(built, checker.allocator)
|
||||||
} else {
|
} else {
|
||||||
body_ok = build_value_arm(ctx, &result, arm.body, slot, slot_type, span)
|
body_ok = build_value_arm(ctx, &result, arm.body, slot, slot_type, span) && body_ok
|
||||||
}
|
}
|
||||||
resize(ctx.locals, capture_start)
|
resize(ctx.locals, capture_start)
|
||||||
return result[:], body_ok
|
return result[:], body_ok
|
||||||
@@ -6911,6 +7208,10 @@ build_value_labeled_block :: proc(
|
|||||||
slot = new_value_slot(ctx, slot_type)
|
slot = new_value_slot(ctx, slot_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if id := add_label_shadow_diagnostic(ctx, span, label); id != source.INVALID_DIAGNOSTIC {
|
||||||
|
ctx.problematic^ = true
|
||||||
|
return invalid_hir_expr(checker, span, id), types.INVALID
|
||||||
|
}
|
||||||
append(ctx.yield_targets, Yield_Target{
|
append(ctx.yield_targets, Yield_Target{
|
||||||
label = label, slot = slot, slot_type = slot_type, result_optional = result_optional,
|
label = label, slot = slot, slot_type = slot_type, result_optional = result_optional,
|
||||||
defer_floor = len(ctx.defers^),
|
defer_floor = len(ctx.defers^),
|
||||||
@@ -7438,7 +7739,18 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
global_type = checker.module.exprs[expr].type
|
global_type = checker.module.exprs[expr].type
|
||||||
}
|
}
|
||||||
diagnostic := source.INVALID_DIAGNOSTIC
|
diagnostic := source.INVALID_DIAGNOSTIC
|
||||||
if !is_runtime_type(checker, global_type) {
|
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
||||||
|
diagnostic = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
global.span,
|
||||||
|
"'undefined' is only valid as a mutable local declaration initializer",
|
||||||
|
)
|
||||||
|
if !is_runtime_type(checker, global_type) {
|
||||||
|
global_type = types.I64
|
||||||
|
}
|
||||||
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
||||||
|
}
|
||||||
|
if diagnostic == source.INVALID_DIAGNOSTIC && !is_runtime_type(checker, global_type) {
|
||||||
diagnostic = source.addf(
|
diagnostic = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
global.span,
|
global.span,
|
||||||
@@ -7457,15 +7769,19 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
expr = invalid_hir_expr(checker, global.span, diagnostic)
|
expr = invalid_hir_expr(checker, global.span, diagnostic)
|
||||||
}
|
}
|
||||||
if !global.immutable {
|
if !global.immutable {
|
||||||
diagnostic = source.add(
|
if !types.is_valid(global.type) {
|
||||||
checker.diagnostics,
|
diagnostic = source.addf(
|
||||||
global.span,
|
checker.diagnostics,
|
||||||
"mutable declarations are only valid inside functions",
|
global.span,
|
||||||
)
|
"mutable global '%s' requires a type annotation",
|
||||||
expr = invalid_hir_expr(checker, global.span, diagnostic)
|
symbol_text(checker, global.name),
|
||||||
|
)
|
||||||
|
global_type = types.I64
|
||||||
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
static_value, is_static := static_integer_value(&checker.module, expr)
|
static_value, is_static := static_integer_value(&checker.module, expr)
|
||||||
is_static = is_static && diagnostic == source.INVALID_DIAGNOSTIC
|
is_static = is_static && diagnostic == source.INVALID_DIAGNOSTIC && global.immutable
|
||||||
_ = hir.global_id(len(checker.module.globals))
|
_ = hir.global_id(len(checker.module.globals))
|
||||||
append(
|
append(
|
||||||
&checker.module.globals,
|
&checker.module.globals,
|
||||||
@@ -7477,7 +7793,7 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
static_value = static_value,
|
static_value = static_value,
|
||||||
is_static = is_static,
|
is_static = is_static,
|
||||||
external = false,
|
external = false,
|
||||||
writable = false,
|
writable = !global.immutable,
|
||||||
dependencies = dependencies,
|
dependencies = dependencies,
|
||||||
calls = calls[:],
|
calls = calls[:],
|
||||||
direct_problem = expr_problematic(checker, expr),
|
direct_problem = expr_problematic(checker, expr),
|
||||||
@@ -7805,6 +8121,9 @@ check :: proc(
|
|||||||
source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", symbol_text(&checker, function.name))
|
source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", symbol_text(&checker, function.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if declared_type_named(&checker, function.pkg, function.name) {
|
||||||
|
source.addf(diagnostics, function.span, "function '%s' shadows visible type", symbol_text(&checker, function.name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for global, index in ast_module.globals {
|
for global, index in ast_module.globals {
|
||||||
for previous in ast_module.globals[:index] {
|
for previous in ast_module.globals[:index] {
|
||||||
@@ -7812,6 +8131,9 @@ check :: proc(
|
|||||||
source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name))
|
source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if declared_type_named(&checker, global.pkg, global.name) {
|
||||||
|
source.addf(diagnostics, global.span, "global '%s' shadows visible type", symbol_text(&checker, global.name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validate_type_nodes(&checker)
|
validate_type_nodes(&checker)
|
||||||
|
|||||||
@@ -1234,6 +1234,11 @@ declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: sy
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type_id := types.find_named(&module.type_store, u32(pkg), u32(name))
|
||||||
|
type_item, type_ok := types.node(&module.type_store, type_id)
|
||||||
|
if type_ok && type_item.declared {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+156
-9
@@ -5480,6 +5480,157 @@ valid_runtime_global_initializes_before_main :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
mutable_runtime_globals_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-mutable-global"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/mutable_global", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 52)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
mutable_globals_infer_constraints_and_emit_writable_storage :: proc(t: ^testing.T) {
|
||||||
|
text := `Point :: struct {
|
||||||
|
x i32
|
||||||
|
}
|
||||||
|
counter int = 0
|
||||||
|
ratio float = 1
|
||||||
|
span range = 0..2
|
||||||
|
point Point = Point { x = 1 }
|
||||||
|
values [_]mut i32 = [10, 20]
|
||||||
|
main func() void {
|
||||||
|
counter = 1
|
||||||
|
counter += 1
|
||||||
|
point.x = counter
|
||||||
|
values[1] = point.x
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
found_counter := false
|
||||||
|
found_ratio := false
|
||||||
|
found_span := false
|
||||||
|
found_values := false
|
||||||
|
for global in hir_module.globals {
|
||||||
|
name := symbol.resolve(&symbols, global.name)
|
||||||
|
if name == "counter" {
|
||||||
|
found_counter = global.writable && !global.is_static && types.equal(global.type, types.I8)
|
||||||
|
} else if name == "ratio" {
|
||||||
|
found_ratio = global.writable && !global.is_static && types.equal(global.type, types.F64)
|
||||||
|
} else if name == "span" {
|
||||||
|
found_span = global.writable && !global.is_static && types.is_range(global.type, &hir_module.types)
|
||||||
|
} else if name == "values" {
|
||||||
|
item, ok := types.node(&hir_module.types, global.type)
|
||||||
|
found_values = global.writable && !global.is_static && ok && item.kind == .Array && item.count == 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect(t, found_counter)
|
||||||
|
testing.expect(t, found_ratio)
|
||||||
|
testing.expect(t, found_span)
|
||||||
|
testing.expect(t, found_values)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "internal global"))
|
||||||
|
testing.expect(t, !strings.contains(llvm_text, "internal constant i8 0"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
mutable_global_diagnostics_and_no_shadowing :: proc(t: ^testing.T) {
|
||||||
|
text := `ID :: distinct i32
|
||||||
|
OtherID :: distinct i32
|
||||||
|
ID i32 = 0
|
||||||
|
counter = 0
|
||||||
|
bad i32 = undefined
|
||||||
|
runtime i32 = 1
|
||||||
|
immutable :: 1
|
||||||
|
foo func(foo i32) void {}
|
||||||
|
main func() void {
|
||||||
|
_ = $runtime
|
||||||
|
immutable = 2
|
||||||
|
OtherID i32 = 0
|
||||||
|
local i32 = 0
|
||||||
|
if true {
|
||||||
|
local i32 = 1
|
||||||
|
}
|
||||||
|
for 0..1 |local| {}
|
||||||
|
scope i32 = 0
|
||||||
|
scope: {}
|
||||||
|
mark: {
|
||||||
|
mark i32 = 0
|
||||||
|
}
|
||||||
|
value i32 = value_label: {
|
||||||
|
value_label i32 = 1
|
||||||
|
yield :value_label 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
missing_type := false
|
||||||
|
undefined_global := false
|
||||||
|
not_comptime := false
|
||||||
|
immutable_write := false
|
||||||
|
global_type_shadow := false
|
||||||
|
param_shadow := false
|
||||||
|
local_type_shadow := false
|
||||||
|
local_shadow := false
|
||||||
|
capture_shadow := false
|
||||||
|
label_shadow := false
|
||||||
|
local_label_shadow := false
|
||||||
|
value_label_shadow := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
missing_type = missing_type || strings.contains(diagnostic.message, "mutable global 'counter' requires a type annotation")
|
||||||
|
undefined_global = undefined_global || strings.contains(diagnostic.message, "'undefined' is only valid as a mutable local declaration initializer")
|
||||||
|
not_comptime = not_comptime || strings.contains(diagnostic.message, "global 'runtime' is not comptime-known")
|
||||||
|
immutable_write = immutable_write || strings.contains(diagnostic.message, "assignment target is not writable")
|
||||||
|
global_type_shadow = global_type_shadow || strings.contains(diagnostic.message, "global 'ID' shadows visible type")
|
||||||
|
param_shadow = param_shadow || strings.contains(diagnostic.message, "parameter 'foo' shadows visible function")
|
||||||
|
local_type_shadow = local_type_shadow || strings.contains(diagnostic.message, "local 'OtherID' shadows visible type")
|
||||||
|
local_shadow = local_shadow || strings.contains(diagnostic.message, "local 'local' shadows visible local")
|
||||||
|
capture_shadow = capture_shadow || strings.contains(diagnostic.message, "capture 'local' shadows visible local")
|
||||||
|
label_shadow = label_shadow || strings.contains(diagnostic.message, "label 'scope' shadows visible local")
|
||||||
|
local_label_shadow = local_label_shadow || strings.contains(diagnostic.message, "local 'mark' shadows visible label")
|
||||||
|
value_label_shadow = value_label_shadow || strings.contains(diagnostic.message, "local 'value_label' shadows visible label")
|
||||||
|
}
|
||||||
|
testing.expect(t, missing_type)
|
||||||
|
testing.expect(t, undefined_global)
|
||||||
|
testing.expect(t, not_comptime)
|
||||||
|
testing.expect(t, immutable_write)
|
||||||
|
testing.expect(t, global_type_shadow)
|
||||||
|
testing.expect(t, param_shadow)
|
||||||
|
testing.expect(t, local_type_shadow)
|
||||||
|
testing.expect(t, local_shadow)
|
||||||
|
testing.expect(t, capture_shadow)
|
||||||
|
testing.expect(t, label_shadow)
|
||||||
|
testing.expect(t, local_label_shadow)
|
||||||
|
testing.expect(t, value_label_shadow)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
unused_global_cycle_is_deferred :: proc(t: ^testing.T) {
|
unused_global_cycle_is_deferred :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-cycle-unused"
|
output := "/tmp/brolang-test-cycle-unused"
|
||||||
@@ -8153,7 +8304,7 @@ main func() void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
distinct_type_construction_defers_to_callable_names :: proc(t: ^testing.T) {
|
type_and_function_names_cannot_shadow :: proc(t: ^testing.T) {
|
||||||
text := `Value :: distinct u32
|
text := `Value :: distinct u32
|
||||||
Value func(value i32) i32 {
|
Value func(value i32) i32 {
|
||||||
return value
|
return value
|
||||||
@@ -8174,15 +8325,11 @@ main func() i32 {
|
|||||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
defer hir.destroy_module(&hir_module)
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
found_call := false
|
found := false
|
||||||
found_retype := false
|
for diagnostic in diagnostics.items {
|
||||||
for expr in hir_module.exprs {
|
found = found || strings.contains(diagnostic.message, "function 'Value' shadows visible type")
|
||||||
found_call = found_call || expr.kind == .Call
|
|
||||||
found_retype = found_retype || expr.kind == .Retype
|
|
||||||
}
|
}
|
||||||
testing.expect_value(t, len(diagnostics.items), 0)
|
testing.expect(t, found)
|
||||||
testing.expect(t, found_call)
|
|
||||||
testing.expect(t, !found_retype)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ main func() i32 {
|
|||||||
total = total + 5 # 35
|
total = total + 5 # 35
|
||||||
}
|
}
|
||||||
|
|
||||||
# block scoping: inner x shadows outer x, outer is unchanged after the block
|
# block scoping: inner bindings do not escape the block
|
||||||
x i32 = 1
|
x i32 = 1
|
||||||
if x == 1 {
|
if x == 1 {
|
||||||
x i32 = 100
|
inner_x i32 = 100
|
||||||
if x == 100 {
|
if inner_x == 100 {
|
||||||
total = total + 5 # 40
|
total = total + 5 # 40
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
bad int = 1
|
bad i32 :: undefined
|
||||||
|
|
||||||
read_bad func() int {
|
read_bad func() int {
|
||||||
return bad
|
return bad
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
bad int = 1
|
bad i32 :: undefined
|
||||||
|
|
||||||
read_bad func() int {
|
read_bad func() int {
|
||||||
return bad
|
return bad
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
Point :: struct {
|
||||||
|
x i32
|
||||||
|
}
|
||||||
|
|
||||||
|
counter int = 0
|
||||||
|
ratio float = 1
|
||||||
|
span range = 0..2
|
||||||
|
point Point = Point { x = 1 }
|
||||||
|
values [_]mut i32 = [10, 20]
|
||||||
|
|
||||||
|
bump func(value @mut i32) void {
|
||||||
|
value^ += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
counter = 10
|
||||||
|
counter += 5
|
||||||
|
bump(&counter)
|
||||||
|
point.x += counter
|
||||||
|
values[1] = point.x
|
||||||
|
|
||||||
|
total i32 = counter + point.x + values[1]
|
||||||
|
for span |i| {
|
||||||
|
total += i
|
||||||
|
}
|
||||||
|
if ratio == 1.0 {
|
||||||
|
total += 1
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
@@ -31,12 +31,12 @@ main func() i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# The body-local k shadows only inside the body. The update still targets
|
# Body-local storage stays scoped to the body. The update still targets
|
||||||
# the mutable k declared before the loop.
|
# the mutable k declared before the loop.
|
||||||
k u32 = 0
|
k u32 = 0
|
||||||
while k < 4 : k = k + 1 {
|
while k < 4 : k = k + 1 {
|
||||||
k u32 = 100
|
body_k u32 = 100
|
||||||
if k == 100 {
|
if body_k == 100 {
|
||||||
total = total + 2
|
total = total + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user