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);
|
(`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
|
the rvalue pointer-capture guard is defensive for when that lands
|
||||||
|
|
||||||
22.6. (`// ponytail:` follow-ups): contextual void construction (`e Event = .quit`) needs
|
22.6. contextual void construction + call-as-match-subject (implemented; see below)
|
||||||
enum-literal→union coercion. Separately, a **call expression directly as a match subject**
|
- **contextual void construction**: a bare enum literal in a tagged-union context constructs a
|
||||||
(`match get()`) is a pre-existing gap (assign to a variable first, as the spec examples do);
|
void-payload variant — `e Event = .quit` (and any expected-union position: `=`, return, call
|
||||||
the rvalue pointer-capture guard is defensive for when that lands
|
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)
|
23. error types (see below)
|
||||||
|
|
||||||
|
|||||||
@@ -1984,6 +1984,42 @@ infer_statements :: proc(
|
|||||||
case .Defer:
|
case .Defer:
|
||||||
deferred := [1]ast.Stmt_Id{statement.update}
|
deferred := [1]ast.Stmt_Id{statement.update}
|
||||||
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
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)
|
resize(locals, scope_start)
|
||||||
@@ -3101,6 +3137,29 @@ build_compound_expr :: proc(
|
|||||||
)
|
)
|
||||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
case .Enum_Literal:
|
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) {
|
if !types.is_enum(expected, store) {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
|
|||||||
@@ -2527,6 +2527,98 @@ main :: func() i32 {
|
|||||||
testing.expect(t, found_incompatible)
|
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)
|
@(test)
|
||||||
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
// A value block that does not end in `yield`, and a `yield` nested inside an
|
// 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
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
make_box :: func() Box {
|
||||||
|
return Box{ point = Point{ x = 4, y = 0 } }
|
||||||
|
}
|
||||||
|
|
||||||
main :: func() i32 {
|
main :: func() i32 {
|
||||||
acc i32 = 0
|
acc i32 = 0
|
||||||
|
|
||||||
@@ -93,8 +97,8 @@ main :: func() i32 {
|
|||||||
}
|
}
|
||||||
acc = acc + bucket # +5
|
acc = acc + bucket # +5
|
||||||
|
|
||||||
# void-payload variant: bare-key construction + a no-capture arm
|
# void-payload variant: contextual construction (`.empty` coerces to Box) + no-capture arm
|
||||||
e Box = Box{ empty }
|
e Box = .empty
|
||||||
hit i32 = 0
|
hit i32 = 0
|
||||||
match e {
|
match e {
|
||||||
.point |pt|: hit = pt.x
|
.point |pt|: hit = pt.x
|
||||||
@@ -110,6 +114,12 @@ main :: func() i32 {
|
|||||||
}
|
}
|
||||||
acc = acc + b.point.x # +10 (mutated through the @mut Point)
|
acc = acc + b.point.x # +10 (mutated through the @mut Point)
|
||||||
|
|
||||||
# 50 + 47 + 3 + 2 + 5 + 7 + 10 = 124
|
# match directly on a call result (no need to bind it to a variable first)
|
||||||
return acc - 124
|
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