feat: support existing io.print formatting

This commit is contained in:
2026-11-01 11:37:46 +01:00
parent e036070bf2
commit 1f457d6b41
23 changed files with 895 additions and 116 deletions
+15 -5
View File
@@ -220,12 +220,13 @@ NodeData :: union {
binary_operator BinaryOperator
}
@hide:file file_local_flag u8 :: 0b0001
@hide:file foreign_type_flag u8 :: 0b0010
@hide:file variadic_flag u8 :: 0b0100
@hide:file opaque_flag u8 :: 0b1000
@hide:file optional_match_flag u8 :: 0b00010000
@hide:file file_local_flag u8 :: 0b00000001
@hide:file foreign_type_flag u8 :: 0b00000010
@hide:file variadic_flag u8 :: 0b00000100
@hide:file opaque_flag u8 :: 0b00001000
@hide:file optional_match_flag u8 :: 0b00010000
@hide:file compound_assignment_flag u8 :: 0b00100000
@hide:file package_local_flag u8 :: 0b01000000
@hide:file
Node :: struct {
@@ -1208,6 +1209,15 @@ file_local proc(tree @Tree, id NodeId) bool {
return (tree.storage.nodes.items[usize(id)].flags & file_local_flag) != 0
}
set_package_local proc(builder @mut Builder, id NodeId) void {
node :: &builder.storage.nodes.items[usize(id)]
node.flags = node.flags | package_local_flag
}
package_local proc(tree @Tree, id NodeId) bool {
return (tree.storage.nodes.items[usize(id)].flags & package_local_flag) != 0
}
set_callable_foreign proc(builder @mut Builder, id NodeId) void {
node :: &builder.storage.nodes.items[usize(id)]
node.flags = node.flags | foreign_type_flag
+8 -3
View File
@@ -765,12 +765,15 @@ dependency_traps_keep_original_spans_and_remain_demand_driven test {
}
}
file_local_declarations_are_isolated_from_siblings_and_import_selection test {
callers [4][]u8 :: [
hidden_declarations_preserve_file_and_package_visibility test {
callers [7][]u8 :: [
"dep :: import \"../dependency\"\nmain proc() int { return dep.answer() }\n",
"dep :: import \"../dependency\"\nmain proc() int { return dep.sibling() }\n",
"dep :: import \"../dependency\"\nmain proc() int { return dep.leak() }\n",
"dep :: import \"../dependency\"\nmain proc() int { return dep.helper() }\n",
"dep :: import \"../dependency\"\nmain proc() int { return dep.package_helper() }\n",
"dep :: import \"../dependency\"\nmain proc() int { return dep.package_value }\n",
"dep :: import \"../dependency\"\nmain proc() int { return imported_namespace.package_helper() }\nimported_namespace :: alias dep\n",
]
for callers |caller, index| {
diagnostic_store := diagnostics.init(mem.c_allocator)
@@ -785,6 +788,8 @@ file_local_declarations_are_isolated_from_siblings_and_import_selection test {
`@hide:file
`helper proc() int { return 42 }
`@hide:file secret :: 7
`@hide package_value :: 40
`@hide:package package_helper proc() int { return package_value + 2 }
`answer proc() int { return helper() }
,
)
@@ -792,7 +797,7 @@ file_local_declarations_are_isolated_from_siblings_and_import_selection test {
second_bytes := try owned_text(
`@hide:file
`helper proc() int { return 99 }
`sibling proc() int { return helper() }
`sibling proc() int { return helper() + package_helper() - 42 }
`leak proc() int { return secret }
,
)
+4
View File
@@ -23,6 +23,8 @@ Code :: enum {
invalid_character
float_must_end_with_digit
unterminated_string
unterminated_character
invalid_character_literal
unexpected_token
duplicate_name
unknown_name
@@ -194,6 +196,8 @@ messages :: $enummap.init(Code, []u8, {
invalid_character = "invalid character",
float_must_end_with_digit = "float literal must end with a digit",
unterminated_string = "unterminated string literal",
unterminated_character = "unterminated character literal",
invalid_character_literal = "character literal must contain one Unicode code point",
unexpected_token = "unexpected token",
duplicate_name = "duplicate name or forbidden shadowing",
unknown_name = "unknown name",
+7 -2
View File
@@ -693,18 +693,21 @@ execute proc(
return try runtime_trap(diagnostic_store, .invalid_shift_count, instruction.span)
value = shift_integer(instruction.op, left, count)
}
.add, .subtract, .multiply, .divide: {
.add, .subtract, .multiply, .divide, .remainder: {
left :: frames.items[frame_index].values[usize(instruction.left)]
right :: frames.items[frame_index].values[usize(instruction.right)]
if (instruction.op == .divide and instruction.type != .float and right.data.unsigned == 0)
if ((instruction.op == .divide or instruction.op == .remainder) and instruction.type != .float and right.data.unsigned == 0)
return try runtime_trap(diagnostic_store, .division_by_zero, instruction.span)
if instruction.type == .float {
if (instruction.op == .remainder and right.data.real == 0)
return try runtime_trap(diagnostic_store, .division_by_zero, instruction.span)
result f64 := 0
match instruction.op {
.add: result = left.data.real + right.data.real
.subtract: result = left.data.real - right.data.real
.multiply: result = left.data.real * right.data.real
.divide: result = left.data.real / right.data.real
.remainder: result = rem!(left.data.real, right.data.real)
else: unreachable
}
value = ir.Value{ type = .float, data = ir.ValueData{ real = result } }
@@ -1076,6 +1079,7 @@ checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
if (left == minimum and right == -1) return null
return divtrunc!(left, right)
}
.remainder: return rem!(left, right)
else: unreachable
}
}
@@ -1117,6 +1121,7 @@ checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
return left * right
}
.divide: return divtrunc!(left, right)
.remainder: return rem!(left, right)
else: unreachable
}
}
+32 -1
View File
@@ -311,6 +311,36 @@ integer_arithmetic_checks_boundaries_before_host_operations test {
}
}
integer_remainder_preserves_dividend_sign_and_minimum_without_overflow test {
cases [6]IntegerCase :: [
IntegerCase{ op = .remainder, left = -5, right = 3, expected = -2 },
IntegerCase{ op = .remainder, left = 5, right = -3, expected = 2 },
IntegerCase{ op = .remainder, left = minval!(i64), right = -1, expected = 0 },
IntegerCase{ op = .remainder, left = minval!(i64), right = 10, expected = -8 },
IntegerCase{ op = .remainder, left = 0, right = -1, expected = 0 },
IntegerCase{ op = .remainder, left = 1, right = 0, expected = null },
]
for (cases) |case| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 8, end = 16 }
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, integer(0, case.left))
try emit(&module, main, integer(1, case.right))
try emit(&module, main, ir.Instruction{ op = .remainder, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
if case.expected |expected| {
try testing.expect(outcome.trap == null)
try testing.expect_equal(expected, outcome.value.data.integer)
} else try expect_trap(outcome, &store, .division_by_zero, span)
}
}
integer_negation_traps_only_at_minimum test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
@@ -1230,7 +1260,7 @@ UnsignedCase :: struct {
unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
wide_sum u64 :: 9223372036854775815
almost_maximum u64 :: 18446744073709551614
cases [10]UnsignedCase :: [
cases [11]UnsignedCase :: [
UnsignedCase{ op = .add, left = maxval!(u64), right = 0, expected = maxval!(u64) },
UnsignedCase{ op = .add, left = maxval!(u64), right = 1, expected = null },
UnsignedCase{ op = .add, left = 9223372036854775808, right = 7, expected = wide_sum },
@@ -1241,6 +1271,7 @@ unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
UnsignedCase{ op = .multiply, left = maxval!(u64), right = 0, expected = u64(0) },
UnsignedCase{ op = .multiply, left = 0, right = maxval!(u64), expected = u64(0) },
UnsignedCase{ op = .multiply, left = 3, right = 14, expected = u64(42) },
UnsignedCase{ op = .remainder, left = maxval!(u64), right = 10, expected = u64(5) },
]
for (cases) |case| {
module := ir.init(mem.c_allocator)
+4 -1
View File
@@ -162,7 +162,7 @@ Op :: enum {
bit_not, bit_and, bit_or, bit_xor
shift_left, shift_right, shift_left_saturating
add, subtract
multiply, divide
multiply, divide, remainder
call
equal, not_equal
less, less_equal
@@ -184,6 +184,8 @@ Instruction :: struct {
constant Value = Value{}
arguments_start usize = 0
arguments_count usize = 0
variadic bool = false
fixed_arguments usize = 0
}
TerminatorKind :: enum {
@@ -215,6 +217,7 @@ Function :: struct {
parameters arraylist.ArrayList(Type)
# owned by the module allocator; empty for interpreted/native-defined functions.
external_name []u8 = ""
variadic bool = false
locals arraylist.ArrayList(Type)
result Type = .void
result_array_type ArrayId = no_array
+15 -11
View File
@@ -16,6 +16,7 @@ ScanError :: struct {
ErrorCode :: enum {
float_must_end_with_digit,
unterminated_string,
unterminated_character,
}
SourceError :: enum { source_too_large }
@@ -153,9 +154,9 @@ scan proc(
continue
}
if char == '"' {
if (char == '"' or char == '\'') {
start :: cursor
result :: scan_string(start, program) catch |err| {
result :: scan_quoted(start, program, char) catch |err| {
id :: try diagnostics.add(
diagnostic_store,
diagnostic_code(err.code),
@@ -169,9 +170,10 @@ scan proc(
cursor = err.end
continue
}
kind :: if (char == '\'') TokenKind.character else .string
try add_token(state, Token{
span = make_span(source, start, result.end),
kind = .string,
kind = kind,
})
cursor = result.end
continue
@@ -440,19 +442,20 @@ scan_number proc(start usize, program []u8, allow_decimal bool) ScanNumResult !
ScanStrResult :: struct { end usize }
@hide:file
scan_string proc(start usize, program []u8) ScanStrResult ! ScanError {
cursor := start + 1 # skip first `"`
while cursor < program.len and program[cursor] != '"' and program[cursor] != '\n' : cursor += 1 {
# escaped quotes do not terminate the string.
if (program[cursor] == '\\' and cursor + 1 < program.len) cursor += 1
scan_quoted proc(start usize, program []u8, quote u8) ScanStrResult ! ScanError {
cursor := start + 1
while (cursor < program.len and program[cursor] != quote and program[cursor] != '\n') : cursor += 1 {
# an escaped delimiter is content, but a newline still ends an invalid literal.
if (program[cursor] == '\\' and cursor + 1 < program.len and program[cursor + 1] != '\n') cursor += 1
}
if (cursor >= program.len or program[cursor] != '"') {
if (cursor >= program.len or program[cursor] != quote) {
code :: if (quote == '\'') ErrorCode.unterminated_character else .unterminated_string
return ScanError{
code = .unterminated_string,
code = code,
end = cursor,
}
}
return ScanStrResult{ end = cursor + 1 } # skip last `"`
return ScanStrResult{ end = cursor + 1 }
}
@hide:file
@@ -480,6 +483,7 @@ is_digit proc(char u8) bool {
diagnostic_codes :: $enummap.init(ErrorCode, diagnostics.Code, {
float_must_end_with_digit = .float_must_end_with_digit,
unterminated_string = .unterminated_string,
unterminated_character = .unterminated_character,
})
@hide:file
+26
View File
@@ -3,6 +3,32 @@ import "@std/testing"
import "@source/diagnostics"
import "@source/strpool"
scans_character_literals_and_preserves_unterminated_line_provenance 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)
source :: "'a' '\\n' '\\'' '\\\\' 'é' '\\\nnext :: 1"
state.source_id = diagnostics.SourceId(7)
try scan(&state, source, &pool, &diagnostic_store)
expected [11]TokenKind :: [
.character, .character, .character, .character, .character,
.invalid, .newline, .ident, .double_colon, .int, .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(1, diagnostics.count(&diagnostic_store))
invalid :: state.tokens.items[5]
diagnostic :: diagnostics.get(&diagnostic_store, invalid.data.diagnostic)?
try testing.expect_equal(diagnostics.Code.unterminated_character, diagnostic.code)
try testing.expect_equal(diagnostics.SourceId(7), diagnostic.span.source)
try testing.expect_equal(invalid.span.start, diagnostic.span.start)
try testing.expect_equal(invalid.span.end, diagnostic.span.end)
try testing.expect_equal(invalid.span.end, state.tokens.items[6].span.start)
}
reports_invalid_input_and_keeps_scanning test {
pool := strpool.init(mem.c_allocator)
defer strpool.deinit(&pool)
+2 -2
View File
@@ -42,7 +42,7 @@ TokenKind :: enum {
undefined
# literals
ident, string
ident, string, character
int, float
# comparison
@@ -92,7 +92,7 @@ TokenKind :: enum {
render_token proc(token Token, program []u8) void {
match token.kind {
.ident, .string, .int, .float:
.ident, .string, .character, .int, .float:
debug.print("{}({})\n", {token.kind, program[token.span.start..token.span.end]})
else:
debug.print("{}\n", {token.kind})
+66 -7
View File
@@ -16,6 +16,7 @@ float_opcodes :: $enummap.init_default(ir.Op, []u8, "", {
subtract = "fsub",
multiply = "fmul",
divide = "fdiv",
remainder = "frem",
})
@hide:file
@@ -329,15 +330,50 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
for (0..instruction.arguments_count) |index| {
_ = try load(state, block.arguments.items[instruction.arguments_start + index])
}
# C default promotions apply only after the fixed prefix.
first_promotion :: state.next_register
if (instruction.variadic) {
for (instruction.fixed_arguments..instruction.arguments_count) |index| {
operand :: block.arguments.items[instruction.arguments_start + index]
kind :: state.values[usize(operand)].kind
if (variadic_type(kind) == kind) continue
promoted :: fresh(state)
opcode []u8 :: if (kind == .bool or ir.unsigned_integer(kind)) "zext" else "sext"
try io.print(" %r{} = {s} ", output, {promoted, opcode})
try write_type(output, state.module, state.values[usize(operand)])
try io.print(" %r{} to i32\n", output, {first_argument + index})
}
}
if (instruction.type != .void) try io.print(" %r{} = call ", output, {result})
else try io.write_all(output, " call ")
try write_abi_result(output, state.module, type)
if (instruction.variadic) {
try io.write_all(output, " (")
for (0..instruction.fixed_arguments) |index| {
if (index != 0) try io.write_all(output, ", ")
operand :: block.arguments.items[instruction.arguments_start + index]
try write_type(output, state.module, state.values[usize(operand)])
}
if (instruction.fixed_arguments != 0) try io.write_all(output, ", ")
try io.write_all(output, "...)")
}
try io.print(" %r{}(", output, {callee})
promotion := first_promotion
for (0..instruction.arguments_count) |index| {
if (index != 0) try io.write_all(output, ", ")
operand :: block.arguments.items[instruction.arguments_start + index]
try write_abi_parameter(output, state.module, state.values[usize(operand)])
try io.print(" %r{}", output, {first_argument + index})
argument_type := state.values[usize(operand)]
register := first_argument + index
if (instruction.variadic and index >= instruction.fixed_arguments) {
kind :: variadic_type(argument_type.kind)
if (kind != argument_type.kind) {
register = promotion
promotion += 1
argument_type.kind = kind
}
}
try write_abi_parameter(output, state.module, argument_type)
try io.print(" %r{}", output, {register})
}
try io.write_all(output, ")\n")
}
@@ -372,18 +408,24 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right})
}
.shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction)
.add, .subtract, .multiply, .divide: {
.add, .subtract, .multiply, .divide, .remainder: {
left :: try load(state, instruction.left)
right :: try load(state, instruction.right)
if (instruction.type == .float) {
if (instruction.op == .remainder) {
zero :: fresh(state)
try io.print(" %r{} = fcmp oeq double %r{}, 0.0\n", output, {zero, right})
try check_failure(state, zero, .division_by_zero, instruction.span)
}
opcode :: enummap.get(&float_opcodes, instruction.op)?
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
} else if (instruction.op == .divide) {
} else if (instruction.op == .divide or instruction.op == .remainder) {
bits :: ir.integer_bits(instruction.type)
zero :: fresh(state)
try io.print(" %r{} = icmp eq i{} %r{}, 0\n", output, {zero, bits, right})
try check_failure(state, zero, .division_by_zero, instruction.span)
if (!ir.unsigned_integer(instruction.type)) {
divisor := right
if (!ir.unsigned_integer(instruction.type) and instruction.op == .divide) {
minimum :: fresh(state)
negative_one :: fresh(state)
overflow :: fresh(state)
@@ -392,8 +434,17 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {overflow, minimum, negative_one})
try check_failure(state, overflow, .integer_overflow, instruction.span)
}
opcode []u8 :: if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right})
if (!ir.unsigned_integer(instruction.type) and instruction.op == .remainder) {
negative_one :: fresh(state)
divisor = fresh(state)
try io.print(" %r{} = icmp eq i{} %r{}, -1\n", output, {negative_one, bits, right})
# LLVM srem is poison for min/-1; every signed value rem -1 is zero.
try io.print(" %r{} = select i1 %r{}, i{} 1, i{} %r{}\n", output, {divisor, negative_one, bits, bits, right})
}
opcode []u8 := if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
if (instruction.op == .remainder)
opcode = if (ir.unsigned_integer(instruction.type)) "urem" else "srem"
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, divisor})
} else {
opcode :: enummap.get(&integer_opcodes, instruction.op)?
bits :: ir.integer_bits(instruction.type)
@@ -700,3 +751,11 @@ emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.Wr
try io.print(" %r{} = {s} i{} %r{} to i{}\n", output, {result, opcode, source_bits, operand, destination_bits})
return result
}
@hide:file
variadic_type proc(type ir.Type) ir.Type {
match type {
.bool, .i8, .u8, .i16, .u16: return .i32
else: return type
}
}
+6 -2
View File
@@ -392,7 +392,7 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
try write_type(output, module, type)
try io.write_all(output, "\n store ")
try write_type(output, module, type)
try io.write_all(output, " %value, ptr %storage\n call void @out(i32 1, ptr @open, i64 1)\n br label %loop\nloop:\n %index = phi i64 [ 0, %entry ], [ %next, %element ]\n")
try io.write_all(output, " %value, ptr %storage\n call void @out(i32 1, ptr @honey.array_open, i64 1)\n br label %loop\nloop:\n %index = phi i64 [ 0, %entry ], [ %next, %element ]\n")
try io.print(" %done = icmp eq i64 %index, {}\n", output, {shape.len})
try io.write_all(output, " br i1 %done, label %end, label %separator\nseparator:\n %first = icmp eq i64 %index, 0\n br i1 %first, label %element, label %comma\ncomma:\n call void @out(i32 1, ptr @separator, i64 2)\n br label %element\nelement:\n %pointer = getelementptr ")
try write_type(output, module, type)
@@ -402,7 +402,7 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
# functions may occur inside arrays without being printable source results.
if (element.kind != .function) try print_call(output, module, element, 0)
else try io.write_all(output, " call void @exit(i32 2)\n")
try io.write_all(output, " %next = add i64 %index, 1\n br label %loop\nend:\n call void @out(i32 1, ptr @close, i64 1)\n ret void\n}\n")
try io.write_all(output, " %next = add i64 %index, 1\n br label %loop\nend:\n call void @out(i32 1, ptr @honey.array_close, i64 1)\n ret void\n}\n")
}
@hide:file
@@ -614,6 +614,10 @@ emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.
try write_abi_parameter(output, state.module, local_type(function, index))
try io.print(" %p{}", output, {index})
}
if (function.variadic) {
if (function.parameters.items.len != 0) try io.write_all(output, ", ")
try io.write_all(output, "...")
}
if (function.external_name.len != 0) {
try io.write_all(output, ")\n")
return
+4 -4
View File
@@ -43,13 +43,13 @@ runtime ::
`@error = private constant [9 x i8] c": error: "
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
`@newline = private constant [1 x i8] c"\0A"
`@open = private constant [1 x i8] c"["
`@close = private constant [1 x i8] c"]"
`@honey.array_open = private constant [1 x i8] c"["
`@honey.array_close = private constant [1 x i8] c"]"
`@record_open = private constant [1 x i8] c"{"
`@record_close = private constant [1 x i8] c"}"
`@separator = private constant [2 x i8] c", "
`@write_error = private constant [44 x i8] c": infrastructure error: cannot write output\0A"
`@read = private constant [42 x i8] c"local value was read before initialization"
`@honey.uninitialized_read = private constant [42 x i8] c"local value was read before initialization"
`@assign = private constant [46 x i8] c"local value was assigned before initialization"
`@cycle = private constant [27 x i8] c"global initialization cycle"
`@overflow = private constant [27 x i8] c"integer arithmetic overflow"
@@ -154,7 +154,7 @@ runtime ::
` %ready = load i1, ptr %flag
` br i1 %ready, label %end, label %fail
`fail:
` %message = select i1 %read, ptr @read, ptr @assign
` %message = select i1 %read, ptr @honey.uninitialized_read, ptr @assign
` %length = select i1 %read, i64 42, i64 46
` call void @trap(i64 -1, i64 %source, i64 %line, i64 %column, ptr %message, i64 %length)
` unreachable
+24 -8
View File
@@ -521,6 +521,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
owned :: try mem.alloc(u8, state.module.allocator, name.len)
memcopy!(owned, name)
state.module.functions.items[state.function].external_name = owned
state.module.functions.items[state.function].variadic = ast.callable_variadic(tree, id)
return
}
try predeclare_locals(state, declaration.body)
@@ -1657,9 +1658,11 @@ convert_value proc(state @mut State, value ir.ValueId, actual, expected typechec
if (actual.kind == .optional and expected.kind == .optional and actual.array_type != expected.array_type) {
source :: typecheck.array_type(state.checked, actual.array_type).element
child :: typecheck.array_type(state.checked, expected.array_type).element
if (source.kind == .pointer or source.kind == .function) return value
nullable :: child.kind == .pointer or child.kind == .function
if ((source.kind == .pointer or source.kind == .function) and nullable) return value
runtime :: lower_type(state, expected)
shape :: try array_shape(state, expected)
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
present :: try optional_present(state, value, actual, span)
some :: try add_block(state)
none :: try add_block(state)
@@ -1670,25 +1673,33 @@ convert_value proc(state @mut State, value ir.ValueId, actual, expected typechec
state.block = some
payload :: try optional_payload(state, value, actual, span)
converted :: try convert_value(state, payload, source, child, span)
wrapped :: try record_pair(state, shape, present, converted, span)
wrapped := converted
if (!nullable) wrapped = try record_pair(state, shape, present, converted, span)
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = join, span = span,
}
state.block = none
absent :: try zero_value(state, .record, shape, span)
absent :: try zero_value(state, runtime, shape, span)
_ = try emit(state, ir.Instruction{ op = .store_local, left = absent, slot = slot, span = span })
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
kind = .jump, target = join, span = span,
}
state.block = join
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
return try emit(state, ir.Instruction{ op = .load_local, type = runtime, array_type = shape, slot = slot, span = span })
}
if (expected.kind == .pointer and actual.kind == .string)
return try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
if (expected.kind == .slice and actual.kind == .string) {
pointer :: try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
length :: try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span })
return try record_pair(state, try array_shape(state, expected), pointer, length, span)
}
if (expected.kind == .slice and actual.kind == .pointer) {
pointer :: typecheck.pointer_type(state.checked, actual.pointer_type)
length :: try usize_constant(state, typecheck.array_type(state.checked, pointer.element.array_type).len, span)
return try record_pair(state, try array_shape(state, expected), value, length, span)
}
if (expected.kind == .slice and actual.kind == .array) {
source_shape :: try array_shape(state, actual)
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .array, source_shape))
@@ -1975,13 +1986,15 @@ lower_builtin proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
else value.data = ir.ValueData{ integer = enummap.get(&ir.integer_minima, type)? }
return try constant(state, value, span)
}
if (mem.eql(name, "divtrunc")) {
if (mem.eql(name, "divtrunc") or mem.eql(name, "rem")) {
left :: try lower_expression(state, ast.call_arg(tree, id, 0))
if (state.terminated) return ir.no_value
right :: try lower_expression(state, ast.call_arg(tree, id, 1))
if (state.terminated) return ir.no_value
op ir.Op :: if (mem.eql(name, "rem")) .remainder else .divide
return try emit(state, ir.Instruction{
op = .divide, type = lower_type(state, typecheck.type_of(state.checked, id)),
op = op,
type = lower_type(state, typecheck.type_of(state.checked, id)),
left = left, right = right, span = span,
})
}
@@ -2471,8 +2484,9 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
count :: ast.call_arg_count(tree, id)
try arraylist.reserve(&state.arguments, scratch_start + count)
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function
fixed_arguments :: if (signature != ast.NodeId(maxval!(u32))) ast.function_param_count(tree, signature) else 0
for (0..count) |i| {
signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function
expected := typecheck.Type{}
if (signature != ast.NodeId(maxval!(u32)) and i < ast.function_param_count(tree, signature)) expected = typecheck.type_of(state.checked, ast.param(tree, ast.function_param(tree, signature, i)).type)
argument :: try lower_as(state, ast.call_arg(tree, id, i), expected)
@@ -2492,6 +2506,8 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
left = callee,
arguments_start = start,
arguments_count = count,
variadic = signature != ast.NodeId(maxval!(u32)) and ast.callable_variadic(tree, signature),
fixed_arguments = fixed_arguments,
span = span,
})
}
+87 -5
View File
@@ -2049,13 +2049,10 @@ keeps_generated_foreign_declarations_demand_driven_and_traps_interpreted_calls t
try expect_trap(foreign_outcome, &diagnostic_store, .unsupported_foreign_call)
}
diagnoses_demanded_unsupported_foreign_signatures_and_attributes test {
sources [5][]u8 :: [
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1) }\n",
diagnoses_demanded_opaque_values_and_incompatible_opaque_pointers test {
sources [2][]u8 :: [
"Handle :: opaque\ntake c_func(_ Handle) void\nmain proc() void { take(undefined) }\n",
"A :: opaque\nB :: opaque\nmain proc() void { a ?*A :: null\n b ?*B :: a }\n",
"@other:file\nhidden :: 1\nmain proc() int { return hidden }\n",
"@hide:package\nhidden :: 1\nmain proc() int { return hidden }\n",
]
for sources |source| {
diagnostic_store := diagnostics.init(mem.c_allocator)
@@ -2087,3 +2084,88 @@ rejects_demanded_unavailable_c_floating_types_without_unknown_names test {
}
}
}
preserves_variadic_call_signatures_and_interpreter_foreign_traps test {
sources [2][]u8 :: [
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1) }\n",
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1, true, i8(-2), u8(3), i16(-4), u16(5), 6.5) }\n",
]
for sources |source| {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile_owned(source, &diagnostic_store)
defer ir.deinit(&module)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
try testing.expect(module.functions.items[0].variadic)
try testing.expect_equal(usize(1), module.functions.items[0].parameters.items.len)
entry :: &module.functions.items[usize(module.entry?)]
found := false
for entry.blocks.items |block| {
for block.instructions.items |instruction| {
if (instruction.op != .call) continue
found = true
try testing.expect(instruction.variadic)
try testing.expect_equal(usize(1), instruction.fixed_arguments)
}
}
try testing.expect(found)
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
try expect_trap(outcome, &diagnostic_store, .unsupported_foreign_call)
}
}
rejects_variadic_arity_fixed_types_and_non_c_trailing_values test {
sources [4][]u8 :: [
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take() }\n",
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(true) }\n",
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1, \"text\") }\n",
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { values [2]i64 :: [1, 2]\nreturn take(1, values) }\n",
]
codes [4]diagnostics.Code :: [
.argument_count_mismatch, .type_mismatch, .invalid_operand, .invalid_operand,
]
for sources |source, index| {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile_owned(source, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
try expect_trap(outcome, &diagnostic_store, codes[index])
}
}
coerces_strings_to_immutable_byte_pointer_arguments test {
sources [3][]u8 :: [
"take c_func(_ ?*c_char) void\nmain proc() void { take(\"format\") }\n",
"take c_func(_ *u8) void\nmain proc() void { take(\"format\") }\n",
"take c_func(_ *i8, ...) void\nmain proc() void { take(\"format\", true) }\n",
]
for sources |source| {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile_owned(source, &diagnostic_store)
defer ir.deinit(&module)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
try expect_trap(outcome, &diagnostic_store, .unsupported_foreign_call)
}
}
rejects_string_arguments_for_mutable_or_nonbyte_pointers test {
sources [2][]u8 :: [
"take c_func(_ ?*mut c_char) void\nmain proc() void { take(\"format\") }\n",
"take c_func(_ *u16) void\nmain proc() void { take(\"format\") }\n",
]
for sources |source| {
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile_owned(source, &diagnostic_store)
defer ir.deinit(&module)
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
defer interpreter.deinit(&outcome)
try expect_trap(outcome, &diagnostic_store, .type_mismatch)
}
}
+57 -8
View File
@@ -118,19 +118,27 @@ parse_decl proc(state @mut State) ast.NodeId ! ParseError {
if (next_is(state, .at)) {
start :: try consume(state, .at)
name :: try consume(state, .ident)
_ = try consume(state, .colon)
qualifier :: try consume(state, .ident)
file_local := false
valid := mem.eql(state.builder.source[usize(name.span.start)..usize(name.span.end)], "hide")
if (!valid) return .unexpected_token
end := name.span.end
if (next_is(state, .colon)) {
state.next_token += 1
qualifier :: try consume(state, .ident)
scope :: state.builder.source[usize(qualifier.span.start)..usize(qualifier.span.end)]
file_local = mem.eql(scope, "file")
valid = valid and (file_local or mem.eql(scope, "package"))
end = qualifier.span.end
}
skip_newlines(state)
declaration :: try parse_decl(state)
if (
!mem.eql(state.builder.source[usize(name.span.start)..usize(name.span.end)], "hide")
or !mem.eql(state.builder.source[usize(qualifier.span.start)..usize(qualifier.span.end)], "file")
) {
span :: ast.Span{ start = start.span.start, end = qualifier.span.end, source = start.span.source }
if (!valid) {
span :: ast.Span{ start = start.span.start, end = end, source = start.span.source }
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, span)
return try ast.add_invalid_decl(state.builder, span, diagnostic)
}
ast.set_file_local(state.builder, declaration)
if (file_local) ast.set_file_local(state.builder, declaration)
else ast.set_package_local(state.builder, declaration)
return declaration
}
if (
@@ -494,6 +502,7 @@ parse_if_expr proc(state @mut State) ast.NodeId ! ParseError {
keyword :: try consume(state, .if)
condition :: try parse_guard(state)
capture :: try parse_optional_capture(state)
skip_newlines(state)
then_value :: try parse_expr(state)
skip_newlines(state)
if (!next_is(state, .else) or next_n_is(state, 1, .colon)) return .unexpected_token
@@ -1792,6 +1801,39 @@ brace_starts_block proc(state @State) bool {
return false
}
@hide:file
decode_character proc(text []u8) ?u64 {
if (text.len < 3) return null
contents :: text[1..text.len - 1]
if (contents.len == 2 and contents[0] == '\\') {
return match contents[1] {
'0': u64(0)
'n': u64('\n')
'r': u64('\r')
't': u64('\t')
'\\': u64('\\')
'\'': u64('\'')
else: null
}
}
first :: contents[0]
if (first < 128) return if (contents.len == 1) u64(first) else null
width :: if (first >= 194 and first <= 223) usize(2)
else if (first >= 224 and first <= 239) usize(3)
else if (first >= 240 and first <= 244) usize(4)
else usize(0)
if (width == 0 or contents.len != width) return null
mask :: if (width == 2) u8(31) else if (width == 3) u8(15) else u8(7)
value := u64(first & mask)
for (contents[1..]) |byte| {
if (byte < 128 or byte > 191) return null
value = value * 64 + u64(byte & 63)
}
if ((width == 3 and value < 2048) or (width == 4 and value < 65536)
or (value >= 55296 and value <= 57343) or value > 1114111) return null
return value
}
@hide:file
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
@@ -1872,6 +1914,13 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
state.next_token += 1
return try ast.add_literal(state.builder, .int, token.span)
}
.character: {
state.next_token += 1
text :: state.builder.source[usize(token.span.start)..usize(token.span.end)]
if (decode_character(text)) |value| return try ast.add_integer(state.builder, token.span, value)
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_character_literal, token.span)
return try ast.add_invalid_expr(state.builder, token.span, diagnostic)
}
.float: {
state.next_token += 1
return try ast.add_literal(state.builder, .float, token.span)
+96
View File
@@ -8,6 +8,102 @@ import "@source/lexer"
import "@source/ast"
import "@source/ast/renderer"
parses_characters_as_integer_literals_with_original_spans test {
source :: "values :: ['a', '\\0', '\\n', '\\r', '\\t', '\\\\', '\\'', 'é', '😀']"
expected [9][]u8 :: ["97", "0", "10", "13", "9", "92", "39", "233", "128512"]
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
builder := ast.init_builder(mem.c_allocator, "")
defer ast.deinit_builder(&builder)
_ = try ast.set_source(&builder, "other.hon", "")
source_id :: try ast.set_source(&builder, "characters.hon", source)
scan_state.source_id = source_id
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
tree :: ast.view(&builder, root)
values :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
try testing.expect_equal(expected.len, ast.array_element_count(&tree, values))
for (expected) |text, index| {
value :: ast.array_element(&tree, values, index)
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, value))
try testing.expect_equal(text, ast.literal_text(&tree, value))
span :: ast.node_span(&tree, value)
try testing.expect_equal(source_id, span.source)
try testing.expect_equal(u8('\''), ast.source_text(&tree, span)[0])
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.start, span.start)
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.end, span.end)
}
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
diagnoses_malformed_character_values_without_losing_sibling_declarations test {
sources [7][]u8 :: [
"bad :: ''\nnext :: 1",
"bad :: 'ab'\nnext :: 1",
"bad :: '\\x41'\nnext :: 1",
"bad :: '\\q'\nnext :: 1",
"bad :: '\xC0\xAF'\nnext :: 1",
"bad :: '\xED\xA0\x80'\nnext :: 1",
"bad :: '\xF4\x90\x80\x80'\nnext :: 1",
]
for (sources) |source| {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
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(1, diagnostics.count(&diagnostic_store))
try testing.expect_equal(2, ast.package_decl_count(&tree))
bad :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, bad))
diagnostic :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, bad).diagnostic)?
try testing.expect_equal(diagnostics.Code.invalid_character_literal, diagnostic.code)
try testing.expect_equal(scan_state.tokens.items[2].span.start, diagnostic.span.start)
try testing.expect_equal(scan_state.tokens.items[2].span.end, diagnostic.span.end)
try testing.expect_equal("next", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
}
}
parses_multiline_conditional_values_and_hide_scopes test {
source ::
`@hide
`hidden :: if (true)
` '0'
`else if (false)
` 'A'
`else
` 'a'
`@hide:package scoped :: 2
`@hide:file local :: 3
`visible :: 4
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
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))
try testing.expect_equal(4, ast.package_decl_count(&tree))
for (usize(0)..4) |index| {
declaration :: ast.package_decl(&tree, index)
try testing.expect_equal(index < 2, ast.package_local(&tree, declaration))
try testing.expect_equal(index == 2, ast.file_local(&tree, declaration))
}
conditional :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
try testing.expect_equal(ast.ExprKind.match_expr, ast.expr_kind(&tree, conditional))
}
parses_expression_precedence_through_tree_views test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
+43 -11
View File
@@ -450,7 +450,8 @@ check_elaborated proc(
try require_numeric(&state, id, expression.right)
}
.builtin_call: {
if (mem.eql(builtin_name(&state, id), "divtrunc") and ast.call_arg_count(tree, id) == 2) {
name :: builtin_name(&state, id)
if ((mem.eql(name, "divtrunc") or mem.eql(name, "rem")) and ast.call_arg_count(tree, id) == 2) {
try require_numeric(&state, id, ast.call_arg(tree, id, 0))
try require_numeric(&state, id, ast.call_arg(tree, id, 1))
}
@@ -487,7 +488,8 @@ check_elaborated proc(
}
}
.builtin_call: {
if (mem.eql(builtin_name(&state, id), "divtrunc") and ast.call_arg_count(tree, id) == 2) {
name :: builtin_name(&state, id)
if ((mem.eql(name, "divtrunc") or mem.eql(name, "rem")) and ast.call_arg_count(tree, id) == 2) {
try numeric_operand(&state, id, ast.call_arg(tree, id, 0))
try numeric_operand(&state, id, ast.call_arg(tree, id, 1))
}
@@ -1037,7 +1039,6 @@ collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError |
if (!ast.function_external(tree, id)) try collect_block(state, function.body, function.return_type, false)
else {
if (mem.eql(ast.source_text(tree, function.name), "main")) try fail(state, id, .invalid_entry, function.name)
if (ast.callable_variadic(tree, id)) try fail(state, id, .invalid_operand, function.span)
if (!c_abi_type(state, state.types[usize(function.return_type)])) try fail(state, id, .invalid_operand, function.span)
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
@@ -2041,11 +2042,21 @@ assignable_type proc(state @State, actual, expected Type) bool {
if (actual.kind == .null) return true
if (actual.kind == .optional) {
source :: state.arrays.items[usize(actual.array_type)].element
if (source.kind != child.kind) return false
if (source.kind != child.kind
and !(source.kind == .pointer and child.kind == .slice)
and !(source.kind == .string and child.kind == .pointer)) return false
return assignable_type(state, source, child)
}
return assignable_type(state, actual, child)
}
if (actual.kind == .string and expected.kind == .pointer) {
pointer :: state.pointers.items[usize(expected.pointer_type)]
return (
expected.nominal == ast.NodeId(maxval!(u32)) and !pointer.mutable
and pointer.element.nominal == ast.NodeId(maxval!(u32))
and (pointer.element.kind == .i8 or pointer.element.kind == .u8)
)
}
if (expected.kind == .slice) {
b :: state.arrays.items[usize(expected.array_type)]
if (actual.kind == .string) return !b.mutable and b.element.kind == .u8 and (b.sentinel == null or b.sentinel? == 0)
@@ -2056,6 +2067,17 @@ assignable_type proc(state @State, actual, expected Type) bool {
and same_type(state, a.element, b.element)
)
}
if (actual.kind == .pointer and actual.nominal == ast.NodeId(maxval!(u32))) {
pointer :: state.pointers.items[usize(actual.pointer_type)]
if (pointer.many or pointer.element.kind != .array
or pointer.element.nominal != ast.NodeId(maxval!(u32))) return false
a :: state.arrays.items[usize(pointer.element.array_type)]
return (
(!b.mutable or (pointer.mutable and a.mutable))
and (b.sentinel == null or same_sentinel(a.sentinel, b.sentinel))
and same_type(state, a.element, b.element)
)
}
}
if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
a :: state.pointers.items[usize(actual.pointer_type)]
@@ -2371,12 +2393,21 @@ validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
return
}
parameter_count :: ast.function_param_count(tree, signature.function)
if (parameter_count != ast.call_arg_count(tree, id)) {
variadic :: ast.callable_variadic(tree, signature.function)
if (ast.call_arg_count(tree, id) < parameter_count or (!variadic and parameter_count != ast.call_arg_count(tree, id))) {
diagnostic :: try diagnostics.add(state.diagnostics, .argument_count_mismatch, expression.span)
if (callee.kind != .invalid) state.types[usize(id)] = invalid_type(diagnostic)
}
for (0..ast.call_arg_count(tree, id)) |i| {
if (i >= parameter_count) continue
if (i >= parameter_count) {
if (variadic) {
argument :: ast.call_arg(tree, id, i)
actual :: state.types[usize(argument)]
if (!c_abi_type(state, actual) or actual.kind == .void)
try fail(state, argument, .invalid_operand, ast.node_span(tree, argument))
}
continue
}
parameter :: ast.function_param(tree, signature.function, i)
try compatible(state, ast.call_arg(tree, id, i), state.types[usize(parameter)])
}
@@ -2697,7 +2728,7 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
result :: ast.callable_result(state.tree, id)
result_type :: try check_annotation(state, result, true)
if (result_type.kind == .invalid) type = result_type
if (ast.callable_variadic(state.tree, id) or (ast.callable_foreign(state.tree, id) and !c_abi_type(state, result_type)))
if (ast.callable_foreign(state.tree, id) and !c_abi_type(state, result_type))
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, id)))
try depend(state, result, id)
for 0..ast.function_param_count(state.tree, id) |i| {
@@ -2764,7 +2795,7 @@ check_container_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocErro
@hide:file
c_abi_type proc(state @State, type Type) bool {
if (type.kind == .optional) return c_abi_pointer(state, state.arrays.items[usize(type.array_type)].element)
if (type.kind == .function) return ast.callable_foreign(state.tree, type.function) and !ast.callable_variadic(state.tree, type.function)
if (type.kind == .function) return ast.callable_foreign(state.tree, type.function)
return integer_kind(type.kind) or type.kind == .float or type.kind == .bool or type.kind == .pointer or type.kind == .void or type.kind == .invalid
}
@@ -2794,7 +2825,7 @@ collect_builtin proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
}
if (layout) state.cells[usize(id)].type = Type{ kind = .usize }
if (limit and count == 1) state.cells[usize(id)].type = state.types[usize(ast.call_arg(state.tree, id, 0))]
if (mem.eql(name, "divtrunc")) {
if (mem.eql(name, "divtrunc") or mem.eql(name, "rem")) {
state.cells[usize(id)].numeric = true
state.cells[usize(id)].producer = id
}
@@ -2872,7 +2903,8 @@ infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.A
span :: ast.node_span(state.tree, id)
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
limit :: mem.eql(name, "maxval") or mem.eql(name, "minval")
division :: mem.eql(name, "divtrunc")
remainder :: mem.eql(name, "rem")
division :: mem.eql(name, "divtrunc") or remainder
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
memory :: mem.eql(name, "memcopy") or mem.eql(name, "memset")
some :: mem.eql(name, "some")
@@ -2892,7 +2924,7 @@ infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.A
left :: try infer_shape(state, ast.call_arg(state.tree, id, 0), defaults)
right :: try infer_shape(state, ast.call_arg(state.tree, id, 1), defaults)
if (defaults and left.kind != .invalid and right.kind != .invalid
and (!integer_kind(left.kind) or !same_type(state, left, right))) try fail(state, id, .invalid_operand, span)
and ((!integer_kind(left.kind) and !(remainder and left.kind == .float)) or !same_type(state, left, right))) try fail(state, id, .invalid_operand, span)
return group_type(state, id)
}
if (some) {
+13 -10
View File
@@ -486,6 +486,17 @@ canonical_binding proc(state @State, original Binding) Binding {
return value
}
@hide:file
namespace_member proc(state @State, root, name ast.NodeId) ?Binding {
if (ast.expr_kind(state.tree, name) != .identifier) return null
namespace :: hashmap.get(&state.namespaces, root)?
if (hashmap.get(&namespace.names, ast.name_id(state.tree, name))) |member| {
if (member.kind == .declaration and ast.package_local(state.tree, member.data.declaration)) return null
return member
}
return null
}
@hide:file
canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
# aliases may forward-reference other aliases or imported namespace members.
@@ -501,11 +512,7 @@ canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics
expression :: ast.selection_expr(state.tree, id)
base :: state.bindings[usize(expression.base)]
if (base.kind != .namespace or state.bindings[usize(id)].kind != .none) continue
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
found :: if (ast.expr_kind(state.tree, expression.member) == .identifier)
hashmap.get(&namespace.names, ast.name_id(state.tree, expression.member))
else
null
found :: namespace_member(state, base.data.namespace, expression.member)
value :: if (found) |member| canonical_binding(state, member) else Binding{
kind = .invalid,
data = BindingData{ diagnostic = try diagnostics.add(
@@ -614,11 +621,7 @@ resolve_expr proc(
if (base.kind == .invalid) {
value = base
} else if (base.kind == .namespace) {
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
found :: if (ast.expr_kind(tree, expression.member) == .identifier)
hashmap.get(&namespace.names, ast.name_id(tree, expression.member))
else
null
found :: namespace_member(state, base.data.namespace, expression.member)
if (found) |member| {
value = member
} else {
+25 -5
View File
@@ -1762,6 +1762,13 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
.union_constant, .slice_constant: return true
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
.enum_literal: return true
.builtin_call: {
name :: ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name)
if (ast.call_arg_count(&state.tree, id) != 1
or (!mem.eql(name, "minval") and !mem.eql(name, "maxval")
and !mem.eql(name, "sizeof") and !mem.eql(name, "alignof"))) return false
return branch_constant(state, ast.call_arg(&state.tree, id, 0), depth + 1)
}
.record_literal: {
# typed literals may omit defaults; materialized constants are complete
# contextual literals inside a typed_constant wrapper.
@@ -2122,7 +2129,7 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
}
supplied :: ast.call_arg_count(&state.tree, call)
explicit :: supplied == count
if (!explicit and (type_result or supplied != runtime)) {
if (!explicit and (type_result or supplied < runtime or supplied > count)) {
try reject(state, call, .argument_count_mismatch)
return
}
@@ -2131,11 +2138,17 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
mem.set(arguments, Value{})
runtime_arguments := arraylist.init(ast.NodeId, state.allocator)
defer arraylist.deinit(&runtime_arguments)
# supplied compile-time arguments form a declaration-order prefix; infer the rest.
supplied_comptime :: if (type_result) count else supplied - runtime
comptime_position := usize(0)
position := 0
for (0..count) |i| {
parameter :: ast.function_param(&state.tree, function, i)
view :: ast.param(&state.tree, parameter)
if (view.comptime and !explicit) continue
if (view.comptime and !type_result) {
if (comptime_position >= supplied_comptime) continue
comptime_position += 1
}
argument :: ast.call_arg(&state.tree, call, position)
position += 1
if (type_result or view.comptime) {
@@ -2288,16 +2301,23 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
try reject(state, call, .specialization_cycle)
return
}
if (explicit) {
if (type_result or supplied_comptime != 0) {
signature :: state.instances.items[instance].signature
position = 0
comptime_position = 0
for (0..count) |i| {
parameter :: ast.function_param(&state.tree, signature, i)
view :: ast.param(&state.tree, parameter)
if (view.comptime and !type_result) {
if (comptime_position >= supplied_comptime) continue
comptime_position += 1
}
original :: ast.call_arg(&state.tree, call, position)
position += 1
if (
(!type_result and !view.comptime) or is_type(state, view.type)
or type_record_storage(state, view.type, 0)
) continue
original :: ast.call_arg(&state.tree, call, i)
constraint :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, original), view.type, original)
try refresh(state)
try arraylist.append(&state.rewrites.constraints, constraint)
@@ -6481,7 +6501,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
if ((mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) and count == 2)
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
if (mem.eql(name, "divtrunc") and count == 2) {
if ((mem.eql(name, "divtrunc") or mem.eql(name, "rem")) and count == 2) {
left :: ast.call_arg(&state.tree, id, 0)
right :: ast.call_arg(&state.tree, id, 1)
operand :: if (flexible(state, left)) right else left