style: align layout and ownership documentation

This commit is contained in:
2026-10-27 11:07:31 +01:00
parent 4a134b49dd
commit 84e3ed0cc8
16 changed files with 459 additions and 217 deletions
+2 -2
View File
@@ -426,7 +426,7 @@ set_source proc(
try arraylist.append(&builder.storage.sources, try arraylist.append(&builder.storage.sources,
diagnostics.Source{ diagnostics.Source{
path = path, path = path,
text = text text = text,
}) })
builder.source = text builder.source = text
builder.source_id = id builder.source_id = id
@@ -446,7 +446,7 @@ remove_source proc(builder @mut Builder) void {
builder.source = builder.storage.sources.items[usize(builder.source_id)].text builder.source = builder.storage.sources.items[usize(builder.source_id)].text
} }
#! borrows builder storage; refresh after builder mutation and never pass this view to deinit. #! borrows `builder` storage; refresh after builder mutation and never pass this view to deinit.
view proc(builder @Builder, root NodeId) Tree { view proc(builder @Builder, root NodeId) Tree {
return Tree{ storage = builder.storage, root = root } return Tree{ storage = builder.storage, root = root }
} }
+15 -6
View File
@@ -102,7 +102,7 @@ add_package proc(compilation @mut Compilation) PackageId ! mem.AllocError {
} }
#! copies `path`, scans/parses into the shared arena and takes `bytes` only on success. #! copies `path`, scans/parses into the shared arena and takes `bytes` only on success.
#! failure leaves bytes caller-owned and rolls back source diagnostics; interned names may remain. #! failure leaves `bytes` caller-owned and rolls back source diagnostics; interned names may remain.
add_source proc( add_source proc(
compilation @mut Compilation, compilation @mut Compilation,
package PackageId, package PackageId,
@@ -329,6 +329,7 @@ package_root proc(compilation @mut Compilation, package PackageId) ast.NodeId !
#! checks every package in a separate scope and lowers one module with root-only entry selection. #! checks every package in a separate scope and lowers one module with root-only entry selection.
#! returned IR borrows no frontend data; release it with ir.deinit. #! returned IR borrows no frontend data; release it with ir.deinit.
#! temporary compilation storage uses the retained allocator and is released before returning.
compile proc( compile proc(
compilation @mut Compilation, compilation @mut Compilation,
root_package PackageId, root_package PackageId,
@@ -357,12 +358,16 @@ compile proc(
name := strpool.no_str name := strpool.no_str
failure ?diagnostics.DiagnosticId := null failure ?diagnostics.DiagnosticId := null
for (diagnostic_store.list.items) |diagnostic, index| { for (diagnostic_store.list.items) |diagnostic, index| {
if (diagnostic.span.source == imported.path.source if (
diagnostic.span.source == imported.path.source
and diagnostic.span.start == imported.path.start and diagnostic.span.start == imported.path.start
and diagnostic.span.end == imported.path.end and diagnostic.span.end == imported.path.end
and (diagnostic.code == .package_unavailable and (
diagnostic.code == .package_unavailable
or diagnostic.code == .invalid_import_path or diagnostic.code == .invalid_import_path
or diagnostic.code == .import_cycle)) { or diagnostic.code == .import_cycle
)
) {
failure = diagnostics.DiagnosticId(index) failure = diagnostics.DiagnosticId(index)
break break
} }
@@ -379,8 +384,12 @@ compile proc(
candidate :: path[start..] candidate :: path[start..]
valid := candidate.len > 0 valid := candidate.len > 0
for (candidate) |byte, index| { for (candidate) |byte, index| {
if (!(byte >= 'a' and byte <= 'z') and !(byte >= 'A' and byte <= 'Z') if (
and byte != '_' and !(index > 0 and byte >= '0' and byte <= '9')) valid = false !(byte >= 'a' and byte <= 'z')
and !(byte >= 'A' and byte <= 'Z')
and byte != '_'
and !(index > 0 and byte >= '0' and byte <= '9')
) valid = false
} }
if (valid) { if (valid) {
name = try strpool.intern(&compilation.strings, candidate) name = try strpool.intern(&compilation.strings, candidate)
+1
View File
@@ -93,6 +93,7 @@ run proc(
#! executes an isolated compile-time module with bounded work and stack depth. #! executes an isolated compile-time module with bounded work and stack depth.
#! ownership and diagnostic identities follow run. #! ownership and diagnostic identities follow run.
#! temporary execution storage uses `allocator` and is released before returning.
run_evaluation proc( run_evaluation proc(
allocator mem.Allocator, allocator mem.Allocator,
module @ir.Module, module @ir.Module,
+3
View File
@@ -254,6 +254,7 @@ Module :: struct {
entry_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic entry_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
} }
#! retains `allocator` for executable storage; release the returned module with deinit.
init proc(allocator mem.Allocator) Module { init proc(allocator mem.Allocator) Module {
return Module{ return Module{
allocator = allocator, allocator = allocator,
@@ -264,6 +265,7 @@ init proc(allocator mem.Allocator) Module {
} }
} }
#! retains `allocator` in its lists; transfer the function to a module using the same allocator for cleanup with deinit.
init_function proc(allocator mem.Allocator) Function { init_function proc(allocator mem.Allocator) Function {
return Function{ return Function{
parameters = arraylist.init(allocator), parameters = arraylist.init(allocator),
@@ -273,6 +275,7 @@ init_function proc(allocator mem.Allocator) Function {
} }
} }
#! retains `allocator` in its lists; transfer the block to a module function for cleanup with deinit.
init_block proc(allocator mem.Allocator) Block { init_block proc(allocator mem.Allocator) Block {
return Block{ return Block{
instructions = arraylist.init(allocator), instructions = arraylist.init(allocator),
+1 -1
View File
@@ -287,7 +287,7 @@ scan proc(
try add_token(state, Token{ try add_token(state, Token{
span = make_span(source, cursor, cursor + 2), span = make_span(source, cursor, cursor + 2),
kind = value, kind = value,
}) })
cursor += 2 cursor += 2
continue continue
} }
+7 -2
View File
@@ -91,8 +91,13 @@ emit proc(
for (module.functions.items[..index]) |previous| { for (module.functions.items[..index]) |previous| {
if (mem.eql(previous.external_name, function.external_name)) duplicate = true if (mem.eql(previous.external_name, function.external_name)) duplicate = true
} }
if (duplicate or mem.eql(function.external_name, "write") or mem.eql(function.external_name, "__error") if (
or mem.eql(function.external_name, "snprintf") or mem.eql(function.external_name, "exit")) continue duplicate
or mem.eql(function.external_name, "write")
or mem.eql(function.external_name, "__error")
or mem.eql(function.external_name, "snprintf")
or mem.eql(function.external_name, "exit")
) continue
} }
values :: try mem.alloc(Type, allocator, function.value_count) values :: try mem.alloc(Type, allocator, function.value_count)
defer mem.free(allocator, values) defer mem.free(allocator, values)
+24 -8
View File
@@ -13,6 +13,7 @@ Error :: alias (
| io.ReadError | io.ReadError
) )
#! returns caller-owned storage; release it with mem.free using the same allocator.
@hide:file @hide:file
copy proc(allocator mem.Allocator, text []u8) []mut u8 ! mem.AllocError { copy proc(allocator mem.Allocator, text []u8) []mut u8 ! mem.AllocError {
bytes :: try mem.alloc(u8, allocator, text.len) bytes :: try mem.alloc(u8, allocator, text.len)
@@ -21,6 +22,7 @@ copy proc(allocator mem.Allocator, text []u8) []mut u8 ! mem.AllocError {
} }
#! returns an owned canonical package directory for either a file or directory input. #! returns an owned canonical package directory for either a file or directory input.
#! free the returned slice with mem.free using the same allocator; temporary storage is released before returning.
directory proc(allocator mem.Allocator, path []u8) []mut u8 ! Error { directory proc(allocator mem.Allocator, path []u8) []mut u8 ! Error {
resolved :: try fs.canonical(allocator, path) resolved :: try fs.canonical(allocator, path)
errdefer mem.free(allocator, resolved) errdefer mem.free(allocator, resolved)
@@ -34,11 +36,14 @@ directory proc(allocator mem.Allocator, path []u8) []mut u8 ! Error {
return parent return parent
} }
#! returns a caller-owned joined path; release it with mem.free using the same allocator.
@hide:file @hide:file
join proc(allocator mem.Allocator, base, name []u8) []mut u8 ! mem.AllocError { join proc(allocator mem.Allocator, base, name []u8) []mut u8 ! mem.AllocError {
separator :: if (base.len == 1 and base[0] == '/') usize(0) else usize(1) separator :: if (base.len == 1 and base[0] == '/') usize(0) else usize(1)
if (base.len > maxval!(usize) - separator or name.len > maxval!(usize) - base.len - separator) if (
return .out_of_memory base.len > maxval!(usize) - separator
or name.len > maxval!(usize) - base.len - separator
) return .out_of_memory
bytes :: try mem.alloc(u8, allocator, base.len + separator + name.len) bytes :: try mem.alloc(u8, allocator, base.len + separator + name.len)
memcopy!(bytes[..base.len], base) memcopy!(bytes[..base.len], base)
@@ -55,12 +60,15 @@ exists proc(allocator mem.Allocator, base, name []u8) bool ! Error {
} }
#! finds the nearest ancestor build.hon/build.bro, otherwise uses the supplied directory. #! finds the nearest ancestor build.hon/build.bro, otherwise uses the supplied directory.
#! free the returned slice with mem.free using the same allocator; temporary search storage is released before returning.
project_root proc(allocator mem.Allocator, package_dir []u8) []mut u8 ! Error { project_root proc(allocator mem.Allocator, package_dir []u8) []mut u8 ! Error {
end := package_dir.len end := package_dir.len
while true { while true {
base :: package_dir[..end] base :: package_dir[..end]
if (try exists(allocator, base, "build.hon") or try exists(allocator, base, "build.bro")) if (
return try copy(allocator, base) try exists(allocator, base, "build.hon")
or try exists(allocator, base, "build.bro")
) return try copy(allocator, base)
if (end == 1) break if (end == 1) break
while (end > 1 and package_dir[end - 1] != '/') end -= 1 while (end > 1 and package_dir[end - 1] != '/') end -= 1
@@ -70,9 +78,12 @@ project_root proc(allocator mem.Allocator, package_dir []u8) []mut u8 ! Error {
} }
#! relative imports start at the importing package; @ imports start at the project root. #! relative imports start at the importing package; @ imports start at the project root.
#! free the returned slice with mem.free using the same allocator; temporary path storage is released before returning.
resolve proc(allocator mem.Allocator, root_dir, package_dir, import_path []u8) []mut u8 ! Error { resolve proc(allocator mem.Allocator, root_dir, package_dir, import_path []u8) []mut u8 ! Error {
if (import_path.len == 0 or import_path[0] == '/') return .invalid_path if (import_path.len == 0 or import_path[0] == '/') return .invalid_path
for (import_path) |byte| if (byte == 0) return .invalid_path for (import_path) |byte| {
if (byte == 0) return .invalid_path
}
base := package_dir base := package_dir
path := import_path path := import_path
if (path[0] == '@') { if (path[0] == '@') {
@@ -99,7 +110,8 @@ before proc(left, right []u8) bool {
return left.len < right.len return left.len < right.len
} }
#! lists production sources in bytewise order; caller frees every path and the list. #! lists production sources in bytewise order; free each path with mem.free using the same allocator.
#! release the list with arraylist.deinit, which uses its retained allocator; temporary directory storage is released before returning.
files proc(allocator mem.Allocator, package_dir []u8) arraylist.ArrayList([]u8) ! Error { files proc(allocator mem.Allocator, package_dir []u8) arraylist.ArrayList([]u8) ! Error {
handle := try fs.open_directory(allocator, package_dir) handle := try fs.open_directory(allocator, package_dir)
closing := false closing := false
@@ -116,8 +128,11 @@ files proc(allocator mem.Allocator, package_dir []u8) arraylist.ArrayList([]u8)
and entry.kind != .unknown and entry.kind != .unknown
) continue ) continue
name :: entry.name name :: entry.name
if ((!ends_with(name, ".hon") and !ends_with(name, ".bro")) if (
or ends_with(name, ".test.hon") or ends_with(name, ".test.bro")) continue (!ends_with(name, ".hon") and !ends_with(name, ".bro"))
or ends_with(name, ".test.hon")
or ends_with(name, ".test.bro")
) continue
path :: try join(allocator, package_dir, name) path :: try join(allocator, package_dir, name)
errdefer mem.free(allocator, path) errdefer mem.free(allocator, path)
if (entry.kind != .file and try fs.kind(allocator, path) != .file) { if (entry.kind != .file and try fs.kind(allocator, path) != .file) {
@@ -157,6 +172,7 @@ sift proc(items []mut []u8, start, count usize) void {
} }
#! the compiler limits source size to its u32 span address space; storage remains caller-owned. #! the compiler limits source size to its u32 span address space; storage remains caller-owned.
#! release the returned list with arraylist.deinit, which uses its retained allocator.
read_source proc(allocator mem.Allocator, runtime io.Io, path []u8) arraylist.ArrayList(u8) ! Error { read_source proc(allocator mem.Allocator, runtime io.Io, path []u8) arraylist.ArrayList(u8) ! Error {
return fs.read_file(allocator, runtime, path, usize(maxval!(u32))) catch |err| { return fs.read_file(allocator, runtime, path, usize(maxval!(u32))) catch |err| {
match err { match err {
+32 -13
View File
@@ -47,6 +47,7 @@ State :: struct {
#! lowers checked source into caller-owned executable storage; release with ir.deinit. #! lowers checked source into caller-owned executable storage; release with ir.deinit.
#! no frontend storage or source bytes are retained by the returned module. #! no frontend storage or source bytes are retained by the returned module.
#! retains `allocator` in the module; temporary lowering storage is released before returning.
lower proc( lower proc(
allocator mem.Allocator, allocator mem.Allocator,
tree @ast.Tree, tree @ast.Tree,
@@ -65,6 +66,7 @@ lower proc(
} }
#! lowers the module arena, selecting an entry only from `root_package`. #! lowers the module arena, selecting an entry only from `root_package`.
#! retains `allocator` in the returned module; release it with ir.deinit; temporary lowering storage is released before returning.
lower_package proc( lower_package proc(
allocator mem.Allocator, allocator mem.Allocator,
tree @ast.Tree, tree @ast.Tree,
@@ -78,6 +80,7 @@ lower_package proc(
#! lowers an isolated compile-time call wrapper without applying program entry-name rules. #! lowers an isolated compile-time call wrapper without applying program entry-name rules.
#! the caller supplies a checked parameterless function and owns the returned IR. #! the caller supplies a checked parameterless function and owns the returned IR.
#! retains `allocator` in the returned module; release it with ir.deinit; temporary lowering storage is released before returning.
lower_evaluation proc( lower_evaluation proc(
allocator mem.Allocator, allocator mem.Allocator,
tree @ast.Tree, tree @ast.Tree,
@@ -392,7 +395,9 @@ lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diag
state.module.functions.items[state.function].result_array_type = global.array_type state.module.functions.items[state.function].result_array_type = global.array_type
if (trap_binding(state, id)) return if (trap_binding(state, id)) return
declaration :: ast.statement_decl(state.tree, id) declaration :: ast.statement_decl(state.tree, id)
if (declaration.type) |annotation| if (trap_type(state, annotation)) return if (declaration.type) |annotation| {
if (trap_type(state, annotation)) return
}
value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id)) value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id))
if (state.terminated or trap_type(state, id)) return if (state.terminated or trap_type(state, id)) return
finish(state, value, declaration.span) finish(state, value, declaration.span)
@@ -792,10 +797,14 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
} }
.assignment_stmt: { .assignment_stmt: {
assignment :: ast.assignment_stmt(tree, id) assignment :: ast.assignment_stmt(tree, id)
if (ast.expr_kind(tree, assignment.target) == .index if (
ast.expr_kind(tree, assignment.target) == .index
or ast.expr_kind(tree, assignment.target) == .unary or ast.expr_kind(tree, assignment.target) == .unary
or (ast.expr_kind(tree, assignment.target) == .selection or (
and sema.binding(state.resolution, assignment.target).kind == .none)) { ast.expr_kind(tree, assignment.target) == .selection
and sema.binding(state.resolution, assignment.target).kind == .none
)
) {
value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target)) value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target))
if (state.terminated) return if (state.terminated) return
if (!is_place(state, assignment.target)) { if (!is_place(state, assignment.target)) {
@@ -837,7 +846,9 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) return if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) return
if (trap_binding(state, id)) return if (trap_binding(state, id)) return
declaration :: ast.statement_decl(tree, id) declaration :: ast.statement_decl(tree, id)
if (declaration.type) |annotation| if (trap_type(state, annotation)) return if (declaration.type) |annotation| {
if (trap_type(state, annotation)) return
}
value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id)) value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id))
if (state.terminated or trap_type(state, id)) return if (state.terminated or trap_type(state, id)) return
_ = try emit(state, ir.Instruction{ _ = try emit(state, ir.Instruction{
@@ -1393,8 +1404,10 @@ convert_value proc(state @mut State, value ir.ValueId, actual, expected typechec
length :: try usize_constant(state, typecheck.array_type(state.checked, actual.array_type).len, span) length :: try usize_constant(state, typecheck.array_type(state.checked, actual.array_type).len, span)
return try record_pair(state, try array_shape(state, expected), pointer, length, span) return try record_pair(state, try array_shape(state, expected), pointer, length, span)
} }
if (!typecheck.error_assignable(state.checked, actual, expected) if (
and !typecheck.error_assignable(state.checked, expected, actual)) return value !typecheck.error_assignable(state.checked, actual, expected)
and !typecheck.error_assignable(state.checked, expected, actual)
) return value
source_union := false source_union := false
if (actual.kind == .record) source_union = typecheck.record_type(state.checked, actual.record_type).is_union if (actual.kind == .record) source_union = typecheck.record_type(state.checked, actual.record_type).is_union
if (expected.kind == .enumeration) { if (expected.kind == .enumeration) {
@@ -1701,8 +1714,10 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
if (state.terminated or trap_type(state, id)) return ir.no_value if (state.terminated or trap_type(state, id)) return ir.no_value
return try pointer_address(state, pointer, checked, span) return try pointer_address(state, pointer, checked, span)
} }
if (ast.expr_kind(state.tree, id) == .selection if (
and sema.binding(state.resolution, id).kind == .none) { ast.expr_kind(state.tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none
) {
expression :: ast.selection_expr(state.tree, id) expression :: ast.selection_expr(state.tree, id)
base_type :: typecheck.type_of(state.checked, expression.base) base_type :: typecheck.type_of(state.checked, expression.base)
base := ir.no_value base := ir.no_value
@@ -1892,8 +1907,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
return try lower_string(state, id) return try lower_string(state, id)
} }
.identifier, .selection: { .identifier, .selection: {
if (ast.expr_kind(tree, id) == .selection if (
and sema.binding(state.resolution, id).kind == .none) { ast.expr_kind(tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none
) {
selection :: ast.selection_expr(tree, id) selection :: ast.selection_expr(tree, id)
base_type :: typecheck.type_of(state.checked, selection.base) base_type :: typecheck.type_of(state.checked, selection.base)
if (base_type.kind == .slice or base_type.kind == .string or base_type.kind == .array) { if (base_type.kind == .slice or base_type.kind == .string or base_type.kind == .array) {
@@ -2018,8 +2035,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
} }
left_type :: typecheck.type_of(state.checked, expression.left) left_type :: typecheck.type_of(state.checked, expression.left)
right_type :: typecheck.type_of(state.checked, expression.right) right_type :: typecheck.type_of(state.checked, expression.right)
if ((expression.operator == .equal or expression.operator == .not_equal) if (
and (left_type.kind == .null or right_type.kind == .null)) { (expression.operator == .equal or expression.operator == .not_equal)
and (left_type.kind == .null or right_type.kind == .null)
) {
optional_id :: if (left_type.kind == .null) expression.right else expression.left optional_id :: if (left_type.kind == .null) expression.right else expression.left
value :: try lower_expression(state, optional_id) value :: try lower_expression(state, optional_id)
if (state.terminated or trap_type(state, id)) return ir.no_value if (state.terminated or trap_type(state, id)) return ir.no_value
+11 -6
View File
@@ -1174,9 +1174,12 @@ parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseEr
name ?ast.NodeId := null name ?ast.NodeId := null
type ?ast.NodeId := null type ?ast.NodeId := null
value ?ast.NodeId := null value ?ast.NodeId := null
named :: member_name(current(state).kind) and ( named :: (
(!declaration and next_n_is(state, 1, .equal)) member_name(current(state).kind)
or (declaration and starts_type(state.tokens[state.next_token + 1].kind)) and (
(!declaration and next_n_is(state, 1, .equal))
or (declaration and starts_type(state.tokens[state.next_token + 1].kind))
)
) )
if (named) { if (named) {
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false) token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
@@ -1901,9 +1904,11 @@ consume proc(state @mut State, kind lexer.TokenKind) lexer.Token ! SyntaxError {
@hide:file @hide:file
consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! SyntaxError { consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! SyntaxError {
token :: current(state) token :: current(state)
for (kinds) |kind| if token.kind == kind { for (kinds) |kind| {
state.next_token += 1 if token.kind == kind {
return token state.next_token += 1
return token
}
} }
return .unexpected_token return .unexpected_token
} }
+257 -121
View File
@@ -227,7 +227,7 @@ State :: struct {
pointer_depth usize = 0 pointer_depth usize = 0
} }
#! borrows resolution and diagnostics during checking; Result retains the AST borrow for signature comparisons. #! borrows `resolution` and `diagnostic_store` during checking; Result retains the AST borrow for signature comparisons.
#! temporary constraints are freed on every exit; Result owns types, aggregate descriptors, initializer mappings, and fallthrough obligations. #! temporary constraints are freed on every exit; Result owns types, aggregate descriptors, initializer mappings, and fallthrough obligations.
check proc( check proc(
allocator mem.Allocator, allocator mem.Allocator,
@@ -322,7 +322,7 @@ check_elaborated proc(
try collect_declaration(&state, constraint) try collect_declaration(&state, constraint)
} else try collect_expression(&state, constraint) } else try collect_expression(&state, constraint)
} }
# Preserve binding errors before RHS errors propagate. Indexed writability # preserve binding errors before RHS errors propagate. indexed writability
# waits for shapes; neither check poisons the target's type. # waits for shapes; neither check poisons the target's type.
for 0..count |i| { for 0..count |i| {
if (cells[i].role != .assignment_stmt) continue if (cells[i].role != .assignment_stmt) continue
@@ -357,8 +357,10 @@ check_elaborated proc(
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
if (cells[i].type.kind == .invalid) continue if (cells[i].type.kind == .invalid) continue
binding :: sema.binding(resolution, id) binding :: sema.binding(resolution, id)
if (binding.kind == .declaration and !cells[i].assignment_value if (
and !cells[usize(binding.data.declaration)].fixed and !has_narrowing(&state, id)) { binding.kind == .declaration and !cells[i].assignment_value
and !cells[usize(binding.data.declaration)].fixed and !has_narrowing(&state, id)
) {
join(&state, id, binding.data.declaration) join(&state, id, binding.data.declaration)
} }
} }
@@ -371,7 +373,8 @@ check_elaborated proc(
if (assignment_values) { if (assignment_values) {
for (0..count) |i| { for (0..count) |i| {
id :: ast.NodeId(i) id :: ast.NodeId(i)
if (!cells[i].assignment_value if (
!cells[i].assignment_value
or cells[i].role != .expression or cells[i].role != .expression
or !declaration_reference(tree, id) or !declaration_reference(tree, id)
) continue ) continue
@@ -380,7 +383,7 @@ check_elaborated proc(
if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)] if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)]
} }
} }
# Calls use the signature result before contextual literal inference. # calls use the signature result before contextual literal inference.
try prepare_optional_captures(&state, false) try prepare_optional_captures(&state, false)
for (0..count) |i| { for (0..count) |i| {
id :: ast.NodeId(i) id :: ast.NodeId(i)
@@ -840,9 +843,13 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
view :: ast.array_type(state.tree, id) view :: ast.array_type(state.tree, id)
element := try check_annotation(state, view.element, true) element := try check_annotation(state, view.element, true)
element_binding :: sema.binding(state.resolution, view.element) element_binding :: sema.binding(state.resolution, view.element)
if ((element_binding.kind == .builtin if (
and (element_binding.data.builtin == .int or element_binding.data.builtin == .float)) (
or element.kind == .void or element.kind == .range) { element_binding.kind == .builtin
and (element_binding.data.builtin == .int or element_binding.data.builtin == .float)
)
or element.kind == .void or element.kind == .range
) {
element = invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_element_type, ast.node_span(state.tree, view.element))) element = invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_element_type, ast.node_span(state.tree, view.element)))
state.types[usize(view.element)] = element state.types[usize(view.element)] = element
state.cells[usize(view.element)].type = element state.cells[usize(view.element)].type = element
@@ -920,9 +927,11 @@ collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic) state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
} }
try collect_expression(state, declaration.initializer) try collect_expression(state, declaration.initializer)
if (type_syntax(ast.expr_kind(state.tree, declaration.initializer)) if (
type_syntax(ast.expr_kind(state.tree, declaration.initializer))
and state.cells[usize(id)].type.kind != .invalid and state.cells[usize(id)].type.kind != .invalid
and state.cells[usize(declaration.initializer)].type.kind != .none) { and state.cells[usize(declaration.initializer)].type.kind != .none
) {
state.cells[usize(id)].fixed = true state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = state.cells[usize(declaration.initializer)].type state.cells[usize(id)].type = state.cells[usize(declaration.initializer)].type
} }
@@ -1208,8 +1217,10 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
base :: ast.selection_expr(tree, id).base base :: ast.selection_expr(tree, id).base
if (type_reference(state, base)) { if (type_reference(state, base)) {
checked :: try check_annotation(state, base, true) checked :: try check_annotation(state, base, true)
if (checked.kind != .invalid if (
and (checked.kind != .enumeration or checked.nominal != ast.NodeId(maxval!(u32)))) { checked.kind != .invalid
and (checked.kind != .enumeration or checked.nominal != ast.NodeId(maxval!(u32)))
) {
try fail(state, base, .type_not_value, ast.node_span(tree, base)) try fail(state, base, .type_not_value, ast.node_span(tree, base))
state.cells[usize(base)].type = state.types[usize(base)] state.cells[usize(base)].type = state.types[usize(base)]
} }
@@ -1219,8 +1230,10 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
if (binding.kind == .declaration) { if (binding.kind == .declaration) {
target :: binding.data.declaration target :: binding.data.declaration
role :: state.cells[usize(target)].role role :: state.cells[usize(target)].role
if (role != .parameter and role != .loop_capture and ast.decl_kind(tree, target) == .statement if (
and type_syntax(ast.expr_kind(tree, ast.statement_decl(tree, target).initializer))) { role != .parameter and role != .loop_capture and ast.decl_kind(tree, target) == .statement
and type_syntax(ast.expr_kind(tree, ast.statement_decl(tree, target).initializer))
) {
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id))) state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
} }
} }
@@ -1451,11 +1464,13 @@ bool_equality proc(state @mut State, id ast.NodeId) bool {
right :: group_type(state, expression.right) right :: group_type(state, expression.right)
return ( return (
(expression.operator == .equal or expression.operator == .not_equal) (expression.operator == .equal or expression.operator == .not_equal)
and ((left.kind == .bool and right.kind == .bool) and (
(left.kind == .bool and right.kind == .bool)
or left.kind == .enumeration or right.kind == .enumeration or left.kind == .enumeration or right.kind == .enumeration
or left.kind == .pointer or right.kind == .pointer or left.kind == .pointer or right.kind == .pointer
or left.kind == .optional or right.kind == .optional or left.kind == .optional or right.kind == .optional
or left.kind == .null or right.kind == .null) or left.kind == .null or right.kind == .null
)
) )
} }
@@ -1534,8 +1549,10 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
if (expected.kind == .fallible) { if (expected.kind == .fallible) {
descriptor :: state.arrays.items[usize(expected.array_type)] descriptor :: state.arrays.items[usize(expected.array_type)]
actual := group_type(state, id) actual := group_type(state, id)
if (state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id) if (
and has_narrowing(state, id)) actual = try infer_shape(state, id, false) state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)
and has_narrowing(state, id)
) actual = try infer_shape(state, id, false)
if (actual.kind == .fallible) return if (actual.kind == .fallible) return
target := descriptor.element target := descriptor.element
literal :: state.cells[index].literal literal :: state.cells[index].literal
@@ -1547,16 +1564,24 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
if (ast.record_literal(state.tree, literal).type) |annotation| { if (ast.record_literal(state.tree, literal).type) |annotation| {
if (ast.expr_kind(state.tree, annotation) == .enum_literal) name = ast.enum_literal(state.tree, annotation) if (ast.expr_kind(state.tree, annotation) == .enum_literal) name = ast.enum_literal(state.tree, annotation)
} }
if (name == ast.NodeId(maxval!(u32)) and actual.kind == .none if (
name == ast.NodeId(maxval!(u32)) and actual.kind == .none
and descriptor.element.kind != .record and descriptor.error_type.kind == .record and descriptor.element.kind != .record and descriptor.error_type.kind == .record
and !state.records.items[usize(descriptor.error_type.record_type)].is_union) target = descriptor.error_type and !state.records.items[usize(descriptor.error_type.record_type)].is_union
) target = descriptor.error_type
} }
if (name != ast.NodeId(maxval!(u32)) and type_has_variant(state, descriptor.error_type, name) if (
and !type_has_variant(state, descriptor.element, name)) target = descriptor.error_type name != ast.NodeId(maxval!(u32)) and type_has_variant(state, descriptor.error_type, name)
else if (actual.kind != .none and !assignable_type(state, actual, target) and !type_has_variant(state, descriptor.element, name)
and assignable_type(state, actual, descriptor.error_type)) target = descriptor.error_type ) target = descriptor.error_type
} else if (actual.kind != .none and !assignable_type(state, actual, target) else if (
and assignable_type(state, actual, descriptor.error_type)) target = descriptor.error_type actual.kind != .none and !assignable_type(state, actual, target)
and assignable_type(state, actual, descriptor.error_type)
) target = descriptor.error_type
} else if (
actual.kind != .none and !assignable_type(state, actual, target)
and assignable_type(state, actual, descriptor.error_type)
) target = descriptor.error_type
try demand(state, id, target, code) try demand(state, id, target, code)
return return
} }
@@ -1587,9 +1612,11 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
} }
return return
} }
if (expected.kind == .pointer and state.cells[usize(id)].role == .expression if (
expected.kind == .pointer and state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .unary and ast.expr_kind(state.tree, id) == .unary
and ast.unary_expr(state.tree, id).operator == .address_of) { and ast.unary_expr(state.tree, id).operator == .address_of
) {
state.cells[index].expected = expected state.cells[index].expected = expected
try demand(state, ast.unary_expr(state.tree, id).operand, try demand(state, ast.unary_expr(state.tree, id).operand,
state.pointers.items[usize(expected.pointer_type)].element, code) state.pointers.items[usize(expected.pointer_type)].element, code)
@@ -1604,8 +1631,10 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
return return
} }
literal_node :: state.cells[index].literal literal_node :: state.cells[index].literal
if (expected.nominal != ast.NodeId(maxval!(u32)) if (
and (expected.kind == .record or expected.kind == .array)) return expected.nominal != ast.NodeId(maxval!(u32))
and (expected.kind == .record or expected.kind == .array)
) return
if (literal_node != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, literal_node) == .enum_literal) { if (literal_node != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, literal_node) == .enum_literal) {
previous :: state.cells[index].expected previous :: state.cells[index].expected
if (previous.kind != .none and !same_type(state, previous, expected)) { if (previous.kind != .none and !same_type(state, previous, expected)) {
@@ -1655,8 +1684,10 @@ demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Cod
if (!numeric_kind(expected.kind)) return if (!numeric_kind(expected.kind)) return
if (!state.cells[index].numeric and state.cells[index].type.kind != .none) return if (!state.cells[index].numeric and state.cells[index].type.kind != .none) return
state.cells[index].numeric = true state.cells[index].numeric = true
if (state.cells[index].expected.kind != .none if (
and !same_type(state, state.cells[index].expected, expected)) { state.cells[index].expected.kind != .none
and !same_type(state, state.cells[index].expected, expected)
) {
state.cells[index].type = invalid_type(try diagnostics.add(state.diagnostics, code, ast.node_span(state.tree, id))) state.cells[index].type = invalid_type(try diagnostics.add(state.diagnostics, code, ast.node_span(state.tree, id)))
return return
} }
@@ -1706,8 +1737,13 @@ mutable_target proc(state @mut State, id ast.NodeId, allow_global bool) bool {
base :: group_type(state, view.base) base :: group_type(state, view.base)
if (base.kind == .pointer) { if (base.kind == .pointer) {
pointer :: state.pointers.items[usize(base.pointer_type)] pointer :: state.pointers.items[usize(base.pointer_type)]
return pointer.mutable and (pointer.many return (
or (pointer.element.kind == .array and state.arrays.items[usize(pointer.element.array_type)].mutable)) pointer.mutable
and (
pointer.many
or (pointer.element.kind == .array and state.arrays.items[usize(pointer.element.array_type)].mutable)
)
)
} }
if (base.kind == .slice) return state.arrays.items[usize(base.array_type)].mutable if (base.kind == .slice) return state.arrays.items[usize(base.array_type)].mutable
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base, allow_global) return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base, allow_global)
@@ -1798,8 +1834,10 @@ assignable_type proc(state @State, actual, expected Type) bool {
or error_subset(state.records.items, state.enums.items, actual, target.error_type) or error_subset(state.records.items, state.enums.items, actual, target.error_type)
) )
} }
if ((composed_type(state, actual) or composed_type(state, expected)) if (
and error_subset(state.records.items, state.enums.items, actual, expected)) return true (composed_type(state, actual) or composed_type(state, expected))
and error_subset(state.records.items, state.enums.items, actual, expected)
) return true
if (expected.kind == .optional) { if (expected.kind == .optional) {
child :: state.arrays.items[usize(expected.array_type)].element child :: state.arrays.items[usize(expected.array_type)].element
if (actual.kind == .null) return true if (actual.kind == .null) return true
@@ -1851,9 +1889,11 @@ assignable_value proc(state @mut State, value ast.NodeId, actual, expected Type)
if (!assignable_type(state, actual, expected)) return false if (!assignable_type(state, actual, expected)) return false
target := expected target := expected
while (target.kind == .optional) target = state.arrays.items[usize(target.array_type)].element while (target.kind == .optional) target = state.arrays.items[usize(target.array_type)].element
if (actual.kind == .array and target.kind == .slice if (
actual.kind == .array and target.kind == .slice
and state.arrays.items[usize(target.array_type)].mutable and state.arrays.items[usize(target.array_type)].mutable
and addressable_target(state, value)) return mutable_target(state, value, true) and addressable_target(state, value)
) return mutable_target(state, value, true)
return true return true
} }
@@ -1907,8 +1947,10 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value)) try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
state.types[usize(id)] = state.types[usize(value)] state.types[usize(id)] = state.types[usize(value)]
} }
} else if (expected.kind != .void } else if (
and !(expected.kind == .fallible and state.arrays.items[usize(expected.array_type)].element.kind == .void)) { expected.kind != .void
and !(expected.kind == .fallible and state.arrays.items[usize(expected.array_type)].element.kind == .void)
) {
try fail(state, id, .return_type_mismatch, statement.span) try fail(state, id, .return_type_mismatch, statement.span)
} }
} }
@@ -1939,8 +1981,10 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
return return
} }
if (expression.operator == .dereference) { if (expression.operator == .dereference) {
if (state.types[usize(expression.operand)].kind != .invalid if (
and state.types[usize(expression.operand)].kind != .pointer) { state.types[usize(expression.operand)].kind != .invalid
and state.types[usize(expression.operand)].kind != .pointer
) {
try fail(state, id, .invalid_dereference, expression.span) try fail(state, id, .invalid_dereference, expression.span)
} }
return return
@@ -1966,11 +2010,15 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
} }
return return
} }
if ((expression.operator == .equal or expression.operator == .not_equal) if (
and ((left.kind == .optional and right.kind == .null) or (left.kind == .null and right.kind == .optional))) return (expression.operator == .equal or expression.operator == .not_equal)
if ((expression.operator == .equal or expression.operator == .not_equal) and ((left.kind == .optional and right.kind == .null) or (left.kind == .null and right.kind == .optional))
) return
if (
(expression.operator == .equal or expression.operator == .not_equal)
and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer) and (left.kind == .bool or left.kind == .enumeration or left.kind == .pointer)
and (assignable_type(state, left, right) or assignable_type(state, right, left))) return and (assignable_type(state, left, right) or assignable_type(state, right, left))
) return
if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or !same_type(state, left, right)) { if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or !same_type(state, left, right)) {
try fail(state, id, .invalid_operand, expression.span) try fail(state, id, .invalid_operand, expression.span)
} else if (expression.operator == .divide and left.kind != .float) { } else if (expression.operator == .divide and left.kind != .float) {
@@ -2066,9 +2114,13 @@ validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
and state.enums.items[usize(actual.enum_type)].explicit_backing and state.enums.items[usize(actual.enum_type)].explicit_backing
) )
backing :: immediate_backing(state, actual) backing :: immediate_backing(state, actual)
if (actual.kind != .invalid if (
and (!numeric_kind(backing.kind) and !allowed_enum actual.kind != .invalid
or backing.nominal != ast.NodeId(maxval!(u32)))) { and (
!numeric_kind(backing.kind) and !allowed_enum
or backing.nominal != ast.NodeId(maxval!(u32))
)
) {
try fail(state, argument, .invalid_operand, ast.node_span(tree, argument)) try fail(state, argument, .invalid_operand, ast.node_span(tree, argument))
} }
} }
@@ -2167,14 +2219,18 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
binding :: sema.binding(state.resolution, id) binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return null if (binding.kind != .declaration) return null
target :: binding.data.declaration target :: binding.data.declaration
if (!state.cells[usize(target)].count_declaration if (
and (!allow_local or !state.cells[usize(target)].local)) return null !state.cells[usize(target)].count_declaration
and (!allow_local or !state.cells[usize(target)].local)
) return null
declaration :: ast.statement_decl(state.tree, target) declaration :: ast.statement_decl(state.tree, target)
if (declaration.mutability != .constant) return null if (declaration.mutability != .constant) return null
if (declaration.type) |annotation| { if (declaration.type) |annotation| {
type_binding :: sema.binding(state.resolution, annotation) type_binding :: sema.binding(state.resolution, annotation)
if (!integer_kind(state.types[usize(annotation)].kind) if (
and (type_binding.kind != .builtin or !integer_kind(builtin_kind(type_binding.data.builtin)))) return null !integer_kind(state.types[usize(annotation)].kind)
and (type_binding.kind != .builtin or !integer_kind(builtin_kind(type_binding.data.builtin)))
) return null
} }
return evaluate_integer(state, declaration.initializer, allow_local) return evaluate_integer(state, declaration.initializer, allow_local)
} }
@@ -2224,8 +2280,10 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
@hide:file @hide:file
intern_pointer proc(state @mut State, descriptor PointerType) Type ! mem.AllocError { intern_pointer proc(state @mut State, descriptor PointerType) Type ! mem.AllocError {
for state.pointers.items |existing, i| { for state.pointers.items |existing, i| {
if (existing.mutable == descriptor.mutable and existing.many == descriptor.many if (
and same_type(state, existing.element, descriptor.element)) { existing.mutable == descriptor.mutable and existing.many == descriptor.many
and same_type(state, existing.element, descriptor.element)
) {
return Type{ kind = .pointer, pointer_type = PointerId(i) } return Type{ kind = .pointer, pointer_type = PointerId(i) }
} }
} }
@@ -2241,10 +2299,12 @@ check_pointer_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
element :: try check_annotation(state, view.element, true) element :: try check_annotation(state, view.element, true)
if (element.kind == .invalid) return element if (element.kind == .invalid) return element
binding :: sema.binding(state.resolution, view.element) binding :: sema.binding(state.resolution, view.element)
if (element.kind == .none or element.kind == .void or element.kind == .range if (
element.kind == .none or element.kind == .void or element.kind == .range
or element.kind == .type_value or element.kind == .type_value
or has_inferred_count(state, element) or has_inferred_count(state, element)
or (binding.kind == .builtin and (binding.data.builtin == .int or binding.data.builtin == .float))) { or (binding.kind == .builtin and (binding.data.builtin == .int or binding.data.builtin == .float))
) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_pointer_type, ast.node_span(state.tree, id))) return invalid_type(try diagnostics.add(state.diagnostics, .invalid_pointer_type, ast.node_span(state.tree, id)))
} }
return try intern_pointer(state, PointerType{ element = element, mutable = view.mutable, many = view.many }) return try intern_pointer(state, PointerType{ element = element, mutable = view.mutable, many = view.many })
@@ -2282,9 +2342,11 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
@hide:file @hide:file
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError { intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
for state.arrays.items |existing, i| { for state.arrays.items |existing, i| {
if (existing.kind == descriptor.kind and existing.len == descriptor.len and existing.mutable == descriptor.mutable if (
existing.kind == descriptor.kind and existing.len == descriptor.len and existing.mutable == descriptor.mutable
and same_sentinel(existing.sentinel, descriptor.sentinel) and same_type(state, existing.element, descriptor.element) and same_sentinel(existing.sentinel, descriptor.sentinel) and same_type(state, existing.element, descriptor.element)
and same_type(state, existing.error_type, descriptor.error_type)) { and same_type(state, existing.error_type, descriptor.error_type)
) {
return Type{ kind = descriptor.kind, array_type = ArrayId(i) } return Type{ kind = descriptor.kind, array_type = ArrayId(i) }
} }
} }
@@ -2527,8 +2589,10 @@ same_type proc(state @State, left, right Type) bool {
and error_subset(state.records.items, state.enums.items, right, left) and error_subset(state.records.items, state.enums.items, right, left)
) )
if (a.is_union or b.is_union) return false if (a.is_union or b.is_union) return false
if (a.declaration != ast.NodeId(maxval!(u32)) or b.declaration != ast.NodeId(maxval!(u32)) if (
or a.fields.len != b.fields.len) return false a.declaration != ast.NodeId(maxval!(u32)) or b.declaration != ast.NodeId(maxval!(u32))
or a.fields.len != b.fields.len
) return false
for a.fields |field, i| { for a.fields |field, i| {
other :: b.fields[i] other :: b.fields[i]
if ((field.name == null) != (other.name == null)) return false if ((field.name == null) != (other.name == null)) return false
@@ -2749,9 +2813,11 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
} }
} }
} }
if (state.cells[usize(id)].role == .expression if (
state.cells[usize(id)].role == .expression
and ast.expr_kind(state.tree, id) == .selection and ast.expr_kind(state.tree, id) == .selection
and sema.binding(state.resolution, id).kind == .none) { and sema.binding(state.resolution, id).kind == .none
) {
view :: ast.selection_expr(state.tree, id) view :: ast.selection_expr(state.tree, id)
if (type_reference(state, view.base)) { if (type_reference(state, view.base)) {
enum_shape :: try check_annotation(state, view.base, false) enum_shape :: try check_annotation(state, view.base, false)
@@ -2777,8 +2843,10 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
} }
if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) { if (base.kind == .pointer and base.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(base.pointer_type)] pointer :: state.pointers.items[usize(base.pointer_type)]
if (!pointer.many and pointer.element.kind == .array if (
and ast.expr_kind(state.tree, view.member) == .identifier) { !pointer.many and pointer.element.kind == .array
and ast.expr_kind(state.tree, view.member) == .identifier
) {
member :: ast.source_text(state.tree, ast.node_span(state.tree, view.member)) member :: ast.source_text(state.tree, ast.node_span(state.tree, view.member))
if (mem.eql(member, "len")) { if (mem.eql(member, "len")) {
type :: Type{ kind = .usize } type :: Type{ kind = .usize }
@@ -2837,8 +2905,10 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
binding :: sema.binding(state.resolution, id) binding :: sema.binding(state.resolution, id)
if (binding.kind == .declaration) { if (binding.kind == .declaration) {
target :: binding.data.declaration target :: binding.data.declaration
if (state.cells[usize(target)].role == .declaration and state.cells[usize(target)].fixed if (
and has_inferred_count(state, state.cells[usize(target)].type)) { state.cells[usize(target)].role == .declaration and state.cells[usize(target)].fixed
and has_inferred_count(state, state.cells[usize(target)].type)
) {
declaration :: ast.statement_decl(state.tree, target) declaration :: ast.statement_decl(state.tree, target)
actual :: try infer_shape(state, declaration.initializer, defaults) actual :: try infer_shape(state, declaration.initializer, defaults)
if (actual.kind == .array and !has_inferred_count(state, actual)) { if (actual.kind == .array and !has_inferred_count(state, actual)) {
@@ -2931,8 +3001,10 @@ prepare_optional_captures proc(state @mut State, defaults bool) void ! (mem.Allo
for (0..(state.cells.len)) |j| { for (0..(state.cells.len)) |j| {
if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue
binding :: sema.binding(state.resolution, ast.NodeId(j)) binding :: sema.binding(state.resolution, ast.NodeId(j))
if (binding.kind == .declaration and binding.data.declaration == capture if (
and !has_narrowing(state, ast.NodeId(j))) seed(state, ast.NodeId(j), child) binding.kind == .declaration and binding.data.declaration == capture
and !has_narrowing(state, ast.NodeId(j))
) seed(state, ast.NodeId(j), child)
} }
} }
} }
@@ -2999,8 +3071,10 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
if (state.backings[i].kind != .none) { if (state.backings[i].kind != .none) {
backing :: state.backings[i] backing :: state.backings[i]
if (ast.call_arg_count(tree, id) == 1 if (
and (backing.nominal != ast.NodeId(maxval!(u32)) or !numeric_kind(backing.kind))) { ast.call_arg_count(tree, id) == 1
and (backing.nominal != ast.NodeId(maxval!(u32)) or !numeric_kind(backing.kind))
) {
try context_demand(state, ast.call_arg(tree, id, 0), backing, .type_mismatch, early) try context_demand(state, ast.call_arg(tree, id, 0), backing, .type_mismatch, early)
} }
continue continue
@@ -3029,9 +3103,11 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
@hide:file @hide:file
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) { context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
if (early and expected.kind != .array and expected.kind != .record if (
early and expected.kind != .array and expected.kind != .record
and expected.kind != .pointer and expected.kind != .slice and expected.kind != .optional and expected.kind != .fallible and expected.kind != .pointer and expected.kind != .slice and expected.kind != .optional and expected.kind != .fallible
and expected.kind != .enumeration and !integer_kind(expected.kind)) return and expected.kind != .enumeration and !integer_kind(expected.kind)
) return
try demand(state, id, expected, code) try demand(state, id, expected, code)
} }
@@ -3080,13 +3156,16 @@ prepare_ranges proc(state @mut State, assignment_values, constrain bool) void !
} }
for 0..(state.cells.len) |i| { for 0..(state.cells.len) |i| {
id :: ast.NodeId(i) id :: ast.NodeId(i)
if (state.cells[i].role != .expression if (
state.cells[i].role != .expression
or state.cells[i].assignment_value or state.cells[i].assignment_value
or !declaration_reference(tree, id) or !declaration_reference(tree, id)
) continue ) continue
binding :: sema.binding(state.resolution, id) binding :: sema.binding(state.resolution, id)
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture if (
and !has_narrowing(state, id)) { binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture
and !has_narrowing(state, id)
) {
seed(state, id, group_type(state, binding.data.declaration)) seed(state, id, group_type(state, binding.data.declaration))
} }
} }
@@ -3142,13 +3221,16 @@ specialize_annotations proc(state @mut State) void ! (mem.AllocError | diagnosti
} }
for 0..(state.cells.len) |i| { for 0..(state.cells.len) |i| {
id :: ast.NodeId(i) id :: ast.NodeId(i)
if (state.cells[i].role != .expression if (
state.cells[i].role != .expression
or state.cells[i].assignment_value or state.cells[i].assignment_value
or !declaration_reference(state.tree, id) or !declaration_reference(state.tree, id)
) continue ) continue
binding :: sema.binding(state.resolution, id) binding :: sema.binding(state.resolution, id)
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].fixed if (
and !has_narrowing(state, id)) { binding.kind == .declaration and state.cells[usize(binding.data.declaration)].fixed
and !has_narrowing(state, id)
) {
seed(state, id, state.cells[usize(binding.data.declaration)].type) seed(state, id, state.cells[usize(binding.data.declaration)].type)
} }
} }
@@ -3187,8 +3269,10 @@ intern_record proc(state @mut State, declaration ast.NodeId, fields []mut Record
for fields |field, j| { for fields |field, j| {
slot := j slot := j
if (field.name) |name| slot = named_field(state.tree, existing.fields, name) if (field.name) |name| slot = named_field(state.tree, existing.fields, name)
if (slot == no_index or (field.name == null and existing.fields[slot].name != null) if (
or !same_type(state, field.type, existing.fields[slot].type)) { slot == no_index or (field.name == null and existing.fields[slot].name != null)
or !same_type(state, field.type, existing.fields[slot].type)
) {
equal = false equal = false
break break
} }
@@ -3480,8 +3564,10 @@ check_type_expression proc(state @mut State, id ast.NodeId, nominal bool) Type !
and (operand_binding.data.builtin == .int or operand_binding.data.builtin == .float) and (operand_binding.data.builtin == .int or operand_binding.data.builtin == .float)
) )
if (kind == .distinct_type and type.kind != .invalid) { if (kind == .distinct_type and type.kind != .invalid) {
if (type.kind == .none or type.kind == .void or type.kind == .range or type.kind == .type_value if (
or type.kind == .function or has_inferred_count(state, type) or constraint) { type.kind == .none or type.kind == .void or type.kind == .range or type.kind == .type_value
or type.kind == .function or has_inferred_count(state, type) or constraint
) {
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_distinct_backing, ast.node_span(state.tree, id))) type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_distinct_backing, ast.node_span(state.tree, id)))
} else { } else {
state.backings[usize(id)] = type state.backings[usize(id)] = type
@@ -3570,13 +3656,18 @@ check_enum proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError | diagno
} else valid = false } else valid = false
} }
if (!integer_fits(value, backing.kind)) valid = false if (!integer_fits(value, backing.kind)) valid = false
if (explicit and i != 0 and constant_order( if (
ConstantInteger{ magnitude = members[i - 1].magnitude, negative = members[i - 1].negative }, explicit and i != 0
value, and constant_order(
) >= 0) valid = false ConstantInteger{ magnitude = members[i - 1].magnitude, negative = members[i - 1].negative },
value,
) >= 0
) valid = false
for members[0..i] |member| { for members[0..i] |member| {
if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?) if (
or (member.magnitude == value.magnitude and member.negative == value.negative)) valid = false ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)
or (member.magnitude == value.magnitude and member.negative == value.negative)
) valid = false
} }
if (!valid) { if (!valid) {
failure :: invalid_type(try diagnostics.add(state.diagnostics, .invalid_enum_value, field.span)) failure :: invalid_type(try diagnostics.add(state.diagnostics, .invalid_enum_value, field.span))
@@ -3648,8 +3739,10 @@ check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.Allo
field :: ast.field(state.tree, ast.record_field(state.tree, id, i)) field :: ast.field(state.tree, ast.record_field(state.tree, id, i))
type :: try check_annotation(state, field.type?, true) type :: try check_annotation(state, field.type?, true)
if (type.kind == .invalid) return type if (type.kind == .invalid) return type
if (type.kind == .none or type.kind == .range or type.kind == .type_value or field.value != null if (
or (type.kind == .void and tag.kind == .none and !view.auto_tag)) { type.kind == .none or type.kind == .range or type.kind == .type_value or field.value != null
or (type.kind == .void and tag.kind == .none and !view.auto_tag)
) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_record_field_type, field.span)) return invalid_type(try diagnostics.add(state.diagnostics, .invalid_record_field_type, field.span))
} }
if (field.name == null or named_field(state.tree, fields[0..i], field.name?) != no_index) { if (field.name == null or named_field(state.tree, fields[0..i], field.name?) != no_index) {
@@ -3896,8 +3989,10 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
) )
if (tagged) key = descriptor.tag_type if (tagged) key = descriptor.tag_type
} }
if (subject.kind != .none and !numeric_kind(subject.kind) and subject.kind != .bool if (
and !(subject.kind == .enumeration and subject.nominal == ast.NodeId(maxval!(u32))) and !tagged) { subject.kind != .none and !numeric_kind(subject.kind) and subject.kind != .bool
and !(subject.kind == .enumeration and subject.nominal == ast.NodeId(maxval!(u32))) and !tagged
) {
try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id)) try fail(state, id, .invalid_match_subject, ast.node_span(state.tree, id))
seed(state, id, state.types[usize(id)]) seed(state, id, state.types[usize(id)])
return state.types[usize(id)] return state.types[usize(id)]
@@ -3942,8 +4037,10 @@ infer_match proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
for 0..(state.cells.len) |j| { for 0..(state.cells.len) |j| {
if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue if (state.cells[j].role != .expression or !declaration_reference(state.tree, ast.NodeId(j))) continue
binding :: sema.binding(state.resolution, ast.NodeId(j)) binding :: sema.binding(state.resolution, ast.NodeId(j))
if (binding.kind == .declaration and binding.data.declaration == capture if (
and !has_narrowing(state, ast.NodeId(j))) seed(state, ast.NodeId(j), payload) binding.kind == .declaration and binding.data.declaration == capture
and !has_narrowing(state, ast.NodeId(j))
) seed(state, ast.NodeId(j), payload)
} }
} }
} }
@@ -4103,10 +4200,20 @@ validate_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
} else try compatible(state, pattern, key) } else try compatible(state, pattern, key)
current :: try pattern_interval(state, pattern, key) current :: try pattern_interval(state, pattern, key)
if (current) |interval| { if (current) |interval| {
if ((interval.real and (interval.real_low > interval.real_high if (
or (!interval.inclusive and interval.real_low == interval.real_high))) (
or (!interval.real and (constant_order(interval.low, interval.high) > 0 interval.real and (
or (!interval.inclusive and constant_order(interval.low, interval.high) == 0)))) { interval.real_low > interval.real_high
or (!interval.inclusive and interval.real_low == interval.real_high)
)
)
or (
!interval.real and (
constant_order(interval.low, interval.high) > 0
or (!interval.inclusive and constant_order(interval.low, interval.high) == 0)
)
)
) {
try fail(state, id, .invalid_match_pattern, ast.node_span(state.tree, pattern)) try fail(state, id, .invalid_match_pattern, ast.node_span(state.tree, pattern))
} }
if (key.kind == .bool) { if (key.kind == .bool) {
@@ -4145,8 +4252,10 @@ validate_match proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
@hide:file @hide:file
global_variant proc(state @mut State, name ast.NodeId, payload Type) u64 ! mem.AllocError { global_variant proc(state @mut State, name ast.NodeId, payload Type) u64 ! mem.AllocError {
for state.variants.items |variant, i| { for state.variants.items |variant, i| {
if (ast.name_id(state.tree, variant.name) == ast.name_id(state.tree, name) if (
and same_type(state, variant.payload, payload)) return u64(i + 1) ast.name_id(state.tree, variant.name) == ast.name_id(state.tree, name)
and same_type(state, variant.payload, payload)
) return u64(i + 1)
} }
try arraylist.append(&state.variants, Variant{ name = name, payload = payload }) try arraylist.append(&state.variants, Variant{ name = name, payload = payload })
return u64(state.variants.items.len) return u64(state.variants.items.len)
@@ -4181,8 +4290,10 @@ error_subset proc(records []RecordType, enums []EnumType, actual, expected Type)
if (!descriptor.is_union or descriptor.tag_type.kind != .enumeration) return false if (!descriptor.is_union or descriptor.tag_type.kind != .enumeration) return false
if (descriptor.composed and actual.kind == .record and !records[usize(actual.record_type)].is_union) { if (descriptor.composed and actual.kind == .record and !records[usize(actual.record_type)].is_union) {
for descriptor.fields |field| { for descriptor.fields |field| {
if (field.type.kind == .record and field.type.record_type == actual.record_type if (
and field.type.nominal == actual.nominal) return true field.type.kind == .record and field.type.record_type == actual.record_type
and field.type.nominal == actual.nominal
) return true
} }
return false return false
} }
@@ -4199,8 +4310,10 @@ error_subset proc(records []RecordType, enums []EnumType, actual, expected Type)
if (source.kind != .enumeration) return false if (source.kind != .enumeration) return false
origin :: enums[usize(source.enum_type)] origin :: enums[usize(source.enum_type)]
if (origin.explicit_backing or destination.explicit_backing) { if (origin.explicit_backing or destination.explicit_backing) {
if (!origin.explicit_backing or !destination.explicit_backing if (
or origin.declaration != destination.declaration or origin.backing != destination.backing) return false !origin.explicit_backing or !destination.explicit_backing
or origin.declaration != destination.declaration or origin.backing != destination.backing
) return false
} }
for origin.members |member| { for origin.members |member| {
found := false found := false
@@ -4233,11 +4346,20 @@ check_fallible_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
failure :: try check_annotation(state, expression.right, false) failure :: try check_annotation(state, expression.right, false)
if (success.kind == .invalid) return success if (success.kind == .invalid) return success
if (failure.kind == .invalid) return failure if (failure.kind == .invalid) return failure
valid_error :: failure.kind == .enumeration or (failure.kind == .record valid_error :: (
and (!state.records.items[usize(failure.record_type)].is_union failure.kind == .enumeration
or state.records.items[usize(failure.record_type)].tag_type.kind == .enumeration)) or (
if (!valid_error or success.kind == .none or success.kind == .range or success.kind == .type_value failure.kind == .record
or success.kind == .anyopaque or has_inferred_count(state, success)) { and (
!state.records.items[usize(failure.record_type)].is_union
or state.records.items[usize(failure.record_type)].tag_type.kind == .enumeration
)
)
)
if (
!valid_error or success.kind == .none or success.kind == .range or success.kind == .type_value
or success.kind == .anyopaque or has_inferred_count(state, success)
) {
return invalid_type(try diagnostics.add(state.diagnostics, .invalid_fallible_type, ast.node_span(state.tree, id))) return invalid_type(try diagnostics.add(state.diagnostics, .invalid_fallible_type, ast.node_span(state.tree, id)))
} }
type :: try intern_array(state, ArrayType{ kind = .fallible, element = success, error_type = failure }) type :: try intern_array(state, ArrayType{ kind = .fallible, element = success, error_type = failure })
@@ -4275,8 +4397,10 @@ append_sum_variants proc(state @mut State, variants @mut arraylist.ArrayList(Sum
name := ast.NodeId(maxval!(u32)) name := ast.NodeId(maxval!(u32))
for 0..ast.package_decl_count(state.tree) |i| { for 0..ast.package_decl_count(state.tree) |i| {
declaration :: ast.package_decl(state.tree, i) declaration :: ast.package_decl(state.tree, i)
if (ast.decl_kind(state.tree, declaration) == .statement if (
and ast.statement_decl(state.tree, declaration).initializer == descriptor.declaration) name = declaration ast.decl_kind(state.tree, declaration) == .statement
and ast.statement_decl(state.tree, declaration).initializer == descriptor.declaration
) name = declaration
} }
if (name == ast.NodeId(maxval!(u32))) { if (name == ast.NodeId(maxval!(u32))) {
for 0..(state.cells.len) |i| { for 0..(state.cells.len) |i| {
@@ -4362,8 +4486,10 @@ seed_capture proc(state @mut State, capture ast.NodeId, type Type) void {
for 0..(state.cells.len) |i| { for 0..(state.cells.len) |i| {
if (state.cells[i].role != .expression or !declaration_reference(state.tree, ast.NodeId(i))) continue if (state.cells[i].role != .expression or !declaration_reference(state.tree, ast.NodeId(i))) continue
binding :: sema.binding(state.resolution, ast.NodeId(i)) binding :: sema.binding(state.resolution, ast.NodeId(i))
if (binding.kind == .declaration and binding.data.declaration == capture if (
and !has_narrowing(state, ast.NodeId(i))) seed(state, ast.NodeId(i), type) binding.kind == .declaration and binding.data.declaration == capture
and !has_narrowing(state, ast.NodeId(i))
) seed(state, ast.NodeId(i), type)
} }
} }
@@ -4434,8 +4560,14 @@ validate_fallible_expression proc(state @mut State, id ast.NodeId) void ! (mem.A
if (result.kind == .invalid) return if (result.kind == .invalid) return
if (result.kind != .fallible) { if (result.kind != .fallible) {
try fail(state, id, .try_outside_fallible, ast.node_span(state.tree, id)) try fail(state, id, .try_outside_fallible, ast.node_span(state.tree, id))
} else if (!error_subset(state.records.items, state.enums.items, descriptor.error_type, } else if (
state.arrays.items[usize(result.array_type)].error_type)) { !error_subset(
state.records.items,
state.enums.items,
descriptor.error_type,
state.arrays.items[usize(result.array_type)].error_type,
)
) {
try fail(state, id, .incompatible_propagated_error, ast.node_span(state.tree, id)) try fail(state, id, .incompatible_propagated_error, ast.node_span(state.tree, id))
} }
} }
@@ -4445,8 +4577,10 @@ matching_narrowing_arm proc(state @State, id ast.NodeId) ast.NodeId {
binding :: sema.binding(state.resolution, id) binding :: sema.binding(state.resolution, id)
if (binding.kind != .declaration) return ast.NodeId(maxval!(u32)) if (binding.kind != .declaration) return ast.NodeId(maxval!(u32))
target :: binding.data.declaration target :: binding.data.declaration
if (state.cells[usize(target)].role == .declaration if (
and ast.statement_decl(state.tree, target).mutability == .variable) return ast.NodeId(maxval!(u32)) state.cells[usize(target)].role == .declaration
and ast.statement_decl(state.tree, target).mutability == .variable
) return ast.NodeId(maxval!(u32))
arm := state.cells[usize(id)].narrowing_arm arm := state.cells[usize(id)].narrowing_arm
while (arm != ast.NodeId(maxval!(u32))) { while (arm != ast.NodeId(maxval!(u32))) {
owner :: state.cells[usize(arm)].match_owner owner :: state.cells[usize(arm)].match_owner
@@ -4467,8 +4601,10 @@ has_narrowing proc(state @State, id ast.NodeId) bool {
arm_names_variant proc(state @State, arm, name ast.NodeId) bool { arm_names_variant proc(state @State, arm, name ast.NodeId) bool {
for 0..ast.match_pattern_count(state.tree, arm) |i| { for 0..ast.match_pattern_count(state.tree, arm) |i| {
pattern :: ast.match_pattern(state.tree, arm, i) pattern :: ast.match_pattern(state.tree, arm, i)
if (ast.expr_kind(state.tree, pattern) == .enum_literal if (
and ast.name_id(state.tree, ast.enum_literal(state.tree, pattern)) == ast.name_id(state.tree, name)) return true ast.expr_kind(state.tree, pattern) == .enum_literal
and ast.name_id(state.tree, ast.enum_literal(state.tree, pattern)) == ast.name_id(state.tree, name)
) return true
} }
return false return false
} }
+76 -45
View File
@@ -54,14 +54,17 @@ Instance :: struct {
@hide:file @hide:file
Rewrite :: struct { target ast.NodeId, saved ast.NodeId } Rewrite :: struct { target ast.NodeId, saved ast.NodeId }
#! owns an undo log and retained checking obligations; generated nodes belong to the caller's AST arena. #! owns an undo log and retained checking obligations; deinit releases them with their retained allocator.
#! borrows the caller's AST builder; call deinit before finishing or deinitializing that builder.
#! generated nodes remain arena-owned and are released with the AST.
Result :: struct { Result :: struct {
builder @mut ast.Builder builder @mut ast.Builder
changes arraylist.ArrayList(Rewrite) changes arraylist.ArrayList(Rewrite)
constraints arraylist.ArrayList(ast.NodeId) constraints arraylist.ArrayList(ast.NodeId)
} }
#! restores source nodes so the same compilation may be compiled again. #! restores rewritten source nodes in the borrowed builder and releases the undo log and checking obligations.
#! does not remove generated arena nodes or restore resolution bindings.
deinit proc(result @mut Result) void { deinit proc(result @mut Result) void {
index := result.changes.items.len index := result.changes.items.len
while (index > 0) { while (index > 0) {
@@ -94,8 +97,11 @@ State :: struct {
type_execution_remaining usize = 0 type_execution_remaining usize = 0
} }
#! elaborates templates into cached concrete declarations in the caller-owned arena. #! elaborates templates into cached concrete declarations in the caller-owned `builder` arena.
#! resolution is extended to cover generated nodes; no template reaches checking or lowering. #! extends `resolution` to cover generated nodes; no template reaches checking or lowering.
#! `allocator` supplies returned logs, temporary storage released on every exit, and replacement resolution bindings.
#! use the allocator retained by `resolution`; the returned Result borrows `builder` until the caller calls deinit.
#! on failure, restores rewritten source nodes and releases logs; generated nodes remain arena-owned.
elaborate proc( elaborate proc(
allocator mem.Allocator, allocator mem.Allocator,
builder @mut ast.Builder, builder @mut ast.Builder,
@@ -105,7 +111,7 @@ elaborate proc(
) Result ! Error { ) Result ! Error {
result := Result{ builder = builder, changes = arraylist.init(allocator), constraints = arraylist.init(allocator) } result := Result{ builder = builder, changes = arraylist.init(allocator), constraints = arraylist.init(allocator) }
errdefer deinit(&result) errdefer deinit(&result)
# Explicit evaluation is useful even in packages without generic declarations. # explicit evaluation is useful even in packages without generic declarations.
original :: ast.view(builder, root) original :: ast.view(builder, root)
needed := false needed := false
for (0..ast.node_count(&original)) |i| { for (0..ast.node_count(&original)) |i| {
@@ -250,9 +256,11 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
callee :: ast.call_expr(&state.tree, id).callee callee :: ast.call_expr(&state.tree, id).callee
try expand(state, callee) try expand(state, callee)
resolved :: binding(state, callee) resolved :: binding(state, callee)
if (resolved.kind == .declaration if (
resolved.kind == .declaration
and ast.node_class(&state.tree, resolved.data.declaration) == .function and ast.node_class(&state.tree, resolved.data.declaration) == .function
and template(state, resolved.data.declaration)) { and template(state, resolved.data.declaration)
) {
try specialize_call(state, id, resolved.data.declaration) try specialize_call(state, id, resolved.data.declaration)
return return
} }
@@ -289,7 +297,7 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
} }
} }
# Only probe side-effect-free expressions. Ordinary calls stay runtime unless the # only probe side-effect-free expressions. ordinary calls stay runtime unless the
# source requests evaluation with $(); probing must not execute FFI or emit traps. # source requests evaluation with $(); probing must not execute FFI or emit traps.
@hide:file @hide:file
branch_constant proc(state @State, id ast.NodeId, depth usize) bool { branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
@@ -362,7 +370,7 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
state.depth += 1 state.depth += 1
defer state.depth -= 1 defer state.depth -= 1
count :: ast.function_param_count(&state.tree, function) count :: ast.function_param_count(&state.tree, function)
# A type result has no runtime call: every input participates in specialization. # a type result has no runtime call: every input participates in specialization.
type_result :: is_type(state, ast.function_decl(&state.tree, function).return_type) type_result :: is_type(state, ast.function_decl(&state.tree, function).return_type)
runtime := usize(0) runtime := usize(0)
for (0..count) |i| { for (0..count) |i| {
@@ -551,13 +559,15 @@ instantiate proc(state @mut State, index usize, call ast.NodeId) void ! Error {
} }
for (0..count) |n| { for (0..count) |n| {
resolved :: binding(state, ast.NodeId(n)) resolved :: binding(state, ast.NodeId(n))
if (resolved.kind == .declaration and resolved.data.declaration == parameter if (
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression) mapping[n] = replacement resolved.kind == .declaration and resolved.data.declaration == parameter
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression
) mapping[n] = replacement
} }
} }
clone :: try ast.clone_subtree(state.builder, function, mapping) clone :: try ast.clone_subtree(state.builder, function, mapping)
try refresh(state) try refresh(state)
# Copy original resolution, remapping references to cloned locals and runtime parameters. # copy original resolution, remapping references to cloned locals and runtime parameters.
for (0..count) |i| { for (0..count) |i| {
target :: mapping[i] target :: mapping[i]
if (target == no_node or usize(target) < clone_start) continue if (target == no_node or usize(target) < clone_start) continue
@@ -655,7 +665,7 @@ reject_evaluation proc(state @mut State, call ast.NodeId) void ! Error {
state.bindings.items[usize(call)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } } state.bindings.items[usize(call)] = sema.Binding{ kind = .invalid, data = sema.BindingData{ diagnostic = diagnostic } }
} }
# Failed deferred elaboration must not leave unchecked type expressions in the package. # failed deferred elaboration must not leave unchecked type expressions in the package.
@hide:file @hide:file
reject_type_result proc(state @mut State, call, result ast.NodeId, constraints_start usize) void ! Error { reject_type_result proc(state @mut State, call, result ast.NodeId, constraints_start usize) void ! Error {
try reject_evaluation(state, call) try reject_evaluation(state, call)
@@ -714,8 +724,10 @@ argument_annotation proc(
value :: try materialize(state, arguments[i]) value :: try materialize(state, arguments[i])
for (0..mapping.len) |n| { for (0..mapping.len) |n| {
resolved :: binding(state, ast.NodeId(n)) resolved :: binding(state, ast.NodeId(n))
if (resolved.kind == .declaration and resolved.data.declaration == parameter if (
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression) mapping[n] = value.node resolved.kind == .declaration and resolved.data.declaration == parameter
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression
) mapping[n] = value.node
} }
} }
start :: ast.node_count(&state.tree) start :: ast.node_count(&state.tree)
@@ -738,8 +750,8 @@ evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Erro
return try evaluate(state, id) return try evaluate(state, id)
} }
# Values leaving the temporary interpreter must own no interpreter addresses or module IDs. # values leaving the temporary interpreter must own no interpreter addresses or module IDs.
# Rebuild contextual source literals while the module and its escaped flat cells are alive. # rebuild contextual source literals while the module and its escaped flat cells are alive.
@hide:file @hide:file
serialize_value proc( serialize_value proc(
state @mut State, module @ir.Module, checked @typecheck.Result, state @mut State, module @ir.Module, checked @typecheck.Result,
@@ -759,7 +771,7 @@ serialize_value proc(
result = try ast.add_array_literal(state.builder, span, elements.items) result = try ast.add_array_literal(state.builder, span, elements.items)
} else if (type.kind == .record) { } else if (type.kind == .record) {
record :: typecheck.record_type(checked, type.record_type) record :: typecheck.record_type(checked, type.record_type)
# Untagged storage has no active member; choosing one could discard bytes. # untagged storage has no active member; choosing one could discard bytes.
if (record.is_union and record.tag_type.kind != .enumeration) return no_node if (record.is_union and record.tag_type.kind != .enumeration) return no_node
fields := arraylist.init(ast.NodeId, state.allocator) fields := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&fields) defer arraylist.deinit(&fields)
@@ -786,7 +798,7 @@ serialize_value proc(
child :: ir.aggregate_item(module, value, index) child :: ir.aggregate_item(module, value, index)
result = try serialize_value(state, module, checked, channel, child, span) result = try serialize_value(state, module, checked, channel, child, span)
if (result == no_node) return no_node if (result == no_node) return no_node
# Keep named success/error types distinct even when their members overlap. # keep named success/error types distinct even when their members overlap.
annotation := no_node annotation := no_node
if (channel.nominal != no_node) annotation = channel.nominal if (channel.nominal != no_node) annotation = channel.nominal
else if (channel.kind == .enumeration) annotation = typecheck.enum_type(checked, channel.enum_type).declaration else if (channel.kind == .enumeration) annotation = typecheck.enum_type(checked, channel.enum_type).declaration
@@ -927,7 +939,7 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
} }
try arraylist.append(&arguments, concrete.node) try arraylist.append(&arguments, concrete.node)
} }
# Reuse checking, typed IR and the interpreter: compile-time arithmetic and local # reuse checking, typed IR and the interpreter: compile-time arithmetic and local
# inference must have exactly the same semantics as an ordinary runtime call. # inference must have exactly the same semantics as an ordinary runtime call.
span :: ast.node_span(&state.tree, call) span :: ast.node_span(&state.tree, call)
invocation :: try ast.add_call(state.builder, span, callee.node, arguments.items) invocation :: try ast.add_call(state.builder, span, callee.node, arguments.items)
@@ -1022,7 +1034,7 @@ TypeExecution :: struct {
@hide:file @hide:file
TypeFlow :: enum { next, returned, break, continue, invalid } TypeFlow :: enum { next, returned, break, continue, invalid }
# Type values cannot enter runtime IR. Execute their control flow here, substituting # type values cannot enter runtime IR. execute their control flow here, substituting
# owned constants into snapshots; ordinary value operations still use checked IR. # owned constants into snapshots; ordinary value operations still use checked IR.
@hide:file @hide:file
return_type_value proc(state @mut State, block, first_owned ast.NodeId) ast.NodeId ! Error { return_type_value proc(state @mut State, block, first_owned ast.NodeId) ast.NodeId ! Error {
@@ -1054,7 +1066,7 @@ type_step proc(state @mut State, at ast.NodeId) bool ! Error {
@hide:file @hide:file
prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) void { prepare_type_snapshot proc(state @mut State, execution @mut TypeExecution, id ast.NodeId) void {
if (usize(id) >= execution.mapping.len) return if (usize(id) >= execution.mapping.len) return
# Substituted arguments belong to the caller, including nominal type identity. # substituted arguments belong to the caller, including nominal type identity.
if (id < execution.first_owned) { if (id < execution.first_owned) {
execution.mapping[usize(id)] = id execution.mapping[usize(id)] = id
return return
@@ -1105,7 +1117,7 @@ nominal_type_value proc(state @State, id ast.NodeId) bool {
return kind == .record_type or kind == .union_type or kind == .enum_type or kind == .distinct_type return kind == .record_type or kind == .union_type or kind == .enum_type or kind == .distinct_type
} }
# Select with ordinary checked IR so patterns, overlap and exhaustiveness have # select with ordinary checked IR so patterns, overlap and exhaustiveness have
# the same rules as runtime matching, without checking unexecuted type bodies. # the same rules as runtime matching, without checking unexecuted type bodies.
@hide:file @hide:file
type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error { type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
@@ -1117,7 +1129,7 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
for (0..ast.match_arm_count(&state.tree, id)) |i| { for (0..ast.match_arm_count(&state.tree, id)) |i| {
arm_id :: ast.match_arm(&state.tree, id, i) arm_id :: ast.match_arm(&state.tree, id, i)
arm :: ast.match_arm_view(&state.tree, arm_id) arm :: ast.match_arm_view(&state.tree, arm_id)
# Selector bodies omit source capture uses, but must retain their resolver failures. # selector bodies omit source capture uses, but must retain their resolver failures.
if (arm.capture) |capture| { if (arm.capture) |capture| {
resolved :: binding(state, capture) resolved :: binding(state, capture)
if (resolved.kind == .invalid) { if (resolved.kind == .invalid) {
@@ -1125,8 +1137,13 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
return no_index return no_index
} }
} }
if (i > 0 and arm.expression != ast.match_arm_view(&state.tree, if (
ast.match_arm(&state.tree, id, 0)).expression) { i > 0
and arm.expression != ast.match_arm_view(
&state.tree,
ast.match_arm(&state.tree, id, 0),
).expression
) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.type_mismatch, ast.node_span(&state.tree, arm_id)) .type_mismatch, ast.node_span(&state.tree, arm_id))
return no_index return no_index
@@ -1152,7 +1169,7 @@ type_match_arm proc(state @mut State, id ast.NodeId) usize ! Error {
return usize(selected.magnitude) return usize(selected.magnitude)
} }
# Match checking has already validated every capture. Copy the active payload # match checking has already validated every capture. copy the active payload
# out of owned constant storage, never an address in the temporary interpreter. # out of owned constant storage, never an address in the temporary interpreter.
@hide:file @hide:file
type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error { type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
@@ -1163,8 +1180,10 @@ type_match_capture proc(state @mut State, id ast.NodeId) ast.NodeId ! Error {
payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0)) payload :: ast.field(&state.tree, ast.record_field(&state.tree, value.node, 0))
for (0..ast.record_field_count(&state.tree, annotation)) |i| { for (0..ast.record_field_count(&state.tree, annotation)) |i| {
field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, i)) field :: ast.field(&state.tree, ast.record_field(&state.tree, annotation, i))
if (field.name != null and payload.name != null if (
and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?)) { field.name != null and payload.name != null
and ast.name_id(&state.tree, field.name?) == ast.name_id(&state.tree, payload.name?)
) {
result :: try ast.add_typed_constant(state.builder, result :: try ast.add_typed_constant(state.builder,
ast.node_span(&state.tree, subject), field.type?, payload.value?) ast.node_span(&state.tree, subject), field.type?, payload.value?)
try refresh(state) try refresh(state)
@@ -1208,8 +1227,8 @@ type_value_expression proc(state @mut State, input ast.NodeId) ast.NodeId ! Erro
return expression return expression
} }
# Normalize only selected expression arms before ordinary expansion can visit them. # normalize only selected expression arms before ordinary expansion can visit them.
# Nominal results enter here after their instance declaration is cached. # nominal results enter here after their instance declaration is cached.
@hide:file @hide:file
expand_type_matches proc(state @mut State, id ast.NodeId) bool ! Error { expand_type_matches proc(state @mut State, id ast.NodeId) bool ! Error {
if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .match_expr) { if (ast.node_class(&state.tree, id) == .expression and ast.expr_kind(&state.tree, id) == .match_expr) {
@@ -1233,7 +1252,7 @@ type_execution_value proc(state @mut State, input, annotation ast.NodeId) ast.No
expression :: try type_value_expression(state, input) expression :: try type_value_expression(state, input)
if (expression == no_node) return no_node if (expression == no_node) return no_node
if (!try expand_type_matches(state, annotation)) return no_node if (!try expand_type_matches(state, annotation)) return no_node
# A recursive record needs its cached declaration before expanding its fields. # a recursive record needs its cached declaration before expanding its fields.
if (is_type(state, annotation) and nominal_type_value(state, expression)) { if (is_type(state, annotation) and nominal_type_value(state, expression)) {
concrete :: try materialize(state, Value{ kind = .type, node = expression }) concrete :: try materialize(state, Value{ kind = .type, node = expression })
constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true) constraint :: try ast.add_type_operand(state.builder, ast.node_span(&state.tree, expression), concrete.node, true)
@@ -1327,9 +1346,11 @@ execute_type_statement proc(state @mut State, execution @mut TypeExecution, stat
if (local.declaration == resolved.data.declaration) index = i if (local.declaration == resolved.data.declaration) index = i
} }
} }
if (index == no_index if (
index == no_index
or ast.node_class(&state.tree, execution.locals.items[index].declaration) != .statement or ast.node_class(&state.tree, execution.locals.items[index].declaration) != .statement
or ast.statement_decl(&state.tree, execution.locals.items[index].declaration).mutability != .variable) { or ast.statement_decl(&state.tree, execution.locals.items[index].declaration).mutability != .variable
) {
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, state.evaluation_diagnostic = try diagnostics.add(state.diagnostics,
.invalid_assignment_target, ast.node_span(&state.tree, assignment.target)) .invalid_assignment_target, ast.node_span(&state.tree, assignment.target))
return .invalid return .invalid
@@ -1572,9 +1593,11 @@ evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
result_type :: try builtin(state, id, kind) result_type :: try builtin(state, id, kind)
return try evaluate_ir(state, id, result_type) return try evaluate_ir(state, id, result_type)
} }
if (left.annotation != no_node and right.annotation != no_node if (
left.annotation != no_node and right.annotation != no_node
and left.kind == .integer and right.kind == .integer and left.kind == .integer and right.kind == .integer
and !try same_type(state, left.annotation, right.annotation)) return Value{} and !try same_type(state, left.annotation, right.annotation)
) return Value{}
if (view.operator == .equal or view.operator == .not_equal) { if (view.operator == .equal or view.operator == .not_equal) {
if (left.kind != right.kind) return Value{} if (left.kind != right.kind) return Value{}
equal :: try value_equal(state, left, right) equal :: try value_equal(state, left, right)
@@ -1790,8 +1813,8 @@ type_hash proc(state @mut State, id ast.NodeId) usize ! Error {
.optional_type: return mix(hash, try type_hash(state, ast.type_operand(&state.tree, node))) .optional_type: return mix(hash, try type_hash(state, ast.type_operand(&state.tree, node)))
.sum_type: { .sum_type: {
view :: ast.binary_expr(&state.tree, node) view :: ast.binary_expr(&state.tree, node)
# Set fingerprints must ignore ordering, grouping and repeated members. # set fingerprints must ignore ordering, grouping and repeated members.
# Colliding fingerprints still use full type equality in the cache chain. # colliding fingerprints still use full type equality in the cache chain.
return try type_hash(state, view.left) | try type_hash(state, view.right) return try type_hash(state, view.left) | try type_hash(state, view.right)
} }
.fallible_type: { .fallible_type: {
@@ -1914,9 +1937,11 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
} }
.binary: { .binary: {
view :: ast.binary_expr(&state.tree, id) view :: ast.binary_expr(&state.tree, id)
if (view.operator == .equal or view.operator == .not_equal or view.operator == .less if (
view.operator == .equal or view.operator == .not_equal or view.operator == .less
or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal
or view.operator == .logical_and or view.operator == .logical_or) return try builtin(state, id, .bool) or view.operator == .logical_and or view.operator == .logical_or
) return try builtin(state, id, .bool)
return try expression_type(state, view.left, depth + 1) return try expression_type(state, view.left, depth + 1)
} }
.selection: { .selection: {
@@ -1927,8 +1952,12 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
if (base == no_node) return no_node if (base == no_node) return no_node
member :: ast.source_text(&state.tree, ast.node_span(&state.tree, view.member)) member :: ast.source_text(&state.tree, ast.node_span(&state.tree, view.member))
base_kind :: ast.expr_kind(&state.tree, base) base_kind :: ast.expr_kind(&state.tree, base)
if (mem.eql(member, "len") and (base_kind == .array_type or base_kind == .slice_type if (
or (binding(state, base).kind == .builtin and binding(state, base).data.builtin == .string))) mem.eql(member, "len") and (
base_kind == .array_type or base_kind == .slice_type
or (binding(state, base).kind == .builtin and binding(state, base).data.builtin == .string)
)
)
return try builtin(state, id, .usize) return try builtin(state, id, .usize)
if (base_kind != .record_type and base_kind != .union_type) return no_node if (base_kind != .record_type and base_kind != .union_type) return no_node
for (0..ast.record_field_count(&state.tree, base)) |i| { for (0..ast.record_field_count(&state.tree, base)) |i| {
@@ -2062,11 +2091,13 @@ materialize proc(state @mut State, value Value) Value ! Error {
result := value result := value
span :: ast.node_span(&state.tree, value.node) span :: ast.node_span(&state.tree, value.node)
if (value.kind == .type and nominal_type_value(state, value.node)) { if (value.kind == .type and nominal_type_value(state, value.node)) {
# Refer to declared nominal types, rather than reusing their definition as # refer to declared nominal types, rather than reusing their definition as
# an annotation and turning its value-role checker cell into an annotation. # an annotation and turning its value-role checker cell into an annotation.
for (state.declarations.items) |declaration| { for (state.declarations.items) |declaration| {
if (ast.node_class(&state.tree, declaration) == .statement if (
and ast.statement_decl(&state.tree, declaration).initializer == value.node) { ast.node_class(&state.tree, declaration) == .statement
and ast.statement_decl(&state.tree, declaration).initializer == value.node
) {
result.node = try reference(state, value.node, declaration) result.node = try reference(state, value.node, declaration)
return result return result
} }
+13 -5
View File
@@ -20,17 +20,21 @@ Entry :: struct {
kind Kind kind Kind
} }
#! returns caller-owned zero-terminated storage; release it with mem.free using the same allocator.
@hide:file @hide:file
terminated proc(allocator mem.Allocator, text []u8) []mut u8 ! Error { terminated proc(allocator mem.Allocator, text []u8) []mut u8 ! Error {
if (text.len == 0 or text.len == maxval!(usize)) return .invalid_path if (text.len == 0 or text.len == maxval!(usize)) return .invalid_path
for (text) |byte| if (byte == 0) return .invalid_path for (text) |byte| {
if (byte == 0) return .invalid_path
}
bytes :: try mem.alloc(u8, allocator, text.len + 1) bytes :: try mem.alloc(u8, allocator, text.len + 1)
memcopy!(bytes[..text.len], text) memcopy!(bytes[..text.len], text)
bytes[text.len] = 0 bytes[text.len] = 0
return bytes return bytes
} }
#! returns an owned absolute path with symlinks resolved; free it with the same allocator. #! returns an owned absolute path with symlinks resolved; release it with mem.free using the same allocator.
#! temporary path storage uses `allocator` and is released before returning.
canonical proc(allocator mem.Allocator, path []u8) []mut u8 ! Error { canonical proc(allocator mem.Allocator, path []u8) []mut u8 ! Error {
input :: try terminated(allocator, path) input :: try terminated(allocator, path)
defer mem.free(allocator, input) defer mem.free(allocator, input)
@@ -49,6 +53,7 @@ canonical proc(allocator mem.Allocator, path []u8) []mut u8 ! Error {
} }
#! follows symlinks; nonexistent paths (including dangling links) return missing. #! follows symlinks; nonexistent paths (including dangling links) return missing.
#! temporary path storage uses `allocator` and is released before returning.
kind proc(allocator mem.Allocator, path []u8) Kind ! Error { kind proc(allocator mem.Allocator, path []u8) Kind ! Error {
input :: try terminated(allocator, path) input :: try terminated(allocator, path)
defer mem.free(allocator, input) defer mem.free(allocator, input)
@@ -69,7 +74,9 @@ exists proc(allocator mem.Allocator, path []u8) bool ! Error {
return (try kind(allocator, path)) != .missing return (try kind(allocator, path)) != .missing
} }
#! opens an owned directory handle; path is borrowed only during this call. #! opens an owned directory handle; `path` is borrowed only during this call.
#! temporary path storage uses `allocator` and is released before returning.
#! release the returned handle with close_directory.
open_directory proc(allocator mem.Allocator, path []u8) Directory ! Error { open_directory proc(allocator mem.Allocator, path []u8) Directory ! Error {
input :: try terminated(allocator, path) input :: try terminated(allocator, path)
defer mem.free(allocator, input) defer mem.free(allocator, input)
@@ -119,9 +126,10 @@ close_directory proc(directory @mut Directory) void ! Error {
if (c.closedir(handle) != 0) return .directory_failed if (c.closedir(handle) != 0) return .directory_failed
} }
#! reads a regular file through runtime IO, accepting at most maximum bytes. #! reads a regular file through runtime IO, accepting at most `maximum` bytes.
#! follows symlinks and rejects devices/FIFOs before opening; metadata uses the host filesystem. #! follows symlinks and rejects devices/FIFOs before opening; metadata uses the host filesystem.
#! returned storage belongs to the caller; release with arraylist.deinit. all errors close the file. #! returned storage belongs to the caller and retains `allocator`; release with arraylist.deinit. all errors close the file.
#! temporary path storage uses `allocator` and is released before returning.
read_file proc( read_file proc(
allocator mem.Allocator, allocator mem.Allocator,
runtime io.Io, runtime io.Io,
+5 -2
View File
@@ -26,7 +26,8 @@ raw_alloc proc(
} }
#! resize caller-owned storage with its original allocator and alignment. #! resize caller-owned storage with its original allocator and alignment.
#! on allocation failure with nonzero `new_size`, original storage remains caller-owned; release the result with raw_free. #! on allocation failure with nonzero `new_size`, original storage remains caller-owned.
#! release returned storage with raw_free using the same allocator and alignment, with `new_size` as its size.
raw_realloc proc( raw_realloc proc(
allocator Allocator, allocator Allocator,
memory ?*mut u8, memory ?*mut u8,
@@ -49,7 +50,9 @@ raw_free proc(
eql proc($T type, left, right []T) bool { eql proc($T type, left, right []T) bool {
if (left.len != right.len) return false if (left.len != right.len) return false
for (0..left.len) |i| if (left[i] != right[i]) return false for (0..left.len) |i| {
if (left[i] != right[i]) return false
}
return true return true
} }
+5 -3
View File
@@ -19,7 +19,7 @@ RunError :: enum {
} }
#! run a program synchronously without a shell, searching PATH for `arguments[0]`. #! run a program synchronously without a shell, searching PATH for `arguments[0]`.
#! arguments and zero-terminated strings are borrowed until this call returns. #! `arguments` and zero-terminated strings are borrowed until this call returns.
#! an empty argument vector, empty executable name, or embedded NUL is invalid. #! an empty argument vector, empty executable name, or embedded NUL is invalid.
#! the child inherits the current environment and stdin/stdout/stderr. #! the child inherits the current environment and stdin/stdout/stderr.
#! temporary argument descriptors use `allocator` and are freed on every return. #! temporary argument descriptors use `allocator` and are freed on every return.
@@ -28,7 +28,9 @@ run proc(allocator mem.Allocator, arguments [][;0]u8) i32 ! (mem.AllocError | Ru
if (arguments.len == 0) return .invalid_arguments if (arguments.len == 0) return .invalid_arguments
if (arguments[0].len == 0) return .invalid_arguments if (arguments[0].len == 0) return .invalid_arguments
for arguments |argument| { for arguments |argument| {
for (argument) |byte| if (byte == 0) return .invalid_arguments for (argument) |byte| {
if (byte == 0) return .invalid_arguments
}
} }
if (arguments.len == maxval!(usize)) return .out_of_memory if (arguments.len == maxval!(usize)) return .out_of_memory
@@ -61,6 +63,6 @@ run proc(allocator mem.Allocator, arguments [][;0]u8) i32 ! (mem.AllocError | Ru
signal :: status[0] & 127 signal :: status[0] & 127
if (signal == 0) return i32(divtrunc!(status[0], 256) & 255) if (signal == 0) return i32(divtrunc!(status[0], 256) & 255)
if (signal != 127) return i32(128 + signal) if (signal != 127) return i32(128 + signal)
# A traced child can report a stop even without WUNTRACED; wait for termination. # a traced child can report a stop even without WUNTRACED; wait for termination.
} }
} }
+3 -1
View File
@@ -58,7 +58,9 @@ parse_float proc(allocator mem.Allocator, text []u8) f64 ! (mem.AllocError | Par
magnitude >= u64(9218868437227405312) magnitude >= u64(9218868437227405312)
or (magnitude == 0 and nonzero) or (magnitude == 0 and nonzero)
or (range_error and magnitude >= u64(4607182418800017408)) or (range_error and magnitude >= u64(4607182418800017408))
) return .out_of_range ) {
return .out_of_range
}
return value return value
} }
+4 -2
View File
@@ -31,8 +31,10 @@ init proc($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
compile_error!("static string map key is too long") compile_error!("static string map key is too long")
} }
for (0..i) |prior| if mem.eql(u8, entry.0, entries[prior].0) { for (0..i) |prior| {
compile_error!("duplicate static string map key") if mem.eql(u8, entry.0, entries[prior].0) {
compile_error!("duplicate static string map key")
}
} }
keys[i] = entry.0 keys[i] = entry.0