contextual void construction
This commit is contained in:
@@ -535,10 +535,26 @@
|
||||
(`match get()`) is a pre-existing gap (assign to a variable first, as the spec examples do);
|
||||
the rvalue pointer-capture guard is defensive for when that lands
|
||||
|
||||
22.6. (`// ponytail:` follow-ups): contextual void construction (`e Event = .quit`) needs
|
||||
enum-literal→union coercion. Separately, a **call expression directly as a match subject**
|
||||
(`match get()`) is a pre-existing gap (assign to a variable first, as the spec examples do);
|
||||
the rvalue pointer-capture guard is defensive for when that lands
|
||||
22.6. contextual void construction + call-as-match-subject (implemented; see below)
|
||||
- **contextual void construction**: a bare enum literal in a tagged-union context constructs a
|
||||
void-payload variant — `e Event = .quit` (and any expected-union position: `=`, return, call
|
||||
argument) coerces `.quit` to the union, equivalent to `Event{ quit }`. Build-pass only: the
|
||||
`.Enum_Literal` case, given a tagged-union `expected`, looks the variant up and emits the
|
||||
tag-only union `.Struct` HIR for a void variant. A payload variant via a bare `.variant`
|
||||
("needs a payload") and an unknown variant are diagnosed; the payload-carrying contextual form
|
||||
`.variant{...}` stays deferred to milestone 23 (error channel)
|
||||
- **call expression directly as a match subject**: `match get() { … }` now specializes the call.
|
||||
Root cause was that the inference/spec-request walker `infer_statements` had no `.Match` case,
|
||||
so a match's subject and arm bodies were never visited and their calls never got a
|
||||
specialization (`find_spec` → "could not resolve specialization"). Added a `.Match` case that
|
||||
infers the subject and recurses into arm bodies (with the capture local typed from the variant
|
||||
payload, mirroring the `.For`/unwrap-`.If` handling). This also covers value-`match` and calls
|
||||
inside arm bodies. As a side effect the rvalue pointer-capture guard from 22.5 is now reachable
|
||||
(`match make_box() { .v |@p|: … }` correctly errors "requires an addressable subject")
|
||||
- checker-only; no parser/AST/HIR/IR/lowering/codegen change
|
||||
- deferred: payload-carrying contextual construction (`.variant{…}`, milestone 23); a
|
||||
`yield call()` inside a value-block/value-match arm still misses its spec (`infer_statements`
|
||||
has no `.Yield` case either — a separate pre-existing gap)
|
||||
|
||||
23. error types (see below)
|
||||
|
||||
|
||||
@@ -1984,6 +1984,42 @@ infer_statements :: proc(
|
||||
case .Defer:
|
||||
deferred := [1]ast.Stmt_Id{statement.update}
|
||||
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
case .Match:
|
||||
// The build pass desugars `match` to an if/else chain, but inference runs first
|
||||
// and must still visit the subject and arm bodies so calls there get specialized
|
||||
// (e.g. `match get()`). Mirror the `.For`/unwrap-`.If` capture handling.
|
||||
subject_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||
is_tagged := types.is_tagged_union(subject_type, &checker.module.types)
|
||||
for arm_id in statement.body {
|
||||
arm := checker.ast_module.statements[arm_id]
|
||||
if arm.kind != .Match_Arm {
|
||||
continue
|
||||
}
|
||||
for pattern in arm.patterns {
|
||||
_ = infer_expr(checker, pattern, locals^[:], pkg, file, demanded, local_types)
|
||||
}
|
||||
capture_start := len(locals^)
|
||||
if len(arm.captures) > 0 && is_tagged && len(arm.patterns) > 0 {
|
||||
capture := arm.captures[0]
|
||||
if capture != checker.sink_symbol {
|
||||
capture_type := types.INVALID
|
||||
pattern := checker.ast_module.exprs[arm.patterns[0]]
|
||||
if pattern.kind == .Enum_Literal {
|
||||
if _, field, ok := find_struct_field(checker, subject_type, pattern.name); ok {
|
||||
capture_type = field.type
|
||||
if arm.pointer_capture {
|
||||
// Mutability is best-effort here; the build pass finalizes
|
||||
// the exact pointer type and coerces the captured value.
|
||||
capture_type = types.pointer(&checker.module.types, field.type, true, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
append(locals, Infer_Local{name=capture, type=capture_type, declared=capture_type, statement=ast.INVALID_STMT})
|
||||
}
|
||||
}
|
||||
infer_statements(checker, arm.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||
resize(locals, capture_start)
|
||||
}
|
||||
}
|
||||
}
|
||||
resize(locals, scope_start)
|
||||
@@ -3101,6 +3137,29 @@ build_compound_expr :: proc(
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||
case .Enum_Literal:
|
||||
// A bare enum literal in a tagged-union context constructs a variant. Only a
|
||||
// void-payload variant can be built this way (it has no value); a payload variant
|
||||
// must use `T{ variant = ... }`. (`.variant{...}` payload construction is the
|
||||
// milestone-23 error-channel form.)
|
||||
if types.is_tagged_union(expected, store) {
|
||||
index, field, found := find_struct_field(checker, expected, expr.name)
|
||||
if !found {
|
||||
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
|
||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||
}
|
||||
if !types.is_void(field.type) {
|
||||
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
|
||||
symbol_text(checker, expr.name), type_label(checker, expected), symbol_text(checker, expr.name))
|
||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||
}
|
||||
values := make([]hir.Expr_Id, 1, checker.allocator)
|
||||
values[0] = hir.INVALID_EXPR
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
if !types.is_enum(expected, store) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
|
||||
@@ -2527,6 +2527,98 @@ main :: func() i32 {
|
||||
testing.expect(t, found_incompatible)
|
||||
}
|
||||
|
||||
@(test)
|
||||
match_call_subject_and_contextual_void_compile :: proc(t: ^testing.T) {
|
||||
// Milestone 22.6: (1) a call expression directly as the match subject (`match get()`)
|
||||
// now specializes — previously "could not resolve specialization of 'get'" — because the
|
||||
// inference pass visits the match subject; (2) a void variant constructed contextually
|
||||
// (`e Box = .empty`) coerces the bare enum literal to the union. Both compile clean to IR.
|
||||
text := `Animal :: enum {
|
||||
dog
|
||||
cat
|
||||
bird
|
||||
}
|
||||
Box :: union(enum) {
|
||||
count i32
|
||||
empty void
|
||||
}
|
||||
get :: func() Animal {
|
||||
return .bird
|
||||
}
|
||||
main :: func() i32 {
|
||||
e Box = .empty
|
||||
r i32 = 0
|
||||
match get() {
|
||||
.dog: r = 1
|
||||
.cat: r = 2
|
||||
.bird: r = 3
|
||||
}
|
||||
match e {
|
||||
.count |c|: r = r + c
|
||||
.empty: r = r + 7
|
||||
}
|
||||
return r
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, strings.contains(llvm_text, "icmp eq")) // the call subject still dispatches
|
||||
}
|
||||
|
||||
@(test)
|
||||
match_contextual_payload_variant_is_diagnosed :: proc(t: ^testing.T) {
|
||||
// Contextual construction is only for void variants; a bare `.point` for a payload
|
||||
// variant must use `Box{ point = ... }` instead.
|
||||
text := `Point :: struct {
|
||||
x i32
|
||||
y i32
|
||||
}
|
||||
Box :: union(enum) {
|
||||
point Point
|
||||
empty void
|
||||
}
|
||||
bad :: func() i32 {
|
||||
e Box = .point
|
||||
return 0
|
||||
}
|
||||
main :: func() i32 {
|
||||
return bad()
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found = found || strings.contains(diagnostic.message, "needs a payload")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
||||
// A value block that does not end in `yield`, and a `yield` nested inside an
|
||||
|
||||
@@ -58,6 +58,10 @@ payload_of :: func(d Data) i32 {
|
||||
return v
|
||||
}
|
||||
|
||||
make_box :: func() Box {
|
||||
return Box{ point = Point{ x = 4, y = 0 } }
|
||||
}
|
||||
|
||||
main :: func() i32 {
|
||||
acc i32 = 0
|
||||
|
||||
@@ -93,8 +97,8 @@ main :: func() i32 {
|
||||
}
|
||||
acc = acc + bucket # +5
|
||||
|
||||
# void-payload variant: bare-key construction + a no-capture arm
|
||||
e Box = Box{ empty }
|
||||
# void-payload variant: contextual construction (`.empty` coerces to Box) + no-capture arm
|
||||
e Box = .empty
|
||||
hit i32 = 0
|
||||
match e {
|
||||
.point |pt|: hit = pt.x
|
||||
@@ -110,6 +114,12 @@ main :: func() i32 {
|
||||
}
|
||||
acc = acc + b.point.x # +10 (mutated through the @mut Point)
|
||||
|
||||
# 50 + 47 + 3 + 2 + 5 + 7 + 10 = 124
|
||||
return acc - 124
|
||||
# match directly on a call result (no need to bind it to a variable first)
|
||||
match make_box() {
|
||||
.point |p|: acc = acc + p.x # +4
|
||||
.empty: hit = hit
|
||||
}
|
||||
|
||||
# 50 + 47 + 3 + 2 + 5 + 7 + 10 + 4 = 128
|
||||
return acc - 128
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user