feat: run existing ArrayList with imported C allocator
This commit is contained in:
@@ -208,6 +208,10 @@ Node :: struct {
|
||||
main NodeData = NodeData{ node_id = no_node }
|
||||
data0 NodeData = NodeData{ node_id = no_node }
|
||||
data1 NodeData = NodeData{ node_id = no_node }
|
||||
file_local bool = false
|
||||
foreign_type bool = false
|
||||
variadic bool = false
|
||||
is_opaque bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -1109,6 +1113,38 @@ function_external proc(tree @Tree, id NodeId) bool {
|
||||
return tree.storage.extra.items[usize(node.data1.extra) - 1] != 0
|
||||
}
|
||||
|
||||
set_file_local proc(builder @mut Builder, id NodeId) void {
|
||||
builder.storage.nodes.items[usize(id)].file_local = true
|
||||
}
|
||||
|
||||
file_local proc(tree @Tree, id NodeId) bool {
|
||||
return get_node(&tree.storage, id).file_local
|
||||
}
|
||||
|
||||
set_callable_foreign proc(builder @mut Builder, id NodeId) void {
|
||||
builder.storage.nodes.items[usize(id)].foreign_type = true
|
||||
}
|
||||
|
||||
callable_foreign proc(tree @Tree, id NodeId) bool {
|
||||
return function_external(tree, id) or get_node(&tree.storage, id).foreign_type
|
||||
}
|
||||
|
||||
set_callable_variadic proc(builder @mut Builder, id NodeId) void {
|
||||
builder.storage.nodes.items[usize(id)].variadic = true
|
||||
}
|
||||
|
||||
callable_variadic proc(tree @Tree, id NodeId) bool {
|
||||
return get_node(&tree.storage, id).variadic
|
||||
}
|
||||
|
||||
set_record_opaque proc(builder @mut Builder, id NodeId) void {
|
||||
builder.storage.nodes.items[usize(id)].is_opaque = true
|
||||
}
|
||||
|
||||
record_opaque proc(tree @Tree, id NodeId) bool {
|
||||
return get_node(&tree.storage, id).is_opaque
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -738,3 +738,57 @@ dependency_traps_keep_original_spans_and_remain_demand_driven test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_local_declarations_are_isolated_from_siblings_and_import_selection test {
|
||||
callers [4][]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",
|
||||
]
|
||||
for callers |caller, index| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
session := init(mem.c_allocator)
|
||||
defer deinit(&session)
|
||||
root :: try add_package(&session)
|
||||
dependency :: try add_package(&session)
|
||||
root_bytes := try owned_text(caller)
|
||||
defer arraylist.deinit(&root_bytes)
|
||||
first_bytes := try owned_text(
|
||||
`@hide:file
|
||||
`helper proc() int { return 42 }
|
||||
`@hide:file secret :: 7
|
||||
`answer proc() int { return helper() }
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&first_bytes)
|
||||
second_bytes := try owned_text(
|
||||
`@hide:file
|
||||
`helper proc() int { return 99 }
|
||||
`sibling proc() int { return helper() }
|
||||
`leak proc() int { return secret }
|
||||
,
|
||||
)
|
||||
defer arraylist.deinit(&second_bytes)
|
||||
root_source :: try add_source(&session, root, "app/main.hon", &root_bytes, &diagnostic_store)
|
||||
_ = try add_source(&session, dependency, "dependency/first.hon", &first_bytes, &diagnostic_store)
|
||||
_ = try add_source(&session, dependency, "dependency/second.hon", &second_bytes, &diagnostic_store)
|
||||
try arraylist.append(&session.imports, Import{
|
||||
node = declaration(&session, root_source, 0).node,
|
||||
target = dependency,
|
||||
})
|
||||
module := try compile(&session, root, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
defer interpreter.deinit(&outcome)
|
||||
if (index < 2) {
|
||||
try testing.expect(outcome.trap == null)
|
||||
expected :: if (index == 0) i64(42) else i64(99)
|
||||
try testing.expect_equal(expected, outcome.value.data.integer)
|
||||
} else {
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||
try testing.expect_equal(diagnostics.Code.unknown_name, diagnostic.code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ Code :: enum {
|
||||
write_before_initialization
|
||||
initialization_cycle
|
||||
integer_overflow
|
||||
division_by_zero
|
||||
import_cycle
|
||||
invalid_import_path
|
||||
package_unavailable
|
||||
@@ -213,6 +214,7 @@ messages :: $enummap.init(Code, []u8, {
|
||||
write_before_initialization = "local value was assigned before initialization",
|
||||
initialization_cycle = "global initialization cycle",
|
||||
integer_overflow = "integer arithmetic overflow",
|
||||
division_by_zero = "integer division by zero",
|
||||
import_cycle = "package import cycle",
|
||||
invalid_import_path = "invalid import path",
|
||||
package_unavailable = "cannot load package directory",
|
||||
|
||||
@@ -590,6 +590,8 @@ execute proc(
|
||||
.add, .subtract, .multiply, .divide: {
|
||||
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)
|
||||
return try runtime_trap(diagnostic_store, .division_by_zero, instruction.span)
|
||||
if instruction.type == .float {
|
||||
result f64 := 0
|
||||
match instruction.op {
|
||||
@@ -924,6 +926,10 @@ checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
||||
}
|
||||
return left * right
|
||||
}
|
||||
.divide: {
|
||||
if (left == minimum and right == -1) return null
|
||||
return divtrunc!(left, right)
|
||||
}
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
@@ -964,6 +970,7 @@ checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
|
||||
if (right != 0 and left > divtrunc!(maxval!(u64), right)) return null
|
||||
return left * right
|
||||
}
|
||||
.divide: return divtrunc!(left, right)
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
||||
{ "or", .or },
|
||||
{ "mut", .mut },
|
||||
{ "struct", .struct },
|
||||
{ "c_struct", .c_struct },
|
||||
{ "opaque", .opaque },
|
||||
{ "enum", .enum },
|
||||
{ "union", .union },
|
||||
{ "distinct", .distinct },
|
||||
@@ -169,8 +171,9 @@ scan proc(
|
||||
|
||||
if (char == '.' and cursor + 1 < program.len and program[cursor + 1] == '.') {
|
||||
inclusive :: cursor + 2 < program.len and program[cursor + 2] == '='
|
||||
width :: if (inclusive) 3 else 2
|
||||
range_kind :: if (inclusive) .range_inclusive else .range
|
||||
variadic :: cursor + 2 < program.len and program[cursor + 2] == '.'
|
||||
width :: if (inclusive or variadic) 3 else 2
|
||||
range_kind :: if (inclusive) .range_inclusive else if (variadic) .ellipsis else .range
|
||||
try add_token(state, Token{
|
||||
span = make_span(source, cursor, cursor + width),
|
||||
kind = range_kind,
|
||||
|
||||
@@ -150,10 +150,10 @@ scans_ranges_without_consuming_float_dots test {
|
||||
state := init(mem.c_allocator)
|
||||
defer deinit(&state)
|
||||
try scan(&state, "0..10 0..=10 1.5..2.5 |@item, _| ... 1.", &pool, &diagnostic_store)
|
||||
expected [19]TokenKind :: [
|
||||
expected [18]TokenKind :: [
|
||||
.int, .range, .int, .int, .range_inclusive, .int,
|
||||
.float, .range, .float, .pipe, .at, .ident, .comma, .ident, .pipe,
|
||||
.range, .dot, .invalid, .eof,
|
||||
.ellipsis, .invalid, .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)
|
||||
|
||||
@@ -28,7 +28,9 @@ TokenKind :: enum {
|
||||
defer, errdefer
|
||||
true, false
|
||||
and, or
|
||||
struct, enum, union
|
||||
struct, c_struct
|
||||
opaque
|
||||
enum, union
|
||||
distinct, alias
|
||||
import, test
|
||||
mut
|
||||
@@ -53,7 +55,7 @@ TokenKind :: enum {
|
||||
plus, minus
|
||||
star, slash
|
||||
|
||||
range, range_inclusive
|
||||
range, range_inclusive, ellipsis
|
||||
at, ampersand, caret, dollar
|
||||
question, semicolon
|
||||
pipe
|
||||
|
||||
@@ -316,6 +316,22 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
||||
else: unreachable
|
||||
}
|
||||
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
|
||||
} else if (instruction.op == .divide) {
|
||||
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)) {
|
||||
minimum :: fresh(state)
|
||||
negative_one :: fresh(state)
|
||||
overflow :: fresh(state)
|
||||
try io.print(" %r{} = icmp eq i{} %r{}, {}\n", output, {minimum, bits, left, ir.integer_min(instruction.type)})
|
||||
try io.print(" %r{} = icmp eq i{} %r{}, -1\n", output, {negative_one, bits, right})
|
||||
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})
|
||||
} else {
|
||||
opcode []u8 :: match instruction.op {
|
||||
.add: "add"
|
||||
|
||||
@@ -64,9 +64,10 @@ emit proc(
|
||||
try bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?, false)
|
||||
try io.write_all(output, "\n")
|
||||
}
|
||||
memory_codes [6]diagnostics.Code :: [
|
||||
memory_codes [7]diagnostics.Code :: [
|
||||
.null_unwrap, .invalid_slice_bounds, .sentinel_mismatch,
|
||||
.overlapping_memory_copy, .unsupported_foreign_call, .invalid_pointer_access,
|
||||
.division_by_zero,
|
||||
]
|
||||
for (memory_codes) |code| {
|
||||
try io.print("@memory_{s} = private constant ", output, {memory_symbol(code)})
|
||||
@@ -502,6 +503,7 @@ memory_symbol proc(code diagnostics.Code) []u8 {
|
||||
.overlapping_memory_copy: "overlapping_memory_copy"
|
||||
.unsupported_foreign_call: "unsupported_foreign_call"
|
||||
.invalid_pointer_access: "invalid_pointer_access"
|
||||
.division_by_zero: "division_by_zero"
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
+21
-1
@@ -133,6 +133,7 @@ lower_module proc(
|
||||
id :: ast.package_decl(tree, i)
|
||||
match ast.decl_kind(tree, id) {
|
||||
.function: {
|
||||
if (ast.function_external(tree, id) and typecheck.type_of(checked, id).kind == .none) continue
|
||||
function :: try add_function(&module, ast.node_span(tree, id))
|
||||
storage[usize(id)] = Storage{ kind = .function, index = u32(function) }
|
||||
}
|
||||
@@ -157,7 +158,9 @@ lower_module proc(
|
||||
for (0..ast.package_decl_count(tree)) |i| {
|
||||
id :: ast.package_decl(tree, i)
|
||||
match ast.decl_kind(tree, id) {
|
||||
.function: try lower_function(&state, id)
|
||||
.function: {
|
||||
if (storage[usize(id)].kind == .function) try lower_function(&state, id)
|
||||
}
|
||||
.statement: {
|
||||
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
||||
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
||||
@@ -1735,6 +1738,23 @@ lower_builtin proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
||||
present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = true } }, span)
|
||||
return try record_pair(state, try array_shape(state, type), present, payload, span)
|
||||
}
|
||||
if (mem.eql(name, "maxval") or mem.eql(name, "minval")) {
|
||||
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
||||
value := ir.Value{ type = type }
|
||||
if (mem.eql(name, "maxval")) value.data = ir.ValueData{ unsigned = ir.integer_max(type) }
|
||||
else value.data = ir.ValueData{ integer = ir.integer_min(type) }
|
||||
return try constant(state, value, span)
|
||||
}
|
||||
if (mem.eql(name, "divtrunc")) {
|
||||
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
|
||||
return try emit(state, ir.Instruction{
|
||||
op = .divide, type = lower_type(state, typecheck.type_of(state.checked, id)),
|
||||
left = left, right = right, span = span,
|
||||
})
|
||||
}
|
||||
if (mem.eql(name, "sizeof") or mem.eql(name, "alignof")) {
|
||||
checked :: typecheck.type_of(state.checked, ast.call_arg(tree, id, 0))
|
||||
type :: lower_type(state, checked)
|
||||
|
||||
@@ -2014,3 +2014,76 @@ fallible_source_handlers_do_not_catch_runtime_allocation_failures test {
|
||||
}
|
||||
try testing.expect(succeeded and failures > 0)
|
||||
}
|
||||
|
||||
keeps_generated_foreign_declarations_demand_driven_and_traps_interpreted_calls test {
|
||||
source ::
|
||||
`Handle :: opaque
|
||||
`Packet :: c_struct { context ?*mut Handle, callback ?*c_func(_ c_int, _ c_int) void }
|
||||
`unsupported c_func(_ Packet) Packet
|
||||
`variadic c_func(_ c_int, ...) c_int
|
||||
`single c_func(_ c_float) c_float
|
||||
`extended c_func(_ c_longdouble) c_longdouble
|
||||
`double c_func(_ c_double) c_double
|
||||
`malloc c_func(_ c_ulong) ?*mut anyopaque
|
||||
`@hide:file
|
||||
`helper proc() int { if (true) return 42
|
||||
`else return 0 }
|
||||
`main proc() int { return helper() }
|
||||
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 testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
|
||||
foreign_module := try compile_owned(
|
||||
"realloc c_func(_ ?*mut anyopaque, _ c_ulong) ?*mut anyopaque\nmain proc() bool { return realloc(null, 8) == null }\n",
|
||||
&diagnostic_store,
|
||||
)
|
||||
defer ir.deinit(&foreign_module)
|
||||
foreign_outcome := try interpreter.run(mem.c_allocator, &foreign_module, &diagnostic_store)
|
||||
defer interpreter.deinit(&foreign_outcome)
|
||||
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",
|
||||
"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)
|
||||
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 testing.expect(outcome.trap != null)
|
||||
}
|
||||
}
|
||||
|
||||
rejects_demanded_unavailable_c_floating_types_without_unknown_names test {
|
||||
sources [2][]u8 :: [
|
||||
"single c_func() c_float\nmain proc() void { single() }\n",
|
||||
"extended c_func() c_longdouble\nmain proc() void { extended() }\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, .invalid_operand)
|
||||
for 0..diagnostics.count(&diagnostic_store) |index| {
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(index))?
|
||||
try testing.expect(diagnostic.code != .unknown_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+59
-21
@@ -114,6 +114,24 @@ parse_package proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.
|
||||
|
||||
@hide:file
|
||||
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)
|
||||
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 }
|
||||
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)
|
||||
return declaration
|
||||
}
|
||||
if (
|
||||
next_is(state, .import) or next_is(state, .test)
|
||||
or (next_is(state, .ident) and next_n_is(state, 1, .double_colon) and next_n_is(state, 2, .import))
|
||||
@@ -158,7 +176,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
|
||||
params arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(¶ms)
|
||||
try parse_parameters(state, ¶ms)
|
||||
variadic :: try parse_parameters(state, ¶ms)
|
||||
_ = try consume(state, .close_paren)
|
||||
return_type :: try parse_type(state)
|
||||
body :: if (external) try ast.add_block(state.builder, ast.builder_span(state.builder, return_type), &[]) else try parse_block(state)
|
||||
@@ -177,17 +195,24 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||
body,
|
||||
)
|
||||
if (external) ast.set_function_external(state.builder, id)
|
||||
if (variadic) ast.set_callable_variadic(state.builder, id)
|
||||
return id
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_parameters proc(
|
||||
state @mut State, params @mut arraylist.ArrayList(ast.NodeId),
|
||||
) void ! ParseError {
|
||||
) bool ! ParseError {
|
||||
skip_newlines(state)
|
||||
names arraylist.ArrayList(ParameterName) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&names)
|
||||
while !next_is(state, .close_paren) {
|
||||
if (next_is(state, .ellipsis)) {
|
||||
state.next_token += 1
|
||||
skip_newlines(state)
|
||||
if (!next_is(state, .close_paren)) return .unexpected_token
|
||||
return true
|
||||
}
|
||||
arraylist.clear(&names)
|
||||
while true {
|
||||
start :: current(state).span.start
|
||||
@@ -221,6 +246,7 @@ parse_parameters proc(
|
||||
skip_newlines(state)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -278,7 +304,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (next_n_is(state, 1, .ident)
|
||||
or next_n_is(state, 1, .struct)
|
||||
or next_n_is(state, 1, .struct) or next_n_is(state, 1, .c_struct) or next_n_is(state, 1, .opaque)
|
||||
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
|
||||
or next_n_is(state, 1, .distinct)
|
||||
or next_n_is(state, 1, .match)
|
||||
@@ -298,7 +324,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.defer, .errdefer: return try parse_cleanup(state, allow_else)
|
||||
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .struct,
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .struct, .c_struct, .opaque, .c_func,
|
||||
.enum, .union, .distinct, .alias, .dot, .match, .ampersand, .at, .star, .question, .null, .undefined, .try, .dollar: {
|
||||
expression :: try parse_expr(state)
|
||||
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
||||
@@ -406,13 +432,11 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .if)
|
||||
condition :: try parse_guard(state)
|
||||
capture :: try parse_optional_capture(state)
|
||||
braced :: next_is(state, .open_curly)
|
||||
then_block :: try parse_arm(state, true)
|
||||
# only a braced arm permits its else on a later line.
|
||||
after_then :: state.next_token
|
||||
if (braced) skip_newlines(state)
|
||||
skip_newlines(state)
|
||||
alternative ?ast.NodeId := null
|
||||
if next_is(state, .else) {
|
||||
if (next_is(state, .else) and !next_n_is(state, 1, .colon)) {
|
||||
state.next_token += 1
|
||||
alternative = try parse_arm(state, true)
|
||||
} else {
|
||||
@@ -744,7 +768,8 @@ 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)) return try parse_function_type(state, token.span, mutable or token.kind == .star)
|
||||
if (next_is(state, .proc) or next_is(state, .c_func))
|
||||
return try parse_function_type(state, token.span, mutable or (token.kind == .star and !next_is(state, .c_func)))
|
||||
element :: try parse_type_atom(state)
|
||||
return try ast.add_pointer_type(state.builder, ast.Span{
|
||||
start = token.span.start,
|
||||
@@ -792,8 +817,14 @@ 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, .proc) or next_is(state, .c_func)) return try parse_function_type(state, current(state).span, false)
|
||||
if (next_is(state, .struct) or next_is(state, .c_struct)) return try parse_record_type(state)
|
||||
if (next_is(state, .opaque)) {
|
||||
token :: try consume(state, .opaque)
|
||||
id :: try ast.add_record_type(state.builder, token.span, &[])
|
||||
ast.set_record_opaque(state.builder, id)
|
||||
return id
|
||||
}
|
||||
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, .match) return try parse_match(state)
|
||||
@@ -824,11 +855,12 @@ parse_type_atom proc(state @mut State) ast.NodeId ! ParseError {
|
||||
|
||||
@hide:file
|
||||
parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .proc)
|
||||
external :: next_is(state, .c_func)
|
||||
_ = try consume_either(state, &[.proc, .c_func])
|
||||
_ = try consume(state, .open_paren)
|
||||
params := arraylist.init(ast.NodeId, state.allocator)
|
||||
defer arraylist.deinit(¶ms)
|
||||
try parse_parameters(state, ¶ms)
|
||||
variadic :: try parse_parameters(state, ¶ms)
|
||||
_ = try consume(state, .close_paren)
|
||||
result :: try parse_type(state)
|
||||
span :: ast.Span{
|
||||
@@ -840,15 +872,18 @@ parse_function_type proc(state @mut State, start ast.Span, invalid bool) ast.Nod
|
||||
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)
|
||||
id :: try ast.add_function_type(state.builder, span, result, params.items)
|
||||
if (external) ast.set_callable_foreign(state.builder, id)
|
||||
if (variadic) ast.set_callable_variadic(state.builder, id)
|
||||
return id
|
||||
}
|
||||
|
||||
@hide:file
|
||||
starts_type proc(kind lexer.TokenKind) bool {
|
||||
return (
|
||||
kind == .ident or kind == .open_bracket or kind == .struct
|
||||
kind == .ident or kind == .open_bracket or kind == .struct or kind == .c_struct or kind == .opaque
|
||||
or kind == .enum or kind == .union or kind == .distinct or kind == .alias or kind == .match
|
||||
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc
|
||||
or kind == .at or kind == .star or kind == .question or kind == .open_paren or kind == .proc or kind == .c_func
|
||||
)
|
||||
}
|
||||
|
||||
@@ -856,7 +891,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
||||
member_name proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .break, .continue,
|
||||
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
|
||||
.true, .false, .and, .or, .mut, .struct, .c_struct, .opaque, .enum, .union, .distinct,
|
||||
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
@@ -1126,8 +1161,11 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
|
||||
@hide:file
|
||||
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .struct)
|
||||
return try parse_record(state, null, keyword.span.start, true)
|
||||
keyword :: try consume_either(state, &[.struct, .c_struct])
|
||||
id :: try parse_record(state, null, keyword.span.start, true)
|
||||
if (keyword.kind == .c_struct and ast.builder_expr_kind(state.builder, id) == .record_type)
|
||||
ast.set_record_c_layout(state.builder, id)
|
||||
return id
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -1631,8 +1669,8 @@ 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, .proc: return try parse_type(state)
|
||||
.struct: return try parse_record_type(state)
|
||||
.at, .star, .question, .proc, .c_func, .opaque: return try parse_type(state)
|
||||
.struct, .c_struct: return try parse_record_type(state)
|
||||
.enum: return try parse_enum_type(state)
|
||||
.union: return try parse_union_type(state)
|
||||
.distinct, .alias: return try parse_type_wrapper(state)
|
||||
|
||||
@@ -1975,3 +1975,68 @@ rejects_malformed_inline_match_captures test {
|
||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||
}
|
||||
}
|
||||
|
||||
parses_generated_foreign_bindings_and_preserves_callable_metadata test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`Handle :: opaque
|
||||
`Record :: c_struct {
|
||||
` context ?*mut Handle
|
||||
` callback ?*c_func(_ c_int, _ ?*anyopaque) void
|
||||
`}
|
||||
`printf c_func(_ ?*c_char, ...) c_int
|
||||
`@hide:file
|
||||
`helper proc() int {
|
||||
` if (true) return 42
|
||||
` else return 0
|
||||
`}
|
||||
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))
|
||||
handle_type :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||
record :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer
|
||||
try testing.expect(ast.record_opaque(&tree, handle_type))
|
||||
try testing.expect(ast.record_c_layout(&tree, record))
|
||||
callback :: ast.type_operand(&tree, ast.field(&tree, ast.record_field(&tree, record, 1)).type?)
|
||||
try testing.expect(ast.callable_foreign(&tree, callback))
|
||||
try testing.expect_equal(2, ast.function_param_count(&tree, callback))
|
||||
try testing.expect(ast.callable_variadic(&tree, ast.package_decl(&tree, 2)))
|
||||
helper :: ast.package_decl(&tree, 3)
|
||||
try testing.expect(ast.file_local(&tree, helper))
|
||||
branch :: ast.if_stmt(&tree, ast.block_statement(&tree, ast.function_decl(&tree, helper).body, 0))
|
||||
try testing.expect(branch.else_block != null)
|
||||
}
|
||||
|
||||
keeps_match_default_arm_after_newline_single_statement_if test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`main proc(value int) void {
|
||||
` match value {
|
||||
` 1: if (true) return
|
||||
` else: return
|
||||
` }
|
||||
`}
|
||||
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))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
expression :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
|
||||
try testing.expect_equal(2, ast.match_arm_count(&tree, expression))
|
||||
first :: ast.match_arm_view(&tree, ast.match_arm(&tree, expression, 0))
|
||||
branch :: ast.if_stmt(&tree, ast.block_statement(&tree, first.body, 0))
|
||||
try testing.expect(branch.else_block == null)
|
||||
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, expression, 1)).catch_all)
|
||||
}
|
||||
|
||||
+81
-11
@@ -312,7 +312,11 @@ check_elaborated proc(
|
||||
id :: ast.package_decl(tree, i)
|
||||
match ast.decl_kind(tree, id) {
|
||||
.statement: try collect_declaration(&state, id)
|
||||
.function: try collect_function(&state, id)
|
||||
.function: {
|
||||
if (!ast.function_external(tree, id)
|
||||
or mem.eql(ast.source_text(tree, ast.function_decl(tree, id).name), "main"))
|
||||
try collect_function(&state, id)
|
||||
}
|
||||
.invalid: collect_invalid(&state, id)
|
||||
.import: {}
|
||||
}
|
||||
@@ -436,6 +440,12 @@ check_elaborated proc(
|
||||
try require_numeric(&state, id, expression.left)
|
||||
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) {
|
||||
try require_numeric(&state, id, ast.call_arg(tree, id, 0))
|
||||
try require_numeric(&state, id, ast.call_arg(tree, id, 1))
|
||||
}
|
||||
}
|
||||
else: {}
|
||||
}
|
||||
}
|
||||
@@ -463,6 +473,12 @@ check_elaborated proc(
|
||||
}
|
||||
}
|
||||
}
|
||||
.builtin_call: {
|
||||
if (mem.eql(builtin_name(&state, id), "divtrunc") 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))
|
||||
}
|
||||
}
|
||||
else: {}
|
||||
}
|
||||
}
|
||||
@@ -522,9 +538,11 @@ check_elaborated proc(
|
||||
for (0..ast.package_decl_count(tree)) |i| {
|
||||
id :: ast.package_decl(tree, i)
|
||||
if (ast.decl_kind(tree, id) != .function or !ast.function_external(tree, id)) continue
|
||||
if (cells[usize(id)].role == .none) continue
|
||||
for (0..i) |j| {
|
||||
previous :: ast.package_decl(tree, j)
|
||||
if (ast.decl_kind(tree, previous) != .function or !ast.function_external(tree, previous)) continue
|
||||
if (cells[usize(previous)].role == .none) continue
|
||||
if (ast.name_id(tree, id) == ast.name_id(tree, previous) and !same_signature(&state, id, previous)) {
|
||||
try fail(&state, id, .type_mismatch, ast.function_decl(tree, id).name)
|
||||
}
|
||||
@@ -846,7 +864,7 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
||||
if (
|
||||
(element_binding.kind == .builtin
|
||||
and (element_binding.data.builtin == .int or element_binding.data.builtin == .float))
|
||||
or element.kind == .void or element.kind == .range
|
||||
or element.kind == .void or element.kind == .range or element.kind == .anyopaque
|
||||
) {
|
||||
element = invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_element_type, ast.node_span(state.tree, view.element)))
|
||||
state.types[usize(view.element)] = element
|
||||
@@ -879,6 +897,8 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
||||
.invalid: type = invalid_type(binding.data.diagnostic)
|
||||
.builtin: {
|
||||
type.kind = builtin_kind(binding.data.builtin)
|
||||
if (binding.data.builtin == .unavailable)
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, id)))
|
||||
if (type.kind == .anyopaque and state.pointer_depth == 0) {
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
@@ -893,6 +913,8 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
||||
initializer :: ast.statement_decl(state.tree, target).initializer
|
||||
if (type_syntax(ast.expr_kind(state.tree, initializer))) {
|
||||
type = try check_type_expression(state, initializer, true)
|
||||
if (type.kind == .anyopaque and state.pointer_depth == 0)
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||
} else {
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||
}
|
||||
@@ -952,6 +974,7 @@ collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
|
||||
@hide:file
|
||||
collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (state.cells[usize(id)].role == .function) return
|
||||
tree :: state.tree
|
||||
function :: ast.function_decl(tree, id)
|
||||
state.cells[usize(id)].role = .function
|
||||
@@ -977,6 +1000,7 @@ 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)
|
||||
@@ -985,6 +1009,19 @@ collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError |
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
collect_foreign_symbol proc(state @mut State, target ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (state.cells[usize(target)].role == .function) return
|
||||
tree :: state.tree
|
||||
# declarations of the same external symbol must agree even across package namespaces.
|
||||
for 0..ast.package_decl_count(tree) |i| {
|
||||
id :: ast.package_decl(tree, i)
|
||||
if (ast.decl_kind(tree, id) == .function and ast.function_external(tree, id)
|
||||
and ast.name_id(tree, id) == ast.name_id(tree, target))
|
||||
try collect_function(state, id)
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
collect_capture proc(state @mut State, id, body ast.NodeId) void ! mem.AllocError {
|
||||
state.cells[usize(id)].role = .loop_capture
|
||||
@@ -1252,6 +1289,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
||||
}
|
||||
if (binding.kind == .declaration) {
|
||||
target :: binding.data.declaration
|
||||
if (ast.function_external(tree, target)) try collect_foreign_symbol(state, target)
|
||||
role :: state.cells[usize(target)].role
|
||||
if (
|
||||
role != .parameter and role != .loop_capture and ast.decl_kind(tree, target) == .statement
|
||||
@@ -1441,6 +1479,7 @@ builtin_kind proc(type sema.BuiltinType) TypeKind {
|
||||
.c_uchar: .u8
|
||||
.c_short: .i16
|
||||
.c_ushort: .u16
|
||||
.unavailable: .none
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1840,7 +1879,8 @@ addressable_target proc(state @mut State, id ast.NodeId) bool {
|
||||
@hide:file
|
||||
same_signature proc(state @State, left, right ast.NodeId) bool {
|
||||
if (left == right) return true
|
||||
if (ast.function_external(state.tree, left) != ast.function_external(state.tree, right)) return false
|
||||
if (ast.callable_foreign(state.tree, left) != ast.callable_foreign(state.tree, right)) return false
|
||||
if (ast.callable_variadic(state.tree, left) != ast.callable_variadic(state.tree, right)) return false
|
||||
tree :: state.tree
|
||||
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
|
||||
left_result :: ast.callable_result(tree, left)
|
||||
@@ -1900,7 +1940,7 @@ assignable_type proc(state @State, actual, expected Type) bool {
|
||||
a :: state.pointers.items[usize(actual.pointer_type)]
|
||||
b :: state.pointers.items[usize(expected.pointer_type)]
|
||||
if (a.many != b.many or (!a.mutable and b.mutable)) return false
|
||||
if (b.element.kind == .anyopaque) return true
|
||||
if (b.element.kind == .anyopaque and b.element.nominal == ast.NodeId(maxval!(u32))) return true
|
||||
if (b.mutable) return same_type(state, a.element, b.element)
|
||||
return assignable_type(state, a.element, b.element)
|
||||
}
|
||||
@@ -2320,11 +2360,11 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
|
||||
if (cached.kind != .none) return cached
|
||||
type := Type{ kind = .function, function = id }
|
||||
state.cells[usize(id)].type = type
|
||||
state.pointer_depth += 1
|
||||
defer state.pointer_depth -= 1
|
||||
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)))
|
||||
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| {
|
||||
parameter :: ast.function_param(state.tree, id, i)
|
||||
@@ -2334,6 +2374,8 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
|
||||
state.cells[usize(parameter)].type = parameter_type
|
||||
state.types[usize(parameter)] = parameter_type
|
||||
if (parameter_type.kind == .invalid and type.kind != .invalid) type = parameter_type
|
||||
if (ast.callable_foreign(state.tree, id) and !c_abi_type(state, parameter_type))
|
||||
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, parameter)))
|
||||
try depend(state, annotation, parameter)
|
||||
try depend(state, parameter, id)
|
||||
}
|
||||
@@ -2387,10 +2429,16 @@ 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 state.arrays.items[usize(type.array_type)].element.kind == .pointer
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@hide:file
|
||||
c_abi_pointer proc(state @State, type Type) bool {
|
||||
return type.kind == .pointer or (type.kind == .function and c_abi_type(state, type))
|
||||
}
|
||||
|
||||
@hide:file
|
||||
builtin_name proc(state @State, id ast.NodeId) []u8 {
|
||||
return ast.literal_text(state.tree, ast.builtin_call(state.tree, id).name)
|
||||
@@ -2401,15 +2449,21 @@ collect_builtin proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
||||
name :: builtin_name(state, id)
|
||||
count :: ast.call_arg_count(state.tree, id)
|
||||
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
|
||||
limit :: mem.eql(name, "maxval") or mem.eql(name, "minval")
|
||||
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
|
||||
for (0..count) |i| {
|
||||
argument :: ast.call_arg(state.tree, id, i)
|
||||
if (layout or (cast and count == 2 and i == 0)) {
|
||||
if (layout or limit or (cast and count == 2 and i == 0)) {
|
||||
_ = try check_annotation(state, argument, true)
|
||||
} else try collect_expression(state, argument)
|
||||
try depend(state, argument, id)
|
||||
}
|
||||
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")) {
|
||||
state.cells[usize(id)].numeric = true
|
||||
state.cells[usize(id)].producer = id
|
||||
}
|
||||
if (mem.eql(name, "memcopy") or mem.eql(name, "memset")) state.cells[usize(id)].type = Type{ kind = .void }
|
||||
}
|
||||
|
||||
@@ -2483,16 +2537,30 @@ infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.A
|
||||
count :: ast.call_arg_count(state.tree, id)
|
||||
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")
|
||||
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")
|
||||
valid_count :: (layout and count == 1) or (cast and (count == 1 or count == 2)) or (memory and count == 2) or (some and count == 1)
|
||||
valid_count :: ((layout or limit) and count == 1) or (cast and (count == 1 or count == 2)) or ((memory or division) and count == 2) or (some and count == 1)
|
||||
if (!valid_count) {
|
||||
code diagnostics.Code :: if (layout or cast or memory or some) .argument_count_mismatch else .invalid_operand
|
||||
code diagnostics.Code :: if (layout or limit or division or cast or memory or some) .argument_count_mismatch else .invalid_operand
|
||||
if (defaults) try fail(state, id, code, span)
|
||||
return group_type(state, id)
|
||||
}
|
||||
if (layout) return group_type(state, id)
|
||||
if (limit) {
|
||||
type :: state.types[usize(ast.call_arg(state.tree, id, 0))]
|
||||
if (defaults and type.kind != .invalid and !integer_kind(type.kind)) try fail(state, id, .invalid_operand, span)
|
||||
return group_type(state, id)
|
||||
}
|
||||
if (division) {
|
||||
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)
|
||||
return group_type(state, id)
|
||||
}
|
||||
if (some) {
|
||||
result :: state.cells[root(state, usize(id))].expected
|
||||
if (result.kind != .optional) {
|
||||
@@ -3359,8 +3427,9 @@ intern_record proc(state @mut State, declaration ast.NodeId, fields []mut Record
|
||||
c_record_field_type proc(state @State, type Type, depth usize) bool {
|
||||
if (depth >= 128 or type.nominal != ast.NodeId(maxval!(u32))) return false
|
||||
if (numeric_kind(type.kind) or type.kind == .bool or type.kind == .pointer) return true
|
||||
if (type.kind == .function) return c_abi_type(state, type)
|
||||
if (type.kind == .optional)
|
||||
return state.arrays.items[usize(type.array_type)].element.kind == .pointer
|
||||
return c_abi_pointer(state, state.arrays.items[usize(type.array_type)].element)
|
||||
if (type.kind == .enumeration)
|
||||
return state.enums.items[usize(type.enum_type)].explicit_backing
|
||||
if (type.kind == .array) {
|
||||
@@ -3379,6 +3448,7 @@ c_record_field_type proc(state @State, type Type, depth usize) bool {
|
||||
|
||||
@hide:file
|
||||
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (ast.record_opaque(state.tree, id)) return Type{ kind = .anyopaque, nominal = id }
|
||||
cached := state.cells[usize(id)].type
|
||||
if (cached.kind == .invalid) return cached
|
||||
if (!state.cells[usize(id)].record_checking and (cached.kind == .record or cached.kind == .type_value)) {
|
||||
|
||||
@@ -18,6 +18,7 @@ BuiltinType :: enum {
|
||||
c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong
|
||||
c_char, c_uchar, c_short, c_ushort
|
||||
type
|
||||
unavailable
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -53,9 +54,13 @@ builtins [_]Builtin :: [
|
||||
{ name = "c_longlong", type = .c_longlong },
|
||||
{ name = "c_ulonglong", type = .c_ulonglong },
|
||||
{ name = "c_char", type = .c_char },
|
||||
{ name = "c_schar", type = .c_char },
|
||||
{ name = "c_uchar", type = .c_uchar },
|
||||
{ name = "c_short", type = .c_short },
|
||||
{ name = "c_ushort", type = .c_ushort },
|
||||
{ name = "c_double", type = .f64 },
|
||||
{ name = "c_float", type = .unavailable },
|
||||
{ name = "c_longdouble", type = .unavailable },
|
||||
{ name = "type", type = .type },
|
||||
]
|
||||
|
||||
@@ -194,6 +199,7 @@ resolve_packages proc(
|
||||
for (0..ast.package_decl_count_at(tree, root)) |i| {
|
||||
id :: ast.package_decl_at(tree, root, i)
|
||||
file_scopes[usize(ast.node_span(tree, id).source)].parent = scope
|
||||
if (ast.file_local(tree, id)) continue
|
||||
match ast.decl_kind(tree, id) {
|
||||
.statement: try predeclare(&state, scope, id, ast.statement_decl(tree, id).name)
|
||||
.function: try predeclare(&state, scope, id, ast.function_decl(tree, id).name)
|
||||
@@ -207,6 +213,18 @@ resolve_packages proc(
|
||||
}
|
||||
}
|
||||
}
|
||||
for (roots) |root| {
|
||||
for (0..ast.package_decl_count_at(tree, root)) |i| {
|
||||
id :: ast.package_decl_at(tree, root, i)
|
||||
if (!ast.file_local(tree, id)) continue
|
||||
scope :: &file_scopes[usize(ast.node_span(tree, id).source)]
|
||||
match ast.decl_kind(tree, id) {
|
||||
.statement: try predeclare(&state, scope, id, ast.statement_decl(tree, id).name)
|
||||
.function: try predeclare(&state, scope, id, ast.function_decl(tree, id).name)
|
||||
.import, .invalid: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (imports) |imported| try resolve_import(&state, &file_scopes[usize(ast.node_span(tree, imported.node).source)], imported)
|
||||
for (roots) |root| try resolve_package(&state, root, file_scopes)
|
||||
try canonicalize_aliases(&state)
|
||||
@@ -316,6 +334,10 @@ predeclare proc(
|
||||
name_span ast.Span,
|
||||
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
name :: ast.name_id(state.tree, id)
|
||||
if (mem.eql(ast.source_text(state.tree, name_span), "_")) {
|
||||
state.bindings[usize(id)] = Binding{ kind = .declaration, data = BindingData{ declaration = id } }
|
||||
return
|
||||
}
|
||||
if hashmap.get(&scope.names, name) |_| {
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .duplicate_name, name_span)
|
||||
state.bindings[usize(id)] = Binding{
|
||||
|
||||
@@ -4349,6 +4349,21 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
.literal_bool: return try builtin(state, id, .bool)
|
||||
.literal_string: return try builtin(state, id, .string)
|
||||
.literal_void: return try builtin(state, id, .void)
|
||||
.builtin_call: {
|
||||
name :: ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name)
|
||||
count :: ast.call_arg_count(&state.tree, id)
|
||||
if ((mem.eql(name, "sizeof") or mem.eql(name, "alignof")) and count == 1)
|
||||
return try builtin(state, id, .usize)
|
||||
if ((mem.eql(name, "maxval") or mem.eql(name, "minval")) and count == 1)
|
||||
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
||||
if (mem.eql(name, "divtrunc") 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
|
||||
return try expression_type(state, operand, depth + 1)
|
||||
}
|
||||
return no_node
|
||||
}
|
||||
.catch_expr: {
|
||||
type :: try expression_type(state, ast.catch_expr(&state.tree, id).operand, depth + 1)
|
||||
if (type == no_node or ast.expr_kind(&state.tree, type) != .fallible_type) return no_node
|
||||
@@ -4447,6 +4462,25 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
||||
)
|
||||
)
|
||||
return try builtin(state, id, .usize)
|
||||
if (mem.eql(member, "ptr")) {
|
||||
element := no_node
|
||||
mutable := false
|
||||
if (base_kind == .array_type) {
|
||||
element = ast.array_type(&state.tree, base).element
|
||||
mutable = ast.array_type(&state.tree, base).mutable
|
||||
}
|
||||
if (base_kind == .slice_type) {
|
||||
element = ast.slice_type(&state.tree, base).element
|
||||
mutable = ast.slice_type(&state.tree, base).mutable
|
||||
}
|
||||
if (binding(state, base).kind == .builtin and binding(state, base).data.builtin == .string)
|
||||
element = try builtin(state, id, .u8)
|
||||
if (element != no_node) {
|
||||
result :: try ast.add_pointer_type(state.builder, view.span, element, mutable, true)
|
||||
try refresh(state)
|
||||
return result
|
||||
}
|
||||
}
|
||||
if (base_kind != .record_type and base_kind != .union_type) return no_node
|
||||
slot :: type_record_slot(state, base, view.member)
|
||||
if (slot == no_index) return no_node
|
||||
@@ -4524,32 +4558,36 @@ infer proc(state @mut State, function, pattern, actual ast.NodeId, arguments []m
|
||||
resolved :: binding(state, pattern)
|
||||
if (resolved.kind == .declaration and parameter_index(state, function, resolved.data.declaration) != no_index)
|
||||
return try infer_value(state, function, pattern, Value{ kind = .type, node = actual }, arguments, contextual)
|
||||
# only normalize the actual type: the pattern may still contain unbound
|
||||
# parameters or a type-generator call whose arguments must be inferred.
|
||||
concrete :: try canonical_type(state, actual)
|
||||
if (concrete == no_node) return false
|
||||
kind :: ast.expr_kind(&state.tree, pattern)
|
||||
actual_kind :: ast.expr_kind(&state.tree, actual)
|
||||
actual_kind :: ast.expr_kind(&state.tree, concrete)
|
||||
match kind {
|
||||
.pointer_type: {
|
||||
if (actual_kind != .pointer_type) return false
|
||||
return try infer(state, function, ast.pointer_type(&state.tree, pattern).element,
|
||||
ast.pointer_type(&state.tree, actual).element, arguments, contextual)
|
||||
ast.pointer_type(&state.tree, concrete).element, arguments, contextual)
|
||||
}
|
||||
.slice_type: {
|
||||
element := no_node
|
||||
if (actual_kind == .slice_type) element = ast.slice_type(&state.tree, actual).element
|
||||
if (actual_kind == .array_type) element = ast.array_type(&state.tree, actual).element
|
||||
if (actual_kind == .slice_type) element = ast.slice_type(&state.tree, concrete).element
|
||||
if (actual_kind == .array_type) element = ast.array_type(&state.tree, concrete).element
|
||||
if (element == no_node) return false
|
||||
return try infer(state, function, ast.slice_type(&state.tree, pattern).element, element, arguments, contextual)
|
||||
}
|
||||
.array_type: {
|
||||
if (actual_kind != .array_type) return false
|
||||
p :: ast.array_type(&state.tree, pattern)
|
||||
a :: ast.array_type(&state.tree, actual)
|
||||
a :: ast.array_type(&state.tree, concrete)
|
||||
if (p.count) |count| {
|
||||
if (a.count == null or !try infer_value(state, function, count, try evaluate(state, a.count?), arguments, false)) return false
|
||||
}
|
||||
return try infer(state, function, p.element, a.element, arguments, contextual)
|
||||
}
|
||||
.optional_type: {
|
||||
element :: if (actual_kind == .optional_type) ast.type_operand(&state.tree, actual) else actual
|
||||
element :: if (actual_kind == .optional_type) ast.type_operand(&state.tree, concrete) else concrete
|
||||
return try infer(state, function, ast.type_operand(&state.tree, pattern), element, arguments, contextual)
|
||||
}
|
||||
.call: {
|
||||
@@ -4558,7 +4596,7 @@ infer proc(state @mut State, function, pattern, actual ast.NodeId, arguments []m
|
||||
for (0..(state.instances.items.len)) |i| {
|
||||
instance :: state.instances.items[i]
|
||||
if (instance.type == no_node or instance.function != callee.data.declaration) continue
|
||||
if (try canonical_type(state, instance.type) != actual) continue
|
||||
if (try canonical_type(state, instance.type) != concrete) continue
|
||||
count :: ast.call_arg_count(&state.tree, pattern)
|
||||
if (count != instance.arguments.len) return false
|
||||
for (0..count) |j| {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import "@std/mem"
|
||||
import "@source/ast"
|
||||
import "@source/diagnostics"
|
||||
import "@source/strpool"
|
||||
sema :: import "@source/sema"
|
||||
typecheck :: import "@source/sema/check"
|
||||
import "@std/testing"
|
||||
import "@source/lexer"
|
||||
import "@source/parser"
|
||||
|
||||
@hide:file
|
||||
expect_specialization proc(
|
||||
source []u8, expected diagnostics.Code, expected_count usize,
|
||||
) void ! (mem.AllocError | diagnostics.CapacityError | strpool.InternError | testing.Error) {
|
||||
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)
|
||||
builder := ast.init_builder(mem.c_allocator, source)
|
||||
defer ast.deinit_builder(&builder)
|
||||
root :: try parser.parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
|
||||
tree := ast.view(&builder, root)
|
||||
resolution := try sema.resolve(mem.c_allocator, &tree, &strings, &diagnostic_store)
|
||||
defer sema.deinit(&resolution)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
elaborated := try elaborate(mem.c_allocator, &builder, root, &resolution, &strings, &diagnostic_store)
|
||||
defer deinit(&elaborated)
|
||||
tree = ast.view(&builder, root)
|
||||
checked := try typecheck.check_elaborated(mem.c_allocator, &tree, &resolution, elaborated.constraints.items, &diagnostic_store)
|
||||
defer typecheck.deinit(&checked)
|
||||
if (expected_count == 0) {
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
for (0..ast.package_decl_count(&tree)) |i| {
|
||||
declaration :: ast.package_decl(&tree, i)
|
||||
if (ast.node_class(&tree, declaration) != .function) continue
|
||||
try testing.expect(!ast.function_generic(&tree, declaration))
|
||||
for (0..ast.function_param_count(&tree, declaration)) |j|
|
||||
try testing.expect(!ast.param(&tree, ast.function_param(&tree, declaration, j)).comptime)
|
||||
}
|
||||
} else {
|
||||
count := usize(0)
|
||||
for (diagnostic_store.list.items) |diagnostic| {
|
||||
if (diagnostic.code == expected) count += 1
|
||||
}
|
||||
try testing.expect_equal(expected_count, count)
|
||||
}
|
||||
}
|
||||
|
||||
infers_nested_nominal_pointer_parameters_and_readonly_slice_views test {
|
||||
try expect_specialization(
|
||||
`Storage proc($T type) type { return struct { items []mut T, capacity usize } }
|
||||
`make proc($T type, values []mut T) Storage(T) {
|
||||
` return Storage(T) { items = values, capacity = values.len }
|
||||
`}
|
||||
`observe proc($T type, values []T) usize { return values.len }
|
||||
`reserve proc($T type, storage @mut Storage(T), count usize) void {
|
||||
` _ = observe(storage.items.ptr[..count])
|
||||
`}
|
||||
`append proc($T type, storage @mut Storage(T), value T) void {
|
||||
` reserve(storage, storage.items.len)
|
||||
` storage.items[0] = value
|
||||
`}
|
||||
`release proc($T type, storage @mut Storage(T)) void {
|
||||
` allocation :: storage.items.ptr[..storage.capacity]
|
||||
` _ = observe(allocation)
|
||||
`}
|
||||
`forward proc(storage @mut Storage(i16)) void {
|
||||
` append(storage, 7)
|
||||
` release(storage)
|
||||
`}
|
||||
`main proc() i16 {
|
||||
` values [2]mut i16 := [1, 2]
|
||||
` storage := make(i16, values[..])
|
||||
` forward(&storage)
|
||||
` append(&storage, 9)
|
||||
` release(&storage)
|
||||
` return storage.items[0]
|
||||
`}
|
||||
, .cannot_infer_generic, 0,
|
||||
)
|
||||
}
|
||||
|
||||
rejects_conflicting_rigid_types_and_unrelated_nominal_generators test {
|
||||
try expect_specialization(
|
||||
`same proc($T type, left, right T) void {}
|
||||
`main proc() void { same(i16(1), u8(2)) }
|
||||
, .cannot_infer_generic, 1,
|
||||
)
|
||||
try expect_specialization(
|
||||
`Box proc($T type) type { return struct { value T } }
|
||||
`Other proc($T type) type { return struct { value T } }
|
||||
`read proc($T type, value @Box(T)) void {}
|
||||
`main proc() void {
|
||||
` value Other(i16) :: { value = 1 }
|
||||
` read(&value)
|
||||
`}
|
||||
, .cannot_infer_generic, 1,
|
||||
)
|
||||
try expect_specialization(
|
||||
`unknown proc($T type, value usize) void {}
|
||||
`main proc() void { unknown(1) }
|
||||
, .cannot_infer_generic, 1,
|
||||
)
|
||||
}
|
||||
|
||||
preserves_rigid_inference_before_contextual_literals test {
|
||||
try expect_specialization(
|
||||
`same proc($T type, left, right T) T { return right }
|
||||
`main proc() i16 { return same(i16(1), 2) }
|
||||
, .cannot_infer_generic, 0,
|
||||
)
|
||||
}
|
||||
|
||||
infers_integer_builtin_results_from_their_type_or_rigid_operand test {
|
||||
try expect_specialization(
|
||||
`same proc($T type, left, right T) T { return right }
|
||||
`main proc() i16 {
|
||||
` upper :: same(maxval!(i16), i16(1))
|
||||
` lower :: same(minval!(i16), i16(2))
|
||||
` left :: same(divtrunc!(i16(8), 2), i16(3))
|
||||
` right :: same(divtrunc!(8, i16(2)), i16(4))
|
||||
` return upper + lower + left + right
|
||||
`}
|
||||
, .cannot_infer_generic, 0,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user