Skip to content

Commit 61f662b

Browse files
committed
v3: accept bare generic struct literals where a concrete instance is expected
`fn make() Box[int] { return Box{...} }` (and the `&Box{...}` heap form, and bare literals passed/assigned where a concrete generic instance is expected) previously failed the checker with `cannot return Box as Box[int]`, because the return/assignment path resolves the value through `resolve_expr(expected)` and that had no `struct_init` case — a bare `Box{...}` stayed the unspecialised base `Box`, incompatible with `Box[int]`. resolve_expr now adopts a matching concrete expected instance for a bare generic struct literal (`bare_generic_literal_adopts`: base short-names match and the base is a known generic struct), covering both the value and `&` heap forms. The cgen side already materialises the concrete `Box_int` / `(Box_int*)memdup(...)` in return position via the `in_return`/`cur_fn_ret` fallback. To avoid silently adopting an unrelated literal (and emitting broken C), each named field initializer is checked against the instantiation (`generic_literal_fields_compatible`, substituting the type params); a definite mismatch like `Box{v: 'str'}` for `Box[int]` is rejected as a type error rather than reaching the C compiler, while valid coercions (`Vec[f64]{x: 1}`) are kept. Adds regression tests (value/heap/multi-param adoption; field mismatch rejected). Verified: matches V1, self-host v5 == v6 byte-identical, no vlib/v3/tests regressions, doka still builds and renders.
1 parent 2e3655c commit 61f662b

2 files changed

Lines changed: 101 additions & 0 deletions

File tree

vlib/v3/tests/type_checker_errors_test.v

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,23 @@ fn test_pr_review_codegen_batch_three() {
439439
"struct G {\n\tn int\n}\nfn (g G) make() ?string {\n\treturn 'hi'\n}\nfn run(cb fn () ?string) {\n\ts := cb() or { 'none' }\n\tprintln(s)\n}\nfn main() {\n\tg := G{\n\t\tn: 1\n\t}\n\trun(g.make)\n}\n")
440440
assert mv == 'hi'
441441
}
442+
443+
// Regression tests: a bare generic struct literal adopts a matching concrete
444+
// expected instance (value and heap), and a field-type mismatch is rejected.
445+
fn test_bare_generic_literal_adopts_expected_instance() {
446+
v3_bin := build_v3()
447+
val := run_good(v3_bin, 'good_bare_generic_literal_return',
448+
'struct Box[T] {\n\tv T\n}\nfn make() Box[int] {\n\treturn Box{\n\t\tv: 7\n\t}\n}\nfn main() {\n\tprintln(int_str(make().v))\n}\n')
449+
assert val == '7'
450+
heap := run_good(v3_bin, 'good_bare_generic_literal_heap_return',
451+
'struct Box[T] {\n\tv T\n}\nfn make() &Box[int] {\n\treturn &Box{\n\t\tv: 9\n\t}\n}\nfn main() {\n\tprintln(int_str(make().v))\n}\n')
452+
assert heap == '9'
453+
pair := run_good(v3_bin, 'good_bare_generic_literal_multi_param',
454+
"struct Pair[L, R] {\n\tl L\n\tr R\n}\nfn make() Pair[string, int] {\n\treturn Pair{\n\t\tl: 'hi'\n\t\tr: 5\n\t}\n}\nfn main() {\n\tp := make()\n\tprintln(p.l)\n\tprintln(int_str(p.r))\n}\n")
455+
assert pair == 'hi\n5'
456+
// A field whose type does not match the concrete instantiation is rejected by the
457+
// checker (rather than adopting the type and emitting broken C).
458+
run_bad(v3_bin, 'bad_bare_generic_literal_field_mismatch',
459+
"struct Box[T] {\n\tv T\n}\nfn make() Box[int] {\n\treturn Box{\n\t\tv: 'str'\n\t}\n}\nfn main() {\n\t_ := make()\n}\n",
460+
'cannot return `Box` as `Box[int]`')
461+
}

vlib/v3/types/checker.v

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3484,6 +3484,70 @@ fn (tc &TypeChecker) is_concrete_generic_instance(name string) bool {
34843484
return tc.generic_args_are_concrete(args)
34853485
}
34863486

3487+
// bare_generic_literal_adopts reports whether a struct literal written as the bare
3488+
// generic base (`Box{...}`, no type args) should adopt the concrete `expected`
3489+
// instance (`Box[int]`, optionally behind a pointer). The base short-names must match
3490+
// and the base must be a known generic struct, so a non-generic same-named struct is
3491+
// left to ordinary checking.
3492+
fn (tc &TypeChecker) bare_generic_literal_adopts(lit_value string, expected Type) bool {
3493+
if lit_value.len == 0 || lit_value.contains('[') {
3494+
return false
3495+
}
3496+
e_base, _, e_ok := generic_type_application_parts(unwrap_pointer(expected).name())
3497+
if !e_ok || e_base.all_after_last('.') != lit_value.all_after_last('.') {
3498+
return false
3499+
}
3500+
return e_base in tc.struct_generic_params
3501+
|| e_base.all_after_last('.') in tc.struct_generic_params
3502+
}
3503+
3504+
// generic_literal_fields_compatible checks a bare generic struct literal's named
3505+
// field initializers against the expected concrete instantiation (`Box[int]`),
3506+
// substituting the struct's type parameters into each field's declared type. It
3507+
// returns false only on a *definite* mismatch (e.g. `Box{v: 'str'}` for `Box[int]`),
3508+
// so a clearly-unrelated literal yields a clean checker error instead of adopting the
3509+
// type and emitting broken C; unresolvable fields stay lenient.
3510+
fn (mut tc TypeChecker) generic_literal_fields_compatible(node flat.Node, expected Type) bool {
3511+
e_base, e_args, e_ok := generic_type_application_parts(unwrap_pointer(expected).name())
3512+
if !e_ok {
3513+
return true
3514+
}
3515+
params := tc.struct_generic_params[e_base] or {
3516+
tc.struct_generic_params[e_base.all_after_last('.')] or { return true }
3517+
}
3518+
if params.len != e_args.len {
3519+
return true
3520+
}
3521+
fields := tc.structs[e_base] or { tc.structs[e_base.all_after_last('.')] or { return true } }
3522+
for i in 0 .. node.children_count {
3523+
fi := tc.a.child_node(&node, i)
3524+
if fi.kind != .field_init || fi.value.len == 0 || fi.children_count == 0 {
3525+
continue
3526+
}
3527+
mut decl_typ := Type(void_)
3528+
mut found := false
3529+
for f in fields {
3530+
if f.name == fi.value {
3531+
decl_typ = f.typ
3532+
found = true
3533+
break
3534+
}
3535+
}
3536+
if !found {
3537+
continue
3538+
}
3539+
sub := tc.substitute_generic_type(decl_typ, e_args, params)
3540+
if sub is Unknown || sub is Void {
3541+
continue
3542+
}
3543+
actual := tc.resolve_expr(tc.a.child(fi, 0), sub)
3544+
if !tc.receiver_compatible(actual, sub) {
3545+
return false
3546+
}
3547+
}
3548+
return true
3549+
}
3550+
34873551
// strip_generic_args_name returns the base name of a generic instance type
34883552
// (`Box[int]` -> `Box`); array/map types (leading `[`) yield the name unchanged.
34893553
fn strip_generic_args_name(name string) string {
@@ -4736,6 +4800,23 @@ fn (mut tc TypeChecker) resolve_expr(id flat.NodeId, expected Type) Type {
47364800
'unknown enum field `${node.value}` for `${expected.name}`', id)
47374801
return Type(int_)
47384802
}
4803+
// A bare generic struct literal (`Box{...}` / `&Box{...}`) adopts a matching concrete
4804+
// expected instance (`Box[int]` / `&Box[int]`), so `fn make() Box[int] { return
4805+
// Box{...} }` and bare literals passed/assigned where a concrete instance is expected
4806+
// type-check and carry the concrete type into codegen.
4807+
if node.kind == .struct_init && tc.bare_generic_literal_adopts(node.value, expected)
4808+
&& tc.generic_literal_fields_compatible(node, expected) {
4809+
tc.register_synth_type(id, expected_raw)
4810+
return expected_raw
4811+
}
4812+
if node.kind == .prefix && node.op == .amp && node.children_count == 1 && expected is Pointer {
4813+
child := tc.a.nodes[int(tc.a.child(&node, 0))]
4814+
if child.kind == .struct_init && tc.bare_generic_literal_adopts(child.value, expected)
4815+
&& tc.generic_literal_fields_compatible(child, expected) {
4816+
tc.register_synth_type(id, expected_raw)
4817+
return expected_raw
4818+
}
4819+
}
47394820
if node.kind == .array_literal {
47404821
mut elem_expected := Type(void_)
47414822
mut expected_is_array := false

0 commit comments

Comments
 (0)