feat: evaluate compile-time aggregates and explicit expressions
This commit is contained in:
@@ -98,7 +98,9 @@ main proc() i16 {
|
|||||||
|
|
||||||
the specialization checkpoint supports type arguments and constant integer, boolean, string, callback and null value arguments; declared value-parameter types survive specialization, including narrow checked arithmetic and optional captures/unwrap. integer arguments are range-checked even when unused, and erased argument expressions still receive ordinary checking. immutable constant expressions may use unary signs, `!`, `+`, `-`, `*`, comparisons and logical operators. type-producing procedures require compile-time parameters (or no parameters) and support immutable locals, conditional returns, nested type-producing calls and generated record/array storage, including pointer-recursive records. runaway specialization is diagnosed at 128 nested evaluation/specialization calls or 4096 instances.
|
the specialization checkpoint supports type arguments and constant integer, boolean, string, callback and null value arguments; declared value-parameter types survive specialization, including narrow checked arithmetic and optional captures/unwrap. integer arguments are range-checked even when unused, and erased argument expressions still receive ordinary checking. immutable constant expressions may use unary signs, `!`, `+`, `-`, `*`, comparisons and logical operators. type-producing procedures require compile-time parameters (or no parameters) and support immutable locals, conditional returns, nested type-producing calls and generated record/array storage, including pointer-recursive records. runaway specialization is diagnosed at 128 nested evaluation/specialization calls or 4096 instances.
|
||||||
|
|
||||||
ordinary procedures may compute integer, boolean and string compile-time arguments from constant inputs, such as `Fixed(i16, length(2))`. calls reuse the ordinary checker, typed IR and interpreter, preserving local type inference, checked arithmetic, mutable locals, range/while loops, break/continue and recursive or specialized helper calls. each evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable global dependencies cannot supply compile-time inputs, and executed foreign calls remain interpreter traps. aggregate/type-valued execution, explicit `$()` expressions, reflection, `inline` expansion and existing generic-library acceptance remain outside this checkpoint.
|
ordinary procedures may compute integer, boolean, float and string compile-time arguments and return nested records/tuples and fixed arrays from constant inputs, such as `Fixed(i16, length(2))`. calls reuse the ordinary checker, typed IR and interpreter, preserving local type inference, checked arithmetic, mutable locals, range/while loops, break/continue and recursive or specialized helper calls. aggregate arguments receive their declared context, including dependent `[N]T` parameters; cached specializations compare normalized values, not literal spelling or evaluation identity. returned aggregates become owned source literals before temporary interpreter storage is released, preserving by-value copies, exact integer types, string bytes, enum fields, optional fields and IEEE float bits.
|
||||||
|
|
||||||
|
`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. data pointers, slices, callbacks, unions and fallible results cannot escape temporary evaluation as generated constants; records/arrays containing these unsupported values are diagnosed rather than retaining interpreter handles. executed foreign calls remain interpreter traps. general type-valued execution, compile-time branch/`inline` expansion, reflection, generated type/default arrays and existing generic-library acceptance remain open.
|
||||||
|
|
||||||
callback signatures use `@proc(value i16) i16`, including grouped parameter names and trailing commas. compatible named functions can initialize callbacks, which may be passed, returned, replaced and stored in records/arrays; `Callback :: alias @proc(value i16) i16` names a reusable signature. parameter/result types match structurally, retaining nominal record identity and exact pointer mutability. callback fields call directly, such as `allocator.vtable.alloc(context, size, alignment)` once the remaining argument types are supported. `@mut proc` and `*proc` are invalid; `@mut Callback` instead points to writable callback storage.
|
callback signatures use `@proc(value i16) i16`, including grouped parameter names and trailing commas. compatible named functions can initialize callbacks, which may be passed, returned, replaced and stored in records/arrays; `Callback :: alias @proc(value i16) i16` names a reusable signature. parameter/result types match structurally, retaining nominal record identity and exact pointer mutability. callback fields call directly, such as `allocator.vtable.alloc(context, size, alignment)` once the remaining argument types are supported. `@mut proc` and `*proc` are invalid; `@mut Callback` instead points to writable callback storage.
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ explicit fallible channels, composed error sets, `try`/`catch` and scoped LIFO
|
|||||||
traps or compiler infrastructure failures.
|
traps or compiler infrastructure failures.
|
||||||
cached generic specialization and type-producing procedures support explicit/inferred
|
cached generic specialization and type-producing procedures support explicit/inferred
|
||||||
compile-time arguments and generated record/array storage through both paths.
|
compile-time arguments and generated record/array storage through both paths.
|
||||||
ordinary procedures can compute integer/boolean/string compile-time arguments using
|
ordinary procedures can compute scalar and nested record/array compile-time values
|
||||||
mutable locals, loops and recursive calls; general aggregate/type-valued execution,
|
using mutable locals, loops and recursive calls; explicit `$()` expressions use
|
||||||
reflection and generic library acceptance remain open.
|
the same constant evaluation path. general type-valued execution, reflection and
|
||||||
|
generic library acceptance remain open.
|
||||||
the driver retains frontend storage and source provenance through
|
the driver retains frontend storage and source provenance through
|
||||||
`@source/compilation`, with compilation-wide AST node identities.
|
`@source/compilation`, with compilation-wide AST node identities.
|
||||||
file commands retain single-file behavior; directory commands compile the
|
file commands retain single-file behavior; directory commands compile the
|
||||||
@@ -60,7 +61,8 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage.
|
- [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage.
|
||||||
- [x] specialization checkpoint: explicit/inferred type and integer/boolean/string/callback/null value parameters, cached concrete runtime calls, nominal type-producing procedures, generated record/array storage and recursive data pointers through interpreter/native execution. preserve declared value types, source diagnostics and repeatable compilation; runtime values cannot supply compile-time arguments.
|
- [x] specialization checkpoint: explicit/inferred type and integer/boolean/string/callback/null value parameters, cached concrete runtime calls, nominal type-producing procedures, generated record/array storage and recursive data pointers through interpreter/native execution. preserve declared value types, source diagnostics and repeatable compilation; runtime values cannot supply compile-time arguments.
|
||||||
- [x] compile-time execution checkpoint: ordinary procedure calls with constant inputs and integer/boolean/string results reuse checking, typed IR and bounded interpreter execution. mutable scalar locals, range/while loops, break/continue, nested/generic/recursive calls, exact numeric types, runtime traps and dependency-file provenance survive argument erasure through interpreter/native execution. runtime parameters and mutable global dependencies are rejected; recompilation retains no prior execution state.
|
- [x] compile-time execution checkpoint: ordinary procedure calls with constant inputs and integer/boolean/string results reuse checking, typed IR and bounded interpreter execution. mutable scalar locals, range/while loops, break/continue, nested/generic/recursive calls, exact numeric types, runtime traps and dependency-file provenance survive argument erasure through interpreter/native execution. runtime parameters and mutable global dependencies are rejected; recompilation retains no prior execution state.
|
||||||
- [ ] remaining gate: general aggregate/type-valued compile-time execution, explicit compile-time expressions, compile-time branch/inline expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), plus existing-library acceptance. required collection/index iteration, expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
|
- [x] aggregate/explicit-expression checkpoint: computed nested records/tuples and fixed arrays, contextual/dependent aggregate value parameters, normalized specialization caching and explicit `$()` expressions through interpreter/native execution. preserve by-value isolation, zero-cell shapes, integer widths, string ownership, enum/optional fields and exact IEEE float bits; reject runtime dependencies and values that would retain temporary interpreter storage. repeatable compilation restores erased expressions.
|
||||||
|
- [ ] remaining gate: general type-valued compile-time execution, compile-time branch/inline expansion and reflection (`typeinfo!`, `field!`, `struct_type!`, generated type/default arrays), broader constant materialization (including unions, fallible results and storage-bearing values), plus existing-library acceptance. required collection/index iteration, expression-valued control flow, compound updates, formatting parsing and imported C-binding support may interleave with later gates.
|
||||||
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: collection/index iteration, expression-valued control flow and `yield`, compound updates and multiline strings. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
||||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||||
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
||||||
|
|||||||
+40
-2
@@ -6,6 +6,7 @@
|
|||||||
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block)
|
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!' | '&' | 'try') expr | expr ('^' | '?') | expr op expr | expr '(' [expr {',' expr} [',']] ')' | identifier '!' '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '[' [expr] '..' [expr] [';' expr] ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}' | expr 'catch' ['|' identifier '|'] (expr | block)
|
||||||
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
|
#! * decl ..... -> identifier [type] binding expr | identifier 'c_func' '(' parameters ')' type | ['test'] [identifier '::'] 'import' string
|
||||||
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
|
#! * parameters -> [['$'] identifier {',' ['$'] identifier} type {',' ['$'] identifier {',' ['$'] identifier} type} [',']]; '$' marks each compile-time parameter independently.
|
||||||
|
#! '$(' expr ')' is an explicit compile-time expression with primary-expression precedence.
|
||||||
#! * block .... -> '{' {stmt} '}'
|
#! * block .... -> '{' {stmt} '}'
|
||||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||||
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
||||||
@@ -69,6 +70,7 @@ ExprKind :: enum {
|
|||||||
try_expr
|
try_expr
|
||||||
catch_expr
|
catch_expr
|
||||||
typed_constant
|
typed_constant
|
||||||
|
comptime_expr
|
||||||
}
|
}
|
||||||
|
|
||||||
DeclKind :: enum {
|
DeclKind :: enum {
|
||||||
@@ -172,6 +174,8 @@ NodeKind :: enum {
|
|||||||
stmt_errdefer
|
stmt_errdefer
|
||||||
stmt_block
|
stmt_block
|
||||||
expr_typed_constant
|
expr_typed_constant
|
||||||
|
expr_comptime
|
||||||
|
literal_float_value
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -540,6 +544,23 @@ add_typed_constant proc(
|
|||||||
}, at)
|
}, at)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_comptime_expr proc(builder @mut Builder, at Span, operand NodeId) NodeId ! mem.AllocError {
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_comptime,
|
||||||
|
main = NodeData{ node_id = operand },
|
||||||
|
}, at)
|
||||||
|
}
|
||||||
|
|
||||||
|
#! retains the exact IEEE representation, including signed zero and NaN payloads.
|
||||||
|
add_float proc(builder @mut Builder, at Span, value f64) NodeId ! mem.AllocError {
|
||||||
|
bits :: ptrcast!(@u64, &value)^
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .literal_float_value,
|
||||||
|
data0 = NodeData{ extra = u32(bits & 4294967295) },
|
||||||
|
data1 = NodeData{ extra = u32(bits >> 32) },
|
||||||
|
}, at)
|
||||||
|
}
|
||||||
|
|
||||||
add_binary_type proc(
|
add_binary_type proc(
|
||||||
builder @mut Builder, span Span, left, right NodeId, fallible bool,
|
builder @mut Builder, span Span, left, right NodeId, fallible bool,
|
||||||
) NodeId ! mem.AllocError {
|
) NodeId ! mem.AllocError {
|
||||||
@@ -1385,6 +1406,8 @@ expression_kind proc(kind NodeKind) ExprKind {
|
|||||||
.expr_try: .try_expr
|
.expr_try: .try_expr
|
||||||
.expr_catch: .catch_expr
|
.expr_catch: .catch_expr
|
||||||
.expr_typed_constant: .typed_constant
|
.expr_typed_constant: .typed_constant
|
||||||
|
.expr_comptime: .comptime_expr
|
||||||
|
.literal_float_value: .literal_float
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1410,6 +1433,20 @@ binary_expr proc(tree @Tree, id NodeId) BinaryExprView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
comptime_operand proc(tree @Tree, id NodeId) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_comptime)
|
||||||
|
return node.main.node_id
|
||||||
|
}
|
||||||
|
|
||||||
|
#! generated IEEE literals have no textual spelling; parsed float literals return null.
|
||||||
|
float_value proc(tree @Tree, id NodeId) ?f64 {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
if (node.kind != .literal_float_value) return null
|
||||||
|
bits :: u64(node.data0.extra) | (u64(node.data1.extra) << 32)
|
||||||
|
return ptrcast!(@f64, &bits)^
|
||||||
|
}
|
||||||
|
|
||||||
typed_constant proc(tree @Tree, id NodeId) TypedConstantView {
|
typed_constant proc(tree @Tree, id NodeId) TypedConstantView {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .expr_typed_constant)
|
debug.assert(node.kind == .expr_typed_constant)
|
||||||
@@ -1765,7 +1802,7 @@ NodeLayout :: struct {
|
|||||||
node_layout proc(node Node) NodeLayout {
|
node_layout proc(node Node) NodeLayout {
|
||||||
layout := NodeLayout{}
|
layout := NodeLayout{}
|
||||||
match node.kind {
|
match node.kind {
|
||||||
.literal_int, .literal_float, .literal_string, .literal_bool, .literal_null,
|
.literal_int, .literal_float, .literal_float_value, .literal_string, .literal_bool, .literal_null,
|
||||||
.expr_identifier, .invalid_expr: {}
|
.expr_identifier, .invalid_expr: {}
|
||||||
.expr_unary: layout.direct_children = 2
|
.expr_unary: layout.direct_children = 2
|
||||||
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant,
|
.expr_binary, .expr_range, .expr_index, .expr_selection, .expr_typed_constant,
|
||||||
@@ -1773,7 +1810,7 @@ node_layout proc(node Node) NodeLayout {
|
|||||||
layout.direct_children = 6
|
layout.direct_children = 6
|
||||||
.expr_pointer_type: layout.direct_children = 4
|
.expr_pointer_type: layout.direct_children = 4
|
||||||
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_enum_literal,
|
.expr_optional_type, .expr_distinct_type, .expr_alias, .expr_enum_literal,
|
||||||
.expr_try: layout.direct_children = 1
|
.expr_try, .expr_comptime: layout.direct_children = 1
|
||||||
.expr_call, .expr_builtin_call, .expr_function_type, .expr_array_literal,
|
.expr_call, .expr_builtin_call, .expr_function_type, .expr_array_literal,
|
||||||
.expr_record_type, .expr_record_literal, .expr_enum_type, .expr_match,
|
.expr_record_type, .expr_record_literal, .expr_enum_type, .expr_match,
|
||||||
.block, .package: {
|
.block, .package: {
|
||||||
@@ -2089,6 +2126,7 @@ add_owned_literal proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#! borrows a literal's spelling, independently of its diagnostic source span.
|
#! borrows a literal's spelling, independently of its diagnostic source span.
|
||||||
|
#! query float_value first for generated IEEE literals.
|
||||||
literal_text proc(tree @Tree, id NodeId) []u8 {
|
literal_text proc(tree @Tree, id NodeId) []u8 {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
if (node.data0.extra == 1) return tree.storage.generated.items[usize(node.main.extra)]
|
if (node.data0.extra == 1) return tree.storage.generated.items[usize(node.main.extra)]
|
||||||
|
|||||||
@@ -85,11 +85,13 @@ recompiles_specialized_calls_without_borrowing_previous_ir test {
|
|||||||
` return result
|
` return result
|
||||||
`}
|
`}
|
||||||
`Fixed proc($N usize) type { return struct { values [N]i16 } }
|
`Fixed proc($N usize) type { return struct { values [N]i16 } }
|
||||||
|
`data proc() [2]i16 { return [40, 2] }
|
||||||
`main proc() i16 {
|
`main proc() i16 {
|
||||||
` box Box(i16) :: { value = identity(i16, 40) }
|
` values :: $(data())
|
||||||
|
` box Box(i16) :: { value = identity(i16, values[0]) }
|
||||||
` fixed Fixed(count()) :: { values = [box.value, 2] }
|
` fixed Fixed(count()) :: { values = [box.value, 2] }
|
||||||
` same Fixed(2) :: fixed
|
` same Fixed(2) :: fixed
|
||||||
` return identity(same.values[0]) + same.values[1]
|
` return identity(same.values[0]) + same.values[$(usize(1))]
|
||||||
`}
|
`}
|
||||||
,
|
,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ Code :: enum {
|
|||||||
specialization_cycle
|
specialization_cycle
|
||||||
specialization_limit
|
specialization_limit
|
||||||
comptime_execution_limit
|
comptime_execution_limit
|
||||||
|
comptime_value_escape
|
||||||
}
|
}
|
||||||
|
|
||||||
Diagnostic :: struct {
|
Diagnostic :: struct {
|
||||||
@@ -245,5 +246,6 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
specialization_cycle = "compile-time specialization depends on its unfinished result",
|
specialization_cycle = "compile-time specialization depends on its unfinished result",
|
||||||
specialization_limit = "compile-time specialization exceeds its recursion or instance limit",
|
specialization_limit = "compile-time specialization exceeds its recursion or instance limit",
|
||||||
comptime_execution_limit = "compile-time execution exceeds its recursion or step limit",
|
comptime_execution_limit = "compile-time execution exceeds its recursion or step limit",
|
||||||
|
comptime_value_escape = "compile-time result cannot escape evaluation as a source value",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1795,6 +1795,9 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
}
|
}
|
||||||
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id)
|
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id)
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
|
.comptime_expr: {
|
||||||
|
if (!trap_type(state, id)) unreachable
|
||||||
|
}
|
||||||
.typed_constant: {
|
.typed_constant: {
|
||||||
if (trap_type(state, id)) return ir.no_value
|
if (trap_type(state, id)) return ir.no_value
|
||||||
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
|
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
|
||||||
@@ -2130,6 +2133,12 @@ exact_float_integer proc(text []u8, value f64) bool {
|
|||||||
@hide:file
|
@hide:file
|
||||||
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
span :: ast.node_span(state.tree, id)
|
span :: ast.node_span(state.tree, id)
|
||||||
|
if (ast.float_value(state.tree, id)) |value| {
|
||||||
|
return try constant(state, ir.Value{
|
||||||
|
type = .float,
|
||||||
|
data = ir.ValueData{ real = value },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
text :: ast.literal_text(state.tree, id)
|
text :: ast.literal_text(state.tree, id)
|
||||||
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
||||||
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
|
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
|
||||||
|
|||||||
@@ -1606,6 +1606,21 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
.union: return try parse_union_type(state)
|
.union: return try parse_union_type(state)
|
||||||
.distinct, .alias: return try parse_type_wrapper(state)
|
.distinct, .alias: return try parse_type_wrapper(state)
|
||||||
.match: return try parse_match(state)
|
.match: return try parse_match(state)
|
||||||
|
.dollar: {
|
||||||
|
state.next_token += 1
|
||||||
|
_ = try consume(state, .open_paren)
|
||||||
|
previous :: state.allow_record_literals
|
||||||
|
state.allow_record_literals = true
|
||||||
|
defer state.allow_record_literals = previous
|
||||||
|
state.paren_depth += 1
|
||||||
|
defer state.paren_depth -= 1
|
||||||
|
expression :: try parse_expr(state)
|
||||||
|
skip_operand_newlines(state)
|
||||||
|
close :: try consume(state, .close_paren)
|
||||||
|
return try ast.add_comptime_expr(state.builder, ast.Span{
|
||||||
|
start = token.span.start, end = close.span.end, source = token.span.source,
|
||||||
|
}, expression)
|
||||||
|
}
|
||||||
.dot: {
|
.dot: {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
member :: try consume_member(state, false)
|
member :: try consume_member(state, false)
|
||||||
|
|||||||
@@ -1144,6 +1144,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
state.cells[usize(id)].in_cleanup = state.in_cleanup
|
state.cells[usize(id)].in_cleanup = state.in_cleanup
|
||||||
state.cells[usize(id)].narrowing_arm = state.narrowing_arm
|
state.cells[usize(id)].narrowing_arm = state.narrowing_arm
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
|
.comptime_expr: try fail(state, id, .comptime_argument_required, ast.node_span(tree, id))
|
||||||
.typed_constant: {
|
.typed_constant: {
|
||||||
value :: ast.typed_constant(tree, id)
|
value :: ast.typed_constant(tree, id)
|
||||||
state.cells[usize(id)].fixed = true
|
state.cells[usize(id)].fixed = true
|
||||||
@@ -3978,6 +3979,7 @@ constant_order proc(left, right ConstantInteger) i32 {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
constant_real proc(state @mut State, id ast.NodeId) ?f64 ! mem.AllocError {
|
constant_real proc(state @mut State, id ast.NodeId) ?f64 ! mem.AllocError {
|
||||||
|
if (ast.float_value(state.tree, id)) |value| return value
|
||||||
missing ?f64 :: null
|
missing ?f64 :: null
|
||||||
if (ast.expr_kind(state.tree, id) == .literal_float) {
|
if (ast.expr_kind(state.tree, id) == .literal_float) {
|
||||||
return strconv.parse_float(state.edges.allocator, ast.literal_text(state.tree, id)) catch |err| {
|
return strconv.parse_float(state.edges.allocator, ast.literal_text(state.tree, id)) catch |err| {
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ resolve_expr proc(
|
|||||||
if (state.bindings[usize(id)].kind != .none) return
|
if (state.bindings[usize(id)].kind != .none) return
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
|
.comptime_expr: try resolve_expr(state, scope, ast.comptime_operand(tree, id))
|
||||||
.typed_constant: {
|
.typed_constant: {
|
||||||
value :: ast.typed_constant(tree, id)
|
value :: ast.typed_constant(tree, id)
|
||||||
try resolve_expr(state, scope, value.type)
|
try resolve_expr(state, scope, value.type)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ ValueKind :: enum {
|
|||||||
string
|
string
|
||||||
function
|
function
|
||||||
null
|
null
|
||||||
|
real
|
||||||
|
aggregate
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -101,19 +103,21 @@ elaborate proc(
|
|||||||
) Result ! Error {
|
) Result ! Error {
|
||||||
result := Result{ builder = builder, changes = arraylist.init(allocator), constraints = arraylist.init(allocator) }
|
result := Result{ builder = builder, changes = arraylist.init(allocator), constraints = arraylist.init(allocator) }
|
||||||
errdefer deinit(&result)
|
errdefer deinit(&result)
|
||||||
|
# Explicit evaluation is useful even in packages without generic declarations.
|
||||||
original :: ast.view(builder, root)
|
original :: ast.view(builder, root)
|
||||||
generic := false
|
needed := false
|
||||||
for (0..ast.package_decl_count(&original)) |i| {
|
for (0..ast.node_count(&original)) |i| {
|
||||||
id :: ast.package_decl(&original, i)
|
id :: ast.NodeId(i)
|
||||||
if (ast.decl_kind(&original, id) != .function) continue
|
class :: ast.node_class(&original, id)
|
||||||
result_type :: sema.binding(resolution, ast.function_decl(&original, id).return_type)
|
if (class == .expression and ast.expr_kind(&original, id) == .comptime_expr) needed = true
|
||||||
if (ast.function_generic(&original, id)
|
if (class == .function and ast.function_generic(&original, id)) needed = true
|
||||||
or (result_type.kind == .builtin and result_type.data.builtin == .type)) {
|
if (class == .function) {
|
||||||
generic = true
|
result_type :: sema.binding(resolution, ast.function_decl(&original, id).return_type)
|
||||||
break
|
if (result_type.kind == .builtin and result_type.data.builtin == .type) needed = true
|
||||||
}
|
}
|
||||||
|
if (needed) break
|
||||||
}
|
}
|
||||||
if (!generic) return result
|
if (!needed) return result
|
||||||
mapping :: try mem.alloc(ast.NodeId, allocator, ast.node_count(&original))
|
mapping :: try mem.alloc(ast.NodeId, allocator, ast.node_count(&original))
|
||||||
defer mem.free(allocator, mapping)
|
defer mem.free(allocator, mapping)
|
||||||
state := State{
|
state := State{
|
||||||
@@ -185,6 +189,26 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
|
|||||||
if (state.expanded.items[usize(id)] != 0) return
|
if (state.expanded.items[usize(id)] != 0) return
|
||||||
state.expanded.items[usize(id)] = 1
|
state.expanded.items[usize(id)] = 1
|
||||||
class :: ast.node_class(&state.tree, id)
|
class :: ast.node_class(&state.tree, id)
|
||||||
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .comptime_expr) {
|
||||||
|
operand :: ast.comptime_operand(&state.tree, id)
|
||||||
|
try arraylist.append(&state.rewrites.constraints, operand)
|
||||||
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||||
|
value :: try evaluate(state, operand)
|
||||||
|
if (value.kind == .invalid) {
|
||||||
|
try reject_evaluation(state, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
concrete :: try materialize(state, value)
|
||||||
|
replacement := concrete.node
|
||||||
|
if (value.annotation != no_node and value.kind != .type) {
|
||||||
|
replacement = try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, id),
|
||||||
|
value.annotation, replacement)
|
||||||
|
try refresh(state)
|
||||||
|
}
|
||||||
|
try replace(state, id, replacement)
|
||||||
|
try refresh(state)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (class == .expression and ast.expr_kind(&state.tree, id) == .call) {
|
if (class == .expression and ast.expr_kind(&state.tree, id) == .call) {
|
||||||
callee :: ast.call_expr(&state.tree, id).callee
|
callee :: ast.call_expr(&state.tree, id).callee
|
||||||
try expand(state, callee)
|
try expand(state, callee)
|
||||||
@@ -277,7 +301,10 @@ specialize_call proc(state @mut State, call, function ast.NodeId) void ! Error {
|
|||||||
try expand(state, argument)
|
try expand(state, argument)
|
||||||
if (view.comptime) {
|
if (view.comptime) {
|
||||||
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
state.evaluation_diagnostic = diagnostics.no_diagnostic
|
||||||
arguments[i] = try evaluate(state, argument)
|
annotation := view.type
|
||||||
|
if (contextual_literal(state, argument))
|
||||||
|
annotation = try argument_annotation(state, function, view.type, arguments)
|
||||||
|
arguments[i] = try evaluate_expected(state, argument, annotation)
|
||||||
if (arguments[i].kind == .invalid) {
|
if (arguments[i].kind == .invalid) {
|
||||||
try reject_evaluation(state, call)
|
try reject_evaluation(state, call)
|
||||||
return
|
return
|
||||||
@@ -527,9 +554,10 @@ evaluation_dependencies proc(
|
|||||||
resolved :: binding(state, id)
|
resolved :: binding(state, id)
|
||||||
if (resolved.kind == .declaration) {
|
if (resolved.kind == .declaration) {
|
||||||
target :: resolved.data.declaration
|
target :: resolved.data.declaration
|
||||||
if ((flags[usize(target)] & 1) != 0 and (flags[usize(target)] & 2) == 0) {
|
if ((flags[usize(target)] & 8) == 0 and (flags[usize(target)] & 2) == 0) {
|
||||||
if (ast.node_class(&state.tree, target) == .statement
|
class :: ast.node_class(&state.tree, target)
|
||||||
and ast.statement_decl(&state.tree, target).mutability == .variable) return false
|
if (class == .parameter) return false
|
||||||
|
if (class == .statement and ast.statement_decl(&state.tree, target).mutability == .variable) return false
|
||||||
flags[usize(target)] = flags[usize(target)] | 2
|
flags[usize(target)] = flags[usize(target)] | 2
|
||||||
try arraylist.append(declarations, target)
|
try arraylist.append(declarations, target)
|
||||||
}
|
}
|
||||||
@@ -540,20 +568,212 @@ evaluation_dependencies proc(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
evaluation_owned proc(state @State, id ast.NodeId, flags []mut u8) void {
|
||||||
|
if ((flags[usize(id)] & 8) != 0) return
|
||||||
|
flags[usize(id)] = flags[usize(id)] | 8
|
||||||
|
for (0..ast.child_count(&state.tree, id)) |i| evaluation_owned(state, ast.child(&state.tree, id, i), flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
contextual_literal proc(state @State, id ast.NodeId) bool {
|
||||||
|
kind :: ast.expr_kind(&state.tree, id)
|
||||||
|
return kind == .record_literal or kind == .array_literal or kind == .enum_literal
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
argument_annotation proc(
|
||||||
|
state @mut State, function, annotation ast.NodeId, arguments []Value,
|
||||||
|
) ast.NodeId ! Error {
|
||||||
|
mapping :: try mem.alloc(ast.NodeId, state.allocator, state.original_count)
|
||||||
|
defer mem.free(state.allocator, mapping)
|
||||||
|
mem.set(mapping, no_node)
|
||||||
|
for (0..ast.function_param_count(&state.tree, function)) |i| {
|
||||||
|
if (arguments[i].kind == .invalid) continue
|
||||||
|
parameter :: ast.function_param(&state.tree, function, i)
|
||||||
|
value :: try materialize(state, arguments[i])
|
||||||
|
for (0..mapping.len) |n| {
|
||||||
|
resolved :: binding(state, ast.NodeId(n))
|
||||||
|
if (resolved.kind == .declaration and resolved.data.declaration == parameter
|
||||||
|
and ast.node_class(&state.tree, ast.NodeId(n)) == .expression) mapping[n] = value.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start :: ast.node_count(&state.tree)
|
||||||
|
result :: try ast.clone_subtree(state.builder, annotation, mapping)
|
||||||
|
try refresh(state)
|
||||||
|
for (0..mapping.len) |i| {
|
||||||
|
target :: mapping[i]
|
||||||
|
if (target != no_node and usize(target) >= start) state.bindings.items[usize(target)] = state.bindings.items[i]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
evaluate_expected proc(state @mut State, id, annotation ast.NodeId) Value ! Error {
|
||||||
|
if (contextual_literal(state, id)) {
|
||||||
|
type :: try canonical_type(state, annotation)
|
||||||
|
if (type != no_node) return try evaluate_ir(state, id, type)
|
||||||
|
}
|
||||||
|
return try evaluate(state, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
aggregate_child proc(module @ir.Module, parent ir.Value, type ir.Type, shape ir.ArrayId, offset usize) ir.Value {
|
||||||
|
if (!ir.aggregate(type)) return parent.data.elements[offset]
|
||||||
|
cells :: module.arrays.items[usize(shape)].cells
|
||||||
|
return ir.Value{ type = type, array_type = shape,
|
||||||
|
data = ir.ValueData{ elements = parent.data.elements[offset..offset + cells] } }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Values leaving the temporary interpreter must own no interpreter addresses or module IDs.
|
||||||
|
# Rebuild contextual source literals while the module and its escaped flat cells are alive.
|
||||||
|
@hide:file
|
||||||
|
serialize_value proc(
|
||||||
|
state @mut State, module @ir.Module, checked @typecheck.Result,
|
||||||
|
type typecheck.Type, value ir.Value, span ast.Span,
|
||||||
|
) ast.NodeId ! Error {
|
||||||
|
result := no_node
|
||||||
|
if (type.kind == .array) {
|
||||||
|
array :: typecheck.array_type(checked, type.array_type)
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
stride :: if (ir.aggregate(shape.element)) module.arrays.items[usize(shape.element_array)].cells else usize(1)
|
||||||
|
elements := arraylist.init(ast.NodeId, state.allocator)
|
||||||
|
defer arraylist.deinit(&elements)
|
||||||
|
for (0..array.len) |i| {
|
||||||
|
child :: aggregate_child(module, value, shape.element, shape.element_array, i * stride)
|
||||||
|
node :: try serialize_value(state, module, checked, array.element, child, span)
|
||||||
|
if (node == no_node) return no_node
|
||||||
|
try arraylist.append(&elements, node)
|
||||||
|
}
|
||||||
|
result = try ast.add_array_literal(state.builder, span, elements.items)
|
||||||
|
} else if (type.kind == .record) {
|
||||||
|
record :: typecheck.record_type(checked, type.record_type)
|
||||||
|
if (record.is_union) return no_node
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
fields := arraylist.init(ast.NodeId, state.allocator)
|
||||||
|
defer arraylist.deinit(&fields)
|
||||||
|
for (record.fields) |field, i| {
|
||||||
|
layout :: shape.fields[i]
|
||||||
|
child :: aggregate_child(module, value, layout.type, layout.array_type, layout.offset)
|
||||||
|
node :: try serialize_value(state, module, checked, field.type, child, span)
|
||||||
|
if (node == no_node) return no_node
|
||||||
|
generated :: try ast.add_field(state.builder, span, field.name, null, node)
|
||||||
|
try refresh(state)
|
||||||
|
try arraylist.append(&fields, generated)
|
||||||
|
}
|
||||||
|
result = try ast.add_record_literal(state.builder, span, null, fields.items)
|
||||||
|
} else if (type.kind == .enumeration) {
|
||||||
|
enumeration :: typecheck.enum_type(checked, type.enum_type)
|
||||||
|
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
|
||||||
|
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
||||||
|
for (enumeration.members) |member| {
|
||||||
|
if (member.magnitude == magnitude and member.negative == negative) {
|
||||||
|
result = try ast.add_enum_literal(state.builder, span, member.name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type.kind == .optional) {
|
||||||
|
container :: typecheck.array_type(checked, type.array_type)
|
||||||
|
if (container.element.kind == .pointer or container.element.kind == .function) {
|
||||||
|
if (value.data.unsigned != 0) return no_node
|
||||||
|
result = try ast.add_literal(state.builder, .null, span)
|
||||||
|
} else if (!value.data.elements[0].data.boolean) {
|
||||||
|
result = try ast.add_literal(state.builder, .null, span)
|
||||||
|
} else {
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
payload :: shape.fields[1]
|
||||||
|
child :: aggregate_child(module, value, payload.type, payload.array_type, payload.offset)
|
||||||
|
return try serialize_value(state, module, checked, container.element, child, span)
|
||||||
|
}
|
||||||
|
} else if (ir.integer_bits(value.type) != 0 and type.kind != .pointer) {
|
||||||
|
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
|
||||||
|
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
||||||
|
result = try ast.add_integer(state.builder, span, magnitude)
|
||||||
|
if (negative) result = try ast.add_unary(state.builder, span, .negate, result)
|
||||||
|
} else if (type.kind == .float) {
|
||||||
|
result = try ast.add_float(state.builder, span, value.data.real)
|
||||||
|
} else if (type.kind == .bool) {
|
||||||
|
result = try ast.add_boolean(state.builder, span, value.data.boolean)
|
||||||
|
} else if (type.kind == .string) {
|
||||||
|
result = try ast.add_string(state.builder, span, ir.string_bytes(module, value.data.string))
|
||||||
|
}
|
||||||
|
try refresh(state)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
constant_equal proc(state @mut State, left, right ast.NodeId) bool ! Error {
|
||||||
|
kind :: ast.expr_kind(&state.tree, left)
|
||||||
|
if (kind != ast.expr_kind(&state.tree, right)) return false
|
||||||
|
if (kind == .array_literal) {
|
||||||
|
count :: ast.array_element_count(&state.tree, left)
|
||||||
|
if (count != ast.array_element_count(&state.tree, right)) return false
|
||||||
|
for (0..count) |i| {
|
||||||
|
if (!try constant_equal(state, ast.array_element(&state.tree, left, i), ast.array_element(&state.tree, right, i))) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (kind == .record_literal) {
|
||||||
|
count :: ast.record_field_count(&state.tree, left)
|
||||||
|
if (count != ast.record_field_count(&state.tree, right)) return false
|
||||||
|
for (0..count) |i| {
|
||||||
|
a :: ast.field(&state.tree, ast.record_field(&state.tree, left, i))
|
||||||
|
b :: ast.field(&state.tree, ast.record_field(&state.tree, right, i))
|
||||||
|
if ((a.name == null) != (b.name == null)) return false
|
||||||
|
if (a.name != null and ast.name_id(&state.tree, a.name?) != ast.name_id(&state.tree, b.name?)) return false
|
||||||
|
if (!try constant_equal(state, a.value?, b.value?)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (kind == .enum_literal)
|
||||||
|
return ast.name_id(&state.tree, ast.enum_literal(&state.tree, left)) == ast.name_id(&state.tree, ast.enum_literal(&state.tree, right))
|
||||||
|
return try value_equal(state, try evaluate(state, left), try evaluate(state, right))
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
constant_hash proc(state @mut State, id ast.NodeId) usize ! Error {
|
||||||
|
kind :: ast.expr_kind(&state.tree, id)
|
||||||
|
hash :: enum_index(kind)
|
||||||
|
if (kind == .array_literal) {
|
||||||
|
result := hash
|
||||||
|
for (0..ast.array_element_count(&state.tree, id)) |i|
|
||||||
|
result = mix(result, try constant_hash(state, ast.array_element(&state.tree, id, i)))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if (kind == .record_literal) {
|
||||||
|
result := hash
|
||||||
|
for (0..ast.record_field_count(&state.tree, id)) |i| {
|
||||||
|
field :: ast.field(&state.tree, ast.record_field(&state.tree, id, i))
|
||||||
|
if (field.name) |name| result = mix(result, usize(ast.name_id(&state.tree, name)))
|
||||||
|
result = mix(result, try constant_hash(state, field.value?))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if (kind == .enum_literal) return mix(hash, usize(ast.name_id(&state.tree, ast.enum_literal(&state.tree, id))))
|
||||||
|
return try value_hash(state, try evaluate(state, id))
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
|
evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
|
||||||
callee :: try evaluate(state, ast.call_expr(&state.tree, call).callee)
|
callee :: try evaluate(state, ast.call_expr(&state.tree, call).callee)
|
||||||
if (callee.kind != .function and callee.kind != .type) return Value{}
|
if (callee.kind != .function and callee.kind != .type) return Value{}
|
||||||
result_type := callee.node
|
result_type := callee.node
|
||||||
|
function := no_node
|
||||||
if (callee.kind == .function) {
|
if (callee.kind == .function) {
|
||||||
resolved :: binding(state, callee.node)
|
resolved :: binding(state, callee.node)
|
||||||
if (resolved.kind != .declaration) return Value{}
|
if (resolved.kind != .declaration) return Value{}
|
||||||
|
function = resolved.data.declaration
|
||||||
result_type = ast.function_decl(&state.tree, resolved.data.declaration).return_type
|
result_type = ast.function_decl(&state.tree, resolved.data.declaration).return_type
|
||||||
}
|
}
|
||||||
arguments := arraylist.init(ast.NodeId, state.allocator)
|
arguments := arraylist.init(ast.NodeId, state.allocator)
|
||||||
defer arraylist.deinit(&arguments)
|
defer arraylist.deinit(&arguments)
|
||||||
for (0..ast.call_arg_count(&state.tree, call)) |i| {
|
for (0..ast.call_arg_count(&state.tree, call)) |i| {
|
||||||
value :: try evaluate(state, ast.call_arg(&state.tree, call, i))
|
argument :: ast.call_arg(&state.tree, call, i)
|
||||||
|
value := Value{}
|
||||||
|
if (function != no_node and i < ast.function_param_count(&state.tree, function)) {
|
||||||
|
annotation :: ast.param(&state.tree, ast.function_param(&state.tree, function, i)).type
|
||||||
|
value = try evaluate_expected(state, argument, annotation)
|
||||||
|
} else value = try evaluate(state, argument)
|
||||||
if (value.kind == .invalid) return Value{}
|
if (value.kind == .invalid) return Value{}
|
||||||
concrete := try materialize(state, value)
|
concrete := try materialize(state, value)
|
||||||
if (value.annotation != no_node) {
|
if (value.annotation != no_node) {
|
||||||
@@ -567,7 +787,15 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
|
|||||||
# inference must have exactly the same semantics as an ordinary runtime call.
|
# inference must have exactly the same semantics as an ordinary runtime call.
|
||||||
span :: ast.node_span(&state.tree, call)
|
span :: ast.node_span(&state.tree, call)
|
||||||
invocation :: try ast.add_call(state.builder, span, callee.node, arguments.items)
|
invocation :: try ast.add_call(state.builder, span, callee.node, arguments.items)
|
||||||
returned :: try ast.add_return_stmt(state.builder, span, invocation)
|
try refresh(state)
|
||||||
|
return try evaluate_ir(state, invocation, result_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
evaluate_ir proc(state @mut State, expression, result_type ast.NodeId) Value ! Error {
|
||||||
|
if (result_type == no_node) return Value{}
|
||||||
|
span :: ast.node_span(&state.tree, expression)
|
||||||
|
returned :: try ast.add_return_stmt(state.builder, span, expression)
|
||||||
statements [1]ast.NodeId :: [returned]
|
statements [1]ast.NodeId :: [returned]
|
||||||
body :: try ast.add_block(state.builder, span, statements[..])
|
body :: try ast.add_block(state.builder, span, statements[..])
|
||||||
wrapper :: try ast.add_function_decl(state.builder, span, span, strpool.no_str, result_type, &[], body)
|
wrapper :: try ast.add_function_decl(state.builder, span, span, strpool.no_str, result_type, &[], body)
|
||||||
@@ -578,17 +806,12 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
|
|||||||
flags := arraylist.init(u8, state.allocator)
|
flags := arraylist.init(u8, state.allocator)
|
||||||
defer arraylist.deinit(&flags)
|
defer arraylist.deinit(&flags)
|
||||||
index := usize(0)
|
index := usize(0)
|
||||||
registered := usize(0)
|
|
||||||
while (index < declarations.items.len) {
|
while (index < declarations.items.len) {
|
||||||
declaration :: declarations.items[index]
|
declaration :: declarations.items[index]
|
||||||
try expand(state, declaration)
|
try expand(state, declaration)
|
||||||
while (flags.items.len < ast.node_count(&state.tree)) try arraylist.append(&flags, u8(0))
|
while (flags.items.len < ast.node_count(&state.tree)) try arraylist.append(&flags, u8(0))
|
||||||
while (registered < state.declarations.items.len) {
|
|
||||||
target :: state.declarations.items[registered]
|
|
||||||
flags.items[usize(target)] = flags.items[usize(target)] | 1
|
|
||||||
registered += 1
|
|
||||||
}
|
|
||||||
flags.items[usize(declaration)] = flags.items[usize(declaration)] | 2
|
flags.items[usize(declaration)] = flags.items[usize(declaration)] | 2
|
||||||
|
evaluation_owned(state, declaration, flags.items)
|
||||||
if (!try evaluation_dependencies(state, declaration, flags.items, &declarations)) return Value{}
|
if (!try evaluation_dependencies(state, declaration, flags.items, &declarations)) return Value{}
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
@@ -605,7 +828,11 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
|
|||||||
original_diagnostics :: diagnostics.count(&diagnostic_store)
|
original_diagnostics :: diagnostics.count(&diagnostic_store)
|
||||||
checked := try typecheck.check(state.allocator, &tree, state.resolution, &diagnostic_store)
|
checked := try typecheck.check(state.allocator, &tree, state.resolution, &diagnostic_store)
|
||||||
defer typecheck.deinit(&checked)
|
defer typecheck.deinit(&checked)
|
||||||
if (diagnostics.count(&diagnostic_store) != original_diagnostics) return Value{}
|
if (diagnostics.count(&diagnostic_store) != original_diagnostics) {
|
||||||
|
diagnostic :: diagnostic_store.list.items[original_diagnostics]
|
||||||
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, diagnostic.code, diagnostic.span)
|
||||||
|
return Value{}
|
||||||
|
}
|
||||||
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked, wrapper, &diagnostic_store)
|
module := try lower.lower_evaluation(state.allocator, &tree, state.resolution, &checked, wrapper, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
outcome := try interpreter.run_evaluation(state.allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run_evaluation(state.allocator, &module, &diagnostic_store)
|
||||||
@@ -619,20 +846,17 @@ evaluate_call proc(state @mut State, call ast.NodeId) Value ! Error {
|
|||||||
return Value{}
|
return Value{}
|
||||||
}
|
}
|
||||||
value :: outcome.value
|
value :: outcome.value
|
||||||
if (ir.integer_bits(value.type) != 0) {
|
source_type :: typecheck.type_of(&checked, expression)
|
||||||
negative :: !ir.unsigned_integer(value.type) and value.data.integer < 0
|
node :: try serialize_value(state, &module, &checked, source_type, value, span)
|
||||||
magnitude :: if (negative) u64(-(value.data.integer + 1)) + 1 else value.data.unsigned
|
if (node == no_node) {
|
||||||
return Value{ kind = .integer, node = call, magnitude = magnitude, negative = negative, annotation = result_type }
|
state.evaluation_diagnostic = try diagnostics.add(state.diagnostics, .comptime_value_escape, span)
|
||||||
|
return Value{}
|
||||||
}
|
}
|
||||||
if (value.type == .bool) return Value{
|
if (ir.aggregate(value.type) or source_type.kind == .enumeration)
|
||||||
kind = .boolean, node = call, boolean = value.data.boolean, annotation = result_type,
|
return Value{ kind = .aggregate, node = node, annotation = result_type }
|
||||||
}
|
result := try evaluate(state, node)
|
||||||
if (value.type == .string) {
|
result.annotation = result_type
|
||||||
literal :: try ast.add_string(state.builder, span, ir.string_bytes(&module, value.data.string))
|
return result
|
||||||
try refresh(state)
|
|
||||||
return Value{ kind = .string, node = literal, annotation = result_type }
|
|
||||||
}
|
|
||||||
return Value{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -726,6 +950,12 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
|||||||
}
|
}
|
||||||
return Value{ kind = .integer, node = id, magnitude = value }
|
return Value{ kind = .integer, node = id, magnitude = value }
|
||||||
}
|
}
|
||||||
|
.literal_float: {
|
||||||
|
if (ast.float_value(&state.tree, id)) |value| return Value{
|
||||||
|
kind = .real, node = id, magnitude = ptrcast!(@u64, &value)^,
|
||||||
|
}
|
||||||
|
return try evaluate_ir(state, id, try builtin(state, id, .float))
|
||||||
|
}
|
||||||
.literal_bool: return Value{ kind = .boolean, node = id,
|
.literal_bool: return Value{ kind = .boolean, node = id,
|
||||||
boolean = mem.eql(ast.literal_text(&state.tree, id), "true") }
|
boolean = mem.eql(ast.literal_text(&state.tree, id), "true") }
|
||||||
.literal_string: {
|
.literal_string: {
|
||||||
@@ -749,6 +979,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
|||||||
value := try evaluate(state, view.operand)
|
value := try evaluate(state, view.operand)
|
||||||
if (view.operator == .negate and value.kind == .integer) {
|
if (view.operator == .negate and value.kind == .integer) {
|
||||||
value.negative = value.magnitude != 0 and !value.negative
|
value.negative = value.magnitude != 0 and !value.negative
|
||||||
|
} else if (view.operator == .negate and value.kind == .real) {
|
||||||
|
value.magnitude = value.magnitude xor (u64(1) << 63)
|
||||||
} else if (view.operator == .logical_not and value.kind == .boolean) {
|
} else if (view.operator == .logical_not and value.kind == .boolean) {
|
||||||
value.boolean = !value.boolean
|
value.boolean = !value.boolean
|
||||||
} else return Value{}
|
} else return Value{}
|
||||||
@@ -757,6 +989,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
|||||||
}
|
}
|
||||||
.binary: return try evaluate_binary(state, id)
|
.binary: return try evaluate_binary(state, id)
|
||||||
.call: return try evaluate_call(state, id)
|
.call: return try evaluate_call(state, id)
|
||||||
|
.record_literal, .array_literal, .selection, .index, .match_expr, .builtin_call:
|
||||||
|
return try evaluate_ir(state, id, try expression_type(state, id, 0))
|
||||||
else: return Value{}
|
else: return Value{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -764,6 +998,7 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
|
|||||||
@hide:file
|
@hide:file
|
||||||
evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
|
evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
|
||||||
view :: ast.binary_expr(&state.tree, id)
|
view :: ast.binary_expr(&state.tree, id)
|
||||||
|
if (view.operator == .divide) return try evaluate_ir(state, id, try builtin(state, id, .float))
|
||||||
left :: try evaluate(state, view.left)
|
left :: try evaluate(state, view.left)
|
||||||
if (left.kind == .invalid) return Value{}
|
if (left.kind == .invalid) return Value{}
|
||||||
if (left.kind == .boolean) {
|
if (left.kind == .boolean) {
|
||||||
@@ -772,6 +1007,15 @@ evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
|
|||||||
}
|
}
|
||||||
right := try evaluate(state, view.right)
|
right := try evaluate(state, view.right)
|
||||||
if (left.kind == .invalid or right.kind == .invalid) return Value{}
|
if (left.kind == .invalid or right.kind == .invalid) return Value{}
|
||||||
|
if (left.kind == .real or right.kind == .real) {
|
||||||
|
boolean :: (
|
||||||
|
view.operator == .equal or view.operator == .not_equal or view.operator == .less
|
||||||
|
or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal
|
||||||
|
)
|
||||||
|
kind sema.BuiltinType :: if (boolean) .bool else .float
|
||||||
|
result_type :: try builtin(state, id, kind)
|
||||||
|
return try evaluate_ir(state, id, result_type)
|
||||||
|
}
|
||||||
if (left.annotation != no_node and right.annotation != no_node
|
if (left.annotation != no_node and right.annotation != no_node
|
||||||
and left.kind == .integer and right.kind == .integer
|
and left.kind == .integer and right.kind == .integer
|
||||||
and !try same_type(state, left.annotation, right.annotation)) return Value{}
|
and !try same_type(state, left.annotation, right.annotation)) return Value{}
|
||||||
@@ -888,6 +1132,11 @@ value_equal proc(state @mut State, left, right Value) bool ! Error {
|
|||||||
.integer: left.magnitude == right.magnitude and left.negative == right.negative
|
.integer: left.magnitude == right.magnitude and left.negative == right.negative
|
||||||
.boolean: left.boolean == right.boolean
|
.boolean: left.boolean == right.boolean
|
||||||
.function: binding(state, left.node).data.declaration == binding(state, right.node).data.declaration
|
.function: binding(state, left.node).data.declaration == binding(state, right.node).data.declaration
|
||||||
|
.real: left.magnitude == right.magnitude
|
||||||
|
.aggregate: (
|
||||||
|
try same_type(state, left.annotation, right.annotation)
|
||||||
|
and try constant_equal(state, left.node, right.node)
|
||||||
|
)
|
||||||
.string: try same_string(state, left.node, right.node)
|
.string: try same_string(state, left.node, right.node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -903,6 +1152,8 @@ value_hash proc(state @mut State, value Value) usize ! Error {
|
|||||||
.boolean: mix(hash, boolean)
|
.boolean: mix(hash, boolean)
|
||||||
.function: mix(hash, usize(binding(state, value.node).data.declaration))
|
.function: mix(hash, usize(binding(state, value.node).data.declaration))
|
||||||
.string: string_hash(hash, ast.literal_text(&state.tree, value.node))
|
.string: string_hash(hash, ast.literal_text(&state.tree, value.node))
|
||||||
|
.real: mix(hash, usize(value.magnitude))
|
||||||
|
.aggregate: mix(mix(hash, try type_hash(state, value.annotation)), try constant_hash(state, value.node))
|
||||||
.invalid, .null: hash
|
.invalid, .null: hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1216,6 +1467,8 @@ materialize proc(state @mut State, value Value) Value ! Error {
|
|||||||
if (value.negative) result.node = try ast.add_unary(state.builder, span, .negate, result.node)
|
if (value.negative) result.node = try ast.add_unary(state.builder, span, .negate, result.node)
|
||||||
} else if (value.kind == .boolean) {
|
} else if (value.kind == .boolean) {
|
||||||
result.node = try ast.add_boolean(state.builder, span, value.boolean)
|
result.node = try ast.add_boolean(state.builder, span, value.boolean)
|
||||||
|
} else if (value.kind == .real) {
|
||||||
|
result.node = try ast.add_float(state.builder, span, ptrcast!(@f64, &value.magnitude)^)
|
||||||
}
|
}
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
return result
|
return result
|
||||||
@@ -1230,6 +1483,7 @@ valid_value proc(state @mut State, annotation ast.NodeId, value Value) bool ! Er
|
|||||||
kind :: resolved.data.builtin
|
kind :: resolved.data.builtin
|
||||||
if (kind == .bool) return value.kind == .boolean
|
if (kind == .bool) return value.kind == .boolean
|
||||||
if (kind == .string) return value.kind == .string
|
if (kind == .string) return value.kind == .string
|
||||||
|
if (kind == .float or kind == .f64) return value.kind == .real
|
||||||
if (value.kind != .integer) return false
|
if (value.kind != .integer) return false
|
||||||
bits := u32(64)
|
bits := u32(64)
|
||||||
signed := false
|
signed := false
|
||||||
@@ -1264,6 +1518,7 @@ valid_value proc(state @mut State, annotation ast.NodeId, value Value) bool ! Er
|
|||||||
return value.magnitude <= limit
|
return value.magnitude <= limit
|
||||||
}
|
}
|
||||||
kind :: ast.expr_kind(&state.tree, type)
|
kind :: ast.expr_kind(&state.tree, type)
|
||||||
|
if (value.kind == .aggregate) return value.annotation != no_node and try same_type(state, type, value.annotation)
|
||||||
if (kind == .optional_type) return value.kind == .null or try valid_value(state, ast.type_operand(&state.tree, type), value)
|
if (kind == .optional_type) return value.kind == .null or try valid_value(state, ast.type_operand(&state.tree, type), value)
|
||||||
if (kind == .slice_type and value.kind == .string) {
|
if (kind == .slice_type and value.kind == .string) {
|
||||||
view :: ast.slice_type(&state.tree, type)
|
view :: ast.slice_type(&state.tree, type)
|
||||||
|
|||||||
+145
@@ -11,6 +11,151 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"comptime_contextual_aggregate_arguments_keep_dependent_types": (
|
||||||
|
"""
|
||||||
|
sum proc(items [2]i16) i16 { return items[0] + items[1] }
|
||||||
|
answer proc($T type, $N usize, $items [N]T) T { return items[0] + items[1] }
|
||||||
|
main proc() i16 { return answer(i16, 2, [20, 20]) + $(sum([1, 1])) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"comptime_zero_cell_aggregates_keep_logical_lengths": (
|
||||||
|
"""
|
||||||
|
Empty :: struct {}
|
||||||
|
Storage :: struct { items [3]Empty, empty [0]i16 }
|
||||||
|
make proc() Storage { return Storage { items = [{}, {}, {}], empty = [] } }
|
||||||
|
main proc() usize {
|
||||||
|
value :: $(make())
|
||||||
|
return value.items.len + value.empty.len
|
||||||
|
}
|
||||||
|
""", b"3\n", None,
|
||||||
|
),
|
||||||
|
"explicit_comptime_float_arithmetic_keeps_ieee_equality": (
|
||||||
|
"""
|
||||||
|
main proc() bool { return $(1.5 + 2.5) == 4.0 and $(-0.0 == 0.0) and $(4 / 2) == 2.0 }
|
||||||
|
""", b"true\n", None,
|
||||||
|
),
|
||||||
|
"comptime_aggregate_specialization_uses_normalized_values": (
|
||||||
|
"""
|
||||||
|
Config :: struct { count usize, bias i16 = 2 }
|
||||||
|
config proc() Config {
|
||||||
|
result Config := { count = 0 }
|
||||||
|
while (result.count < 2) result.count = result.count + 1
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
Fixed proc($settings Config) type {
|
||||||
|
return struct { values [settings.count]i16 }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
first Fixed(config()) :: { values = [40, 2] }
|
||||||
|
same Fixed({ bias = 2, count = 2 }) :: first
|
||||||
|
return same.values[0] + same.values[1]
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"comptime_array_arguments_and_explicit_copy_isolation": (
|
||||||
|
"""
|
||||||
|
values proc() [2]mut i16 {
|
||||||
|
result [2]mut i16 := [0, 2]
|
||||||
|
for (0..usize(2)) |index| result[index] = result[index] + 20
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
sum proc($items [2]mut i16) i16 { return items[0] + items[1] }
|
||||||
|
main proc() i16 {
|
||||||
|
first := $(values())
|
||||||
|
copy := first
|
||||||
|
first[0] = 99
|
||||||
|
return sum(values()) + copy[0] - 20
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"explicit_comptime_without_templates_preserves_nested_values": (
|
||||||
|
r"""
|
||||||
|
Kind :: enum { ready, empty }
|
||||||
|
Inner :: struct { text string, count ?i16 }
|
||||||
|
Packet :: struct { rows [2]mut Inner, kind Kind, weight f64 }
|
||||||
|
make proc() Packet {
|
||||||
|
result Packet := {
|
||||||
|
rows = [{ text = "a\"\\\x00é", count = 20 }, { text = "ok", count = null }],
|
||||||
|
kind = .ready, weight = 2.5,
|
||||||
|
}
|
||||||
|
result.rows[0].count = 40
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
packet := $(make())
|
||||||
|
copy :: packet
|
||||||
|
packet.rows[0].text = "changed"
|
||||||
|
if (copy.kind != .ready or copy.weight != 2.5 or copy.rows[1].count != null) return 0
|
||||||
|
return copy.rows[0].count? + i16(copy.rows[1].text.len)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"explicit_comptime_composes_and_supplies_array_lengths": (
|
||||||
|
"""
|
||||||
|
size proc() usize { return 2 }
|
||||||
|
main proc() i16 {
|
||||||
|
amount :: 40
|
||||||
|
values [$(size())]i16 :: [$(amount + 1), $(1)]
|
||||||
|
return values[$(usize(0))] + values[1]
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"explicit_comptime_cannot_read_runtime_parameters": (
|
||||||
|
"""
|
||||||
|
select proc(value i16) i16 { return $(value + 1) }
|
||||||
|
main proc() i16 { return select(41) }
|
||||||
|
""", b"", b"generic argument must be a compile-time value",
|
||||||
|
),
|
||||||
|
"comptime_aggregate_cannot_read_mutable_globals": (
|
||||||
|
"""
|
||||||
|
state i16 := 42
|
||||||
|
make proc() [1]i16 { return [state] }
|
||||||
|
main proc() i16 { return $(make())[0] }
|
||||||
|
""", b"", b"generic argument must be a compile-time value",
|
||||||
|
),
|
||||||
|
"comptime_aggregate_keeps_narrow_overflow": (
|
||||||
|
"""
|
||||||
|
Counter :: struct { value i8 }
|
||||||
|
make proc() Counter {
|
||||||
|
result Counter := { value = 127 }
|
||||||
|
result.value = result.value + 1
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
main proc() i8 { return $(make()).value }
|
||||||
|
""", b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"comptime_aggregate_rejects_pointer_escape": (
|
||||||
|
"""
|
||||||
|
Borrow :: struct { pointer @i16 }
|
||||||
|
make proc() Borrow {
|
||||||
|
value i16 :: 42
|
||||||
|
return Borrow { pointer = &value }
|
||||||
|
}
|
||||||
|
main proc() i16 { return $(make()).pointer^ }
|
||||||
|
""", b"", b"compile-time result cannot escape evaluation",
|
||||||
|
),
|
||||||
|
"comptime_aggregate_rejects_slice_escape": (
|
||||||
|
"""
|
||||||
|
make proc() [1][]u8 { return ["borrowed"] }
|
||||||
|
main proc() usize { return $(make())[0].len }
|
||||||
|
""", b"", b"compile-time result cannot escape evaluation",
|
||||||
|
),
|
||||||
|
"comptime_float_bits_survive_aggregate_materialization": (
|
||||||
|
"""
|
||||||
|
Floats :: struct { zero f64, nan f64 }
|
||||||
|
make proc() Floats {
|
||||||
|
bits u64 :: 9221120237041090626
|
||||||
|
return Floats { zero = -0.0, nan = ptrcast!(@f64, &bits)^ }
|
||||||
|
}
|
||||||
|
main proc() bool {
|
||||||
|
values :: $(make())
|
||||||
|
return (
|
||||||
|
ptrcast!(@u64, &values.zero)^ == 9223372036854775808
|
||||||
|
and ptrcast!(@u64, &values.nan)^ == 9221120237041090626
|
||||||
|
)
|
||||||
|
}
|
||||||
|
""", b"true\n", None,
|
||||||
|
),
|
||||||
"comptime_calls_share_runtime_inference_loops_and_recursion": (
|
"comptime_calls_share_runtime_inference_loops_and_recursion": (
|
||||||
"""
|
"""
|
||||||
length proc() usize {
|
length proc() usize {
|
||||||
|
|||||||
Reference in New Issue
Block a user