feat: specialize generic type and value parameters

This commit is contained in:
2026-10-26 13:10:52 +01:00
parent 9058135326
commit 6886d743b5
18 changed files with 2499 additions and 103 deletions
+428 -5
View File
@@ -1,10 +1,11 @@
#! grammar:
#! * type ..... -> type_sum ['!' type]; type_sum -> type_atom {'|' type_atom}; type_atom includes '(' type ')', identifier {'.' member}, '?' type_atom, ('@' | '*') ['mut'] type_atom, '@proc' '(' parameters ')' type, '[' (expr | '_') ']' ['mut'] type_atom, '[' [';' expr] ']' ['mut'] type_atom, record/enum/union declarations, and ('distinct' | 'alias') type.
#! * type ..... -> type_sum ['!' type]; type_sum -> type_atom {'|' type_atom}; type_atom includes '(' type ')', identifier {'.' member | '(' [expr {',' expr} [',']] ')'}, '?' type_atom, ('@' | '*') ['mut'] type_atom, ['@'] 'proc' '(' parameters ')' type, '[' (expr | '_') ']' ['mut'] type_atom, '[' [';' expr] ']' ['mut'] type_atom, record/enum/union declarations, and ('distinct' | 'alias') type.
#! * binding ... -> :: | :=
#! * literal .. -> int | float | string | 'true' | 'false' | 'null'
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block)
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
#! * block .... -> '{' {stmt} '}'
#! * arm ...... -> block | stmt (single statements become canonical blocks)
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
@@ -67,6 +68,7 @@ ExprKind :: enum {
sum_type
try_expr
catch_expr
typed_constant
}
DeclKind :: enum {
@@ -169,6 +171,7 @@ NodeKind :: enum {
stmt_defer
stmt_errdefer
stmt_block
expr_typed_constant
}
@hide:file
@@ -196,16 +199,17 @@ Storage :: struct {
extra arraylist.ArrayList(u32)
spans arraylist.ArrayList(Span)
sources arraylist.ArrayList(diagnostics.Source)
generated arraylist.ArrayList([]mut u8)
}
#! owns node storage and source descriptors; borrows registered source paths and text.
#! owns node storage, source descriptors, and generated text; borrows registered source paths and text.
Builder :: struct {
storage Storage
source []u8
source_id diagnostics.SourceId = diagnostics.SourceId(0)
}
#! owns storage after finish, or borrows it after view; registered source paths and text remain borrowed.
#! owns storage after finish, or borrows it after view; source paths and non-generated text remain borrowed.
Tree :: struct {
storage Storage
root NodeId
@@ -238,6 +242,7 @@ ParamView :: struct {
span Span
name Span
type NodeId
comptime bool = false
}
UnaryExprView :: struct {
@@ -253,6 +258,11 @@ BinaryExprView :: struct {
right NodeId
}
TypedConstantView :: struct {
type NodeId
value NodeId
}
CatchExprView :: struct {
span Span
operand NodeId
@@ -390,6 +400,7 @@ init_builder proc(allocator mem.Allocator, source []u8) Builder {
extra = arraylist.init(allocator),
spans = arraylist.init(allocator),
sources = arraylist.init(allocator),
generated = arraylist.init(allocator),
},
source = source,
}
@@ -432,8 +443,10 @@ source_files proc(tree @Tree) []diagnostics.Source {
return tree.storage.sources.items
}
#! releases owned AST and source-descriptor storage, without freeing source paths or text.
#! releases owned AST, generated text, and source descriptors, without freeing borrowed sources.
deinit_builder proc(builder @mut Builder) void {
for (builder.storage.generated.items) |text| mem.free(builder.storage.generated.allocator, text)
arraylist.deinit(&builder.storage.generated)
arraylist.deinit(&builder.storage.nodes)
arraylist.deinit(&builder.storage.extra)
arraylist.deinit(&builder.storage.spans)
@@ -449,14 +462,17 @@ finish proc(builder @mut Builder, root NodeId) Tree {
extra = arraylist.init(allocator),
spans = arraylist.init(allocator),
sources = arraylist.init(allocator),
generated = arraylist.init(allocator),
}
builder.source = &[]
builder.source_id = diagnostics.SourceId(0)
return tree
}
#! releases owned AST and source-descriptor storage, without freeing source paths or text.
#! releases owned AST, generated text, and source descriptors, without freeing borrowed sources.
deinit proc(tree @mut Tree) void {
for (tree.storage.generated.items) |text| mem.free(tree.storage.generated.allocator, text)
arraylist.deinit(&tree.storage.generated)
arraylist.deinit(&tree.storage.nodes)
arraylist.deinit(&tree.storage.extra)
arraylist.deinit(&tree.storage.spans)
@@ -513,6 +529,17 @@ add_binary proc(
}, span)
}
#! preserves a generated constant's declared type without introducing a runtime binding.
add_typed_constant proc(
builder @mut Builder, at Span, type, value NodeId,
) NodeId ! mem.AllocError {
return try add_node(builder, Node{
kind = .expr_typed_constant,
data0 = NodeData{ node_id = type },
data1 = NodeData{ node_id = value },
}, at)
}
add_binary_type proc(
builder @mut Builder, span Span, left, right NodeId, fallible bool,
) NodeId ! mem.AllocError {
@@ -936,6 +963,7 @@ add_param proc(
) NodeId ! mem.AllocError {
extra :: try add_extra(builder, name.start)
_ = try add_extra(builder, name.end)
_ = try add_extra(builder, 0)
return try add_node(builder, Node{
kind = .param_decl,
main = NodeData{ extra = extra },
@@ -944,6 +972,13 @@ add_param proc(
}, span)
}
set_param_comptime proc(builder @mut Builder, id NodeId) void {
node :: builder.storage.nodes.items[usize(id)]
debug.assert(node.kind == .param_decl)
builder.storage.extra.items[usize(node.main.extra) + 2] = 1
}
add_function_decl proc(
builder @mut Builder,
span Span,
@@ -980,6 +1015,15 @@ function_external proc(tree @Tree, id NodeId) bool {
return tree.storage.extra.items[usize(node.data1.extra) - 1] != 0
}
function_generic proc(tree @Tree, id NodeId) bool {
node :: get_node(&tree.storage, id)
if (node.kind != .func_decl and node.kind != .expr_function_type) return false
for 0..function_param_count(tree, id) |index| {
if (param(tree, function_param(tree, id, index)).comptime) return true
}
return false
}
add_import_decl proc(
builder @mut Builder,
span, path Span,
@@ -1279,6 +1323,7 @@ param proc(tree @Tree, id NodeId) ParamView {
span = get_span(&tree.storage, id),
name = span_from_extra(&tree.storage, usize(node.main.extra), get_span(&tree.storage, id).source),
type = node.data0.node_id,
comptime = tree.storage.extra.items[usize(node.main.extra) + 2] != 0,
}
}
@@ -1339,6 +1384,7 @@ expression_kind proc(kind NodeKind) ExprKind {
.expr_sum_type: .sum_type
.expr_try: .try_expr
.expr_catch: .catch_expr
.expr_typed_constant: .typed_constant
else: unreachable
}
}
@@ -1364,6 +1410,15 @@ binary_expr proc(tree @Tree, id NodeId) BinaryExprView {
}
}
typed_constant proc(tree @Tree, id NodeId) TypedConstantView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_typed_constant)
return TypedConstantView{
type = node.data0.node_id,
value = node.data1.node_id,
}
}
catch_expr proc(tree @Tree, id NodeId) CatchExprView {
node :: get_node(&tree.storage, id)
debug.assert(node.kind == .expr_catch)
@@ -1688,3 +1743,371 @@ maybe_node_id proc(value u32) ?NodeId {
if (value == u32(no_node)) return null
return NodeId(value)
}
NodeClass :: enum {
expression, statement, function, parameter, block, field, match_arm, package, import, invalid
}
@hide:file
NodeLayout :: struct {
class NodeClass = .expression
direct_children u32 = 0
extra_pointers u32 = 0
extra_start usize = 0
extra_count usize = 0
extra_children u32 = 0
list_start usize = 0
list_end usize = 0
}
#! describes owned extra storage and child slots, excluding names, spans, and flags.
@hide:file
node_layout proc(node Node) NodeLayout {
layout := NodeLayout{}
match node.kind {
.literal_int, .literal_float, .literal_string, .literal_bool, .literal_null,
.expr_identifier, .invalid_expr: {}
.expr_unary: layout.direct_children = 2
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant,
.expr_fallible_type, .expr_sum_type, .expr_array_type, .expr_slice_type:
layout.direct_children = 6
.expr_pointer_type: layout.direct_children = 4
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_enum_literal,
.expr_try: layout.direct_children = 1
.expr_call, .expr_builtin_call, .expr_function_type, .expr_array_literal,
.expr_record_type, .expr_record_literal, .expr_enum_type, .expr_match,
.block, .package: {
layout.extra_pointers = 6
layout.extra_start = usize(node.data0.extra)
layout.extra_count = usize(node.data1.extra - node.data0.extra)
layout.list_end = layout.extra_count
if (node.kind == .block) layout.class = .block
else if (node.kind == .package) layout.class = .package
else if (node.kind != .expr_array_literal and node.kind != .expr_record_type)
layout.direct_children = 1
}
.expr_union_type: {
layout.extra_pointers = 7
layout.extra_start = usize(node.main.extra)
layout.extra_count = usize(node.data1.extra - node.main.extra)
layout.extra_children = 1
layout.list_start = 2
layout.list_end = layout.extra_count
}
.record_field: {
layout.class = .field
layout.direct_children = 7
}
.match_arm: {
layout.class = .match_arm
layout.direct_children = 1
layout.extra_pointers = 6
layout.extra_start = usize(node.data0.extra)
layout.extra_count = usize(node.data1.extra - node.data0.extra)
layout.extra_children = 1
layout.list_start = 3
layout.list_end = layout.extra_count
}
.expr_catch: {
layout.direct_children = 6
layout.extra_pointers = 1
layout.extra_start = usize(node.main.extra)
layout.extra_count = 2
layout.extra_children = 1
}
.expr_slice: {
layout.direct_children = 1
layout.extra_pointers = 2
layout.extra_start = usize(node.data0.extra)
layout.extra_count = 3
layout.extra_children = 7
}
.stmt_expr, .stmt_return, .stmt_block, .stmt_defer, .stmt_errdefer: {
layout.class = .statement
layout.direct_children = 1
if (node.kind == .stmt_defer or node.kind == .stmt_errdefer)
layout.direct_children = 3
}
.stmt_break, .stmt_continue, .invalid_stmt: layout.class = .statement
.stmt_assignment: {
layout.class = .statement
layout.direct_children = 3
}
.stmt_while: {
layout.class = .statement
layout.direct_children = 7
}
.stmt_if: {
layout.class = .statement
layout.direct_children = 6
layout.extra_pointers = 1
layout.extra_start = usize(node.main.extra)
layout.extra_count = 2
layout.extra_children = 3
}
.stmt_for: {
layout.class = .statement
layout.direct_children = 3
layout.extra_pointers = 4
layout.extra_start = usize(node.data1.extra)
layout.extra_count = 4
layout.extra_children = 3
}
.stmt_decl: {
layout.class = .statement
layout.extra_pointers = 1
layout.extra_start = usize(node.main.extra)
layout.extra_count = 4
layout.extra_children = 12
}
.func_decl: {
layout.class = .function
layout.direct_children = 2
layout.extra_pointers = 5
layout.extra_start = usize(node.main.extra)
layout.extra_count = usize(node.data1.extra - node.main.extra)
layout.extra_children = 4
layout.list_start = 4
layout.list_end = layout.extra_count - 1
}
.param_decl: {
layout.class = .parameter
layout.direct_children = 2
layout.extra_pointers = 1
layout.extra_start = usize(node.main.extra)
layout.extra_count = 3
}
.import_decl: {
layout.class = .import
layout.extra_pointers = 1
layout.extra_start = usize(node.main.extra)
layout.extra_count = 2
}
.invalid_decl: layout.class = .invalid
}
return layout
}
@hide:file
has_slot proc(mask u32, index usize) bool {
return (mask & (u32(1) << u32(index))) != 0
}
@hide:file
node_word proc(node Node, index usize) NodeData {
return match index {
0: node.main
1: node.data0
2: node.data1
else: unreachable
}
}
@hide:file
set_node_word proc(node @mut Node, index usize, word NodeData) void {
match index {
0: node.main = word
1: node.data0 = word
2: node.data1 = word
else: unreachable
}
}
node_class proc(tree @Tree, id NodeId) NodeClass {
return node_layout(get_node(&tree.storage, id)).class
}
#! counts actual AST children, excluding absent optional nodes and non-node metadata.
child_count proc(tree @Tree, id NodeId) usize {
node :: get_node(&tree.storage, id)
layout :: node_layout(node)
count := layout.list_end - layout.list_start
for (0..usize(3)) |i| {
if (has_slot(layout.direct_children, i) and node_word(node, i).node_id != no_node)
count += 1
}
for (0..usize(4)) |i| {
if (!has_slot(layout.extra_children, i)) continue
if (NodeId(tree.storage.extra.items[layout.extra_start + i]) != no_node) count += 1
}
return count
}
#! returns a child in stable payload order; `index` must be less than child_count.
child proc(tree @Tree, id NodeId, index usize) NodeId {
node :: get_node(&tree.storage, id)
layout :: node_layout(node)
remaining := index
for (0..usize(3)) |i| {
if (!has_slot(layout.direct_children, i)) continue
value :: node_word(node, i).node_id
if (value == no_node) continue
if (remaining == 0) return value
remaining -= 1
}
for (0..usize(4)) |i| {
if (!has_slot(layout.extra_children, i)) continue
value :: NodeId(tree.storage.extra.items[layout.extra_start + i])
if (value == no_node) continue
if (remaining == 0) return value
remaining -= 1
}
debug.assert(remaining < layout.list_end - layout.list_start)
return NodeId(tree.storage.extra.items[layout.extra_start + layout.list_start + remaining])
}
#! clones owned payload storage and preserves spans and interned names.
#! `mapping` covers original nodes, initially maxval!(NodeId), with substitutions prefilled.
#! nodes outside `mapping` are already concrete and returned unchanged.
clone_subtree proc(
builder @mut Builder, root NodeId, mapping []mut NodeId,
) NodeId ! mem.AllocError {
if (root == no_node or usize(root) >= mapping.len) return root
if (mapping[usize(root)] != no_node) return mapping[usize(root)]
original :: get_node(&builder.storage, root)
span :: get_span(&builder.storage, root)
layout :: node_layout(original)
cloned := original
start :: builder.storage.extra.items.len
for (0..layout.extra_count) |i| {
value :: builder.storage.extra.items[layout.extra_start + i]
_ = try add_extra(builder, value)
}
for (0..usize(3)) |i| {
if (has_slot(layout.extra_pointers, i)) {
offset :: usize(node_word(original, i).extra) - layout.extra_start
set_node_word(&cloned, i, NodeData{ extra = u32(start + offset) })
}
if (has_slot(layout.direct_children, i)) {
value :: try clone_subtree(builder, node_word(original, i).node_id, mapping)
set_node_word(&cloned, i, NodeData{ node_id = value })
}
}
for (0..layout.extra_count) |i| {
is_child :: (
(i < 4 and has_slot(layout.extra_children, i))
or (i >= layout.list_start and i < layout.list_end)
)
if (!is_child) continue
original_child :: NodeId(builder.storage.extra.items[layout.extra_start + i])
value :: try clone_subtree(builder, original_child, mapping)
builder.storage.extra.items[start + i] = u32(value)
}
result :: try add_node(builder, cloned, span)
mapping[usize(root)] = result
return result
}
#! shallow-copies the source kind and payload, retaining the target's diagnostic span.
replace_node proc(builder @mut Builder, target, source NodeId) void {
builder.storage.nodes.items[usize(target)] = get_node(&builder.storage, source)
}
#! shallow-copies kind, payload, and span; extra storage remains shared.
copy_node proc(builder @mut Builder, id NodeId) NodeId ! mem.AllocError {
node :: get_node(&builder.storage, id)
span :: get_span(&builder.storage, id)
return try add_node(builder, node, span)
}
#! replaces package children without changing its node identity or source span.
set_package_declarations proc(
builder @mut Builder, root NodeId, declarations []NodeId,
) void ! mem.AllocError {
debug.assert(get_node(&builder.storage, root).kind == .package)
start :: builder.storage.extra.items.len
for (declarations) |declaration| _ = try add_extra(builder, u32(declaration))
builder.storage.nodes.items[usize(root)].data0 = NodeData{ extra = u32(start) }
builder.storage.nodes.items[usize(root)].data1 = NodeData{ extra = u32(builder.storage.extra.items.len) }
}
#! retains generated decimal text until deinit_builder or ownership transfer through finish.
#! generated literals retain `at` for diagnostics; read their spelling through literal_text.
add_integer proc(builder @mut Builder, at Span, magnitude u64) NodeId ! mem.AllocError {
digits [20]mut u8 := undefined
start := digits.len
remaining := magnitude
while true {
start -= 1
digits[start] = '0' + u8(rem!(remaining, 10))
remaining = divtrunc!(remaining, 10)
if (remaining == 0) break
}
return try add_generated_literal(builder, at, .int, digits[start..])
}
#! retains generated boolean text with the same ownership and provenance rules as add_integer.
add_boolean proc(builder @mut Builder, at Span, value bool) NodeId ! mem.AllocError {
text :: if (value) "true" else "false"
return try add_generated_literal(builder, at, .bool, text)
}
@hide:file
add_generated_literal proc(
builder @mut Builder, at Span, kind LiteralKind, bytes []u8,
) NodeId ! mem.AllocError {
debug.assert(usize(at.source) < builder.storage.sources.items.len)
allocator :: builder.storage.generated.allocator
text :: try mem.alloc(u8, allocator, bytes.len)
errdefer mem.free(allocator, text)
memcopy!(text, bytes)
index :: builder.storage.generated.items.len
debug.assert(u64(index) < u64(maxval!(u32)))
try arraylist.append(&builder.storage.generated, text)
errdefer _ = arraylist.pop(&builder.storage.generated)
id :: try add_literal(builder, kind, at)
builder.storage.nodes.items[usize(id)].main = NodeData{ extra = u32(index) }
builder.storage.nodes.items[usize(id)].data0 = NodeData{ extra = 1 }
return id
}
#! borrows a literal's spelling, independently of its diagnostic source span.
literal_text proc(tree @Tree, id NodeId) []u8 {
node :: get_node(&tree.storage, id)
if (node.data0.extra == 1) return tree.storage.generated.items[usize(node.main.extra)]
return source_text(tree, get_span(&tree.storage, id))
}
StringError :: enum { invalid_escape }
#! decodes one byte from quoted literal text without allocation.
#! start `cursor` at one and call only while it is less than `text.len - 1`.
string_next proc(text []u8, cursor @mut usize) u8 ! StringError {
debug.assert(text.len >= 2 and cursor^ >= 1 and cursor^ < text.len - 1)
end :: text.len - 1
byte := text[cursor^]
cursor^ += 1
if (byte != '\\') return byte
if (cursor^ >= end) return .invalid_escape
escape :: text[cursor^]
cursor^ += 1
match escape {
'\\': byte = '\\'
'"': byte = '"'
'n': byte = '\n'
'r': byte = '\r'
't': byte = '\t'
'0': byte = 0
'x': {
if (end - cursor^ < 2) return .invalid_escape
high := u8(0)
low := u8(0)
if (hex_digit(text[cursor^])) |digit| high = digit
else return .invalid_escape
if (hex_digit(text[cursor^ + 1])) |digit| low = digit
else return .invalid_escape
byte = high * 16 + low
cursor^ += 2
}
else: return .invalid_escape
}
return byte
}
@hide:file
hex_digit proc(byte u8) ?u8 {
if (byte >= '0' and byte <= '9') return byte - '0'
if (byte >= 'a' and byte <= 'f') return byte - 'a' + 10
if (byte >= 'A' and byte <= 'F') return byte - 'A' + 10
return null
}
+56
View File
@@ -40,6 +40,7 @@ exposes_typed_declaration_views test {
try testing.expect_equal("value", source_text(&tree, statement.name))
}
exposes_array_type_literal_and_index_views test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
@@ -112,3 +113,58 @@ removes_only_registered_source_without_allocating_builder test {
replacement :: try set_source(&builder, "replacement.hon", "")
try testing.expect_equal(diagnostics.SourceId(0), replacement)
}
owns_generated_literal_text_across_finish test {
tree Tree := undefined
{
builder := init_builder(mem.c_allocator, "")
defer deinit_builder(&builder)
source :: try set_source(&builder, "generated.hon", "input")
at :: Span{ start = 0, end = 5, source = source }
zero :: try add_integer(&builder, at, 0)
maximum :: try add_integer(&builder, at, maxval!(u64))
enabled :: try add_boolean(&builder, at, true)
disabled :: try add_boolean(&builder, at, false)
try testing.expect_equal(source, builder.source_id)
try testing.expect_equal("input", builder.source)
declarations [4]NodeId :: [zero, maximum, enabled, disabled]
root :: try add_package(&builder, at, declarations[..])
tree = finish(&builder, root)
}
defer deinit(&tree)
zero :: package_decl(&tree, 0)
maximum :: package_decl(&tree, 1)
enabled :: package_decl(&tree, 2)
disabled :: package_decl(&tree, 3)
try testing.expect_equal(ExprKind.literal_int, expr_kind(&tree, zero))
try testing.expect_equal("0", literal_text(&tree, zero))
try testing.expect_equal("18446744073709551615", literal_text(&tree, maximum))
try testing.expect_equal(ExprKind.literal_bool, expr_kind(&tree, enabled))
try testing.expect_equal("true", literal_text(&tree, enabled))
try testing.expect_equal("false", literal_text(&tree, disabled))
try testing.expect_equal(1, source_files(&tree).len)
try testing.expect_equal(diagnostics.SourceId(0), node_span(&tree, zero).source)
try testing.expect_equal(0, node_span(&tree, zero).start)
try testing.expect_equal(5, node_span(&tree, zero).end)
try testing.expect_equal("input", source_text(&tree, node_span(&tree, zero)))
try testing.expect_equal("generated.hon", source_files(&tree)[usize(node_span(&tree, zero).source)].path)
try testing.expect_equal("generated.hon", source_files(&tree)[usize(node_span(&tree, maximum).source)].path)
}
streams_decoded_string_literal_bytes test {
text :: "\"a\\\\\\\"\\n\\r\\t\\0\\x4a\\xFf\""
expected [9]u8 :: ['a', '\\', '"', '\n', '\r', '\t', 0, 'J', 255]
cursor := usize(1)
for (expected) |byte| try testing.expect_equal(byte, try string_next(text, &cursor))
try testing.expect_equal(text.len - 1, cursor)
malformed [6][]u8 :: ["\"\\q\"", "\"\\x\"", "\"\\x1\"", "\"\\xG0\"", "\"\\x0G\"", "\"\\\""]
for (malformed) |literal| {
cursor = 1
_ = string_next(literal, &cursor) catch |error| {
try testing.expect_equal(StringError.invalid_escape, error)
continue
}
try testing.expect(false)
}
}
+6 -2
View File
@@ -14,6 +14,7 @@ import "@source/lower"
import "@source/loader"
import "@source/ir"
typecheck :: import "@source/sema/check"
import "@source/sema/specialize"
#! identities are valid only within their owning compilation.
SourceId :: alias diagnostics.SourceId
@@ -350,7 +351,7 @@ compile proc(
}
imports := arraylist.init(sema.Import, compilation.allocator)
defer arraylist.deinit(&imports)
tree :: ast.view(&compilation.builder, compilation.module_root?)
tree := ast.view(&compilation.builder, compilation.module_root?)
for (compilation.imports.items) |link| {
imported :: ast.import_decl(&tree, link.node)
name := strpool.no_str
@@ -392,7 +393,10 @@ compile proc(
}
resolution := try sema.resolve_packages(compilation.allocator, &tree, roots.items, imports.items, &compilation.strings, diagnostic_store)
defer sema.deinit(&resolution)
checked := try typecheck.check(compilation.allocator, &tree, &resolution, diagnostic_store)
elaborated := try specialize.elaborate(compilation.allocator, &compilation.builder, compilation.module_root?, &resolution, diagnostic_store)
defer specialize.deinit(&elaborated)
tree = ast.view(&compilation.builder, compilation.module_root?)
checked := try typecheck.check_elaborated(compilation.allocator, &tree, &resolution, elaborated.constraints.items, diagnostic_store)
defer typecheck.deinit(&checked)
return try lower.lower_package(compilation.allocator, &tree, &resolution, &checked, roots.items[usize(root_package)], diagnostic_store)
}
+34
View File
@@ -68,6 +68,40 @@ keeps_package_declarations_distinct_and_ir_independent test {
try testing.expect_equal(i64(6), right_result.value.data.integer)
}
recompiles_specialized_calls_without_borrowing_previous_ir test {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
modules [2]mut ir.Module := undefined
{
session := init(mem.c_allocator)
defer deinit(&session)
root :: try add_package(&session)
bytes := try owned_text(
`identity proc($T type, value T) T { return value }
`Box proc($T type) type { return struct { value T } }
`main proc() i16 {
` box Box(i16) :: { value = identity(i16, 40) }
` return identity(box.value) + 2
`}
,
)
defer arraylist.deinit(&bytes)
_ = try add_source(&session, root, "generic.hon", &bytes, &diagnostic_store)
modules[0] = try compile(&session, root, &diagnostic_store)
errdefer ir.deinit(&modules[0])
modules[1] = try compile(&session, root, &diagnostic_store)
}
defer ir.deinit(&modules[0])
defer ir.deinit(&modules[1])
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
for (0..modules.len) |index| {
outcome := try interpreter.run(mem.c_allocator, &modules[index], &diagnostic_store)
defer interpreter.deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(42), outcome.value.data.integer)
}
}
dependency_traps_keep_original_spans_and_remain_demand_driven test {
mains [2][]u8 :: [
`dep :: import "../dependency"
+8
View File
@@ -107,6 +107,10 @@ Code :: enum {
catch_type_mismatch
errdefer_outside_fallible
cleanup_control_flow
comptime_argument_required
cannot_infer_generic
specialization_cycle
specialization_limit
}
Diagnostic :: struct {
@@ -235,5 +239,9 @@ messages :: $enummap.init(Code, []u8, {
catch_type_mismatch = "catch handler must produce the success type or terminate",
errdefer_outside_fallible = "errdefer requires an enclosing fallible function",
cleanup_control_flow = "cleanup cannot escape with return, break, continue, or try",
comptime_argument_required = "generic argument must be a compile-time value",
cannot_infer_generic = "cannot infer consistent compile-time arguments from runtime arguments",
specialization_cycle = "compile-time specialization depends on its unfinished result",
specialization_limit = "compile-time specialization exceeds its recursion or instance limit",
})
+1
View File
@@ -301,6 +301,7 @@ scan proc(
',': .comma
'|': .pipe
'@': .at
'$': .dollar
'&': .ampersand
'^': .caret
'?': .question
+23 -2
View File
@@ -11,7 +11,7 @@ reports_invalid_input_and_keeps_scanning test {
state := init(mem.c_allocator)
defer deinit(&state)
try scan(&state, "if name # comment\n$1. 2 3.5 \"hi\"", &pool, &diagnostic_store)
try scan(&state, "if name # comment\n~1. 2 3.5 \"hi\"", &pool, &diagnostic_store)
try testing.expect_equal(9, state.tokens.items.len)
try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind)
@@ -32,6 +32,27 @@ reports_invalid_input_and_keeps_scanning test {
try testing.expect_equal(u32(7), state.tokens.items[1].span.end)
}
scans_compile_time_parameter_markers test {
pool := strpool.init(mem.c_allocator)
defer strpool.deinit(&pool)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
state := init(mem.c_allocator)
defer deinit(&state)
try scan(&state, "$T $K, $V type $N usize", &pool, &diagnostic_store)
expected [12]TokenKind :: [
.dollar, .ident, .dollar, .ident, .comma, .dollar, .ident, .ident,
.dollar, .ident, .ident, .eof,
]
try testing.expect_equal(expected.len, state.tokens.items.len)
for (expected) |kind, index| try testing.expect_equal(kind, state.tokens.items[index].kind)
try testing.expect_equal(u32(0), state.tokens.items[0].span.start)
try testing.expect_equal(u32(1), state.tokens.items[0].span.end)
try testing.expect_equal(u32(15), state.tokens.items[8].span.start)
try testing.expect_equal(u32(16), state.tokens.items[8].span.end)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
reserves_boolean_keywords_and_scans_comparisons test {
pool := strpool.init(mem.c_allocator)
defer strpool.deinit(&pool)
@@ -226,7 +247,7 @@ scans_import_keywords_and_preserves_source_in_tokens_and_diagnostics test {
state := init(mem.c_allocator)
defer deinit(&state)
state.source_id = diagnostics.SourceId(7)
try scan(&state, "import test $\n1.\n\"unfinished", &pool, &diagnostic_store)
try scan(&state, "import test ~\n1.\n\"unfinished", &pool, &diagnostic_store)
try testing.expect_equal(TokenKind.import, state.tokens.items[0].kind)
try testing.expect_equal(TokenKind.test, state.tokens.items[1].kind)
for (state.tokens.items) |token| try testing.expect_equal(state.source_id, token.span.source)
+1 -1
View File
@@ -52,7 +52,7 @@ TokenKind :: enum {
star, slash
range, range_inclusive
at, ampersand, caret
at, ampersand, caret, dollar
question, semicolon
pipe
+10 -57
View File
@@ -1768,6 +1768,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
}
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id)
match ast.expr_kind(tree, id) {
.typed_constant: {
if (trap_type(state, id)) return ir.no_value
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
}
.invalid: {
invalid :: ast.invalid(tree, id)
trap(state, invalid.diagnostic, invalid.span)
@@ -1849,7 +1853,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (trap_type(state, id)) return ir.no_value
return try constant(state, ir.Value{
type = .bool,
data = ir.ValueData{ boolean = mem.eql(ast.source_text(tree, span), "true") },
data = ir.ValueData{ boolean = mem.eql(ast.literal_text(tree, id), "true") },
}, span)
}
.literal_string: {
@@ -1959,7 +1963,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
})
}
if (expression.operator == .negate and ir.integer_bits(type) != 0 and !ir.unsigned_integer(type) and ast.expr_kind(tree, expression.operand) == .literal_int) {
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
text :: ast.literal_text(tree, expression.operand)
magnitude_limit :: ir.integer_max(type) + 1
if (integer_magnitude(text, magnitude_limit)) |magnitude| {
if (magnitude == magnitude_limit) {
@@ -2099,7 +2103,7 @@ exact_float_integer proc(text []u8, value f64) bool {
@hide:file
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
text :: ast.source_text(state.tree, span)
text :: ast.literal_text(state.tree, id)
type :: lower_type(state, typecheck.type_of(state.checked, id))
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
if (integer_magnitude(text, ir.integer_max(type))) |magnitude| {
@@ -2129,14 +2133,6 @@ lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
return ir.no_value
}
@hide:file
hex_digit proc(byte u8) ?u8 {
if (byte >= '0' and byte <= '9') return byte - '0'
if (byte >= 'a' and byte <= 'f') return byte - 'a' + 10
if (byte >= 'A' and byte <= 'F') return byte - 'A' + 10
return null
}
@hide:file
lower_string proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
span :: ast.node_span(state.tree, id)
@@ -2147,57 +2143,14 @@ lower_string proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
length := usize(0)
cursor := usize(1)
end :: text.len - 1
valid := true
while (cursor < end) {
byte := text[cursor]
cursor += 1
if (byte == '\\') {
if (cursor >= end) {
valid = false
break
}
escape :: text[cursor]
cursor += 1
match escape {
'\\': byte = '\\'
'"': byte = '"'
'n': byte = '\n'
'r': byte = '\r'
't': byte = '\t'
'0': byte = 0
'x': {
if (end - cursor < 2) {
valid = false
break
}
high := u8(0)
low := u8(0)
if (hex_digit(text[cursor])) |digit| high = digit
else {
valid = false
break
}
if (hex_digit(text[cursor + 1])) |digit| low = digit
else {
valid = false
break
}
byte = high * 16 + low
cursor += 2
}
else: {
valid = false
break
}
}
byte :: ast.string_next(text, &cursor) catch |_| {
trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span)
return ir.no_value
}
owned[length] = byte
length += 1
}
if (!valid) {
trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span)
return ir.no_value
}
if (state.module.strings.items.len > usize(maxval!(ir.StringId)))
return .out_of_memory
+51 -27
View File
@@ -34,6 +34,13 @@ State :: struct {
match_arm_body bool = false
}
@hide:file
ParameterName :: struct {
token lexer.Token
start u32
comptime bool
}
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
#! `allocator` supplies returned node storage and temporary lists released before return.
#! the caller owns the Tree and must release it with ast.deinit.
@@ -178,13 +185,20 @@ parse_parameters proc(
state @mut State, params @mut arraylist.ArrayList(ast.NodeId),
) void ! ParseError {
skip_newlines(state)
names arraylist.ArrayList(lexer.Token) := arraylist.init(state.allocator)
names arraylist.ArrayList(ParameterName) := arraylist.init(state.allocator)
defer arraylist.deinit(&names)
while !next_is(state, .close_paren) {
arraylist.clear(&names)
while true {
start :: current(state).span.start
comptime :: next_is(state, .dollar)
if (comptime) state.next_token += 1
token :: try consume(state, .ident)
try arraylist.append(&names, token)
try arraylist.append(&names, ParameterName{
token = token,
start = start,
comptime = comptime,
})
if (starts_type(current(state).kind)) break
_ = try consume(state, .comma)
}
@@ -193,11 +207,12 @@ parse_parameters proc(
for (names.items) |parameter_name| {
param_id :: try ast.add_param(
state.builder,
ast.Span{ start = parameter_name.span.start, end = type_end, source = parameter_name.span.source },
parameter_name.span,
parameter_name.data.str_id,
ast.Span{ start = parameter_name.start, end = type_end, source = parameter_name.token.span.source },
parameter_name.token.span,
parameter_name.token.data.str_id,
type_id,
)
if (parameter_name.comptime) ast.set_param_comptime(state.builder, param_id)
try arraylist.append(params, param_id)
}
skip_newlines(state)
@@ -733,25 +748,7 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1
mutable :: next_is(state, .mut)
if (mutable) state.next_token += 1
if (next_is(state, .proc)) {
state.next_token += 1
_ = try consume(state, .open_paren)
params := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&params)
try parse_parameters(state, &params)
_ = try consume(state, .close_paren)
result :: try parse_type(state)
span :: ast.Span{
start = token.span.start,
end = state.tokens[state.next_token - 1].span.end,
source = token.span.source,
}
if (mutable or token.kind == .star) {
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_pointer_type, span)
return try ast.add_invalid_expr(state.builder, span, diagnostic)
}
return try ast.add_function_type(state.builder, span, result, params.items)
}
if (next_is(state, .proc)) return try parse_function_type(state, token.span, mutable or token.kind == .star)
element :: try parse_type_atom(state)
return try ast.add_pointer_type(state.builder, ast.Span{
start = token.span.start,
@@ -799,13 +796,19 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
count, element, mutable,
)
}
if next_is(state, .proc) return try parse_function_type(state, current(state).span, false)
if next_is(state, .struct) return try parse_record_type(state)
if next_is(state, .enum) return try parse_enum_type(state)
if next_is(state, .union) return try parse_union_type(state)
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
token :: try consume(state, .ident)
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
while next_is(state, .dot) {
while true {
if (next_is(state, .open_paren)) {
type = try parse_call(state, type, token.span.start, false)
continue
}
if (!next_is(state, .dot)) break
state.next_token += 1
member :: try consume_member(state, false)
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
@@ -818,12 +821,33 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
return type
}
@hide:file
parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.NodeId ! ParseError {
_ = try consume(state, .proc)
_ = try consume(state, .open_paren)
params := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&params)
try parse_parameters(state, &params)
_ = try consume(state, .close_paren)
result :: try parse_type(state)
span :: ast.Span{
start = start.start,
end = state.tokens[state.next_token - 1].span.end,
source = start.source,
}
if (invalid) {
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_pointer_type, span)
return try ast.add_invalid_expr(state.builder, span, diagnostic)
}
return try ast.add_function_type(state.builder, span, result, params.items)
}
@hide:file
starts_type proc(kind lexer.TokenKind) bool {
return (
kind == .ident or kind == .open_bracket or kind == .struct
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
or kind == .at or kind == .star or kind == .question or kind == .open_paren
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc
)
}
@@ -1576,7 +1600,7 @@ named_binding_starts proc(state @State) bool {
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
match token.kind {
.at, .star, .question: return try parse_type(state)
.at, .star, .question, .proc: return try parse_type(state)
.struct: return try parse_record_type(state)
.enum: return try parse_enum_type(state)
.union: return try parse_union_type(state)
+86 -1
View File
@@ -107,10 +107,95 @@ parses_function_structure_through_tree_views test {
try testing.expect_equal("U", strpool.get_str(&strings, ast.name_id(&tree, third.type))?)
try testing.expect_equal(first.type, second.type)
try testing.expect(first.type != third.type)
try testing.expect(!first.comptime)
try testing.expect(!second.comptime)
try testing.expect(!third.comptime)
try testing.expect(!ast.function_generic(&tree, declaration))
try testing.expect_equal(1, ast.block_statement_count(&tree, function.body))
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
parses_compile_time_parameter_groups_and_callbacks test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`generic proc($T type, $K, $V type, $N usize, $first, second T, $build proc(value T) T, legacy @proc(value T) T) T {}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
declaration :: ast.package_decl(&tree, 0)
try testing.expect(ast.function_generic(&tree, declaration))
expected [8]bool :: [true, true, true, true, true, false, true, false]
try testing.expect_equal(expected.len, ast.function_param_count(&tree, declaration))
for (expected) |comptime, index| {
parameter :: ast.param(&tree, ast.function_param(&tree, declaration, index))
try testing.expect_equal(comptime, parameter.comptime)
}
first :: ast.param(&tree, ast.function_param(&tree, declaration, 0))
key :: ast.param(&tree, ast.function_param(&tree, declaration, 1))
value :: ast.param(&tree, ast.function_param(&tree, declaration, 2))
count :: ast.param(&tree, ast.function_param(&tree, declaration, 3))
try testing.expect_equal("T", ast.source_text(&tree, first.name))
try testing.expect_equal("$T type", ast.source_text(&tree, first.span))
try testing.expect_equal("$K, $V type", ast.source_text(&tree, key.span))
try testing.expect_equal("$V type", ast.source_text(&tree, value.span))
try testing.expect_equal(key.type, value.type)
try testing.expect_equal("usize", ast.source_text(&tree, ast.node_span(&tree, count.type)))
callback :: ast.param(&tree, ast.function_param(&tree, declaration, 6)).type
legacy :: ast.param(&tree, ast.function_param(&tree, declaration, 7)).type
try testing.expect_equal(ast.ExprKind.function_type, ast.expr_kind(&tree, callback))
try testing.expect_equal(ast.ExprKind.function_type, ast.expr_kind(&tree, legacy))
try testing.expect_equal("proc(value T) T", ast.source_text(&tree, ast.node_span(&tree, callback)))
try testing.expect_equal("@proc(value T) T", ast.source_text(&tree, ast.node_span(&tree, legacy)))
try testing.expect_equal(1, ast.function_param_count(&tree, callback))
try testing.expect(!ast.param(&tree, ast.function_param(&tree, callback, 0)).comptime)
}
parses_dependent_type_calls_in_annotations_and_constructors test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`example proc($T type, $N usize, input library.Buffer(T, N)) library.ArrayList(T) {
` direct library.Buffer(T, N) :: library.Buffer(T, N){}
` chained library.Factory(T).Buffer(N) :: library.Factory(T).Buffer(N){}
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
declaration :: ast.package_decl(&tree, 0)
function :: ast.function_decl(&tree, declaration)
input :: ast.param(&tree, ast.function_param(&tree, declaration, 2))
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, input.type))
try testing.expect_equal(2, ast.call_arg_count(&tree, input.type))
try testing.expect_equal("library.Buffer", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, input.type).callee)))
try testing.expect_equal("T", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, input.type, 0))))
try testing.expect_equal("N", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, input.type, 1))))
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, function.return_type))
try testing.expect_equal(1, ast.call_arg_count(&tree, function.return_type))
direct :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0))
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, direct.type?))
constructor :: ast.record_literal(&tree, direct.initializer).type?
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, constructor))
try testing.expect_equal("library.Buffer(T, N)", ast.source_text(&tree, ast.node_span(&tree, constructor)))
chained :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1))
selected :: ast.selection_expr(&tree, ast.call_expr(&tree, chained.type?).callee)
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, selected.base))
try testing.expect_equal("Buffer", ast.source_text(&tree, ast.node_span(&tree, selected.member)))
try testing.expect_equal(1, ast.call_arg_count(&tree, chained.type?))
}
recovers_invalid_declaration_and_preserves_sibling test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
@@ -1355,7 +1440,7 @@ preserves_array_error_ids_control_bodies_and_following_siblings test {
defer diagnostics.deinit(&diagnostic_store)
source ::
`main proc() void {
` bad [$]i64 :: [$, 2]
` bad [~]i64 :: [~, 2]
` while (items[0) { break }
` for items[0 |i| { continue }
` unfinished :: [1,
+38 -3
View File
@@ -234,6 +234,17 @@ check proc(
tree @ast.Tree,
resolution @sema.Resolution,
diagnostic_store @mut diagnostics.Store,
) Result ! (mem.AllocError | diagnostics.CapacityError) {
return try check_elaborated(allocator, tree, resolution, &[], diagnostic_store)
}
#! checks additional compile-time expressions retained when elaboration erased their calls.
check_elaborated proc(
allocator mem.Allocator,
tree @ast.Tree,
resolution @sema.Resolution,
constraints []ast.NodeId,
diagnostic_store @mut diagnostics.Store,
) Result ! (mem.AllocError | diagnostics.CapacityError) {
count :: ast.node_count(tree)
types :: try mem.alloc(Type, allocator, count)
@@ -305,6 +316,12 @@ check proc(
.import: {}
}
}
for (constraints) |constraint| {
if (ast.node_class(tree, constraint) == .statement) {
state.cells[usize(constraint)].local = true
try collect_declaration(&state, constraint)
} else try collect_expression(&state, constraint)
}
# Preserve binding errors before RHS errors propagate. Indexed writability
# waits for shapes; neither check poisons the target's type.
for 0..count |i| {
@@ -1127,6 +1144,14 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
state.cells[usize(id)].in_cleanup = state.in_cleanup
state.cells[usize(id)].narrowing_arm = state.narrowing_arm
match ast.expr_kind(tree, id) {
.typed_constant: {
value :: ast.typed_constant(tree, id)
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = try check_annotation(state, value.type, false)
try collect_expression(state, value.value)
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.try_expr: {
operand :: ast.type_operand(tree, id)
try collect_expression(state, operand)
@@ -1371,6 +1396,7 @@ builtin_kind proc(type sema.BuiltinType) TypeKind {
.string: .string
.void: .void
.anyopaque: .anyopaque
.type: .type_value
.c_int: .i32
.c_uint: .u32
.c_long, .c_longlong: .int
@@ -1886,6 +1912,10 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
}
.expression: {
match ast.expr_kind(tree, id) {
.typed_constant: {
value :: ast.typed_constant(tree, id)
try compatible(state, value.value, state.cells[usize(id)].type)
}
.try_expr, .catch_expr: try validate_fallible_expression(state, id)
.unary: {
expression :: ast.unary_expr(tree, id)
@@ -2114,8 +2144,9 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
state.cells[usize(id)].constant_visiting = true
defer state.cells[usize(id)].constant_visiting = false
match ast.expr_kind(state.tree, id) {
.typed_constant: return evaluate_integer(state, ast.typed_constant(state.tree, id).value, allow_local)
.literal_int: {
text :: ast.source_text(state.tree, ast.node_span(state.tree, id))
text :: ast.literal_text(state.tree, id)
value := u64(0)
for text |byte| {
if (byte < '0' or byte > '9') return null
@@ -2935,6 +2966,10 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
for 0..(state.cells.len) |i| {
id :: ast.NodeId(i)
if (state.cells[i].role == .expression and state.cells[i].assignment_value == assignment_values) {
if (ast.expr_kind(tree, id) == .typed_constant) {
value :: ast.typed_constant(tree, id)
try context_demand(state, value.value, state.cells[i].type, .type_mismatch, early)
}
if (ast.expr_kind(tree, id) == .match_expr) _ = try infer_match(state, id, false)
if (ast.expr_kind(tree, id) == .binary) {
expression :: ast.binary_expr(tree, id)
@@ -3945,7 +3980,7 @@ constant_order proc(left, right ConstantInteger) i32 {
constant_real proc(state @mut State, id ast.NodeId) ?f64 ! mem.AllocError {
missing ?f64 :: null
if (ast.expr_kind(state.tree, id) == .literal_float) {
return strconv.parse_float(state.edges.allocator, ast.source_text(state.tree, ast.node_span(state.tree, id))) catch |err| {
return strconv.parse_float(state.edges.allocator, ast.literal_text(state.tree, id)) catch |err| {
match err {
.out_of_memory: return err
.invalid_number, .out_of_range: return missing
@@ -3994,7 +4029,7 @@ pattern_interval proc(state @mut State, id ast.NodeId, key Type) ?PatternInterva
if (try constant_real(state, high)) |value| interval.real_high = value else return missing
} else if (key.kind == .bool) {
if (ast.expr_kind(state.tree, low) != .literal_bool or low != high) return missing
value :: mem.eql(ast.source_text(state.tree, ast.node_span(state.tree, low)), "true")
value :: mem.eql(ast.literal_text(state.tree, low), "true")
interval.low.magnitude = if (value) 1 else 0
interval.high = interval.low
} else {
+21 -4
View File
@@ -17,6 +17,7 @@ BuiltinType :: enum {
anyopaque
c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong
c_char, c_uchar, c_short, c_ushort
type
}
@hide:file
@@ -55,6 +56,7 @@ builtins [_]Builtin :: [
{ name = "c_uchar", type = .c_uchar },
{ name = "c_short", type = .c_short },
{ name = "c_ushort", type = .c_ushort },
{ name = "type", type = .type },
]
@hide:file
@@ -275,6 +277,12 @@ binding proc(resolution @Resolution, id ast.NodeId) Binding {
return resolution.storage.bindings[usize(id)]
}
#! replaces owned binding storage after AST elaboration; takes ownership of `bindings`.
replace_bindings proc(resolution @mut Resolution, bindings []mut Binding) void {
mem.free(resolution.storage.allocator, resolution.storage.bindings)
resolution.storage.bindings = bindings
}
@hide:file
lookup proc(state @State, scope ?@Scope, name strpool.StringId) ?Binding {
if scope |current| {
@@ -335,13 +343,17 @@ resolve_function proc(
) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.tree
function :: ast.function_decl(tree, id)
# signatures cannot see parameters or body-local declarations.
try resolve_expr(state, package_scope, function.return_type)
# compile-time parameters are in scope for subsequent dependent annotations.
signature := Scope{ names = hashmap.init(state.allocator), parent = package_scope }
defer hashmap.deinit(&signature.names)
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
try resolve_expr(state, package_scope, ast.param(tree, parameter).type)
try resolve_expr(state, &signature, ast.param(tree, parameter).type)
if (ast.param(tree, parameter).comptime) {
try predeclare(state, &signature, parameter, ast.param(tree, parameter).name)
}
}
try resolve_expr(state, &signature, function.return_type)
scope := Scope{
names = hashmap.init(state.allocator),
parent = package_scope,
@@ -514,6 +526,11 @@ resolve_expr proc(
if (state.bindings[usize(id)].kind != .none) return
tree :: state.tree
match ast.expr_kind(tree, id) {
.typed_constant: {
value :: ast.typed_constant(tree, id)
try resolve_expr(state, scope, value.type)
try resolve_expr(state, scope, value.value)
}
.literal_int, .literal_float, .literal_bool, .literal_string, .literal_null: {}
.enum_literal: {}
.distinct_type, .alias_expr, .optional_type, .try_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
+1 -1
View File
@@ -719,7 +719,7 @@ resolves_every_array_child_error_and_preserves_lexer_diagnostics test {
` return
` data [missing_count]MissingElement :: [absent, missing_element]
` (missing_base[missing_index])[missing_outer] = missing_rhs
` malformed [$]i64 :: [$]
` malformed [~]i64 :: [~]
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
File diff suppressed because it is too large Load Diff