@@ -151,6 +151,7 @@ pub:
151151 children_count i16
152152 kind NodeKind
153153 op Op
154+ is_mut bool
154155}
155156
156157// FlatAst represents flat ast data used by flat.
@@ -237,6 +238,7 @@ pub fn (n Node) with_shifted_children(shift i32) Node {
237238 children_count: n.children_count
238239 kind: n.kind
239240 op: n.op
241+ is_mut: n.is_mut
240242 }
241243}
242244
@@ -32,6 +32,7 @@ mut:
3232 global_init_order []string // qualified global names, in declaration order
3333 iface_impls map[string][]string // interface name -> implementing concrete type names
3434 iface_type_ids map[string]int // "${iface}::${concrete}" -> 1-based type id
35+ ierror_method_emit_names map[string]bool // names/lowered names of concrete IError msg/code methods
3536 module_init_fns []string // C names of module-level `init()` fns, in source order
3637 module_init_fn_modules map[string]string // C init fn name -> V module name
3738 module_imports map[string][]string // module -> imported modules
@@ -134,6 +135,7 @@ pub fn FlatGen.new() FlatGen {
134135 global_init_order: []string{}
135136 iface_impls: map[string][]string{}
136137 iface_type_ids: map[string]int{}
138+ ierror_method_emit_names: map[string]bool{}
137139 module_init_fns: []string{}
138140 module_init_fn_modules: map[string]string{}
139141 module_imports: map[string][]string{}
@@ -221,6 +223,7 @@ pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[strin
221223 g.global_init_order = []string{}
222224 g.iface_impls = map[string][]string{}
223225 g.iface_type_ids = map[string]int{}
226+ g.ierror_method_emit_names = map[string]bool{}
224227 g.module_init_fns = []string{}
225228 g.module_init_fn_modules = map[string]string{}
226229 g.module_imports = map[string][]string{}
@@ -2066,6 +2069,11 @@ fn (mut g FlatGen) gen_expr_with_expected_type(id flat.NodeId, expected types.Ty
20662069 g.expected_enum = old_expected_enum
20672070 return
20682071 }
2072+ if g.gen_sum_constructor_call_with_expected_type(id, node, expected) {
2073+ g.expected_expr_type = old_expected
2074+ g.expected_enum = old_expected_enum
2075+ return
2076+ }
20692077 if g.gen_sum_value_expr(id, expected) {
20702078 g.expected_expr_type = old_expected
20712079 g.expected_enum = old_expected_enum
@@ -2076,6 +2084,45 @@ fn (mut g FlatGen) gen_expr_with_expected_type(id flat.NodeId, expected types.Ty
20762084 g.expected_enum = old_expected_enum
20772085}
20782086
2087+fn (mut g FlatGen) gen_sum_constructor_call_with_expected_type(id flat.NodeId, node flat.Node, expected types.Type) bool {
2088+ _ = id
2089+ sum_type0 := if expected is types.Alias { expected.base_type } else { expected }
2090+ if sum_type0 !is types.SumType || node.kind != .call || node.children_count < 2 {
2091+ return false
2092+ }
2093+ sum_type := sum_type0 as types.SumType
2094+ callee := g.a.child_node(&node, 0)
2095+ if !g.call_callee_names_sum_base(callee, sum_type.name) {
2096+ return false
2097+ }
2098+ g.gen_sum_cast_expr(sum_type, g.a.child(&node, 1))
2099+ return true
2100+}
2101+
2102+fn (g &FlatGen) call_callee_names_sum_base(callee flat.Node, sum_name string) bool {
2103+ _ = g
2104+ base := generic_sum_base_name(sum_name)
2105+ short_base := base.all_after_last('.')
2106+ if callee.kind == .ident {
2107+ return callee.value == base || callee.value == short_base
2108+ }
2109+ if callee.kind == .selector {
2110+ return callee.value == short_base || callee.value == base
2111+ }
2112+ if callee.kind == .index && callee.children_count > 0 {
2113+ return g.call_callee_names_sum_base(g.a.child_node(&callee, 0), sum_name)
2114+ }
2115+ return false
2116+}
2117+
2118+fn generic_sum_base_name(name string) string {
2119+ bracket := name.index_u8(`[`)
2120+ if bracket > 0 {
2121+ return name[..bracket]
2122+ }
2123+ return name
2124+}
2125+
20792126// gen_sum_value_expr emits sum value expr output for c.
20802127fn (mut g FlatGen) gen_sum_value_expr(id flat.NodeId, expected types.Type) bool {
20812128 sum_type0 := if expected is types.Alias { expected.base_type } else { expected }
@@ -2432,7 +2479,7 @@ fn (g &FlatGen) const_ref_name(name string) string {
24322479 }
24332480 if name in g.const_vals {
24342481 mod := g.const_modules[name] or { '' }
2435- if mod.len == 0 || mod == g.tc.cur_module
2482+ if mod.len == 0 || mod == g.tc.cur_module || mod == 'builtin'
24362483 || (g.tc.cur_module in ['', 'main', 'builtin'] && mod in ['', 'main', 'builtin']) {
24372484 return g.const_primary_name(name)
24382485 }
@@ -2659,7 +2706,7 @@ fn (g &FlatGen) const_ident_c_name(name string) string {
26592706 return c_name(name)
26602707 }
26612708 mod := if name in g.const_modules { g.const_modules[name] } else { '' }
2662- if mod.len > 0 && mod != 'main' && mod != 'builtin' {
2709+ if mod.len > 0 && mod != 'main' {
26632710 return c_name('${mod}.${name}')
26642711 }
26652712 if (mod == '' || mod == 'main') && name in g.const_modules {
@@ -3557,7 +3604,8 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
35573604 .is_expr {
35583605 expr_id := g.a.child(&node, 0)
35593606 expr_type := g.tc.resolve_type(expr_id)
3560- clean := types.unwrap_pointer(expr_type)
3607+ clean0 := types.unwrap_pointer(expr_type)
3608+ clean := if clean0 is types.Alias { clean0.base_type } else { clean0 }
35613609 if clean is types.SumType {
35623610 idx := g.sum_type_index(clean.name, node.value)
35633611 g.write('(')
@@ -3569,6 +3617,40 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
35693617 g.write('.typ == ${idx}')
35703618 }
35713619 g.write(')')
3620+ } else if clean is types.Interface {
3621+ idx := if g.is_ierror_type_name(clean.name) {
3622+ g.ierror_type_id_for_pattern(node.value)
3623+ } else {
3624+ g.iface_type_id_for_pattern(clean.name, node.value)
3625+ }
3626+ if idx == 0 {
3627+ g.write('0')
3628+ return
3629+ }
3630+ g.write('(')
3631+ if expr_type.is_pointer() {
3632+ g.gen_expr(expr_id)
3633+ g.write('->_typ == ${idx}')
3634+ } else {
3635+ g.gen_expr(expr_id)
3636+ g.write('._typ == ${idx}')
3637+ }
3638+ g.write(')')
3639+ } else if g.is_ierror_type_name(types.Type(clean).name()) {
3640+ idx := g.ierror_type_id_for_pattern(node.value)
3641+ if idx == 0 {
3642+ g.write('0')
3643+ return
3644+ }
3645+ g.write('(')
3646+ if expr_type.is_pointer() {
3647+ g.gen_expr(expr_id)
3648+ g.write('->_typ == ${idx}')
3649+ } else {
3650+ g.gen_expr(expr_id)
3651+ g.write('._typ == ${idx}')
3652+ }
3653+ g.write(')')
35723654 } else {
35733655 g.write('1')
35743656 }
@@ -183,6 +183,9 @@ fn (mut g FlatGen) should_emit_fn_node_in_module(node flat.Node, node_index int,
183183 if node.value.starts_with('__anon_fn_') || qfn.contains('__anon_fn_') {
184184 return true
185185 }
186+ if g.should_emit_ierror_method(node.value, qfn) {
187+ return true
188+ }
186189 if g.has_used_fn_filter() && !g.used_fn_contains_in_module(node.value, module_name) {
187190 return false
188191 }
@@ -185,24 +185,26 @@ fn (mut g FlatGen) collect_fn_gen_items() []FlatFnGenItem {
185185 mut cur_file := ''
186186 for i in 0 .. g.a.nodes.len {
187187 node := g.a.nodes[i]
188- kind_id := node_kind_id(node)
189- if kind_id == 77 {
190- cur_file = node.value
191- g.tc.cur_file = cur_file
192- cur_module = ''
193- g.tc.cur_module = cur_module
194- continue
195- }
196- if kind_id == 73 {
197- cur_module = node.value
198- g.tc.cur_file = cur_file
199- g.tc.cur_module = cur_module
200- continue
188+ match node.kind {
189+ .file {
190+ cur_file = node.value
191+ g.tc.cur_file = cur_file
192+ cur_module = ''
193+ g.tc.cur_module = cur_module
194+ continue
195+ }
196+ .module_decl {
197+ cur_module = node.value
198+ g.tc.cur_file = cur_file
199+ g.tc.cur_module = cur_module
200+ continue
201+ }
202+ .fn_decl {}
203+ else {
204+ continue
205+ }
201206 }
202207
203- if kind_id != 61 {
204- continue
205- }
206208 if !g.should_emit_fn_node_in_module(node, i, cur_module) {
207209 continue
208210 }
@@ -241,6 +243,9 @@ fn (mut g FlatGen) prepare_parallel_items(items []FlatFnGenItem) {
241243 g.tc.cur_module = item.module
242244 g.prepare_parallel_node(item.node_id)
243245 }
246+ if _ := g.ierror_interface_name() {
247+ g.intern_string('')
248+ }
244249 g.register_interface_strings()
245250}
246251
@@ -374,6 +379,7 @@ fn (g &FlatGen) new_parallel_worker(worker_id int) &FlatGen {
374379 global_init_order: g.global_init_order
375380 iface_impls: g.iface_impls
376381 iface_type_ids: g.iface_type_ids
382+ ierror_method_emit_names: g.ierror_method_emit_names
377383 module_init_fns: g.module_init_fns
378384 module_init_fn_modules: g.module_init_fn_modules
379385 module_imports: g.module_imports
@@ -431,58 +437,60 @@ fn (g &FlatGen) clone_parallel_type_checker() &types.TypeChecker {
431437 fs.names = g.tc.file_scope.names.clone()
432438 fs.types = g.tc.file_scope.types.clone()
433439 return &types.TypeChecker{
434- a: unsafe { g.tc.a }
435- fn_ret_types: g.tc.fn_ret_types
436- fn_param_types: g.tc.fn_param_types
437- fn_ret_type_texts: g.tc.fn_ret_type_texts
438- fn_param_type_texts: g.tc.fn_param_type_texts
439- fn_type_files: g.tc.fn_type_files
440- fn_type_modules: g.tc.fn_type_modules
441- fn_variadic: g.tc.fn_variadic
442- fn_implicit_veb_ctx: g.tc.fn_implicit_veb_ctx
443- c_variadic_fns: g.tc.c_variadic_fns
444- structs: g.tc.structs
445- struct_generic_params: g.tc.struct_generic_params
446- struct_field_c_abi_fns: g.tc.struct_field_c_abi_fns
447- unions: g.tc.unions
448- type_aliases: g.tc.type_aliases
449- type_alias_c_abi_fns: g.tc.type_alias_c_abi_fns
450- sum_types: g.tc.sum_types
451- enum_names: g.tc.enum_names
452- enum_fields: g.tc.enum_fields
453- flag_enums: g.tc.flag_enums
454- interface_names: g.tc.interface_names
455- interface_fields: g.tc.interface_fields
456- interface_embeds: g.tc.interface_embeds
457- interface_abstract_methods: g.tc.interface_abstract_methods
458- c_globals: g.tc.c_globals
459- const_types: g.tc.const_types
460- const_exprs: g.tc.const_exprs
461- const_modules: g.tc.const_modules
462- const_suffixes: g.tc.const_suffixes
463- imports: g.tc.imports
464- file_imports: g.tc.file_imports
465- file_selective_imports: g.tc.file_selective_imports
466- file_modules: g.tc.file_modules
467- file_scope: fs
468- cur_scope: fs
469- scope_pool: []&types.Scope{}
470- has_builtins: g.tc.has_builtins
471- cur_module: g.tc.cur_module
472- cur_file: g.tc.cur_file
473- errors: g.tc.errors.clone()
474- resolved_call_names: g.tc.resolved_call_names
475- resolved_call_set: g.tc.resolved_call_set
476- resolved_fn_value_names: g.tc.resolved_fn_value_names
477- resolved_fn_value_set: g.tc.resolved_fn_value_set
478- expr_type_values: g.tc.expr_type_values
479- expr_type_set: g.tc.expr_type_set
480- checking_nodes: g.tc.checking_nodes
481- diagnose_unknown_calls: g.tc.diagnose_unknown_calls
482- reject_unlowered_map_mutation: g.tc.reject_unlowered_map_mutation
483- diagnostic_files: g.tc.diagnostic_files
484- cur_fn_ret_type: g.tc.cur_fn_ret_type
485- smartcasts: g.tc.smartcasts
440+ a: unsafe { g.tc.a }
441+ fn_ret_types: g.tc.fn_ret_types
442+ fn_param_types: g.tc.fn_param_types
443+ fn_ret_type_texts: g.tc.fn_ret_type_texts
444+ fn_param_type_texts: g.tc.fn_param_type_texts
445+ fn_type_files: g.tc.fn_type_files
446+ fn_type_modules: g.tc.fn_type_modules
447+ fn_variadic: g.tc.fn_variadic
448+ fn_implicit_veb_ctx: g.tc.fn_implicit_veb_ctx
449+ c_variadic_fns: g.tc.c_variadic_fns
450+ structs: g.tc.structs
451+ struct_modules: g.tc.struct_modules
452+ struct_files: g.tc.struct_files
453+ struct_error_embeds_shadow_builtin: g.tc.struct_error_embeds_shadow_builtin
454+ struct_generic_params: g.tc.struct_generic_params
455+ struct_field_c_abi_fns: g.tc.struct_field_c_abi_fns
456+ unions: g.tc.unions
457+ type_aliases: g.tc.type_aliases
458+ type_alias_c_abi_fns: g.tc.type_alias_c_abi_fns
459+ sum_types: g.tc.sum_types
460+ enum_names: g.tc.enum_names
461+ enum_fields: g.tc.enum_fields
462+ flag_enums: g.tc.flag_enums
463+ interface_names: g.tc.interface_names
464+ interface_fields: g.tc.interface_fields
465+ interface_embeds: g.tc.interface_embeds
466+ interface_abstract_methods: g.tc.interface_abstract_methods
467+ c_globals: g.tc.c_globals
468+ const_types: g.tc.const_types
469+ const_exprs: g.tc.const_exprs
470+ const_modules: g.tc.const_modules
471+ const_suffixes: g.tc.const_suffixes
472+ imports: g.tc.imports
473+ file_imports: g.tc.file_imports
474+ file_selective_imports: g.tc.file_selective_imports
475+ file_modules: g.tc.file_modules
476+ file_scope: fs
477+ cur_scope: fs
478+ scope_pool: []&types.Scope{}
479+ has_builtins: g.tc.has_builtins
480+ cur_module: g.tc.cur_module
481+ cur_file: g.tc.cur_file
482+ errors: g.tc.errors.clone()
483+ resolved_call_names: g.tc.resolved_call_names
484+ resolved_call_set: g.tc.resolved_call_set
485+ resolved_fn_value_names: g.tc.resolved_fn_value_names
486+ expr_type_values: g.tc.expr_type_values
487+ expr_type_set: g.tc.expr_type_set
488+ checking_nodes: g.tc.checking_nodes
489+ diagnose_unknown_calls: g.tc.diagnose_unknown_calls
490+ reject_unlowered_map_mutation: g.tc.reject_unlowered_map_mutation
491+ diagnostic_files: g.tc.diagnostic_files
492+ cur_fn_ret_type: g.tc.cur_fn_ret_type
493+ smartcasts: g.tc.smartcasts
486494 // Read-only map cgen uses to recover substituted signatures for generic-receiver
487495 // method values (`Box[int].method` as a callback); without it a parallel worker
488496 // sees an empty map and gen_method_value_closure falls through.
@@ -195,6 +195,7 @@ fn (mut g FlatGen) register_interface_strings() {
195195// type id. The id is stored in the boxed interface value's `_typ` field and is
196196// what the generated method-dispatch switch matches on.
197197fn (mut g FlatGen) collect_interface_impls() {
198+ g.ierror_method_emit_names = map[string]bool{}
198199 mut iface_names := []string{}
199200 for name, _ in g.interfaces {
200201 iface_names << name
@@ -206,12 +207,13 @@ fn (mut g FlatGen) collect_interface_impls() {
206207 }
207208 struct_names.sort()
208209 for iface in iface_names {
209- if c_name(iface) == 'IError' {
210- continue
211- }
212210 mut impls := []string{}
213211 for concrete in struct_names {
214- if g.tc.named_type_implements_interface(concrete, iface) {
212+ if g.is_ierror_type_name(iface) {
213+ if g.type_can_box_as_ierror(concrete) {
214+ impls << concrete
215+ }
216+ } else if g.tc.named_type_implements_interface(concrete, iface) {
215217 impls << concrete
216218 }
217219 }
@@ -219,6 +221,29 @@ fn (mut g FlatGen) collect_interface_impls() {
219221 for idx, concrete in impls {
220222 g.iface_type_ids['${iface}::${concrete}'] = idx + 1
221223 }
224+ if g.is_ierror_type_name(iface) {
225+ g.collect_ierror_method_emit_names(impls)
226+ }
227+ }
228+}
229+
230+fn (mut g FlatGen) collect_ierror_method_emit_names(impls []string) {
231+ for concrete in impls {
232+ for method in ['msg', 'code'] {
233+ call := g.ierror_method_call(concrete, method) or { continue }
234+ g.add_ierror_method_emit_name(call.method_name)
235+ }
236+ }
237+}
238+
239+fn (mut g FlatGen) add_ierror_method_emit_name(name string) {
240+ if name.len == 0 {
241+ return
242+ }
243+ g.ierror_method_emit_names[name] = true
244+ lowered := c_name(name)
245+ if lowered != name {
246+ g.ierror_method_emit_names[lowered] = true
222247 }
223248}
224249
@@ -228,12 +253,273 @@ fn (g &FlatGen) iface_type_id(iface string, concrete string) int {
228253 return g.iface_type_ids['${iface}::${concrete}'] or { 0 }
229254}
230255
256+fn (g &FlatGen) iface_type_id_for_pattern(iface string, pattern string) int {
257+ id := g.iface_type_id(iface, pattern)
258+ if id != 0 {
259+ return id
260+ }
261+ qpattern := g.tc.qualify_name(pattern)
262+ if qpattern != pattern {
263+ qid := g.iface_type_id(iface, qpattern)
264+ if qid != 0 {
265+ return qid
266+ }
267+ }
268+ if pattern.contains('.') {
269+ return 0
270+ }
271+ mut found := 0
272+ for concrete in g.iface_impls[iface] or { []string{} } {
273+ if concrete.all_after_last('.') != pattern {
274+ continue
275+ }
276+ cid := g.iface_type_id(iface, concrete)
277+ if cid == 0 {
278+ continue
279+ }
280+ if found != 0 {
281+ return 0
282+ }
283+ found = cid
284+ }
285+ return found
286+}
287+
288+fn (g &FlatGen) ierror_interface_name() ?string {
289+ for name, _ in g.interfaces {
290+ if g.is_ierror_type_name(name) {
291+ return name
292+ }
293+ }
294+ return none
295+}
296+
297+fn (g &FlatGen) is_ierror_type_name(name string) bool {
298+ _ = g
299+ return name == 'IError' || name == 'builtin.IError' || c_name(name) == 'IError'
300+}
301+
302+fn (g &FlatGen) ierror_direct_method_name(concrete string, method string) ?string {
303+ direct := '${concrete}.${method}'
304+ if g.ierror_method_signature_matches(direct, concrete, method) {
305+ return direct
306+ }
307+ qconcrete := g.tc.qualify_name(concrete)
308+ if qconcrete != concrete {
309+ qdirect := '${qconcrete}.${method}'
310+ if g.ierror_method_signature_matches(qdirect, qconcrete, method) {
311+ return qdirect
312+ }
313+ }
314+ return none
315+}
316+
317+fn (g &FlatGen) ierror_method_signature_matches(name string, concrete string, method string) bool {
318+ params := g.tc.fn_param_types[name] or { return false }
319+ if params.len != 1 {
320+ return false
321+ }
322+ ret := g.tc.fn_ret_types[name] or { return false }
323+ if !g.ierror_method_return_matches(method, ret) {
324+ return false
325+ }
326+ receiver := g.ierror_clean_type(params[0])
327+ expected := g.ierror_clean_type(g.tc.parse_type(concrete))
328+ return g.type_names_match(receiver, expected)
329+}
330+
331+fn (g &FlatGen) ierror_method_return_matches(method string, ret types.Type) bool {
332+ clean := if ret is types.Alias { ret.base_type } else { ret }
333+ return match method {
334+ 'msg' { clean is types.String }
335+ 'code' { clean.name() == 'int' }
336+ else { false }
337+ }
338+}
339+
340+fn (g &FlatGen) ierror_clean_type(typ types.Type) types.Type {
341+ clean0 := types.unwrap_pointer(typ)
342+ return if clean0 is types.Alias { clean0.base_type } else { clean0 }
343+}
344+
345+struct IErrorMethodCall {
346+ method_name string
347+ path []types.StructField
348+}
349+
350+fn (g &FlatGen) ierror_method_call(concrete string, method string) ?IErrorMethodCall {
351+ if direct := g.ierror_direct_method_name(concrete, method) {
352+ return IErrorMethodCall{
353+ method_name: direct
354+ }
355+ }
356+ mut seen := map[string]bool{}
357+ return g.ierror_promoted_method_call(concrete, method, mut seen)
358+}
359+
360+fn (g &FlatGen) ierror_promoted_method_call(concrete string, method string, mut seen map[string]bool) ?IErrorMethodCall {
361+ if concrete in seen {
362+ return none
363+ }
364+ seen[concrete] = true
365+ for field in g.struct_embedded_fields(concrete) {
366+ embedded_name := g.embedded_field_type_name(field)
367+ if embedded_name.len == 0 {
368+ continue
369+ }
370+ if direct := g.ierror_direct_method_name(embedded_name, method) {
371+ return IErrorMethodCall{
372+ method_name: direct
373+ path: [field]
374+ }
375+ }
376+ if nested := g.ierror_promoted_method_call(embedded_name, method, mut seen) {
377+ mut path := [field]
378+ path << nested.path
379+ return IErrorMethodCall{
380+ method_name: nested.method_name
381+ path: path
382+ }
383+ }
384+ }
385+ return none
386+}
387+
388+fn (g &FlatGen) ierror_method_receiver_expr(concrete string, path []types.StructField, recv_is_ptr bool) string {
389+ concrete_ct := g.tc.c_type(g.tc.parse_type(concrete))
390+ object := '(${concrete_ct}*)i->_object'
391+ if path.len == 0 {
392+ return if recv_is_ptr { object } else { '*${object}' }
393+ }
394+ mut access := object
395+ mut access_is_ptr := true
396+ for field in path {
397+ op := if access_is_ptr { '->' } else { '.' }
398+ access = '(${access})${op}${c_field_name(field.name)}'
399+ access_is_ptr = field.typ is types.Pointer
400+ }
401+ if access_is_ptr == recv_is_ptr {
402+ return access
403+ }
404+ return if recv_is_ptr { '&(${access})' } else { '*(${access})' }
405+}
406+
407+fn (g &FlatGen) type_can_box_as_ierror(concrete string) bool {
408+ return g.tc.named_type_compatible_with_ierror(concrete)
409+}
410+
411+fn (g &FlatGen) ierror_concrete_name(t types.Type) ?string {
412+ clean0 := types.unwrap_pointer(t)
413+ clean := if clean0 is types.Alias { clean0.base_type } else { clean0 }
414+ if clean !is types.Struct {
415+ return none
416+ }
417+ iface := g.ierror_interface_name() or { return none }
418+ name := (clean as types.Struct).name
419+ if g.iface_type_id(iface, name) != 0 {
420+ return name
421+ }
422+ qname := g.tc.qualify_name(name)
423+ if qname != name && g.iface_type_id(iface, qname) != 0 {
424+ return qname
425+ }
426+ return none
427+}
428+
429+fn (g &FlatGen) ierror_type_id_for_pattern(pattern string) int {
430+ iface := g.ierror_interface_name() or { return 0 }
431+ return g.iface_type_id_for_pattern(iface, pattern)
432+}
433+
434+fn (g &FlatGen) should_emit_ierror_method(name string, qname string) bool {
435+ if name in g.ierror_method_emit_names || qname in g.ierror_method_emit_names {
436+ return true
437+ }
438+ return c_name(qname) in g.ierror_method_emit_names
439+}
440+
441+fn (mut g FlatGen) gen_ierror_from_expr(id flat.NodeId) bool {
442+ s := g.ierror_from_expr_string(id) or { return false }
443+ g.write(s)
444+ return true
445+}
446+
447+fn (mut g FlatGen) ierror_from_expr_string(id flat.NodeId) ?string {
448+ node := g.a.nodes[int(id)]
449+ mut actual := g.usable_expr_type(id)
450+ if node.kind == .ident {
451+ if param_type := g.current_param_type(node.value) {
452+ actual = param_type
453+ } else if param_type := g.cur_param_types[node.value] {
454+ actual = param_type
455+ }
456+ }
457+ return g.ierror_from_expr_string_with_type(id, actual)
458+}
459+
460+fn (mut g FlatGen) ierror_from_expr_string_with_type(id flat.NodeId, actual types.Type) ?string {
461+ node := g.a.nodes[int(id)]
462+ concrete := g.ierror_concrete_name(actual) or { return none }
463+ iface := g.ierror_interface_name() or { return none }
464+ type_id := g.iface_type_id(iface, concrete)
465+ if type_id == 0 {
466+ return none
467+ }
468+ expr := g.expr_to_string(id)
469+ concrete_ct := g.tc.c_type(g.tc.parse_type(concrete))
470+ object := if actual is types.Pointer {
471+ if g.ierror_pointer_payload_needs_heap_copy(node) {
472+ 'memdup(${expr}, sizeof(${concrete_ct}))'
473+ } else {
474+ expr
475+ }
476+ } else {
477+ 'memdup((${concrete_ct}[]){${expr}}, sizeof(${concrete_ct}))'
478+ }
479+ empty_sid := g.intern_string('')
480+ return '(IError){._typ = ${type_id}, ._object = ${object}, .message = _str_${empty_sid}, .code = 0}'
481+}
482+
483+fn (g &FlatGen) ierror_pointer_payload_needs_heap_copy(node flat.Node) bool {
484+ if node.kind != .prefix || node.op != .amp || node.children_count == 0 {
485+ return false
486+ }
487+ mut child_id := g.a.child(&node, 0)
488+ for {
489+ child := g.a.nodes[int(child_id)]
490+ if child.kind !in [.selector, .index] || child.children_count == 0 {
491+ break
492+ }
493+ child_id = g.a.child(&child, 0)
494+ }
495+ root := g.a.nodes[int(child_id)]
496+ if root.kind != .ident {
497+ return false
498+ }
499+ if param_type := g.current_param_type(root.value) {
500+ return param_type !is types.Pointer
501+ }
502+ if param_type := g.cur_param_types[root.value] {
503+ return param_type !is types.Pointer
504+ }
505+ if local_type := g.tc.cur_scope.lookup(root.value) {
506+ return local_type !is types.Pointer
507+ }
508+ return false
509+}
510+
231511fn (mut g FlatGen) gen_interface_value_expr(id flat.NodeId, expected types.Type) bool {
232512 iface_type := if expected is types.Alias { expected.base_type } else { expected }
233513 if iface_type !is types.Interface {
234514 return false
235515 }
236516 iface := iface_type as types.Interface
517+ if g.is_ierror_type_name(iface.name) {
518+ if s := g.ierror_from_expr_string(id) {
519+ g.write(s)
520+ return true
521+ }
522+ }
237523 node := g.a.nodes[int(id)]
238524 mut actual := g.usable_expr_type(id)
239525 if node.kind == .ident {
@@ -300,7 +586,7 @@ fn (g &FlatGen) is_interface_type_name(name string) bool {
300586// has_ierror_interface reports whether has ierror interface applies in c.
301587fn (g &FlatGen) has_ierror_interface() bool {
302588 for name, _ in g.interfaces {
303- if c_name(name) == 'IError' {
589+ if g.is_ierror_type_name(name) {
304590 return true
305591 }
306592 }
@@ -389,6 +675,17 @@ fn (mut g FlatGen) gen_interface_dispatch(iface_name string, cn string, method s
389675 if cn == 'IError' {
390676 ret_ct := if method == 'code' { 'int' } else { 'string' }
391677 g.writeln('${ret_ct} ${cn}__${method}(${cn}* i) {')
678+ for concrete in impls {
679+ id := g.iface_type_id(iface_name, concrete)
680+ call := g.ierror_method_call(concrete, method) or { continue }
681+ if id == 0 {
682+ continue
683+ }
684+ params := g.tc.fn_param_types[call.method_name] or { []types.Type{} }
685+ recv_is_ptr := params.len > 0 && params[0] is types.Pointer
686+ recv := g.ierror_method_receiver_expr(concrete, call.path, recv_is_ptr)
687+ g.writeln('\tif (i->_typ == ${id}) return ${c_name(call.method_name)}(${recv});')
688+ }
392689 match method {
393690 'msg' {
394691 g.writeln('\treturn i->message;')
@@ -250,6 +250,12 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) {
250250 return
251251 }
252252 if base is types.Void {
253+ if g.cur_fn_ret is types.ResultType {
254+ if err := g.result_error_from_expr_string(ret_id) {
255+ g.writeln('return (${ct}){.ok = false, .err = ${err}};')
256+ return
257+ }
258+ }
253259 g.writeln('return (${ct}){.ok = false};')
254260 } else if base is types.ArrayFixed {
255261 // The optional's `.value` is a fixed-array member, which can't be set
@@ -287,11 +293,18 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) {
287293 && !g.type_names_match(expr_value_type, base)
288294 && !g.expr_is_nil_pointer_payload(ret_id, base)
289295 && !g.type_can_wrap_as_sum(expr_value_type, base)
296+ && !g.type_can_wrap_as_ierror_payload(expr_value_type, base)
290297 && !g.types_numeric_compatible(expr_value_type, base)
291298 && !g.call_constructs_type(ret_id, base)
292299 && !g.clone_call_matches_base(ret_node, base)
293300 && expr_value_type !is types.Primitive
294301 && expr_value_type !is types.Unknown {
302+ if g.cur_fn_ret is types.ResultType {
303+ if err := g.result_error_from_expr_string(ret_id) {
304+ g.writeln('return (${ct}){.ok = false, .err = ${err}};')
305+ return
306+ }
307+ }
295308 g.writeln('return (${ct}){.ok = false};')
296309 } else {
297310 g.write('return (${ct}){.ok = true, .value = ')
@@ -350,7 +363,10 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) {
350363 g.gen_expr(ret_id)
351364 }
352365 } else {
353- g.gen_expr(ret_id)
366+ if !g.gen_sum_constructor_call_with_expected_type(ret_id, ret_node,
367+ g.cur_fn_ret) {
368+ g.gen_expr(ret_id)
369+ }
354370 }
355371 g.writeln(';')
356372 }
@@ -550,6 +566,11 @@ fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_no
550566 return '(${ct}){.ok = false}'
551567 }
552568 if base is types.Void {
569+ if g.cur_fn_ret is types.ResultType {
570+ if err := g.result_error_from_expr_string(ret_id) {
571+ return '(${ct}){.ok = false, .err = ${err}}'
572+ }
573+ }
553574 return '(${ct}){.ok = false}'
554575 }
555576 if base is types.ArrayFixed {
@@ -586,9 +607,15 @@ fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_no
586607 && !g.type_names_match(expr_value_type, base)
587608 && !g.expr_is_nil_pointer_payload(ret_id, base)
588609 && !g.type_can_wrap_as_sum(expr_value_type, base)
610+ && !g.type_can_wrap_as_ierror_payload(expr_value_type, base)
589611 && !g.types_numeric_compatible(expr_value_type, base)
590612 && !g.call_constructs_type(ret_id, base) && !g.clone_call_matches_base(ret_node, base)
591613 && expr_value_type !is types.Primitive && expr_value_type !is types.Unknown {
614+ if g.cur_fn_ret is types.ResultType {
615+ if err := g.result_error_from_expr_string(ret_id) {
616+ return '(${ct}){.ok = false, .err = ${err}}'
617+ }
618+ }
592619 return '(${ct}){.ok = false}'
593620 }
594621 return '(${ct}){.ok = true, .value = ${g.expr_to_string_with_expected_type(ret_id, base)}}'
@@ -612,9 +639,42 @@ fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_no
612639 // preserves `_typ`/`_object` instead of zeroing the interface.
613640 return g.interface_value_to_string(ret_id, g.cur_fn_ret)
614641 }
642+ if expr := g.sum_constructor_return_expr_string(ret_id, ret_node, g.cur_fn_ret) {
643+ return expr
644+ }
615645 return g.expr_to_string(ret_id)
616646}
617647
648+fn (mut g FlatGen) sum_constructor_return_expr_string(ret_id flat.NodeId, ret_node flat.Node, expected types.Type) ?string {
649+ sum_type0 := if expected is types.Alias { expected.base_type } else { expected }
650+ if sum_type0 !is types.SumType || ret_node.kind != .call || ret_node.children_count < 2 {
651+ return none
652+ }
653+ sum_type := sum_type0 as types.SumType
654+ callee := g.a.child_node(&ret_node, 0)
655+ if !g.call_callee_names_sum_base(callee, sum_type.name) {
656+ return none
657+ }
658+ return g.expr_to_string_with_expected_type(ret_id, expected)
659+}
660+
661+fn (mut g FlatGen) result_error_from_expr_string(id flat.NodeId) ?string {
662+ if err := g.ierror_from_expr_string(id) {
663+ return err
664+ }
665+ expr_type := g.usable_expr_type(id)
666+ mut expr_value_type := expr_type
667+ if expr_type is types.OptionType {
668+ expr_value_type = expr_type.base_type
669+ } else if expr_type is types.ResultType {
670+ expr_value_type = expr_type.base_type
671+ }
672+ if g.is_ierror_type_name(expr_value_type.name()) {
673+ return g.expr_to_string(id)
674+ }
675+ return none
676+}
677+
618678fn (g &FlatGen) local_fn_call_return_type(call_id flat.NodeId, call_node flat.Node) types.Type {
619679 if call_node.kind != .call || call_node.children_count == 0 {
620680 return types.Type(types.void_)
@@ -1080,6 +1140,23 @@ fn (g &FlatGen) type_can_wrap_as_sum(actual types.Type, expected types.Type) boo
10801140 return variant in variants
10811141}
10821142
1143+fn (g &FlatGen) type_can_wrap_as_ierror_payload(actual types.Type, expected types.Type) bool {
1144+ expected0 := if expected is types.Alias { expected.base_type } else { expected }
1145+ if expected0 !is types.Interface || !g.is_ierror_type_name(expected0.name()) {
1146+ return false
1147+ }
1148+ actual0 := types.unwrap_pointer(actual)
1149+ actual_base := if actual0 is types.Alias { actual0.base_type } else { actual0 }
1150+ if actual_base is types.Interface {
1151+ return false
1152+ }
1153+ concrete := actual_base.name()
1154+ if concrete.len == 0 {
1155+ return false
1156+ }
1157+ return g.type_can_box_as_ierror(concrete)
1158+}
1159+
10831160// types_numeric_compatible supports types numeric compatible handling for FlatGen.
10841161fn (g &FlatGen) types_numeric_compatible(a types.Type, b types.Type) bool {
10851162 _ = g
@@ -45,11 +45,15 @@ fn (mut g FlatGen) gen_string_interp(node flat.Node) {
4545 g.write('.message')
4646 } else if typ is types.Primitive {
4747 prim_name := types.Type(typ).name()
48- g.write('${c_name('${prim_name}.str')}(')
48+ prim_method := prim_name + '.str'
49+ g.write(c_name(prim_method))
50+ g.write('(')
4951 g.gen_expr(child_id)
5052 g.write(')')
5153 } else if typ is types.ISize || typ is types.USize {
52- g.write('${c_name('${typ.name()}.str')}(')
54+ size_method := typ.name() + '.str'
55+ g.write(c_name(size_method))
56+ g.write('(')
5357 g.gen_expr(child_id)
5458 g.write(')')
5559 } else if typ is types.Struct {
@@ -148,7 +148,7 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
148148 'string.==', 'string.<', 'string.free', 'string.all_before', 'string.all_before_last',
149149 'string.all_after', 'string.all_after_last', 'string.substr', 'string__substr', 'u8.vstring',
150150 '[]rune.string', 'map.set', 'map.exists', 'map.get', 'map.get_check', 'map.get_and_set',
151- 'map.delete', 'map.clone', 'map.clear', 'memdup', 'strings.Builder.write_ptr',
151+ 'map.delete', 'map.clone', 'map.clear', 'map.keys', 'memdup', 'strings.Builder.write_ptr',
152152 'strings.Builder.write_runes', 'strings.Builder.free', 'strconv.format_int',
153153 'strconv.format_uint', 'bool.str', 'ptr_str', 'strconv__f32_to_str_l',
154154 'strconv__f64_to_str_l', 'sync.new_channel_st', 'sync.Channel.push', 'sync.Channel.pop',
@@ -268,6 +268,10 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
268268 if !valid_symbol_name(callee) {
269269 continue
270270 }
271+ if callee == 'string__plus' {
272+ enqueue(callee, mut used, mut queue)
273+ continue
274+ }
271275 mut found_direct := false
272276 if callee_info := fn_decls[callee] {
273277 found_direct = true
@@ -351,7 +355,11 @@ fn ensure_iface_impls(recv string, cur_module string, tc &types.TypeChecker, mut
351355 }
352356 mut impls := []string{}
353357 for struct_name, _ in tc.structs {
354- if tc.named_type_implements_interface(struct_name, iface_name) {
358+ if markused_is_ierror_interface_name(iface_name) {
359+ if tc.named_type_compatible_with_ierror(struct_name) {
360+ impls << struct_name
361+ }
362+ } else if tc.named_type_implements_interface(struct_name, iface_name) {
355363 impls << struct_name
356364 }
357365 }
@@ -378,6 +386,10 @@ fn interface_name_for_receiver(recv string, cur_module string, tc &types.TypeChe
378386 return none
379387}
380388
389+fn markused_is_ierror_interface_name(name string) bool {
390+ return name == 'IError' || name == 'builtin.IError'
391+}
392+
381393// add_suffix_candidate updates add suffix candidate state for markused.
382394fn add_suffix_candidate(mut suffix_map map[string][]string, short string, name string) {
383395 if !valid_symbol_name(short) || !valid_symbol_name(name) {
@@ -969,6 +981,29 @@ fn markused_call_lowers_to_join_path_single(a &flat.FlatAst, fn_node flat.Node,
969981 return base.value == 'os' || imports[base.value] == 'os'
970982}
971983
984+fn markused_type_name_lowers_to_map_str(type_name string, tc &types.TypeChecker) bool {
985+ if markused_clean_map_type(type_name).starts_with('map[') {
986+ return true
987+ }
988+ return markused_type_lowers_to_map_str(tc.parse_type(type_name))
989+}
990+
991+fn markused_type_lowers_to_map_str(typ0 types.Type) bool {
992+ mut typ := typ0
993+ for _ in 0 .. 8 {
994+ if typ is types.Alias {
995+ typ = typ.base_type
996+ continue
997+ }
998+ if typ is types.Pointer {
999+ typ = typ.base_type
1000+ continue
1001+ }
1002+ break
1003+ }
1004+ return typ is types.Map || markused_clean_map_type(typ.name()).starts_with('map[')
1005+}
1006+
9721007// enqueue_stringified_custom_str_method supports enqueue_stringified_custom_str_method handling.
9731008fn enqueue_stringified_custom_str_method(expr_id flat.NodeId, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
9741009 mut typ := tc.expr_type(expr_id) or { tc.resolve_type(expr_id) }
@@ -1018,14 +1053,21 @@ fn enqueue_enum_str_method(type_name string, cur_module string, tc &types.TypeCh
10181053// enqueue_structlike_str_method supports enqueue structlike str method handling for markused.
10191054fn enqueue_structlike_str_method(type_name string, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
10201055 for candidate in stringification_type_candidates(type_name, cur_module) {
1021- lowered := '${markused_c_name(candidate)}__str'
1022- if lowered in tc.fn_ret_types {
1023- enqueue(lowered, mut used, mut queue)
1024- }
1025- method := '${candidate}.str'
1026- if method in tc.fn_ret_types {
1027- enqueue(method, mut used, mut queue)
1028- }
1056+ enqueue_structlike_str_candidate(candidate, tc, mut used, mut queue)
1057+ }
1058+ for candidate in generic_stringification_type_candidates(type_name, cur_module, tc) {
1059+ enqueue_structlike_str_candidate(candidate, tc, mut used, mut queue)
1060+ }
1061+}
1062+
1063+fn enqueue_structlike_str_candidate(candidate string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
1064+ lowered := '${markused_c_name(candidate)}__str'
1065+ if lowered in tc.fn_ret_types {
1066+ enqueue(lowered, mut used, mut queue)
1067+ }
1068+ method := '${candidate}.str'
1069+ if method in tc.fn_ret_types {
1070+ enqueue(method, mut used, mut queue)
10291071 }
10301072}
10311073
@@ -1043,6 +1085,97 @@ fn stringification_type_candidates(type_name string, cur_module string) []string
10431085 return candidates
10441086}
10451087
1088+fn generic_stringification_type_candidates(type_name string, cur_module string, tc &types.TypeChecker) []string {
1089+ base, args, ok := markused_generic_app_parts(type_name)
1090+ if !ok || args.len == 0 {
1091+ return []string{}
1092+ }
1093+ mut candidates := []string{}
1094+ for base_candidate in stringification_type_candidates(base, cur_module) {
1095+ params := generic_stringification_params(base_candidate, tc) or { continue }
1096+ if params.len != args.len {
1097+ continue
1098+ }
1099+ candidates << '${base_candidate}[${params.join(', ')}]'
1100+ }
1101+ return candidates
1102+}
1103+
1104+fn generic_stringification_params(base_candidate string, tc &types.TypeChecker) ?[]string {
1105+ if params := tc.struct_generic_params[base_candidate] {
1106+ return params
1107+ }
1108+ if params := tc.sum_generic_params[base_candidate] {
1109+ return params
1110+ }
1111+ return none
1112+}
1113+
1114+fn markused_generic_app_parts(typ string) (string, []string, bool) {
1115+ clean := typ.trim_space()
1116+ if clean.starts_with('fn(') || clean.starts_with('fn (') {
1117+ return '', []string{}, false
1118+ }
1119+ bracket := clean.index_u8(`[`)
1120+ if bracket <= 0 {
1121+ return '', []string{}, false
1122+ }
1123+ bracket_end := markused_generic_matching_bracket(clean, bracket)
1124+ if bracket_end <= bracket || bracket_end >= clean.len {
1125+ return '', []string{}, false
1126+ }
1127+ return clean[..bracket], markused_split_generic_args(clean[bracket + 1..bracket_end]), true
1128+}
1129+
1130+fn markused_generic_matching_bracket(s string, start int) int {
1131+ mut depth := 0
1132+ for i in start .. s.len {
1133+ if s[i] == `[` {
1134+ depth++
1135+ } else if s[i] == `]` {
1136+ depth--
1137+ if depth == 0 {
1138+ return i
1139+ }
1140+ }
1141+ }
1142+ return s.len
1143+}
1144+
1145+fn markused_split_generic_args(s string) []string {
1146+ mut args := []string{}
1147+ mut start := 0
1148+ mut bracket_depth := 0
1149+ mut paren_depth := 0
1150+ for i in 0 .. s.len {
1151+ match s[i] {
1152+ `[` {
1153+ bracket_depth++
1154+ }
1155+ `]` {
1156+ bracket_depth--
1157+ }
1158+ `(` {
1159+ paren_depth++
1160+ }
1161+ `)` {
1162+ paren_depth--
1163+ }
1164+ `,` {
1165+ if bracket_depth == 0 && paren_depth == 0 {
1166+ args << s[start..i].trim_space()
1167+ start = i + 1
1168+ }
1169+ }
1170+ else {}
1171+ }
1172+ }
1173+ if start <= s.len {
1174+ args << s[start..].trim_space()
1175+ }
1176+ return args
1177+}
1178+
10461179// enqueue_function_value_selectors supports enqueue function value selectors handling for markused.
10471180fn enqueue_function_value_selectors(a &flat.FlatAst, collector CallCollector, fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) {
10481181 ignored_top_level_nodes := markused_ignored_top_level_nodes(a)
@@ -1360,6 +1493,7 @@ fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports
13601493 if resolved := c.tc.resolved_call_name(child_id) {
13611494 resolved_call = resolved
13621495 }
1496+ c.collect_lowered_join_path_single(child, resolved_call, mut calls)
13631497 if child.children_count > 0 {
13641498 callee_id := c.a.child(child, 0)
13651499 if int(callee_id) >= 0 {
@@ -1368,6 +1502,13 @@ fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports
13681502 if resolved_call.len > 0 {
13691503 calls << resolved_call
13701504 }
1505+ if callee.value in ['print', 'println', 'eprint', 'eprintln']
1506+ && child.children_count >= 2 {
1507+ arg_id := c.a.child(child, 1)
1508+ if c.expr_lowers_to_map_str(arg_id, local_values, local_types) {
1509+ calls << 'string__plus'
1510+ }
1511+ }
13711512 if callee.value !in local_values {
13721513 calls << callee.value
13731514 qcallee := qualify_fn(cur_module, callee.value)
@@ -1383,6 +1524,10 @@ fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports
13831524 base := c.a.nodes[int(base_id)]
13841525 has_exact_selector_call = c.collect_checker_selected_call(resolved_call, mut
13851526 calls)
1527+ if callee.value == 'str'
1528+ && c.expr_lowers_to_map_str(base_id, local_values, local_types) {
1529+ calls << 'string__plus'
1530+ }
13861531 if !has_exact_selector_call {
13871532 has_exact_selector_call = c.collect_typed_receiver_method(base_id,
13881533 callee.value, cur_module, imports, local_values,
@@ -1572,7 +1717,8 @@ fn (c &CallCollector) local_value_type_names(node &flat.Node, cur_module string,
15721717 type_name := if child.children_count == 2 && child.typ.len > 0 {
15731718 child.typ
15741719 } else {
1575- c.top_level_decl_rhs_type_name(rhs_id, cur_module, imports)
1720+ c.top_level_decl_rhs_type_name(rhs_id, cur_module, imports,
1721+ map[string]bool{}, result)
15761722 }
15771723 if type_name.len > 0 {
15781724 result[lhs.value] = type_name
@@ -1711,13 +1857,14 @@ fn markused_mark_visible_local_ident(a &flat.FlatAst, id flat.NodeId, local_valu
17111857 }
17121858}
17131859
1714-fn (c &CallCollector) top_level_decl_rhs_type_name(rhs_id flat.NodeId, cur_module string, imports map[string]string) string {
1860+fn (c &CallCollector) top_level_decl_rhs_type_name(rhs_id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string) string {
17151861 rhs := c.a.node(rhs_id)
17161862 if rhs.kind == .struct_init && rhs.value.len > 0 {
17171863 return c.struct_lookup_name(markused_resolve_imported_type_name(rhs.value, imports),
17181864 cur_module)
17191865 }
1720- type_name := resolve_type_name(c.node_type(rhs_id))
1866+ type_name := c.top_level_expr_type_name(rhs_id, cur_module, imports, local_values, local_types,
1867+ false)
17211868 if type_name.len > 0 {
17221869 struct_type := c.struct_lookup_name(type_name, cur_module)
17231870 if struct_type.len > 0 {
@@ -1807,7 +1954,8 @@ fn (c &CallCollector) collect_top_level_assign_calls(node &flat.Node, cur_module
18071954 lhs := c.a.node(lhs_id)
18081955 if lhs.kind == .ident && lhs.value.len > 0 {
18091956 local_values[lhs.value] = true
1810- type_name := c.top_level_decl_rhs_type_name(rhs_id, cur_module, imports)
1957+ type_name := c.top_level_decl_rhs_type_name(rhs_id, cur_module, imports,
1958+ pre_values, pre_types)
18111959 if type_name.len > 0 {
18121960 local_types[lhs.value] = type_name
18131961 }
@@ -1844,7 +1992,8 @@ fn (c &CallCollector) collect_top_level_global_decl_calls(node &flat.Node, cur_m
18441992 local_values[field.value] = true
18451993 if field.children_count > 0 {
18461994 expr_id := c.a.child(field, 0)
1847- type_name := c.top_level_decl_rhs_type_name(expr_id, cur_module, imports)
1995+ type_name := c.top_level_decl_rhs_type_name(expr_id, cur_module, imports, pre_values,
1996+ pre_types)
18481997 if type_name.len > 0 {
18491998 local_types[field.value] = type_name
18501999 }
@@ -1962,11 +2111,40 @@ fn (c &CallCollector) selector_base_is_local(node &flat.Node, local_values map[s
19622111 return base.kind == .ident && base.value in local_values
19632112}
19642113
2114+fn (c &CallCollector) expr_lowers_to_map_str(id flat.NodeId, local_values map[string]bool, local_types map[string]string) bool {
2115+ if int(id) < 0 {
2116+ return false
2117+ }
2118+ if markused_type_lowers_to_map_str(c.node_type(id)) {
2119+ return true
2120+ }
2121+ expr := c.a.node(id)
2122+ if expr.kind == .ident && expr.value.len > 0 && expr.value in local_values {
2123+ if type_name := local_types[expr.value] {
2124+ return markused_type_name_lowers_to_map_str(type_name, c.tc)
2125+ }
2126+ }
2127+ return false
2128+}
2129+
2130+fn (c &CallCollector) top_level_expr_lowers_to_map_str(id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string) bool {
2131+ if int(id) < 0 {
2132+ return false
2133+ }
2134+ if markused_type_lowers_to_map_str(c.node_type(id)) {
2135+ return true
2136+ }
2137+ type_name := c.top_level_expr_type_name(id, cur_module, imports, local_values, local_types,
2138+ false)
2139+ return type_name.len > 0 && markused_type_name_lowers_to_map_str(type_name, c.tc)
2140+}
2141+
19652142fn (c &CallCollector) collect_top_level_call(call_id flat.NodeId, call &flat.Node, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) {
19662143 mut resolved_call := ''
19672144 if resolved := c.tc.resolved_call_name(call_id) {
19682145 resolved_call = resolved
19692146 }
2147+ c.collect_lowered_join_path_single(call, resolved_call, mut calls)
19702148 if call.children_count == 0 {
19712149 if resolved_call.len > 0 {
19722150 calls << resolved_call
@@ -1982,6 +2160,14 @@ fn (c &CallCollector) collect_top_level_call(call_id flat.NodeId, call &flat.Nod
19822160 if resolved_call.len > 0 {
19832161 calls << resolved_call
19842162 }
2163+ if callee.value in ['print', 'println', 'eprint', 'eprintln'] && call.children_count >= 2 {
2164+ arg_id := c.a.child(call, 1)
2165+ if c.top_level_expr_lowers_to_map_str(arg_id, cur_module, imports, local_values,
2166+ local_types)
2167+ {
2168+ calls << 'string__plus'
2169+ }
2170+ }
19852171 if callee.value !in local_values {
19862172 calls << callee.value
19872173 qcallee := qualify_fn(cur_module, callee.value)
@@ -2016,6 +2202,10 @@ fn (c &CallCollector) collect_top_level_selector_call(callee &flat.Node, method
20162202 if int(base_id) >= 0 {
20172203 base := c.a.node(base_id)
20182204 has_exact_selector_call = c.collect_checker_selected_call(resolved_call, mut calls)
2205+ if method == 'str'
2206+ && c.top_level_expr_lowers_to_map_str(base_id, cur_module, imports, local_values, local_types) {
2207+ calls << 'string__plus'
2208+ }
20192209 if !has_exact_selector_call {
20202210 has_exact_selector_call = c.collect_top_level_typed_receiver_method(base_id,
20212211 method, cur_module, imports, local_values, local_types, mut calls)
@@ -2101,6 +2291,44 @@ fn (c &CallCollector) collect_top_level_selector_fallback(base &flat.Node, metho
21012291 }
21022292}
21032293
2294+fn (c &CallCollector) collect_lowered_join_path_single(call &flat.Node, resolved_call string, mut calls []string) {
2295+ mut call_names := []string{}
2296+ if resolved_call.len > 0 {
2297+ call_names << resolved_call
2298+ }
2299+ if call.children_count > 0 {
2300+ callee_id := c.a.child(call, 0)
2301+ if int(callee_id) >= 0 {
2302+ callee := c.a.node(callee_id)
2303+ if callee.kind == .ident && callee.value.len > 0 {
2304+ call_names << callee.value
2305+ } else if callee.kind == .selector && callee.value.len > 0 && callee.children_count > 0 {
2306+ base_id := c.a.child(callee, 0)
2307+ if int(base_id) >= 0 {
2308+ base := c.a.node(base_id)
2309+ if base.kind == .ident && base.value.len > 0 {
2310+ call_names << '${base.value}.${callee.value}'
2311+ }
2312+ }
2313+ }
2314+ }
2315+ }
2316+ if !call_names.any(it == 'join_path' || it == 'os.join_path') {
2317+ return
2318+ }
2319+ if call.children_count <= 2 {
2320+ return
2321+ }
2322+ for i in 1 .. call.children_count {
2323+ arg := c.a.child_node(call, i)
2324+ if arg.kind == .prefix && arg.value == '...' {
2325+ return
2326+ }
2327+ }
2328+ calls << 'join_path_single'
2329+ calls << 'os.join_path_single'
2330+}
2331+
21042332fn (c &CallCollector) collect_top_level_typed_receiver_method(base_id flat.NodeId, method string, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) bool {
21052333 type_name := c.top_level_receiver_type_name(base_id, cur_module, imports, local_values,
21062334 local_types)
@@ -2133,6 +2361,13 @@ fn (c &CallCollector) top_level_receiver_type_name(base_id flat.NodeId, cur_modu
21332361 return ''
21342362 }
21352363 }
2364+ if base.kind == .or_expr {
2365+ or_type_name := c.top_level_or_expr_base_type_name(base_id, cur_module, imports,
2366+ local_values, local_types)
2367+ if or_type_name.len > 0 {
2368+ return or_type_name
2369+ }
2370+ }
21362371 if base.kind == .ident && base.value.len > 0 {
21372372 return c.value_type_name(base.value, cur_module, imports)
21382373 }
@@ -2145,6 +2380,88 @@ fn (c &CallCollector) top_level_receiver_type_name(base_id flat.NodeId, cur_modu
21452380 return ''
21462381}
21472382
2383+fn (c &CallCollector) top_level_expr_type_name(id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, unwrap_optional_result bool) string {
2384+ typ := c.node_type(id)
2385+ if type_name := markused_type_name(typ, unwrap_optional_result) {
2386+ return type_name
2387+ }
2388+ node := c.a.node(id)
2389+ match node.kind {
2390+ .call {
2391+ return c.top_level_call_return_type_name(id, cur_module, imports, local_values,
2392+ local_types, unwrap_optional_result)
2393+ }
2394+ .or_expr {
2395+ return c.top_level_or_expr_base_type_name(id, cur_module, imports, local_values,
2396+ local_types)
2397+ }
2398+ .ident {
2399+ return local_types[node.value] or { '' }
2400+ }
2401+ else {}
2402+ }
2403+
2404+ return ''
2405+}
2406+
2407+fn (c &CallCollector) top_level_call_return_type_name(call_id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, unwrap_optional_result bool) string {
2408+ call := c.a.node(call_id)
2409+ if call.kind != .call || call.children_count == 0 {
2410+ return ''
2411+ }
2412+ callee_id := c.a.child(call, 0)
2413+ if int(callee_id) < 0 {
2414+ return ''
2415+ }
2416+ callee := c.a.node(callee_id)
2417+ if callee.kind == .selector && callee.value.len > 0 && callee.children_count > 0 {
2418+ base_id := c.a.child(callee, 0)
2419+ base := c.a.node(base_id)
2420+ type_name := c.top_level_receiver_type_name(base_id, cur_module, imports, local_values,
2421+ local_types)
2422+ if type_name.len > 0 {
2423+ if method_name := c.typed_receiver_method_name(type_name, callee.value, cur_module) {
2424+ return c.fn_return_type_name(method_name, unwrap_optional_result)
2425+ }
2426+ }
2427+ if base.kind == .ident && base.value in imports {
2428+ return c.fn_return_type_name('${imports[base.value]}.${callee.value}',
2429+ unwrap_optional_result)
2430+ }
2431+ }
2432+ if callee.kind == .ident && callee.value.len > 0 {
2433+ name := qualify_fn(cur_module, callee.value)
2434+ type_name := c.fn_return_type_name(name, unwrap_optional_result)
2435+ if type_name.len > 0 {
2436+ return type_name
2437+ }
2438+ return c.fn_return_type_name(callee.value, unwrap_optional_result)
2439+ }
2440+ return ''
2441+}
2442+
2443+fn (c &CallCollector) top_level_or_expr_base_type_name(id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string) string {
2444+ node := c.a.node(id)
2445+ if node.kind != .or_expr || node.children_count == 0 {
2446+ return ''
2447+ }
2448+ return c.top_level_expr_type_name(c.a.child(node, 0), cur_module, imports, local_values,
2449+ local_types, true)
2450+}
2451+
2452+fn (c &CallCollector) fn_return_type_name(name string, unwrap_optional_result bool) string {
2453+ if typ := c.tc.fn_ret_types[name] {
2454+ return markused_type_name_or_empty(typ, unwrap_optional_result)
2455+ }
2456+ lowered := markused_c_name(name)
2457+ if lowered != name {
2458+ if typ := c.tc.fn_ret_types[lowered] {
2459+ return markused_type_name_or_empty(typ, unwrap_optional_result)
2460+ }
2461+ }
2462+ return ''
2463+}
2464+
21482465// collect_struct_operator_call updates collect struct operator call state for markused.
21492466fn (c &CallCollector) collect_struct_operator_call(lhs_id flat.NodeId, op flat.Op, cur_module string, mut calls []string) {
21502467 lhs_type := c.node_type(lhs_id)
@@ -3037,6 +3354,32 @@ fn markused_nested_type_name(t types.Type) string {
30373354 return t.name()
30383355}
30393356
3357+fn markused_type_name(t types.Type, unwrap_optional_result bool) ?string {
3358+ if unwrap_optional_result {
3359+ if t is types.OptionType {
3360+ name := resolve_type_name(t.base_type)
3361+ if name.len > 0 {
3362+ return name
3363+ }
3364+ }
3365+ if t is types.ResultType {
3366+ name := resolve_type_name(t.base_type)
3367+ if name.len > 0 {
3368+ return name
3369+ }
3370+ }
3371+ }
3372+ name := resolve_type_name(t)
3373+ if name.len == 0 {
3374+ return none
3375+ }
3376+ return name
3377+}
3378+
3379+fn markused_type_name_or_empty(t types.Type, unwrap_optional_result bool) string {
3380+ return markused_type_name(t, unwrap_optional_result) or { '' }
3381+}
3382+
30403383// markused_c_name converts markused c name data for markused.
30413384fn markused_c_name(name string) string {
30423385 if name.starts_with('C.') {
@@ -625,6 +625,7 @@ fn (mut p Parser) fn_decl() flat.NodeId {
625625 mut name := ''
626626 mut receiver_name := ''
627627 mut receiver_type := ''
628+ mut receiver_is_mut := false
628629 mut is_method := false
629630
630631 // method receiver: fn (mut r Type) name()
@@ -634,6 +635,7 @@ fn (mut p Parser) fn_decl() flat.NodeId {
634635 mut is_mut := false
635636 if p.tok == .key_mut || p.tok == .key_shared {
636637 is_mut = p.tok == .key_mut
638+ receiver_is_mut = is_mut
637639 p.next()
638640 }
639641 receiver_name = p.expect_name()
@@ -648,7 +650,8 @@ fn (mut p Parser) fn_decl() flat.NodeId {
648650 if p.tok.is_overloadable() {
649651 op_name := overload_token_name(p.tok)
650652 p.next()
651- return p.fn_operator_overload(receiver_name, receiver_type, op_name)
653+ return p.fn_operator_overload(receiver_name, receiver_type, receiver_is_mut,
654+ op_name)
652655 }
653656 }
654657 }
@@ -669,7 +672,8 @@ fn (mut p Parser) fn_decl() flat.NodeId {
669672 clean_type := receiver_type.trim_left('&')
670673 name = '${clean_type}.${name}'
671674 }
672- return p.fn_decl_body(name, receiver_name, receiver_type, is_method, true)
675+ return p.fn_decl_body(name, receiver_name, receiver_type, receiver_is_mut,
676+ is_method, true)
673677 }
674678 // module.func or Type.static_method
675679 second := p.expect_name_or_keyword()
@@ -690,10 +694,10 @@ fn (mut p Parser) fn_decl() flat.NodeId {
690694 name = '${clean_type}.${name}'
691695 }
692696
693- return p.fn_decl_body(name, receiver_name, receiver_type, is_method, false)
697+ return p.fn_decl_body(name, receiver_name, receiver_type, receiver_is_mut, is_method, false)
694698}
695699
696-fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type string, op_name string) flat.NodeId {
700+fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type string, receiver_is_mut bool, op_name string) flat.NodeId {
697701 disable_body := p.disable_fn_body
698702 p.disable_fn_body = false
699703 // parse parameter
@@ -701,9 +705,10 @@ fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type strin
701705 mut param_ids := []flat.NodeId{}
702706 // receiver
703707 param_ids << p.a.add_node(flat.Node{
704- kind: .param
705- value: receiver_name
706- typ: receiver_type
708+ kind: .param
709+ value: receiver_name
710+ typ: receiver_type
711+ is_mut: receiver_is_mut
707712 })
708713 // operator param
709714 if p.tok == .key_mut {
@@ -765,7 +770,7 @@ fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type strin
765770 return id
766771}
767772
768-fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type string, is_method bool, is_c_decl bool) flat.NodeId {
773+fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type string, receiver_is_mut bool, is_method bool, is_c_decl bool) flat.NodeId {
769774 // Capture & clear here so it applies only to this function (not nested closures
770775 // or a following declaration), and is cleared even on the extern/no-body path.
771776 disable_body := p.disable_fn_body
@@ -781,9 +786,10 @@ fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type
781786 mut param_ids := []flat.NodeId{}
782787 if is_method && receiver_name.len > 0 {
783788 param_ids << p.a.add_node(flat.Node{
784- kind: .param
785- value: receiver_name
786- typ: receiver_type
789+ kind: .param
790+ value: receiver_name
791+ typ: receiver_type
792+ is_mut: receiver_is_mut
787793 })
788794 }
789795 for p.tok != .rpar && p.tok != .eof {
@@ -905,9 +911,10 @@ fn (mut p Parser) parse_param_group(is_c_decl bool) []flat.NodeId {
905911 if p.tok == .ellipsis {
906912 typ := p.parse_type_name()
907913 ids << p.a.add_node(flat.Node{
908- kind: .param
909- value: ''
910- typ: typ
914+ kind: .param
915+ value: ''
916+ typ: typ
917+ is_mut: is_mut
911918 })
912919 if p.tok == .comma {
913920 p.next()
@@ -920,9 +927,10 @@ fn (mut p Parser) parse_param_group(is_c_decl bool) []flat.NodeId {
920927 typ = '&' + typ
921928 }
922929 ids << p.a.add_node(flat.Node{
923- kind: .param
924- value: ''
925- typ: typ
930+ kind: .param
931+ value: ''
932+ typ: typ
933+ is_mut: is_mut
926934 })
927935 if p.tok == .comma {
928936 p.next()
@@ -947,9 +955,10 @@ fn (mut p Parser) parse_param_group(is_c_decl bool) []flat.NodeId {
947955 }
948956 for name in names {
949957 ids << p.a.add_node(flat.Node{
950- kind: .param
951- value: name
952- typ: typ
958+ kind: .param
959+ value: name
960+ typ: typ
961+ is_mut: is_mut
953962 })
954963 }
955964 if p.tok == .comma {
@@ -2607,8 +2616,10 @@ fn (mut p Parser) for_stmt() flat.NodeId {
26072616 })
26082617 }
26092618
2610- // Check for for-in: `for x in ...` or `for i, x in ...` or `for mut x in ...`
2611- if p.tok == .name && (p.peek() == .key_in || p.peek() == .comma) {
2619+ // Check for for-in: `for x in ...` or `for mut x in ...`.
2620+ // A comma after the first name is ambiguous with C-style multi-init
2621+ // (`for h, t := ...`), so handle it after parsing the first expression.
2622+ if p.tok == .name && p.peek() == .key_in {
26122623 first_expr := p.expr(.bit_or)
26132624 return p.for_in(first_expr, false)
26142625 }
@@ -2625,9 +2636,12 @@ fn (mut p Parser) for_stmt() flat.NodeId {
26252636 } else {
26262637 first_expr = p.expr(.lowest)
26272638 }
2628- if p.tok == .key_in || p.tok == .comma {
2639+ if p.tok == .key_in {
26292640 return p.for_in(first_expr, true)
26302641 }
2642+ if p.tok == .comma {
2643+ return p.for_comma_header(first_expr, true)
2644+ }
26312645 if p.tok == .lcbr {
26322646 body_ids := p.parse_block_body()
26332647 init_empty := p.a.add(flat.NodeKind.empty)
@@ -2681,10 +2695,13 @@ fn (mut p Parser) for_stmt() flat.NodeId {
26812695
26822696 first_expr := p.expr(.lowest)
26832697
2684- // for-in: `for x in expr` or `for i, x in expr`
2685- if p.tok == .key_in || p.tok == .comma {
2698+ // for-in: `for x in expr`
2699+ if p.tok == .key_in {
26862700 return p.for_in(first_expr, false)
26872701 }
2702+ if p.tok == .comma {
2703+ return p.for_comma_header(first_expr, false)
2704+ }
26882705
26892706 // C-style: `for i := 0; ...`
26902707 if p.tok == .decl_assign || token_is_assignment(p.tok) {
@@ -2757,6 +2774,133 @@ fn (mut p Parser) for_stmt() flat.NodeId {
27572774 return flat.empty_node
27582775}
27592776
2777+fn (mut p Parser) for_comma_header(first_expr flat.NodeId, first_is_mut bool) flat.NodeId {
2778+ mut lhs_ids := []flat.NodeId{}
2779+ lhs_ids << first_expr
2780+ mut val_id := flat.empty_node
2781+ mut value_is_mut := first_is_mut
2782+ for p.tok == .comma {
2783+ p.next()
2784+ if p.tok == .key_mut {
2785+ p.next()
2786+ value_is_mut = true
2787+ }
2788+ next_lhs := p.expr(.bit_or)
2789+ lhs_ids << next_lhs
2790+ if lhs_ids.len == 2 {
2791+ val_id = next_lhs
2792+ }
2793+ }
2794+ if p.tok == .key_in {
2795+ if lhs_ids.len != 2 {
2796+ return flat.empty_node
2797+ }
2798+ if !p.for_in_var_is_ident(first_expr) || !p.for_in_var_is_ident(val_id) {
2799+ for_id := p.for_in_parts(first_expr, val_id, value_is_mut)
2800+ return p.invalid_for_in_header(for_id)
2801+ }
2802+ return p.for_in_parts(first_expr, val_id, value_is_mut)
2803+ }
2804+ if p.tok == .decl_assign || token_is_assignment(p.tok) {
2805+ return p.for_c_style_multi(lhs_ids)
2806+ }
2807+ return flat.empty_node
2808+}
2809+
2810+fn (mut p Parser) for_in_var_is_ident(id flat.NodeId) bool {
2811+ if int(id) < 0 {
2812+ return false
2813+ }
2814+ return p.a.nodes[int(id)].kind == .ident
2815+}
2816+
2817+fn (mut p Parser) invalid_for_in_header(for_id flat.NodeId) flat.NodeId {
2818+ lhs := p.a.add(flat.NodeKind.empty)
2819+ rhs := p.a.add(flat.NodeKind.empty)
2820+ marker_start := p.add_children2(lhs, rhs)
2821+ marker_id := p.a.add_node(flat.Node{
2822+ kind: .assign
2823+ value: 'for init assignment mismatch: invalid for-in header: expected identifiers before `in`'
2824+ op: .assign
2825+ children_start: marker_start
2826+ children_count: 2
2827+ })
2828+ block_start := p.add_children2(marker_id, for_id)
2829+ return p.a.add_node(flat.Node{
2830+ kind: .block
2831+ children_start: block_start
2832+ children_count: 2
2833+ })
2834+}
2835+
2836+fn (mut p Parser) for_c_style_multi(lhs_ids []flat.NodeId) flat.NodeId {
2837+ is_decl := p.tok == .decl_assign
2838+ op_id := int(p.tok)
2839+ p.next()
2840+ mut rhs_ids := []flat.NodeId{}
2841+ rhs_ids << p.expr(.lowest)
2842+ for p.tok == .comma {
2843+ p.next()
2844+ rhs_ids << p.expr(.lowest)
2845+ }
2846+ mut all_ids := []flat.NodeId{cap: lhs_ids.len * 2}
2847+ for i in 0 .. lhs_ids.len {
2848+ all_ids << lhs_ids[i]
2849+ if i < rhs_ids.len {
2850+ all_ids << rhs_ids[i]
2851+ }
2852+ }
2853+ arity_msg := if lhs_ids.len == rhs_ids.len {
2854+ ''
2855+ } else {
2856+ 'for init assignment mismatch: ${lhs_ids.len} variables but ${rhs_ids.len} values'
2857+ }
2858+ init_start := p.add_children(all_ids)
2859+ init_id := p.a.add_node(flat.Node{
2860+ kind: if is_decl { flat.NodeKind.decl_assign } else { flat.NodeKind.assign }
2861+ value: arity_msg
2862+ op: if is_decl { .assign } else { token_id_to_op(op_id) }
2863+ children_start: init_start
2864+ children_count: flat.child_count(all_ids.len)
2865+ })
2866+ if p.tok == .semicolon {
2867+ p.next()
2868+ }
2869+ cond := if p.tok == .semicolon {
2870+ p.a.add(flat.NodeKind.empty)
2871+ } else {
2872+ p.expr(.lowest)
2873+ }
2874+ if p.tok == .semicolon {
2875+ p.next()
2876+ }
2877+ post := if p.tok != .lcbr && p.tok != .eof {
2878+ p.assign_or_expr_inline()
2879+ } else {
2880+ p.a.add(flat.NodeKind.empty)
2881+ }
2882+ body_ids := p.parse_block_body()
2883+ mut for_ids := []flat.NodeId{}
2884+ for_ids << p.a.add(flat.NodeKind.empty)
2885+ for_ids << cond
2886+ for_ids << post
2887+ for id in body_ids {
2888+ for_ids << id
2889+ }
2890+ for_start := p.add_children(for_ids)
2891+ for_id := p.a.add_node(flat.Node{
2892+ kind: .for_stmt
2893+ children_start: for_start
2894+ children_count: flat.child_count(for_ids.len)
2895+ })
2896+ block_start := p.add_children2(init_id, for_id)
2897+ return p.a.add_node(flat.Node{
2898+ kind: .block
2899+ children_start: block_start
2900+ children_count: 2
2901+ })
2902+}
2903+
27602904fn (mut p Parser) for_c_style(lhs_expr flat.NodeId) flat.NodeId {
27612905 op_id := int(p.tok)
27622906 p.next()
@@ -2829,6 +2973,10 @@ fn (mut p Parser) for_in(first_expr flat.NodeId, first_is_mut bool) flat.NodeId
28292973 val_id = p.a.add_val(.ident, p.expect_name())
28302974 }
28312975
2976+ return p.for_in_parts(key_id, val_id, value_is_mut)
2977+}
2978+
2979+fn (mut p Parser) for_in_parts(key_id flat.NodeId, val_id flat.NodeId, value_is_mut bool) flat.NodeId {
28322980 p.check(.key_in)
28332981 was_in_for_container := p.in_for_container
28342982 p.in_for_container = true
@@ -2906,16 +3054,8 @@ fn (mut p Parser) match_branch_cond() flat.NodeId {
29063054 p.next()
29073055 p.next()
29083056 if p.tok == .name && p.lit.len > 0 && p.lit[0] >= `A` && p.lit[0] <= `Z` {
2909- type_name := p.lit
2910- p.next()
2911- mod_id := p.a.add_val(.ident, mod_name)
2912- start := p.add_child(mod_id)
2913- return p.a.add_node(flat.Node{
2914- kind: .selector
2915- value: type_name
2916- children_start: start
2917- children_count: 1
2918- })
3057+ type_name := mod_name + '.' + p.parse_type_name()
3058+ return p.match_type_pattern_node(type_name)
29193059 }
29203060 base_id := p.a.add_val(.ident, mod_name)
29213061 start := p.add_child(base_id)
@@ -2934,9 +3074,8 @@ fn (mut p Parser) match_branch_cond() flat.NodeId {
29343074 return sel
29353075 }
29363076 if p.tok == .name && p.lit.len > 0 && p.lit[0] >= `A` && p.lit[0] <= `Z` {
2937- name := p.lit
2938- p.next()
2939- return p.a.add_val(.ident, name)
3077+ name := p.parse_type_name()
3078+ return p.match_type_pattern_node(name)
29403079 }
29413080 if p.tok == .name && is_builtin_type(p.lit) && p.peek() == .lcbr {
29423081 name := p.lit
@@ -2957,6 +3096,45 @@ fn (mut p Parser) match_branch_cond() flat.NodeId {
29573096 return cond
29583097}
29593098
3099+fn (mut p Parser) match_type_pattern_node(type_name string) flat.NodeId {
3100+ if dot := top_level_dot_index(type_name) {
3101+ base := type_name[..dot]
3102+ name := type_name[dot + 1..]
3103+ base_id := p.a.add_val(.ident, base)
3104+ start := p.add_child(base_id)
3105+ return p.a.add_node(flat.Node{
3106+ kind: .selector
3107+ value: name
3108+ children_start: start
3109+ children_count: 1
3110+ })
3111+ }
3112+ return p.a.add_val(.ident, type_name)
3113+}
3114+
3115+fn top_level_dot_index(s string) ?int {
3116+ mut depth := 0
3117+ for i := 0; i < s.len; i++ {
3118+ match s[i] {
3119+ `[`, `(` {
3120+ depth++
3121+ }
3122+ `]`, `)` {
3123+ if depth > 0 {
3124+ depth--
3125+ }
3126+ }
3127+ `.` {
3128+ if depth == 0 {
3129+ return i
3130+ }
3131+ }
3132+ else {}
3133+ }
3134+ }
3135+ return none
3136+}
3137+
29603138fn (mut p Parser) match_branch() flat.NodeId {
29613139 mut branch_ids := []flat.NodeId{}
29623140 mut is_else := false
@@ -1,13 +1,15 @@
11#!/usr/bin/env -S v
22
33import os
4+import time
45
5-const total_steps = 6
6+const total_steps = 7
67
78struct Config {
89 vexe string
910 script_dir string
1011 repo_root string
12+ vlib_dir string
1113 tests_dir string
1214 v3_src string
1315 c99 bool
@@ -17,7 +19,28 @@ struct Config {
1719 temp_prefix string
1820}
1921
22+struct ExampleCase {
23+ path string
24+ args []string
25+ stdin string
26+ compile_flags []string
27+ mode ExampleRunMode
28+ timeout_seconds int
29+}
30+
31+struct ProcessRunResult {
32+ exit_code int
33+ output string
34+ timed_out bool
35+}
36+
37+enum ExampleRunMode {
38+ normal
39+ gui_smoke
40+}
41+
2042fn main() {
43+ self_check_gui_smoke_timeout_status()
2144 cfg := parse_config()
2245 os.chdir(cfg.repo_root) or { fail('failed to enter ${cfg.repo_root}: ${err}') }
2346
@@ -46,10 +69,10 @@ fn main() {
4669 ])
4770
4871 section(1, 'V3 unit tests')
49- run('${q(cfg.vexe)} -silent test ${q(cfg.script_dir)}')
72+ run('${host_v_cmd(cfg)} -silent test ${q(cfg.script_dir)}')
5073
5174 section(2, 'Build v3')
52- run('${q(cfg.vexe)} -o ${q(v3_bin)} ${q(cfg.v3_src)}')
75+ run('${host_v_cmd(cfg)} -o ${q(v3_bin)} ${q(cfg.v3_src)}')
5376
5477 section(3, 'C backend hello world')
5578 hello_v := os.join_path(cfg.tests_dir, 'hello.v')
@@ -57,7 +80,10 @@ fn main() {
5780 run(q(hello_c_bin))
5881 cleanup_files([hello_c_bin, hello_c_bin + '.c'])
5982
60- section(4, 'ARM64 self-host hello world')
83+ section(4, 'Unlocked examples C oracle')
84+ run_unlocked_examples(cfg, v3_bin)
85+
86+ section(5, 'ARM64 self-host hello world')
6187 if cfg.c99 {
6288 println(' Skipping ARM64 self-host in C99 mode (-c99 applies to the C backend)')
6389 } else if cfg.host_backend == 'arm64' && cfg.host_os == 'macos' {
@@ -69,7 +95,7 @@ fn main() {
6995 println(' Skipping ARM64 self-host on ${cfg.host_os}/${cfg.host_backend} host (Mach-O only)')
7096 }
7197
72- section(5, 'Self-host chain (v3->v4->v5->v6)')
98+ section(6, 'Self-host chain (v3->v4->v5->v6)')
7399 println(' Building v4 from v3...')
74100 run('${q(v3_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v4_bin)} ${q(cfg.v3_src)}')
75101 println(' Building v5 from v4...')
@@ -81,7 +107,7 @@ fn main() {
81107 println(' v5.c=v6.c (${converged_size} bytes) - chain converged')
82108 cleanup_files([v4_bin, v4_bin + '.c', v5_bin, v5_bin + '.c', v6_bin, v6_bin + '.c'])
83109
84- section(6, 'Language feature parity')
110+ section(7, 'Language feature parity')
85111 lang_v := os.join_path(cfg.tests_dir, 'test_all_lang_features.v')
86112 lang_out := os.join_path(cfg.tests_dir, 'test_all_lang_features.out')
87113 run('${q(v3_bin)} ${cfg.c99_flag} ${q(lang_v)} -b c -o ${q(v3_lang_bin)}')
@@ -89,7 +115,7 @@ fn main() {
89115 expected_out := read_text_file(lang_out)
90116 assert_same_text('language feature output', v3_c_out, expected_out)
91117 println(' v3 C OK (${v3_c_out.split_into_lines().len} lines)')
92- println(' ARM64 coverage is the one-generation macOS self-host smoke test in step 4')
118+ println(' ARM64 coverage is the one-generation macOS self-host smoke test in the ARM64 step')
93119 cleanup_files([v3_bin, v3_lang_bin, v3_lang_bin + '.c'])
94120
95121 println('')
@@ -109,6 +135,7 @@ fn parse_config() Config {
109135 vexe: vexe
110136 script_dir: script_dir
111137 repo_root: repo_root
138+ vlib_dir: os.join_path(repo_root, 'vlib')
112139 tests_dir: tests_dir
113140 v3_src: os.join_path(script_dir, 'v3.v')
114141 c99: c99
@@ -138,6 +165,10 @@ fn parse_args() bool {
138165 return c99
139166}
140167
168+fn host_v_cmd(cfg Config) string {
169+ return '${q(cfg.vexe)} -path ${q(cfg.vlib_dir)}'
170+}
171+
141172fn native_backend_arch() string {
142173 machine := os.uname().machine.to_lower()
143174 match machine {
@@ -192,6 +223,251 @@ fn run_output(cfg Config, cmd string) string {
192223 return content
193224}
194225
226+fn unlocked_examples() []ExampleCase {
227+ return [
228+ example('examples/dump_factorial.v'),
229+ example_args('examples/fibonacci.v', ['10']),
230+ example('examples/fizz_buzz.v'),
231+ example('examples/function_types.v'),
232+ example_stdin('examples/get_raw_line.v', 'alpha\nbeta\n'),
233+ example('examples/graphs/bellman-ford.v'),
234+ example('examples/graphs/bfs.v'),
235+ example('examples/graphs/bfs3.v'),
236+ example('examples/graphs/dfs.v'),
237+ example('examples/graphs/dfs2.v'),
238+ example('examples/graphs/dijkstra.v'),
239+ example('examples/graphs/minimal_spann_tree_prim.v'),
240+ example('examples/graphs/topological_sorting_dfs.v'),
241+ example('examples/graphs/topological_sorting_greedy.v'),
242+ example('examples/hanoi.v'),
243+ example('examples/hello_world.v'),
244+ example('examples/js_hello_world.v'),
245+ example_stdin('examples/mini_calculator.v', '2*(5-1)\nexit\n'),
246+ example_stdin('examples/mini_calculator_recursive_descent.v', '2 * (5-1)\nexit\n'),
247+ example_args('examples/primes.v', ['10']),
248+ example('examples/quick_sort.v'),
249+ example('examples/random_ips.v'),
250+ example_args('examples/rule110.v', ['5']),
251+ example('examples/rune.v'),
252+ example_args('examples/spectral.v', ['10']),
253+ example('examples/submodule/main.v'),
254+ example('examples/sudoku.v'),
255+ example('examples/tree_of_nodes.v'),
256+ example('examples/vascii.v'),
257+ example('examples/binary_search_tree.v'),
258+ example('examples/custom_error.v'),
259+ example('examples/errors.v'),
260+ example_gui('examples/2048/2048.v', 5),
261+ example_gui('examples/tetris/tetris.v', 5),
262+ example_flags('vlib/v/tests/options/option_test.c.v', ['-autofree']),
263+ ]
264+}
265+
266+fn example(path string) ExampleCase {
267+ return ExampleCase{
268+ path: path
269+ }
270+}
271+
272+fn example_args(path string, args []string) ExampleCase {
273+ return ExampleCase{
274+ path: path
275+ args: args
276+ }
277+}
278+
279+fn example_stdin(path string, stdin string) ExampleCase {
280+ return ExampleCase{
281+ path: path
282+ stdin: stdin
283+ }
284+}
285+
286+fn example_flags(path string, flags []string) ExampleCase {
287+ return ExampleCase{
288+ path: path
289+ compile_flags: flags
290+ }
291+}
292+
293+fn example_gui(path string, timeout_seconds int) ExampleCase {
294+ return ExampleCase{
295+ path: path
296+ mode: .gui_smoke
297+ timeout_seconds: timeout_seconds
298+ }
299+}
300+
301+fn run_unlocked_examples(cfg Config, v3_bin string) {
302+ examples := unlocked_examples()
303+ mut ran := 0
304+ for i, example_case in examples {
305+ if run_unlocked_example(cfg, v3_bin, example_case, i) {
306+ ran++
307+ }
308+ }
309+ println(' ${ran}/${examples.len} real C oracle cases compiled and ran/smoked through V3 C')
310+}
311+
312+fn run_unlocked_example(cfg Config, v3_bin string, example_case ExampleCase, index int) bool {
313+ src := os.join_path(cfg.repo_root, example_case.path)
314+ bin := temp_path(cfg, 'example_${index}')
315+ stdin_path := temp_path(cfg, 'example_${index}_stdin')
316+ cleanup_files([bin, bin + '.c', stdin_path])
317+ mut compile_cmd := q(v3_bin)
318+ if cfg.c99_flag.len > 0 {
319+ compile_cmd += ' ' + cfg.c99_flag
320+ }
321+ if example_case.compile_flags.len > 0 {
322+ compile_cmd += ' ' + quote_args(example_case.compile_flags)
323+ }
324+ compile_cmd += ' ${q(src)} -b c -o ${q(bin)}'
325+ compile := os.execute(compile_cmd)
326+ if compile.exit_code != 0 {
327+ cleanup_files([bin, bin + '.c', stdin_path])
328+ print_command_failure('compile ${example_case.path}', compile_cmd, compile.output)
329+ }
330+ if example_case.mode == .gui_smoke {
331+ ran := run_gui_smoke_example(example_case, bin, stdin_path)
332+ cleanup_files([bin, bin + '.c', stdin_path])
333+ if ran {
334+ println(' OK ${example_case.path}')
335+ }
336+ return ran
337+ }
338+ mut run_cmd := q(bin)
339+ if example_case.args.len > 0 {
340+ run_cmd += ' ' + quote_args(example_case.args)
341+ }
342+ if example_case.stdin.len > 0 {
343+ os.write_file(stdin_path, example_case.stdin) or {
344+ cleanup_files([bin, bin + '.c', stdin_path])
345+ fail('FAIL: failed to write stdin for ${example_case.path}: ${err}')
346+ }
347+ run_cmd += ' < ${q(stdin_path)}'
348+ }
349+ run_result := os.execute(run_cmd)
350+ if run_result.exit_code != 0 {
351+ cleanup_files([bin, bin + '.c', stdin_path])
352+ print_command_failure('run ${example_case.path}', run_cmd, run_result.output)
353+ }
354+ cleanup_files([bin, bin + '.c', stdin_path])
355+ println(' OK ${example_case.path}')
356+ return true
357+}
358+
359+fn run_gui_smoke_example(example_case ExampleCase, bin string, stdin_path string) bool {
360+ if example_case.args.len > 0 || example_case.stdin.len > 0 {
361+ cleanup_files([bin, bin + '.c', stdin_path])
362+ fail('FAIL: GUI smoke case ${example_case.path} cannot use args/stdin')
363+ }
364+ if example_case.timeout_seconds <= 0 {
365+ cleanup_files([bin, bin + '.c', stdin_path])
366+ fail('FAIL: GUI smoke case ${example_case.path} needs a positive timeout')
367+ }
368+ mut command := bin
369+ mut args := []string{}
370+ if os.getenv('DISPLAY').len == 0 && os.getenv('WAYLAND_DISPLAY').len == 0 {
371+ if shell_command_exists('xvfb-run') {
372+ command = 'xvfb-run'
373+ args = ['-a', bin]
374+ } else {
375+ cleanup_files([bin, bin + '.c', stdin_path])
376+ println(' SKIP ${example_case.path} (requires `xvfb-run` or an active display)')
377+ return false
378+ }
379+ }
380+ run_cmd := command_with_args(command, args)
381+ run_result := run_process_with_timeout(command, args, example_case.timeout_seconds)
382+ if !gui_smoke_run_succeeded(run_result) {
383+ cleanup_files([bin, bin + '.c', stdin_path])
384+ print_command_failure('run ${example_case.path}', run_cmd, run_result.output)
385+ }
386+ return true
387+}
388+
389+fn gui_smoke_run_succeeded(result ProcessRunResult) bool {
390+ return result.exit_code == 0 || result.timed_out
391+}
392+
393+fn self_check_gui_smoke_timeout_status() {
394+ assert gui_smoke_run_succeeded(ProcessRunResult{
395+ exit_code: 0
396+ })
397+ assert gui_smoke_run_succeeded(ProcessRunResult{
398+ exit_code: 124
399+ timed_out: true
400+ })
401+ assert !gui_smoke_run_succeeded(ProcessRunResult{
402+ exit_code: 124
403+ })
404+ assert !gui_smoke_run_succeeded(ProcessRunResult{
405+ exit_code: 1
406+ })
407+}
408+
409+fn shell_command_exists(name string) bool {
410+ return os.execute('command -v ${q(name)} >/dev/null 2>&1').exit_code == 0
411+}
412+
413+fn run_process_with_timeout(command string, args []string, seconds int) ProcessRunResult {
414+ mut process := os.new_process(command)
415+ if args.len > 0 {
416+ process.set_args(args)
417+ }
418+ process.set_redirect_stdio()
419+ process.run()
420+ mut elapsed_ms := 0
421+ limit_ms := seconds * 1000
422+ for process.is_alive() {
423+ if elapsed_ms >= limit_ms {
424+ process.signal_kill()
425+ process.wait()
426+ output := process.stdout_slurp() + process.stderr_slurp()
427+ process.close()
428+ return ProcessRunResult{
429+ exit_code: 124
430+ output: output
431+ timed_out: true
432+ }
433+ }
434+ time.sleep(50 * time.millisecond)
435+ elapsed_ms += 50
436+ }
437+ process.wait()
438+ output := process.stdout_slurp() + process.stderr_slurp()
439+ exit_code := process.code
440+ process.close()
441+ return ProcessRunResult{
442+ exit_code: exit_code
443+ output: output
444+ }
445+}
446+
447+fn command_with_args(command string, args []string) string {
448+ if args.len == 0 {
449+ return q(command)
450+ }
451+ return q(command) + ' ' + quote_args(args)
452+}
453+
454+fn quote_args(args []string) string {
455+ mut quoted := []string{cap: args.len}
456+ for arg in args {
457+ quoted << q(arg)
458+ }
459+ return quoted.join(' ')
460+}
461+
462+fn print_command_failure(label string, cmd string, output string) {
463+ eprintln('FAIL: ${label}')
464+ eprintln('> ${cmd}')
465+ if output.len > 0 {
466+ eprintln(output)
467+ }
468+ exit(1)
469+}
470+
195471fn read_text_file(path string) string {
196472 content := os.read_file(path) or {
197473 fail('FAIL: failed to read ${path}: ${err}')
@@ -0,0 +1,50 @@
1+import os
2+
3+const builtin_const_imported_vexe = @VEXE
4+const builtin_const_imported_tests_dir = os.dir(@FILE)
5+const builtin_const_imported_v3_dir = os.dir(builtin_const_imported_tests_dir)
6+const builtin_const_imported_vlib_dir = os.dir(builtin_const_imported_v3_dir)
7+const builtin_const_imported_v3_src = os.join_path(builtin_const_imported_v3_dir, 'v3.v')
8+
9+fn builtin_const_imported_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_builtin_const_imported_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${builtin_const_imported_vexe} -gc none -path "${builtin_const_imported_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${builtin_const_imported_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_builtin_const_ref_from_imported_module_is_qualified() {
19+ v3_bin := builtin_const_imported_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_builtin_const_imported_input_${os.getpid()}.v')
21+ os.write_file(src, "import strconv
22+
23+fn main() {
24+ n, err := strconv.common_parse_uint2('18446744073709551615', 10, 64)
25+ assert err == 0
26+ assert n == max_u64
27+ println('ok')
28+}
29+") or {
30+ panic(err)
31+ }
32+
33+ bin := os.join_path(os.temp_dir(), 'v3_builtin_const_imported_input_${os.getpid()}')
34+ os.rm(bin) or {}
35+ os.rm(bin + '.c') or {}
36+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
37+ assert compile.exit_code == 0, compile.output
38+ assert !compile.output.contains('C compilation failed'), compile.output
39+
40+ run := os.execute(bin)
41+ assert run.exit_code == 0, run.output
42+ assert run.output.trim_space() == 'ok'
43+
44+ c_code := os.read_file(bin + '.c') or { panic(err) }
45+ assert c_code.contains('#define builtin__max_u64'), c_code
46+ assert c_code.contains('builtin__max_u64 / (u64)(base)'), c_code
47+ assert c_code.contains('__if_val_') && c_code.contains('builtin__max_u64;'), c_code
48+ assert !c_code.contains('(max_u64 /'), c_code
49+ assert !c_code.contains('= max_u64;'), c_code
50+}
@@ -0,0 +1,60 @@
1+import os
2+
3+const builtin_const_collision_vexe = @VEXE
4+const builtin_const_collision_tests_dir = os.dir(@FILE)
5+const builtin_const_collision_v3_dir = os.dir(builtin_const_collision_tests_dir)
6+const builtin_const_collision_vlib_dir = os.dir(builtin_const_collision_v3_dir)
7+const builtin_const_collision_v3_src = os.join_path(builtin_const_collision_v3_dir, 'v3.v')
8+
9+fn builtin_const_collision_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_builtin_const_collision_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${builtin_const_collision_vexe} -gc none -path "${builtin_const_collision_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${builtin_const_collision_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_builtin_const_macros_do_not_collide_with_locals() {
19+ v3_bin := builtin_const_collision_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_builtin_const_collision_input_${os.getpid()}.v')
21+ os.write_file(src, "fn make_degree() map[string]int {
22+ mut degree := map[string]int{}
23+ degree['A'] = 1
24+ return degree
25+}
26+
27+fn local_hashbits() int {
28+ mut hashbits := 5
29+ hashbits = hashbits + 1
30+ return hashbits
31+}
32+
33+fn main() {
34+ result := make_degree()
35+ assert result['A'] == 1
36+ assert local_hashbits() == 6
37+ println('ok')
38+}
39+") or {
40+ panic(err)
41+ }
42+ bin := os.join_path(os.temp_dir(), 'v3_builtin_const_collision_input_${os.getpid()}')
43+ os.rm(bin) or {}
44+ os.rm(bin + '.c') or {}
45+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
46+ assert compile.exit_code == 0, compile.output
47+ assert !compile.output.contains('C compilation failed'), compile.output
48+
49+ run := os.execute(bin)
50+ assert run.exit_code == 0, run.output
51+ assert run.output.trim_space() == 'ok'
52+
53+ c_code := os.read_file(bin + '.c') or { panic(err) }
54+ assert !c_code.contains('#define degree ('), c_code
55+ assert !c_code.contains('#define hashbits ('), c_code
56+ assert c_code.contains('#define builtin__degree ('), c_code
57+ assert c_code.contains('#define builtin__hashbits ('), c_code
58+ assert c_code.contains('map degree ='), c_code
59+ assert c_code.contains('int hashbits = 5;'), c_code
60+}
@@ -0,0 +1,77 @@
1+import os
2+import v3.parser
3+import v3.pref
4+import v3.types
5+
6+fn check_diagnostic_project(name string, files map[string]string, diagnostic_rels []string) []types.TypeError {
7+ root := os.join_path(os.temp_dir(), 'v3_checker_diagnostic_files_${name}_${os.getpid()}')
8+ if os.exists(root) {
9+ os.rmdir_all(root) or { panic(err) }
10+ }
11+ os.mkdir_all(root) or { panic(err) }
12+ mut paths := []string{}
13+ mut rels := files.keys()
14+ rels.sort()
15+ for rel in rels {
16+ path := os.join_path(root, rel)
17+ os.mkdir_all(os.dir(path)) or { panic(err) }
18+ os.write_file(path, files[rel]) or { panic(err) }
19+ paths << path
20+ }
21+ builtin_path := os.join_path(root, 'builtin/ierror.v')
22+ os.mkdir_all(os.dir(builtin_path)) or { panic(err) }
23+ os.write_file(builtin_path, 'module builtin
24+
25+pub interface IError {
26+ msg() string
27+ code() int
28+}
29+') or {
30+ panic(err)
31+ }
32+ prefs := pref.new_preferences()
33+ mut p := parser.Parser.new(prefs)
34+ p.parse_file(builtin_path)
35+ p.a.user_code_start = p.a.nodes.len
36+ mut a := p.parse_files(paths)
37+ mut tc := types.TypeChecker.new(a)
38+ tc.collect(a)
39+ tc.diagnose_unknown_calls = true
40+ for rel in diagnostic_rels {
41+ tc.diagnostic_files[os.join_path(root, rel)] = true
42+ }
43+ tc.check_semantics()
44+ return tc.errors.clone()
45+}
46+
47+const invalid_ierror_return_project = {
48+ 'bad/bad.v': 'module bad
49+
50+pub struct NotError {}
51+
52+pub fn fail() !int {
53+ return NotError{}
54+}
55+'
56+ 'main.v': 'module main
57+
58+import bad
59+
60+fn main() {
61+ _ := 1
62+}
63+'
64+}
65+
66+fn test_invalid_ierror_result_return_respects_diagnostic_files() {
67+ bad_errors := check_diagnostic_project('ierror_return_bad', invalid_ierror_return_project, [
68+ 'bad/bad.v',
69+ ])
70+ assert bad_errors.len == 1, bad_errors.str()
71+ assert bad_errors[0].msg.contains('bad.NotError')
72+ assert bad_errors[0].msg.contains('as `int`')
73+
74+ main_errors := check_diagnostic_project('ierror_return_filtered',
75+ invalid_ierror_return_project, ['main.v'])
76+ assert main_errors.len == 0, main_errors.str()
77+}
@@ -0,0 +1,59 @@
1+import os
2+
3+const filelock_helpers_vexe = @VEXE
4+const filelock_helpers_tests_dir = os.dir(@FILE)
5+const filelock_helpers_v3_dir = os.dir(filelock_helpers_tests_dir)
6+const filelock_helpers_vlib_dir = os.dir(filelock_helpers_v3_dir)
7+const filelock_helpers_v3_src = os.join_path(filelock_helpers_v3_dir, 'v3.v')
8+
9+fn filelock_helpers_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_filelock_helpers_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${filelock_helpers_vexe} -gc none -path "${filelock_helpers_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${filelock_helpers_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_direct_filelock_c_helpers_emit_inline_runtime_defs() {
19+ v3_bin := filelock_helpers_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_filelock_helpers_input_${os.getpid()}.v')
21+ os.write_file(src, 'module main
22+
23+fn C.v_filelock_lock(i32, i32, i32, u64, u64) int
24+fn C.v_filelock_unlock(i32, u64, u64) int
25+
26+fn filelock_score() int {
27+ lock_result := C.v_filelock_lock(i32(-1), 1, 1, u64(0), u64(0))
28+ unlock_result := C.v_filelock_unlock(i32(-1), u64(0), u64(0))
29+ if lock_result != 0 && unlock_result != 0 {
30+ return 47
31+ }
32+ return 0
33+}
34+
35+fn main() {
36+ println(filelock_score())
37+}
38+ ') or {
39+ panic(err)
40+ }
41+
42+ bin := os.join_path(os.temp_dir(), 'v3_filelock_helpers_input_${os.getpid()}')
43+ os.rm(bin) or {}
44+ os.rm(bin + '.c') or {}
45+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
46+ assert compile.exit_code == 0, compile.output
47+ assert !compile.output.contains('C compilation failed'), compile.output
48+
49+ c_code := os.read_file(bin + '.c') or { panic(err) }
50+ assert !c_code.contains('filelock_helpers.h'), c_code
51+ assert c_code.contains('\nint v_filelock_lock('), c_code
52+ assert c_code.contains('\nint v_filelock_unlock('), c_code
53+ assert !c_code.contains('\ni32 v_filelock_lock('), c_code
54+ assert !c_code.contains('\ni32 v_filelock_unlock('), c_code
55+
56+ run := os.execute(bin)
57+ assert run.exit_code == 0, run.output
58+ assert run.output.trim_space() == '47'
59+}
@@ -0,0 +1,209 @@
1+import os
2+
3+const for_multi_init_vexe = @VEXE
4+const for_multi_init_tests_dir = os.dir(@FILE)
5+const for_multi_init_v3_dir = os.dir(for_multi_init_tests_dir)
6+const for_multi_init_vlib_dir = os.dir(for_multi_init_v3_dir)
7+const for_multi_init_v3_src = os.join_path(for_multi_init_v3_dir, 'v3.v')
8+
9+fn for_multi_init_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_for_multi_init_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${for_multi_init_vexe} -gc none -path "${for_multi_init_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${for_multi_init_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn for_multi_init_run_bad(v3_bin string, name string, source string, expected string) {
19+ src := os.join_path(os.temp_dir(), 'v3_for_multi_init_${name}_${os.getpid()}.v')
20+ os.write_file(src, source) or { panic(err) }
21+ bin := os.join_path(os.temp_dir(), 'v3_for_multi_init_${name}_${os.getpid()}')
22+ os.rm(bin) or {}
23+ os.rm(bin + '.c') or {}
24+ result := os.execute('${v3_bin} ${src} -b c -o ${bin}')
25+ assert result.exit_code != 0, result.output
26+ assert result.output.contains(expected), result.output
27+ assert !result.output.contains('C compilation failed'), result.output
28+}
29+
30+fn test_c_style_for_multi_init_does_not_swallow_following_fn() {
31+ v3_bin := for_multi_init_build_v3()
32+ src := os.join_path(os.temp_dir(), 'v3_for_multi_init_input_${os.getpid()}.v')
33+ os.write_file(src, "struct Iter {
34+ label string
35+}
36+
37+fn scan_empty_post() int {
38+ mut total := 0
39+ for h, t := 0, 3; h <= t; {
40+ total += h + t
41+ h += 1
42+ t -= 1
43+ }
44+ return total
45+}
46+
47+fn following_iterator() Iter {
48+ return Iter{
49+ label: 'ok'
50+ }
51+}
52+
53+fn scan_with_post() int {
54+ mut total := 0
55+ for h, t := 0, 3; h <= t; h += 1 {
56+ total += h + t
57+ t -= 1
58+ }
59+ return total
60+}
61+
62+fn indexed_for_in_score() int {
63+ xs := [2, 4, 6]
64+ mut total := 0
65+ for i, v in xs {
66+ total += i * v
67+ }
68+ return total
69+}
70+
71+fn main() {
72+ assert scan_empty_post() == 6
73+ assert following_iterator().label == 'ok'
74+ assert scan_with_post() == 6
75+ assert indexed_for_in_score() == 16
76+ println('ok')
77+}
78+") or {
79+ panic(err)
80+ }
81+
82+ bin := os.join_path(os.temp_dir(), 'v3_for_multi_init_input_${os.getpid()}')
83+ os.rm(bin) or {}
84+ os.rm(bin + '.c') or {}
85+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
86+ assert compile.exit_code == 0, compile.output
87+ assert !compile.output.contains('C compilation failed'), compile.output
88+
89+ run := os.execute(bin)
90+ assert run.exit_code == 0, run.output
91+ assert run.output.trim_space() == 'ok'
92+}
93+
94+fn test_c_style_for_multi_init_rejects_extra_rhs() {
95+ v3_bin := for_multi_init_build_v3()
96+ for_multi_init_run_bad(v3_bin, 'extra_rhs', 'fn bump(n int) int {
97+ println(n.str())
98+ return n
99+}
100+
101+fn main() {
102+ for a, b := bump(1), bump(2), bump(3); false; {
103+ println(a)
104+ println(b)
105+ }
106+}
107+',
108+ 'for init assignment mismatch: 2 variables but 3 values')
109+}
110+
111+fn test_c_style_for_multi_init_allows_multi_return_call() {
112+ v3_bin := for_multi_init_build_v3()
113+ src := os.join_path(os.temp_dir(), 'v3_for_multi_init_pair_${os.getpid()}.v')
114+ os.write_file(src, 'fn pair() (int, int) {
115+ return 1, 2
116+}
117+
118+fn main() {
119+ mut total := 0
120+ for a, b := pair(); a == 1; {
121+ total = a + b
122+ break
123+ }
124+ assert total == 3
125+ println("ok")
126+}
127+') or {
128+ panic(err)
129+ }
130+
131+ bin := os.join_path(os.temp_dir(), 'v3_for_multi_init_pair_${os.getpid()}')
132+ os.rm(bin) or {}
133+ os.rm(bin + '.c') or {}
134+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
135+ assert compile.exit_code == 0, compile.output
136+ assert !compile.output.contains('C compilation failed'), compile.output
137+
138+ run := os.execute(bin)
139+ assert run.exit_code == 0, run.output
140+ assert run.output.trim_space() == 'ok'
141+}
142+
143+fn test_c_style_for_multi_init_allows_selector_lhs_after_comma() {
144+ v3_bin := for_multi_init_build_v3()
145+ src := os.join_path(os.temp_dir(), 'v3_for_multi_init_selector_lhs_${os.getpid()}.v')
146+ os.write_file(src, 'struct Pair {
147+mut:
148+ x int
149+ y int
150+}
151+
152+fn main() {
153+ mut a := Pair{}
154+ mut total := 0
155+ for a.x, a.y = 0, 1; a.x < 3; {
156+ total += a.x + a.y
157+ a.x += 1
158+ a.y += 2
159+ }
160+ assert total == 12
161+ assert a.x == 3
162+ assert a.y == 7
163+ println("ok")
164+}
165+') or {
166+ panic(err)
167+ }
168+
169+ bin := os.join_path(os.temp_dir(), 'v3_for_multi_init_selector_lhs_${os.getpid()}')
170+ os.rm(bin) or {}
171+ os.rm(bin + '.c') or {}
172+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
173+ assert compile.exit_code == 0, compile.output
174+ assert !compile.output.contains('C compilation failed'), compile.output
175+
176+ run := os.execute(bin)
177+ assert run.exit_code == 0, run.output
178+ assert run.output.trim_space() == 'ok'
179+}
180+
181+fn test_c_style_for_multi_init_rejects_missing_rhs() {
182+ v3_bin := for_multi_init_build_v3()
183+ for_multi_init_run_bad(v3_bin, 'missing_rhs', 'fn main() {
184+ for a, b := 1; false; {
185+ println(a)
186+ println(b)
187+ }
188+}
189+',
190+ 'for init assignment mismatch: 2 variables but 1 values')
191+}
192+
193+fn test_for_in_rejects_selector_value_var_after_comma() {
194+ v3_bin := for_multi_init_build_v3()
195+ for_multi_init_run_bad(v3_bin, 'for_in_selector_value', 'struct Box {
196+mut:
197+ x int
198+}
199+
200+fn main() {
201+ xs := [1, 2]
202+ mut b := Box{}
203+ for i, b.x in xs {
204+ println(i)
205+ }
206+}
207+',
208+ 'invalid for-in header: expected identifiers before `in`')
209+}
@@ -0,0 +1,113 @@
1+import os
2+
3+const generic_receiver_nested_vexe = @VEXE
4+const generic_receiver_nested_tests_dir = os.dir(@FILE)
5+const generic_receiver_nested_v3_dir = os.dir(generic_receiver_nested_tests_dir)
6+const generic_receiver_nested_vlib_dir = os.dir(generic_receiver_nested_v3_dir)
7+const generic_receiver_nested_v3_src = os.join_path(generic_receiver_nested_v3_dir, 'v3.v')
8+
9+fn generic_receiver_nested_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_generic_receiver_nested_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${generic_receiver_nested_vexe} -gc none -path "${generic_receiver_nested_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${generic_receiver_nested_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn generic_receiver_nested_write_project() string {
19+ root := os.join_path(os.temp_dir(), 'v3_generic_receiver_nested_${os.getpid()}')
20+ os.rmdir_all(root) or {}
21+ os.mkdir_all(os.join_path(root, 'gr')) or { panic(err) }
22+ os.write_file(os.join_path(root, 'v.mod'), "Module { name: 'generic_receiver_nested' }\n") or {
23+ panic(err)
24+ }
25+ os.write_file(os.join_path(root, 'gr/gr.v'), 'module gr
26+
27+pub struct Inner[T] {
28+mut:
29+ value T
30+ has bool
31+}
32+
33+pub fn (inner Inner[T]) is_empty() bool {
34+ return !inner.has
35+}
36+
37+pub fn (mut inner Inner[T]) push(item T) {
38+ if inner.is_empty() {
39+ inner.value = item
40+ inner.has = true
41+ return
42+ }
43+ inner.value = item
44+}
45+
46+pub fn (mut inner Inner[T]) pop() !T {
47+ if inner.is_empty() {
48+ return error("empty")
49+ }
50+ inner.has = false
51+ return inner.value
52+}
53+
54+pub struct Outer[T] {
55+mut:
56+ inner Inner[T]
57+}
58+
59+pub fn (mut outer Outer[T]) push(item T) {
60+ outer.inner.push(item)
61+}
62+
63+pub fn (mut outer Outer[T]) pop() !T {
64+ return outer.inner.pop()
65+}
66+
67+pub fn (outer Outer[T]) is_empty() bool {
68+ return outer.inner.is_empty()
69+}
70+') or {
71+ panic(err)
72+ }
73+ os.write_file(os.join_path(root, 'main.v'), "module main
74+
75+import gr
76+
77+fn main() {
78+ mut outer := gr.Outer[[]string]{}
79+ outer.push(['A'])
80+ assert !outer.is_empty()
81+ got := outer.pop() or { panic(err) }
82+ assert got[0] == 'A'
83+ assert outer.is_empty()
84+ println('ok')
85+}
86+") or {
87+ panic(err)
88+ }
89+ return os.join_path(root, 'main.v')
90+}
91+
92+fn test_generic_receiver_nested_calls_use_specialized_receiver_methods() {
93+ v3_bin := generic_receiver_nested_build_v3()
94+ main_path := generic_receiver_nested_write_project()
95+ out := os.join_path(os.temp_dir(), 'v3_generic_receiver_nested_out_${os.getpid()}')
96+ os.rm(out) or {}
97+ os.rm(out + '.c') or {}
98+ compile := os.execute('${v3_bin} ${main_path} -b c -o ${out}')
99+ assert compile.exit_code == 0, compile.output
100+ assert !compile.output.contains('C compilation failed'), compile.output
101+
102+ run := os.execute(out)
103+ assert run.exit_code == 0, run.output
104+ assert run.output.trim_space() == 'ok'
105+
106+ generated := os.read_file(out + '.c') or { panic(err) }
107+ assert generated.contains('gr__Inner_Array_string__push'), generated
108+ assert generated.contains('gr__Inner_Array_string__pop'), generated
109+ assert generated.contains('Optional_Array'), generated
110+ assert !generated.contains('Inner_T__'), generated
111+ assert !generated.contains('Outer_T__'), generated
112+ assert !generated.contains('Optional_int __return_opt'), generated
113+}
@@ -0,0 +1,94 @@
1+import os
2+
3+const generic_struct_str_vexe = @VEXE
4+const generic_struct_str_tests_dir = os.dir(@FILE)
5+const generic_struct_str_v3_dir = os.dir(generic_struct_str_tests_dir)
6+const generic_struct_str_vlib_dir = os.dir(generic_struct_str_v3_dir)
7+const generic_struct_str_v3_src = os.join_path(generic_struct_str_v3_dir, 'v3.v')
8+
9+fn generic_struct_str_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_generic_struct_str_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${generic_struct_str_vexe} -gc none -path "${generic_struct_str_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${generic_struct_str_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn generic_struct_str_write_project() string {
19+ root := os.join_path(os.temp_dir(), 'v3_generic_struct_str_${os.getpid()}')
20+ os.rmdir_all(root) or {}
21+ os.mkdir_all(os.join_path(root, 'gr')) or { panic(err) }
22+ os.write_file(os.join_path(root, 'v.mod'), "Module { name: 'generic_struct_str' }\n") or {
23+ panic(err)
24+ }
25+ os.write_file(os.join_path(root, 'gr/gr.v'), 'module gr
26+
27+pub struct Inner[T] {
28+mut:
29+ items []T
30+}
31+
32+pub fn (mut inner Inner[T]) push(item T) {
33+ inner.items << item
34+}
35+
36+pub fn (inner Inner[T]) str() string {
37+ return "inner=" + inner.items.str()
38+}
39+
40+pub struct Outer[T] {
41+mut:
42+ inner Inner[T]
43+}
44+
45+pub fn (mut outer Outer[T]) push(item T) {
46+ outer.inner.push(item)
47+}
48+
49+pub fn (outer Outer[T]) str() string {
50+ return "outer=" + outer.inner.str()
51+}
52+') or {
53+ panic(err)
54+ }
55+ os.write_file(os.join_path(root, 'main.v'), "module main
56+
57+import gr
58+
59+fn main() {
60+ mut outer := gr.Outer[[]string]{}
61+ outer.push(['A', 'B'])
62+ interpolated := 'value \${outer}'
63+ direct := outer.str()
64+ assert interpolated == 'value ' + direct
65+ assert direct.len > 0
66+ println('ok')
67+}
68+") or {
69+ panic(err)
70+ }
71+ return os.join_path(root, 'main.v')
72+}
73+
74+fn test_generic_struct_str_is_used_for_string_interpolation_and_direct_calls() {
75+ v3_bin := generic_struct_str_build_v3()
76+ main_path := generic_struct_str_write_project()
77+ out := os.join_path(os.temp_dir(), 'v3_generic_struct_str_out_${os.getpid()}')
78+ os.rm(out) or {}
79+ os.rm(out + '.c') or {}
80+ compile := os.execute('${v3_bin} ${main_path} -b c -o ${out}')
81+ assert compile.exit_code == 0, compile.output
82+ assert !compile.output.contains('C compilation failed'), compile.output
83+
84+ run := os.execute(out)
85+ assert run.exit_code == 0, run.output
86+ assert run.output.trim_space() == 'ok'
87+
88+ generated := os.read_file(out + '.c') or { panic(err) }
89+ assert generated.contains('gr__Outer_Array_string__str'), generated
90+ assert generated.contains('gr__Inner_Array_string__str'), generated
91+ assert !generated.contains(' = outer;'), generated
92+ assert !generated.contains('Outer_T__str'), generated
93+ assert !generated.contains('Inner_T__str'), generated
94+}
@@ -0,0 +1,60 @@
1+import os
2+
3+const generic_sum_str_vexe = @VEXE
4+const generic_sum_str_tests_dir = os.dir(@FILE)
5+const generic_sum_str_v3_dir = os.dir(generic_sum_str_tests_dir)
6+const generic_sum_str_vlib_dir = os.dir(generic_sum_str_v3_dir)
7+const generic_sum_str_v3_src = os.join_path(generic_sum_str_v3_dir, 'v3.v')
8+
9+fn generic_sum_str_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_generic_sum_str_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${generic_sum_str_vexe} -gc none -path "${generic_sum_str_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${generic_sum_str_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_generic_sum_str_is_used_for_string_interpolation() {
19+ v3_bin := generic_sum_str_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_generic_sum_str_input_${os.getpid()}.v')
21+ os.write_file(src, "type Maybe[T] = Empty | Some[T]
22+
23+struct Empty {}
24+
25+struct Some[T] {
26+ value T
27+}
28+
29+fn (m Maybe[T]) str() string {
30+ return 'maybe'
31+}
32+
33+fn main() {
34+ x := Maybe[int](Some[int]{
35+ value: 7
36+ })
37+ interpolated := 'value \${x}'
38+ assert interpolated == 'value maybe'
39+ println(interpolated)
40+}
41+") or {
42+ panic(err)
43+ }
44+
45+ bin := os.join_path(os.temp_dir(), 'v3_generic_sum_str_input_${os.getpid()}')
46+ os.rm(bin) or {}
47+ os.rm(bin + '.c') or {}
48+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
49+ assert compile.exit_code == 0, compile.output
50+ assert !compile.output.contains('C compilation failed'), compile.output
51+
52+ run := os.execute(bin)
53+ assert run.exit_code == 0, run.output
54+ assert run.output.trim_space() == 'value maybe'
55+
56+ generated := os.read_file(bin + '.c') or { panic(err) }
57+ assert generated.contains('Maybe_int__str'), generated
58+ assert !generated.contains(' = x;'), generated
59+ assert !generated.contains('Maybe_T__str'), generated
60+}
@@ -0,0 +1,509 @@
1+import os
2+
3+const generic_sum_type_vexe = @VEXE
4+const generic_sum_type_tests_dir = os.dir(@FILE)
5+const generic_sum_type_v3_dir = os.dir(generic_sum_type_tests_dir)
6+const generic_sum_type_vlib_dir = os.dir(generic_sum_type_v3_dir)
7+const generic_sum_type_v3_src = os.join_path(generic_sum_type_v3_dir, 'v3.v')
8+
9+fn generic_sum_type_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_generic_sum_type_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${generic_sum_type_vexe} -gc none -path "${generic_sum_type_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${generic_sum_type_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_generic_sum_instances_emit_concrete_c_types() {
19+ v3_bin := generic_sum_type_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_generic_sum_type_input_${os.getpid()}.v')
21+ os.write_file(src, '
22+struct Empty {}
23+
24+struct Node[T] {
25+ value T
26+ left Tree[T]
27+ right Tree[T]
28+}
29+
30+type Tree[T] = Empty | Node[T]
31+
32+fn leaf[T](value T, empty Tree[T]) Tree[T] {
33+ return Node[T]{value, empty, empty}
34+}
35+
36+fn (tree Tree[T]) size[T]() int {
37+ return match tree {
38+ Empty { 0 }
39+ Node[T] { 1 + tree.left.size() + tree.right.size() }
40+ }
41+}
42+
43+fn (tree Tree[T]) add_left[T](value T) Tree[T] {
44+ return match tree {
45+ Empty {
46+ Node[T]{value, tree, tree}
47+ }
48+ Node[T] {
49+ Node[T]{
50+ ...tree
51+ left: Node[T]{value, Empty{}, Empty{}}
52+ }
53+ }
54+ }
55+}
56+
57+fn main() {
58+ empty_int := Tree[int](Empty{})
59+ mut ints := leaf(10, empty_int)
60+ ints = ints.add_left(5)
61+ assert ints.size() == 2
62+
63+ empty_f64 := Tree[f64](Empty{})
64+ mut floats := leaf(1.25, empty_f64)
65+ floats = floats.add_left(0.5)
66+ assert floats.size() == 2
67+ println("ok")
68+}
69+') or {
70+ panic(err)
71+ }
72+
73+ bin := os.join_path(os.temp_dir(), 'v3_generic_sum_type_input_${os.getpid()}')
74+ os.rm(bin) or {}
75+ os.rm(bin + '.c') or {}
76+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
77+ assert compile.exit_code == 0, compile.output
78+ assert !compile.output.contains('C compilation failed'), compile.output
79+
80+ run := os.execute(bin)
81+ assert run.exit_code == 0, run.output
82+ assert run.output.trim_space() == 'ok'
83+
84+ c_code := os.read_file(bin + '.c') or { panic(err) }
85+ assert c_code.contains('typedef struct Tree_int Tree_int;'), c_code
86+ assert c_code.contains('typedef struct Tree_f64 Tree_f64;'), c_code
87+ assert c_code.contains('struct Node_int {'), c_code
88+ assert c_code.contains('struct Node_f64 {'), c_code
89+ assert c_code.contains('struct Tree_int {'), c_code
90+ assert c_code.contains('struct Tree_f64 {'), c_code
91+ assert !c_code.contains('Array_fixed_Node_T'), c_code
92+ assert !c_code.contains('Tree_int ints = (Tree){'), c_code
93+ assert !c_code.contains('Tree_f64 floats = (Tree){'), c_code
94+}
95+
96+fn test_generic_sum_bare_variant_match_lowers_to_tag_check() {
97+ v3_bin := generic_sum_type_build_v3()
98+ src := os.join_path(os.temp_dir(), 'v3_generic_sum_type_bare_match_${os.getpid()}.v')
99+ os.write_file(src, '
100+struct Empty {}
101+
102+struct Node[T] {
103+ value T
104+}
105+
106+type Tree[T] = Empty | Node[T]
107+
108+fn value(tree Tree[int]) int {
109+ return match tree {
110+ Node {
111+ tree.value
112+ }
113+ else {
114+ 0
115+ }
116+ }
117+}
118+
119+fn main() {
120+ tree := Tree[int](Node[int]{
121+ value: 42
122+ })
123+ println(value(tree).str())
124+ println(value(Tree[int](Empty{})).str())
125+}
126+') or {
127+ panic(err)
128+ }
129+
130+ bin := os.join_path(os.temp_dir(), 'v3_generic_sum_type_bare_match_${os.getpid()}')
131+ os.rm(bin) or {}
132+ os.rm(bin + '.c') or {}
133+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
134+ assert compile.exit_code == 0, compile.output
135+ assert !compile.output.contains('C compilation failed'), compile.output
136+
137+ run := os.execute(bin)
138+ assert run.exit_code == 0, run.output
139+ assert run.output.trim_space() == '42\n0'
140+
141+ c_code := os.read_file(bin + '.c') or { panic(err) }
142+ assert !c_code.contains('if (true)'), c_code
143+ assert !c_code.contains('tree == Node'), c_code
144+}
145+
146+fn test_generic_sum_constructor_return_uses_concrete_c_type() {
147+ v3_bin := generic_sum_type_build_v3()
148+ src := os.join_path(os.temp_dir(), 'v3_generic_sum_type_return_ctor_${os.getpid()}.v')
149+ os.write_file(src, '
150+struct Empty {}
151+
152+struct Node[T] {
153+ value T
154+}
155+
156+type Tree[T] = Empty | Node[T]
157+
158+fn empty_tree() Tree[int] {
159+ return Tree[int](Empty{})
160+}
161+
162+fn empty_tree_defer() Tree[int] {
163+ defer {}
164+ return Tree[int](Empty{})
165+}
166+
167+fn main() {
168+ tree := empty_tree()
169+ assert tree is Empty
170+ tree_defer := empty_tree_defer()
171+ assert tree_defer is Empty
172+ println("ok")
173+}
174+') or {
175+ panic(err)
176+ }
177+
178+ bin := os.join_path(os.temp_dir(), 'v3_generic_sum_type_return_ctor_${os.getpid()}')
179+ os.rm(bin) or {}
180+ os.rm(bin + '.c') or {}
181+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
182+ assert compile.exit_code == 0, compile.output
183+ assert !compile.output.contains('C compilation failed'), compile.output
184+
185+ run := os.execute(bin)
186+ assert run.exit_code == 0, run.output
187+ assert run.output.trim_space() == 'ok'
188+
189+ c_code := os.read_file(bin + '.c') or { panic(err) }
190+ assert !c_code.contains('Tree((Empty){})'), c_code
191+ assert c_code.contains('return (Tree_int){'), c_code
192+}
193+
194+fn test_generic_sum_rejects_mismatched_concrete_variant() {
195+ v3_bin := generic_sum_type_build_v3()
196+ src := os.join_path(os.temp_dir(), 'v3_generic_sum_type_negative_${os.getpid()}.v')
197+ os.write_file(src, '
198+struct Empty {}
199+
200+struct Node[T] {
201+ value T
202+}
203+
204+type Tree[T] = Empty | Node[T]
205+
206+fn bad() Tree[int] {
207+ return Node[string]{"nope"}
208+}
209+
210+fn main() {}
211+') or {
212+ panic(err)
213+ }
214+
215+ bin := os.join_path(os.temp_dir(), 'v3_generic_sum_type_negative_${os.getpid()}')
216+ os.rm(bin) or {}
217+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
218+ assert compile.exit_code != 0, compile.output
219+ assert compile.output.contains('cannot return') || compile.output.contains('incompatible'), compile.output
220+
221+ assert !compile.output.contains('C compilation failed'), compile.output
222+}
223+
224+fn test_generic_sum_rejects_mismatched_qualified_generic_variant_pattern() {
225+ v3_bin := generic_sum_type_build_v3()
226+ root := os.join_path(os.temp_dir(), 'v3_generic_sum_type_qualified_negative_${os.getpid()}')
227+ os.rmdir_all(root) or {}
228+ left_dir := os.join_path(root, 'left')
229+ right_dir := os.join_path(root, 'right')
230+ os.mkdir_all(left_dir) or { panic(err) }
231+ os.mkdir_all(right_dir) or { panic(err) }
232+ os.write_file(os.join_path(left_dir, 'left.v'), 'module left
233+
234+pub struct Foo {}
235+') or {
236+ panic(err)
237+ }
238+ os.write_file(os.join_path(right_dir, 'right.v'), 'module right
239+
240+pub struct Foo {}
241+') or {
242+ panic(err)
243+ }
244+
245+ src := os.join_path(root, 'main.v')
246+ os.write_file(src, 'module main
247+
248+import left
249+import right
250+
251+struct Empty {}
252+
253+struct Node[T] {
254+ value T
255+}
256+
257+type Tree[T] = Empty | Node[T]
258+
259+fn bad(tree Tree[left.Foo]) int {
260+ return match tree {
261+ Node[right.Foo] { 1 }
262+ else { 0 }
263+ }
264+}
265+
266+fn main() {}
267+') or {
268+ panic(err)
269+ }
270+
271+ bin := os.join_path(os.temp_dir(), 'v3_generic_sum_type_qualified_negative_${os.getpid()}')
272+ os.rm(bin) or {}
273+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
274+ assert compile.exit_code != 0, compile.output
275+ assert compile.output.contains('Node[right.Foo]'), compile.output
276+ assert compile.output.contains('Tree[left.Foo]'), compile.output
277+ assert !compile.output.contains('C compilation failed'), compile.output
278+}
279+
280+fn test_generic_sum_rejects_qualified_generic_variant_from_other_module() {
281+ v3_bin := generic_sum_type_build_v3()
282+ root := os.join_path(os.temp_dir(),
283+ 'v3_generic_sum_type_qualified_variant_negative_${os.getpid()}')
284+ os.rmdir_all(root) or {}
285+ left_dir := os.join_path(root, 'left')
286+ right_dir := os.join_path(root, 'right')
287+ os.mkdir_all(left_dir) or { panic(err) }
288+ os.mkdir_all(right_dir) or { panic(err) }
289+ os.write_file(os.join_path(left_dir, 'left.v'), 'module left
290+
291+pub struct Foo {}
292+') or {
293+ panic(err)
294+ }
295+ os.write_file(os.join_path(right_dir, 'right.v'), 'module right
296+
297+pub struct Node[T] {
298+ pub:
299+ value T
300+}
301+') or {
302+ panic(err)
303+ }
304+
305+ src := os.join_path(root, 'main.v')
306+ os.write_file(src, 'module main
307+
308+import left
309+import right
310+
311+struct Empty {}
312+
313+struct Node[T] {
314+ value T
315+}
316+
317+type Tree[T] = Empty | Node[T]
318+
319+fn bad(tree Tree[left.Foo]) int {
320+ return match tree {
321+ right.Node[left.Foo] { 1 }
322+ else { 0 }
323+ }
324+}
325+
326+fn main() {}
327+') or {
328+ panic(err)
329+ }
330+
331+ bin := os.join_path(os.temp_dir(),
332+ 'v3_generic_sum_type_qualified_variant_negative_${os.getpid()}')
333+ os.rm(bin) or {}
334+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
335+ assert compile.exit_code != 0, compile.output
336+ assert compile.output.contains('right.Node[left.Foo]'), compile.output
337+ assert compile.output.contains('Tree[left.Foo]'), compile.output
338+ assert !compile.output.contains('C compilation failed'), compile.output
339+}
340+
341+fn test_generic_sum_rejects_qualified_bare_variants_from_other_module() {
342+ v3_bin := generic_sum_type_build_v3()
343+ root := os.join_path(os.temp_dir(),
344+ 'v3_generic_sum_type_qualified_bare_variant_negative_${os.getpid()}')
345+ os.rmdir_all(root) or {}
346+ left_dir := os.join_path(root, 'left')
347+ right_dir := os.join_path(root, 'right')
348+ os.mkdir_all(left_dir) or { panic(err) }
349+ os.mkdir_all(right_dir) or { panic(err) }
350+ os.write_file(os.join_path(left_dir, 'left.v'), 'module left
351+
352+pub struct Foo {}
353+') or {
354+ panic(err)
355+ }
356+ os.write_file(os.join_path(right_dir, 'right.v'), 'module right
357+
358+pub struct Empty {}
359+
360+pub struct Node[T] {
361+ pub:
362+ value T
363+}
364+') or {
365+ panic(err)
366+ }
367+
368+ src := os.join_path(root, 'main.v')
369+ os.write_file(src, 'module main
370+
371+import left
372+import right
373+
374+struct Empty {}
375+
376+struct Node[T] {
377+ value T
378+}
379+
380+type Tree[T] = Empty | Node[T]
381+
382+fn bad_node(tree Tree[left.Foo]) int {
383+ return match tree {
384+ right.Node { 1 }
385+ else { 0 }
386+ }
387+}
388+
389+fn bad_empty(tree Tree[left.Foo]) int {
390+ return match tree {
391+ right.Empty { 1 }
392+ else { 0 }
393+ }
394+}
395+
396+fn main() {}
397+') or {
398+ panic(err)
399+ }
400+
401+ bin := os.join_path(os.temp_dir(),
402+ 'v3_generic_sum_type_qualified_bare_variant_negative_${os.getpid()}')
403+ os.rm(bin) or {}
404+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
405+ assert compile.exit_code != 0, compile.output
406+ assert compile.output.contains('right.Node'), compile.output
407+ assert compile.output.contains('right.Empty'), compile.output
408+ assert compile.output.contains('Tree[left.Foo]'), compile.output
409+ assert !compile.output.contains('C compilation failed'), compile.output
410+}
411+
412+fn test_generic_sum_accepts_declared_qualified_bare_variant_pattern() {
413+ v3_bin := generic_sum_type_build_v3()
414+ root := os.join_path(os.temp_dir(),
415+ 'v3_generic_sum_type_declared_qualified_bare_${os.getpid()}')
416+ os.rmdir_all(root) or {}
417+ foo_dir := os.join_path(root, 'foo')
418+ os.mkdir_all(foo_dir) or { panic(err) }
419+ os.write_file(os.join_path(foo_dir, 'foo.v'), 'module foo
420+
421+pub struct Node[T] {
422+ pub:
423+ value T
424+}
425+') or {
426+ panic(err)
427+ }
428+
429+ src := os.join_path(root, 'main.v')
430+ os.write_file(src, 'module main
431+
432+import foo
433+
434+struct Empty {}
435+
436+type Tree[T] = Empty | foo.Node[T]
437+
438+fn value(tree Tree[int]) int {
439+ return match tree {
440+ foo.Node { tree.value }
441+ Empty { 0 }
442+ }
443+}
444+
445+fn main() {}
446+') or {
447+ panic(err)
448+ }
449+
450+ c_out := os.join_path(os.temp_dir(),
451+ 'v3_generic_sum_type_declared_qualified_bare_${os.getpid()}.c')
452+ os.rm(c_out) or {}
453+ compile := os.execute('${v3_bin} ${src} -b c -o ${c_out}')
454+ assert compile.exit_code == 0, compile.output
455+ assert !compile.output.contains('tcc.exe'), compile.output
456+ assert !compile.output.contains('C compilation failed'), compile.output
457+ assert os.exists(c_out)
458+}
459+
460+fn test_generic_sum_concrete_generic_variants_keep_distinct_tags() {
461+ v3_bin := generic_sum_type_build_v3()
462+ src := os.join_path(os.temp_dir(), 'v3_generic_sum_type_distinct_tags_${os.getpid()}.v')
463+ os.write_file(src, '
464+struct Box[T] {
465+ value T
466+}
467+
468+type S = Box[int] | Box[string]
469+
470+fn score(s S) int {
471+ return match s {
472+ Box[int] { 10 + s.value }
473+ Box[string] { 100 + s.value.len }
474+ }
475+}
476+
477+fn main() {
478+ s := S(Box[string]{
479+ value: "ok"
480+ })
481+ assert s is Box[string]
482+ assert !(s is Box[int])
483+ b := s as Box[string]
484+ assert b.value == "ok"
485+ assert score(s) == 102
486+
487+ i := S(Box[int]{
488+ value: 7
489+ })
490+ assert i is Box[int]
491+ assert !(i is Box[string])
492+ assert score(i) == 17
493+ println("ok")
494+}
495+ ') or {
496+ panic(err)
497+ }
498+
499+ bin := os.join_path(os.temp_dir(), 'v3_generic_sum_type_distinct_tags_${os.getpid()}')
500+ os.rm(bin) or {}
501+ os.rm(bin + '.c') or {}
502+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
503+ assert compile.exit_code == 0, compile.output
504+ assert !compile.output.contains('C compilation failed'), compile.output
505+
506+ run := os.execute(bin)
507+ assert run.exit_code == 0, run.output
508+ assert run.output.trim_space() == 'ok'
509+}
@@ -0,0 +1,624 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn test_ierror_concrete_match_uses_type_ids() {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+
15+ root := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_${os.getpid()}')
16+ mod_dir := os.join_path(root, 'myerrs')
17+ os.mkdir_all(mod_dir) or { panic(err) }
18+ os.write_file(os.join_path(mod_dir, 'myerrs.v'), "module myerrs
19+
20+pub struct FirstError {
21+ Error
22+pub:
23+ input string
24+}
25+
26+pub fn (err FirstError) msg() string {
27+ return 'first ' + err.input
28+}
29+
30+pub fn (err FirstError) code() int {
31+ return 11
32+}
33+
34+pub struct SecondError {
35+ Error
36+}
37+
38+pub fn (err SecondError) msg() string {
39+ return 'second'
40+}
41+
42+pub fn (err SecondError) code() int {
43+ return 22
44+}
45+
46+pub fn fail(code int) !int {
47+ if code == 1 {
48+ return &FirstError{
49+ input: 'alpha'
50+ }
51+ }
52+ if code == 2 {
53+ return &SecondError{}
54+ }
55+ return 7
56+}
57+
58+pub fn fail_from_match(code int) !int {
59+ return match code {
60+ 1 {
61+ &FirstError{
62+ input: 'match'
63+ }
64+ }
65+ 2 {
66+ &SecondError{}
67+ }
68+ else {
69+ 7
70+ }
71+ }
72+}
73+
74+pub fn fail_with_defer(code int) !int {
75+ defer {
76+ _ := code
77+ }
78+ if code == 1 {
79+ return &FirstError{
80+ input: 'defer'
81+ }
82+ }
83+ return 7
84+}
85+ ") or {
86+ panic(err)
87+ }
88+
89+ src := os.join_path(root, 'main.v')
90+ os.write_file(src, "import myerrs
91+
92+fn classify(err IError) string {
93+ match err {
94+ myerrs.FirstError {
95+ return 'first'
96+ }
97+ myerrs.SecondError {
98+ return 'second'
99+ }
100+ else {
101+ return 'unknown'
102+ }
103+ }
104+}
105+
106+fn is_first(err IError) bool {
107+ return err is myerrs.FirstError
108+}
109+
110+fn main() {
111+ myerrs.fail(1) or {
112+ assert err.msg() == 'first alpha'
113+ assert err.code() == 11
114+ assert classify(err) == 'first'
115+ assert is_first(err)
116+ }
117+ myerrs.fail(2) or {
118+ assert err.msg() == 'second'
119+ assert err.code() == 22
120+ assert classify(err) == 'second'
121+ assert !is_first(err)
122+ }
123+ assert myerrs.fail(3)! == 7
124+ myerrs.fail_from_match(1) or {
125+ assert err.msg() == 'first match'
126+ assert classify(err) == 'first'
127+ }
128+ myerrs.fail_with_defer(1) or {
129+ assert err.msg() == 'first defer'
130+ assert classify(err) == 'first'
131+ }
132+ println('ok')
133+}
134+") or {
135+ panic(err)
136+ }
137+
138+ bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_input')
139+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
140+ assert compile.exit_code == 0, compile.output
141+ assert !compile.output.contains('C compilation failed'), compile.output
142+
143+ c_code := os.read_file(bin + '.c') or { panic(err) }
144+ assert c_code.contains('._typ == '), c_code
145+ assert c_code.contains('IError__msg(&err)'), c_code
146+ assert !c_code.contains('err == myerrs__FirstError'), c_code
147+ assert !c_code.contains('err == myerrs__SecondError'), c_code
148+
149+ run := os.execute(bin)
150+ assert run.exit_code == 0, run.output
151+ assert run.output.trim_space() == 'ok'
152+}
153+
154+fn test_ierror_patterns_resolve_nested_alias_and_selective_imports() {
155+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
156+ build :=
157+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
158+ assert build.exit_code == 0, build.output
159+
160+ root := os.join_path(os.temp_dir(), 'v3_ierror_alias_match_codegen_${os.getpid()}')
161+ alias_dir := os.join_path(root, 'nested', 'aliaserrs')
162+ selective_dir := os.join_path(root, 'nested', 'selecterrs')
163+ os.mkdir_all(alias_dir) or { panic(err) }
164+ os.mkdir_all(selective_dir) or { panic(err) }
165+ os.write_file(os.join_path(alias_dir, 'aliaserrs.v'), "module aliaserrs
166+
167+pub struct AliasError {
168+ Error
169+pub:
170+ label string
171+}
172+
173+pub fn (err AliasError) msg() string {
174+ return 'alias ' + err.label
175+}
176+
177+pub fn (err AliasError) code() int {
178+ return 31
179+}
180+
181+pub fn fail_alias() !int {
182+ return &AliasError{
183+ label: 'nested'
184+ }
185+}
186+") or {
187+ panic(err)
188+ }
189+ os.write_file(os.join_path(selective_dir, 'selecterrs.v'), "module selecterrs
190+
191+pub struct SelectiveError {
192+ Error
193+pub:
194+ label string
195+}
196+
197+pub fn (err SelectiveError) msg() string {
198+ return 'selective ' + err.label
199+}
200+
201+pub fn (err SelectiveError) code() int {
202+ return 41
203+}
204+
205+pub fn fail_selective() !int {
206+ return &SelectiveError{
207+ label: 'nested'
208+ }
209+}
210+") or {
211+ panic(err)
212+ }
213+
214+ src := os.join_path(root, 'main.v')
215+ os.write_file(src, "import nested.aliaserrs as aliased
216+import nested.selecterrs { SelectiveError, fail_selective }
217+
218+fn classify_alias(err IError) string {
219+ return match err {
220+ aliased.AliasError {
221+ 'alias'
222+ }
223+ else {
224+ 'other'
225+ }
226+ }
227+}
228+
229+fn classify_selective(err IError) string {
230+ return match err {
231+ SelectiveError {
232+ 'selective'
233+ }
234+ else {
235+ 'other'
236+ }
237+ }
238+}
239+
240+fn main() {
241+ aliased.fail_alias() or {
242+ assert err.msg() == 'alias nested'
243+ assert classify_alias(err) == 'alias'
244+ assert classify_selective(err) == 'other'
245+ assert err is aliased.AliasError
246+ }
247+
248+ fail_selective() or {
249+ assert err.msg() == 'selective nested'
250+ assert classify_alias(err) == 'other'
251+ assert classify_selective(err) == 'selective'
252+ assert err is SelectiveError
253+ }
254+ println('ok')
255+}
256+") or {
257+ panic(err)
258+ }
259+
260+ bin := os.join_path(os.temp_dir(), 'v3_ierror_alias_match_codegen_input')
261+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
262+ assert compile.exit_code == 0, compile.output
263+ assert !compile.output.contains('C compilation failed'), compile.output
264+
265+ run := os.execute(bin)
266+ assert run.exit_code == 0, run.output
267+ assert run.output.trim_space() == 'ok'
268+}
269+
270+fn test_selective_imported_error_pattern_prefers_scoped_error() {
271+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
272+ build :=
273+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
274+ assert build.exit_code == 0, build.output
275+
276+ root := os.join_path(os.temp_dir(), 'v3_ierror_selective_error_pattern_${os.getpid()}')
277+ mod_dir := os.join_path(root, 'fake')
278+ os.mkdir_all(mod_dir) or { panic(err) }
279+ os.write_file(os.join_path(mod_dir, 'fake.v'), "module fake
280+
281+pub struct Error {
282+ MessageError
283+pub:
284+ label string
285+}
286+
287+pub fn fail() !int {
288+ return Error{
289+ label: 'selective'
290+ }
291+}
292+") or {
293+ panic(err)
294+ }
295+
296+ src := os.join_path(root, 'main.v')
297+ os.write_file(src, "import fake { Error, fail }
298+
299+fn matches_fake(err IError) bool {
300+ if err is Error {
301+ assert err.label == 'selective'
302+ return true
303+ }
304+ return false
305+}
306+
307+fn classify(err IError) string {
308+ return match err {
309+ Error {
310+ assert err.label == 'selective'
311+ err.label
312+ }
313+ else {
314+ 'other'
315+ }
316+ }
317+}
318+
319+fn main() {
320+ fail() or {
321+ assert matches_fake(err)
322+ assert classify(err) == 'selective'
323+ println('ok')
324+ return
325+ }
326+ assert false
327+}
328+") or {
329+ panic(err)
330+ }
331+
332+ bin := os.join_path(os.temp_dir(), 'v3_ierror_selective_error_pattern_input')
333+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
334+ assert compile.exit_code == 0, compile.output
335+ assert !compile.output.contains('C compilation failed'), compile.output
336+
337+ run := os.execute(bin)
338+ assert run.exit_code == 0, run.output
339+ assert run.output.trim_space() == 'ok'
340+}
341+
342+fn test_scoped_error_pattern_does_not_fallback_to_builtin_error() {
343+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
344+ build :=
345+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
346+ assert build.exit_code == 0, build.output
347+
348+ root := os.join_path(os.temp_dir(), 'v3_ierror_scoped_error_no_fallback_${os.getpid()}')
349+ mod_dir := os.join_path(root, 'fake')
350+ os.mkdir_all(mod_dir) or { panic(err) }
351+ os.write_file(os.join_path(mod_dir, 'fake.v'), 'module fake
352+
353+pub struct Error {}
354+
355+pub fn fail() !int {
356+ return Error{}
357+}
358+') or {
359+ panic(err)
360+ }
361+
362+ src := os.join_path(root, 'main.v')
363+ os.write_file(src, 'import fake { Error, fail }
364+
365+fn is_fake(err IError) bool {
366+ return err is Error
367+}
368+
369+fn main() {
370+ fail() or {
371+ _ := is_fake(err)
372+ return
373+ }
374+}
375+') or {
376+ panic(err)
377+ }
378+
379+ bin := os.join_path(os.temp_dir(), 'v3_ierror_scoped_error_no_fallback_input')
380+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
381+ assert compile.exit_code != 0, compile.output
382+ assert compile.output.contains('fake.Error') || compile.output.contains('Error'), compile.output
383+ assert compile.output.contains('IError') || compile.output.contains('cannot return'), compile.output
384+ assert !compile.output.contains('C compilation failed'), compile.output
385+}
386+
387+fn test_local_error_pattern_does_not_fallback_to_builtin_error() {
388+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
389+ build :=
390+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
391+ assert build.exit_code == 0, build.output
392+
393+ root := os.join_path(os.temp_dir(), 'v3_ierror_local_error_no_fallback_${os.getpid()}')
394+ os.mkdir_all(root) or { panic(err) }
395+ src := os.join_path(root, 'main.v')
396+ os.write_file(src, 'struct Error {}
397+
398+fn is_local(err IError) bool {
399+ return err is Error
400+}
401+
402+fn main() {
403+ err := error("plain")
404+ _ := is_local(err)
405+}
406+') or {
407+ panic(err)
408+ }
409+
410+ bin := os.join_path(os.temp_dir(), 'v3_ierror_local_error_no_fallback_input')
411+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
412+ assert compile.exit_code != 0, compile.output
413+ assert compile.output.contains('Error') && compile.output.contains('IError'), compile.output
414+ assert !compile.output.contains('C compilation failed'), compile.output
415+}
416+
417+fn test_msg_only_struct_is_not_ierror_pattern() {
418+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
419+ build :=
420+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
421+ assert build.exit_code == 0, build.output
422+
423+ root := os.join_path(os.temp_dir(), 'v3_ierror_not_error_${os.getpid()}')
424+ mod_dir := os.join_path(root, 'noterrs')
425+ os.mkdir_all(mod_dir) or { panic(err) }
426+ os.write_file(os.join_path(mod_dir, 'noterrs.v'), "module noterrs
427+
428+pub struct NotError {}
429+
430+pub fn (err NotError) msg() string {
431+ return 'not actually an error'
432+}
433+") or {
434+ panic(err)
435+ }
436+
437+ src := os.join_path(root, 'main.v')
438+ os.write_file(src, "import noterrs
439+
440+fn classify(err IError) string {
441+ match err {
442+ noterrs.NotError {
443+ return 'not-error'
444+ }
445+ else {
446+ return 'unknown'
447+ }
448+ }
449+}
450+
451+fn is_not_error(err IError) bool {
452+ return err is noterrs.NotError
453+}
454+
455+fn main() {
456+ err := error('plain')
457+ assert classify(err) == 'unknown'
458+ assert !is_not_error(err)
459+}
460+") or {
461+ panic(err)
462+ }
463+
464+ bin := os.join_path(os.temp_dir(), 'v3_ierror_not_error_input')
465+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
466+ assert compile.exit_code != 0, compile.output
467+ assert compile.output.contains('noterrs.NotError') && compile.output.contains('IError'), compile.output
468+
469+ assert !compile.output.contains('C compilation failed'), compile.output
470+}
471+
472+fn test_module_local_error_embeds_are_not_ierror_patterns() {
473+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
474+ build :=
475+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
476+ assert build.exit_code == 0, build.output
477+
478+ root := os.join_path(os.temp_dir(), 'v3_ierror_fake_error_pattern_${os.getpid()}')
479+ mod_dir := os.join_path(root, 'fake')
480+ os.mkdir_all(mod_dir) or { panic(err) }
481+ os.write_file(os.join_path(mod_dir, 'fake.v'), "module fake
482+
483+pub struct Error {}
484+
485+pub struct MessageError {}
486+
487+pub struct FalseError {
488+ Error
489+}
490+
491+pub fn (err FalseError) msg() string {
492+ return 'false error'
493+}
494+
495+pub fn (err FalseError) code() int {
496+ return 91
497+}
498+
499+pub struct FalseMessageError {
500+ MessageError
501+}
502+
503+pub fn (err FalseMessageError) msg() string {
504+ return 'false message error'
505+}
506+
507+pub fn (err FalseMessageError) code() int {
508+ return 92
509+}
510+") or {
511+ panic(err)
512+ }
513+
514+ src := os.join_path(root, 'main.v')
515+ os.write_file(src, "import fake
516+
517+fn classify(err IError) string {
518+ match err {
519+ fake.FalseError {
520+ return 'false-error'
521+ }
522+ fake.FalseMessageError {
523+ return 'false-message-error'
524+ }
525+ else {
526+ return 'unknown'
527+ }
528+ }
529+}
530+
531+fn is_false_error(err IError) bool {
532+ return err is fake.FalseError || err is fake.FalseMessageError
533+}
534+
535+fn main() {
536+ err := error('plain')
537+ assert classify(err) == 'unknown'
538+ assert !is_false_error(err)
539+}
540+") or {
541+ panic(err)
542+ }
543+
544+ bin := os.join_path(os.temp_dir(), 'v3_ierror_fake_error_pattern_input')
545+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
546+ assert compile.exit_code != 0, compile.output
547+ assert compile.output.contains('fake.FalseError') && compile.output.contains('IError'), compile.output
548+
549+ assert compile.output.contains('fake.FalseMessageError') && compile.output.contains('IError'), compile.output
550+
551+ assert !compile.output.contains('C compilation failed'), compile.output
552+}
553+
554+fn test_module_local_error_embeds_are_not_boxed_as_ierror() {
555+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_match_codegen_test')
556+ build :=
557+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
558+ assert build.exit_code == 0, build.output
559+
560+ root := os.join_path(os.temp_dir(), 'v3_ierror_fake_error_box_${os.getpid()}')
561+ mod_dir := os.join_path(root, 'fakebox')
562+ os.mkdir_all(mod_dir) or { panic(err) }
563+ mod_src := os.join_path(mod_dir, 'fakebox.v')
564+ os.write_file(mod_src, "module fakebox
565+
566+pub struct Error {}
567+
568+pub struct MessageError {}
569+
570+pub struct FalseError {
571+ Error
572+}
573+
574+pub fn (err FalseError) msg() string {
575+ return 'false error'
576+}
577+
578+pub fn (err FalseError) code() int {
579+ return 91
580+}
581+
582+pub struct FalseMessageError {
583+ MessageError
584+}
585+
586+pub fn (err FalseMessageError) msg() string {
587+ return 'false message error'
588+}
589+
590+pub fn (err FalseMessageError) code() int {
591+ return 92
592+}
593+
594+pub fn fail_error() !int {
595+ return &FalseError{}
596+}
597+
598+pub fn fail_message_error() !int {
599+ return &FalseMessageError{}
600+}
601+") or {
602+ panic(err)
603+ }
604+
605+ src := os.join_path(root, 'main.v')
606+ os.write_file(src, 'import fakebox
607+
608+fn main() {
609+ a := fakebox.fail_error() or { 0 }
610+ b := fakebox.fail_message_error() or { 0 }
611+ _ = a + b
612+}
613+') or {
614+ panic(err)
615+ }
616+
617+ bin := os.join_path(os.temp_dir(), 'v3_ierror_fake_error_box_input')
618+ compile := os.execute('${v3_bin} ${mod_src} -b c -o ${bin}')
619+ assert compile.exit_code != 0, compile.output
620+ assert compile.output.contains('cannot return'), compile.output
621+ assert compile.output.contains('fakebox.FalseError')
622+ || compile.output.contains('fakebox.FalseMessageError'), compile.output
623+ assert !compile.output.contains('C compilation failed'), compile.output
624+}
@@ -0,0 +1,757 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_compat_test')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn run_bad_ierror_payload(v3_bin string, name string, source string, expected string, detail string) {
18+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_${name}_${os.getpid()}')
19+ os.mkdir_all(root) or { panic(err) }
20+ src := os.join_path(root, 'main.v')
21+ os.write_file(src, source) or { panic(err) }
22+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_${name}_out_${os.getpid()}')
23+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
24+ assert compile.exit_code != 0, compile.output
25+ assert compile.output.contains(expected), compile.output
26+ assert compile.output.contains(detail), compile.output
27+ assert !compile.output.contains('C compilation failed'), compile.output
28+}
29+
30+fn test_ierror_method_implementers_can_be_result_payloads() {
31+ v3_bin := build_v3()
32+
33+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_compat_${os.getpid()}')
34+ os.mkdir_all(root) or { panic(err) }
35+ src := os.join_path(root, 'main.v')
36+ os.write_file(src, "import os
37+
38+struct CustomError {
39+ text string
40+}
41+
42+fn (err CustomError) msg() string {
43+ return err.text
44+}
45+
46+fn (err CustomError) code() int {
47+ return 77
48+}
49+
50+fn os_error() !int {
51+ return os.NotExpected{}
52+}
53+
54+fn custom_error() !int {
55+ return CustomError{
56+ text: 'custom payload'
57+ }
58+}
59+
60+fn main() {
61+ os_error() or {
62+ assert err.msg() == ''
63+ assert err.code() == 0
64+ }
65+ custom_error() or {
66+ assert err.msg() == 'custom payload'
67+ assert err.code() == 77
68+ }
69+ println('ok')
70+}
71+") or {
72+ panic(err)
73+ }
74+
75+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_compat_input')
76+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
77+ assert compile.exit_code == 0, compile.output
78+ assert !compile.output.contains('C compilation failed'), compile.output
79+
80+ run := os.execute(bin)
81+ assert run.exit_code == 0, run.output
82+ assert run.output.trim_space() == 'ok'
83+}
84+
85+fn test_ierror_values_can_be_returned_as_result_errors() {
86+ v3_bin := build_v3()
87+
88+ root := os.join_path(os.temp_dir(), 'v3_ierror_return_value_${os.getpid()}')
89+ os.mkdir_all(root) or { panic(err) }
90+ src := os.join_path(root, 'main.v')
91+ os.write_file(src, "fn direct() !int {
92+ return error('direct boom')
93+}
94+
95+fn main() {
96+ direct() or {
97+ assert err.msg() == 'direct boom'
98+ }
99+ println('ok')
100+}
101+") or {
102+ panic(err)
103+ }
104+
105+ bin := os.join_path(os.temp_dir(), 'v3_ierror_return_value_input')
106+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
107+ assert compile.exit_code == 0, compile.output
108+ assert !compile.output.contains('C compilation failed'), compile.output
109+
110+ run := os.execute(bin)
111+ assert run.exit_code == 0, run.output
112+ assert run.output.trim_space() == 'ok'
113+}
114+
115+fn test_ierror_payloads_are_preserved_for_void_results() {
116+ v3_bin := build_v3()
117+
118+ root := os.join_path(os.temp_dir(), 'v3_ierror_void_return_payload_${os.getpid()}')
119+ os.mkdir_all(root) or { panic(err) }
120+ src := os.join_path(root, 'main.v')
121+ os.write_file(src, "struct CustomError {
122+ text string
123+}
124+
125+fn (err CustomError) msg() string {
126+ return err.text
127+}
128+
129+fn (err CustomError) code() int {
130+ return 77
131+}
132+
133+fn custom_error() ! {
134+ return CustomError{
135+ text: 'void payload'
136+ }
137+}
138+
139+fn custom_error_with_defer() ! {
140+ defer {
141+ _ := 1
142+ }
143+ return CustomError{
144+ text: 'void defer payload'
145+ }
146+}
147+
148+fn builtin_error() ! {
149+ return error('builtin void')
150+}
151+
152+fn main() {
153+ custom_error() or {
154+ assert err.msg() == 'void payload'
155+ assert err.code() == 77
156+ }
157+ custom_error_with_defer() or {
158+ assert err.msg() == 'void defer payload'
159+ assert err.code() == 77
160+ }
161+ builtin_error() or {
162+ assert err.msg() == 'builtin void'
163+ assert err.code() == 0
164+ }
165+ println('ok')
166+}
167+") or {
168+ panic(err)
169+ }
170+
171+ bin := os.join_path(os.temp_dir(), 'v3_ierror_void_return_payload_input')
172+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
173+ assert compile.exit_code == 0, compile.output
174+ assert !compile.output.contains('C compilation failed'), compile.output
175+
176+ run := os.execute(bin)
177+ assert run.exit_code == 0, run.output
178+ assert run.output.trim_space() == 'ok'
179+
180+ c_code := os.read_file(bin + '.c') or { panic(err) }
181+ assert c_code.contains('Optional custom_error(void)'), c_code
182+ assert c_code.contains('Optional custom_error_with_defer(void)'), c_code
183+ assert c_code.contains('.err = (IError){._typ = '), c_code
184+ assert !c_code.contains('Optional custom_error(void) {\n\treturn (Optional){.ok = false};'), c_code
185+}
186+
187+fn test_selective_imported_ierror_payload_is_result_error() {
188+ v3_bin := build_v3()
189+
190+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_selective_custom_${os.getpid()}')
191+ mod_dir := os.join_path(root, 'errmod')
192+ os.mkdir_all(mod_dir) or { panic(err) }
193+ os.write_file(os.join_path(mod_dir, 'errmod.v'), 'module errmod
194+
195+pub struct CustomError {
196+ text string
197+}
198+
199+pub fn (err CustomError) msg() string {
200+ return err.text
201+}
202+
203+pub fn (err CustomError) code() int {
204+ return 77
205+}
206+ ') or {
207+ panic(err)
208+ }
209+
210+ src := os.join_path(root, 'main.v')
211+ os.write_file(src, "import errmod { CustomError }
212+
213+fn fail() !int {
214+ return CustomError{
215+ text: 'imported'
216+ }
217+}
218+
219+fn main() {
220+ fail() or {
221+ assert err.msg() == 'imported'
222+ assert err.code() == 77
223+ println('ok')
224+ return
225+ }
226+ println('bad')
227+}
228+ ") or {
229+ panic(err)
230+ }
231+
232+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_selective_custom_input')
233+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
234+ assert compile.exit_code == 0, compile.output
235+ assert !compile.output.contains('C compilation failed'), compile.output
236+
237+ run := os.execute(bin)
238+ assert run.exit_code == 0, run.output
239+ assert run.output.trim_space() == 'ok'
240+}
241+
242+fn test_bare_ierror_payload_is_not_call_argument_result_value() {
243+ v3_bin := build_v3()
244+ run_bad_ierror_payload(v3_bin, 'bad_call_arg', "struct CustomError {
245+ text string
246+}
247+
248+fn (err CustomError) msg() string {
249+ return err.text
250+}
251+
252+fn (err CustomError) code() int {
253+ return 77
254+}
255+
256+fn takes(x !int) {
257+ _ = x
258+}
259+
260+fn main() {
261+ takes(CustomError{
262+ text: 'payload'
263+ })
264+}
265+",
266+ 'cannot use', 'CustomError')
267+ run_bad_ierror_payload(v3_bin, 'bad_ierror_call_arg', "fn takes(x !int) {
268+ _ = x
269+}
270+
271+fn main() {
272+ err := error('boom')
273+ takes(err)
274+}
275+",
276+ 'cannot use', 'IError')
277+}
278+
279+fn test_bare_ierror_payload_is_not_assigned_to_result_value() {
280+ v3_bin := build_v3()
281+ run_bad_ierror_payload(v3_bin, 'bad_assignment', "struct CustomError {
282+ text string
283+}
284+
285+fn (err CustomError) msg() string {
286+ return err.text
287+}
288+
289+fn (err CustomError) code() int {
290+ return 77
291+}
292+
293+fn ok() !int {
294+ return 1
295+}
296+
297+fn main() {
298+ mut value := ok()
299+ value = CustomError{
300+ text: 'payload'
301+ }
302+}
303+",
304+ 'cannot assign', 'CustomError')
305+}
306+
307+fn test_result_error_match_rejects_non_ierror_branch() {
308+ v3_bin := build_v3()
309+ run_bad_ierror_payload(v3_bin, 'bad_match_branch', "struct CustomError {}
310+
311+fn (err CustomError) msg() string {
312+ return 'custom'
313+}
314+
315+fn (err CustomError) code() int {
316+ return 77
317+}
318+
319+struct Other {}
320+
321+fn other() Other {
322+ return Other{}
323+}
324+
325+fn fail_match(x int) !int {
326+ return match x {
327+ 0 { CustomError{} }
328+ else { other() }
329+ }
330+}
331+
332+fn main() {
333+ fail_match(1) or { return }
334+}
335+",
336+ 'cannot return', 'Other')
337+}
338+
339+fn test_unqualified_builtin_error_in_imported_module_is_result_payload() {
340+ v3_bin := build_v3()
341+
342+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_builtin_error_${os.getpid()}')
343+ mod_dir := os.join_path(root, 'payloadmod')
344+ os.mkdir_all(mod_dir) or { panic(err) }
345+ os.write_file(os.join_path(mod_dir, 'payloadmod.v'), "module payloadmod
346+
347+pub struct EmbeddedBuiltinError {
348+ Error
349+}
350+
351+pub fn (err EmbeddedBuiltinError) msg() string {
352+ return 'embedded builtin error'
353+}
354+
355+pub fn (err EmbeddedBuiltinError) code() int {
356+ return 33
357+}
358+
359+pub fn builtin_error() !int {
360+ return Error{}
361+}
362+
363+pub fn embedded_error() !int {
364+ return EmbeddedBuiltinError{}
365+}
366+ ") or {
367+ panic(err)
368+ }
369+
370+ src := os.join_path(root, 'main.v')
371+ os.write_file(src, "import payloadmod
372+
373+fn main() {
374+ payloadmod.builtin_error() or {
375+ assert err.msg() == ''
376+ assert err.code() == 0
377+ }
378+ payloadmod.embedded_error() or {
379+ assert err.msg() == 'embedded builtin error'
380+ assert err.code() == 33
381+ }
382+ println('ok')
383+}
384+ ") or {
385+ panic(err)
386+ }
387+
388+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_builtin_error_input')
389+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
390+ assert compile.exit_code == 0, compile.output
391+ assert !compile.output.contains('C compilation failed'), compile.output
392+
393+ run := os.execute(bin)
394+ assert run.exit_code == 0, run.output
395+ assert run.output.trim_space() == 'ok'
396+}
397+
398+fn test_msg_only_struct_is_not_result_payload() {
399+ v3_bin := build_v3()
400+
401+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_msg_only_${os.getpid()}')
402+ os.mkdir_all(root) or { panic(err) }
403+ src := os.join_path(root, 'main.v')
404+ os.write_file(src, "struct NotError {}
405+
406+fn (err NotError) msg() string {
407+ return 'msg only'
408+}
409+
410+fn fail() !int {
411+ return NotError{}
412+}
413+
414+fn main() {
415+ _ := fail() or { 0 }
416+}
417+") or {
418+ panic(err)
419+ }
420+
421+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_msg_only_input')
422+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
423+ assert compile.exit_code != 0, compile.output
424+ assert compile.output.contains('cannot return'), compile.output
425+ assert compile.output.contains('NotError'), compile.output
426+ assert !compile.output.contains('C compilation failed'), compile.output
427+}
428+
429+fn test_module_local_error_embeds_are_not_result_payloads() {
430+ v3_bin := build_v3()
431+
432+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_fake_embed_${os.getpid()}')
433+ mod_dir := os.join_path(root, 'fake')
434+ os.mkdir_all(mod_dir) or { panic(err) }
435+ os.write_file(os.join_path(mod_dir, 'fake.v'), "module fake
436+
437+pub struct Error {}
438+
439+pub struct MessageError {}
440+
441+pub struct FalseError {
442+ Error
443+}
444+
445+pub fn (err FalseError) msg() string {
446+ return 'false error'
447+}
448+
449+pub fn (err FalseError) code() int {
450+ return 91
451+}
452+
453+pub struct FalseMessageError {
454+ MessageError
455+}
456+
457+pub fn (err FalseMessageError) msg() string {
458+ return 'false message error'
459+}
460+
461+pub fn (err FalseMessageError) code() int {
462+ return 92
463+}
464+
465+pub fn fail_error() !int {
466+ return FalseError{}
467+}
468+
469+pub fn fail_message_error() !int {
470+ return FalseMessageError{}
471+}
472+") or {
473+ panic(err)
474+ }
475+
476+ src := os.join_path(root, 'main.v')
477+ os.write_file(src, 'import fake
478+
479+fn main() {
480+ a := fake.fail_error() or { 0 }
481+ b := fake.fail_message_error() or { 0 }
482+ _ = a + b
483+}
484+') or {
485+ panic(err)
486+ }
487+
488+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_fake_embed_input')
489+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
490+ assert compile.exit_code != 0, compile.output
491+ assert compile.output.contains('cannot return'), compile.output
492+ assert compile.output.contains('fake.FalseError')
493+ || compile.output.contains('fake.FalseMessageError'), compile.output
494+
495+ assert !compile.output.contains('C compilation failed'), compile.output
496+}
497+
498+fn test_selective_imported_error_embed_is_not_result_payload() {
499+ v3_bin := build_v3()
500+
501+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_selective_error_${os.getpid()}')
502+ mod_dir := os.join_path(root, 'fake')
503+ os.mkdir_all(mod_dir) or { panic(err) }
504+ os.write_file(os.join_path(mod_dir, 'fake.v'), 'module fake
505+
506+pub struct Error {}
507+ ') or {
508+ panic(err)
509+ }
510+
511+ src := os.join_path(root, 'main.v')
512+ os.write_file(src, "import fake { Error }
513+
514+struct MyErr {
515+ Error
516+}
517+
518+fn (err MyErr) msg() string {
519+ return 'bad'
520+}
521+
522+fn (err MyErr) code() int {
523+ return 7
524+}
525+
526+fn fail() !int {
527+ return MyErr{}
528+}
529+
530+fn main() {
531+ _ := fail() or { 0 }
532+}
533+ ") or {
534+ panic(err)
535+ }
536+
537+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_selective_error_input')
538+ compile := os.execute('${v3_bin} ${root} -b c -o ${bin}')
539+ assert compile.exit_code != 0, compile.output
540+ assert compile.output.contains('cannot return'), compile.output
541+ assert compile.output.contains('MyErr'), compile.output
542+ assert !compile.output.contains('C compilation failed'), compile.output
543+}
544+
545+fn test_selective_imported_message_error_embed_is_not_result_payload() {
546+ v3_bin := build_v3()
547+
548+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_selective_message_error_${os.getpid()}')
549+ mod_dir := os.join_path(root, 'fake')
550+ os.mkdir_all(mod_dir) or { panic(err) }
551+ os.write_file(os.join_path(mod_dir, 'fake.v'), 'module fake
552+
553+pub struct MessageError {}
554+ ') or {
555+ panic(err)
556+ }
557+
558+ src := os.join_path(root, 'main.v')
559+ os.write_file(src, "import fake { MessageError }
560+
561+struct MyErr {
562+ MessageError
563+}
564+
565+fn (err MyErr) msg() string {
566+ return 'bad'
567+}
568+
569+fn (err MyErr) code() int {
570+ return 7
571+}
572+
573+fn fail() !int {
574+ return MyErr{}
575+}
576+
577+fn main() {
578+ _ := fail() or { 0 }
579+}
580+ ") or {
581+ panic(err)
582+ }
583+
584+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_selective_message_error_input')
585+ compile := os.execute('${v3_bin} ${root} -b c -o ${bin}')
586+ assert compile.exit_code != 0, compile.output
587+ assert compile.output.contains('cannot return'), compile.output
588+ assert compile.output.contains('MyErr'), compile.output
589+ assert !compile.output.contains('C compilation failed'), compile.output
590+}
591+
592+fn test_normal_imported_error_does_not_shadow_builtin_error_payload() {
593+ v3_bin := build_v3()
594+
595+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_normal_import_error_${os.getpid()}')
596+ mod_dir := os.join_path(root, 'fake')
597+ os.mkdir_all(mod_dir) or { panic(err) }
598+ os.write_file(os.join_path(mod_dir, 'fake.v'), 'module fake
599+
600+pub struct Error {}
601+ ') or {
602+ panic(err)
603+ }
604+
605+ src := os.join_path(root, 'main.v')
606+ os.write_file(src, "import fake
607+
608+fn fail() !int {
609+ _ := fake.Error{}
610+ return Error{}
611+}
612+
613+fn main() {
614+ fail() or {
615+ assert err.msg() == ''
616+ assert err.code() == 0
617+ }
618+ println('ok')
619+}
620+ ") or {
621+ panic(err)
622+ }
623+
624+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_normal_import_error_input')
625+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
626+ assert compile.exit_code == 0, compile.output
627+ assert !compile.output.contains('C compilation failed'), compile.output
628+
629+ run := os.execute(bin)
630+ assert run.exit_code == 0, run.output
631+ assert run.output.trim_space() == 'ok'
632+}
633+
634+fn test_main_error_does_not_shadow_imported_builtin_error_embed() {
635+ v3_bin := build_v3()
636+
637+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_imported_builtin_embed_${os.getpid()}')
638+ mod_dir := os.join_path(root, 'payloadmod')
639+ os.mkdir_all(mod_dir) or { panic(err) }
640+ os.write_file(os.join_path(mod_dir, 'payloadmod.v'), 'module payloadmod
641+
642+pub struct EmbeddedBuiltinError {
643+ Error
644+}
645+
646+pub fn payload() !IError {
647+ return EmbeddedBuiltinError{}
648+}
649+ ') or {
650+ panic(err)
651+ }
652+
653+ src := os.join_path(root, 'main.v')
654+ os.write_file(src, "import payloadmod
655+
656+struct Error {}
657+
658+fn main() {
659+ value := payloadmod.payload() or {
660+ println('bad error: ' + err.msg())
661+ return
662+ }
663+ assert value.msg() == ''
664+ assert value.code() == 0
665+ println('ok')
666+}
667+ ") or {
668+ panic(err)
669+ }
670+
671+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_imported_builtin_embed_input')
672+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
673+ assert compile.exit_code == 0, compile.output
674+ assert !compile.output.contains('C compilation failed'), compile.output
675+
676+ run := os.execute(bin)
677+ assert run.exit_code == 0, run.output
678+ assert run.output.trim_space() == 'ok'
679+}
680+
681+fn test_main_local_error_embed_is_not_result_payload() {
682+ v3_bin := build_v3()
683+
684+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_main_error_${os.getpid()}')
685+ os.mkdir_all(root) or { panic(err) }
686+ src := os.join_path(root, 'main.v')
687+ os.write_file(src, "struct Error {}
688+
689+struct MyErr {
690+ Error
691+}
692+
693+fn (err MyErr) msg() string {
694+ return 'bad'
695+}
696+
697+fn (err MyErr) code() int {
698+ return 7
699+}
700+
701+fn fail() !int {
702+ return MyErr{}
703+}
704+
705+fn main() {
706+ _ := fail() or { 0 }
707+}
708+") or {
709+ panic(err)
710+ }
711+
712+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_main_error_input')
713+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
714+ assert compile.exit_code != 0, compile.output
715+ assert compile.output.contains('cannot return'), compile.output
716+ assert compile.output.contains('MyErr'), compile.output
717+ assert !compile.output.contains('C compilation failed'), compile.output
718+}
719+
720+fn test_main_local_message_error_embed_is_not_result_payload() {
721+ v3_bin := build_v3()
722+
723+ root := os.join_path(os.temp_dir(), 'v3_ierror_payload_main_message_error_${os.getpid()}')
724+ os.mkdir_all(root) or { panic(err) }
725+ src := os.join_path(root, 'main.v')
726+ os.write_file(src, "struct MessageError {}
727+
728+struct MyErr {
729+ MessageError
730+}
731+
732+fn (err MyErr) msg() string {
733+ return 'bad'
734+}
735+
736+fn (err MyErr) code() int {
737+ return 7
738+}
739+
740+fn fail() !int {
741+ return MyErr{}
742+}
743+
744+fn main() {
745+ _ := fail() or { 0 }
746+}
747+") or {
748+ panic(err)
749+ }
750+
751+ bin := os.join_path(os.temp_dir(), 'v3_ierror_payload_main_message_error_input')
752+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
753+ assert compile.exit_code != 0, compile.output
754+ assert compile.output.contains('cannot return'), compile.output
755+ assert compile.output.contains('MyErr'), compile.output
756+ assert !compile.output.contains('C compilation failed'), compile.output
757+}
@@ -0,0 +1,160 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_pointer_payload_test')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn c_fn_body(c_code string, header string) string {
18+ start := c_code.index(header) or { return '' }
19+ tail := c_code[start..]
20+ end := tail.index('\n}\n') or { return tail }
21+ return tail[..end + 3]
22+}
23+
24+fn test_local_pointer_ierror_payload_is_heap_copied() {
25+ v3_bin := build_v3()
26+
27+ root := os.join_path(os.temp_dir(), 'v3_ierror_local_pointer_payload_${os.getpid()}')
28+ os.mkdir_all(root) or { panic(err) }
29+ src := os.join_path(root, 'main.v')
30+ os.write_file(src, "struct PtrErr {
31+ text string
32+}
33+
34+fn (err &PtrErr) msg() string {
35+ return err.text
36+}
37+
38+fn (err &PtrErr) code() int {
39+ return 7
40+}
41+
42+struct Holder {
43+ err PtrErr
44+}
45+
46+fn local_ptr() !int {
47+ err := PtrErr{
48+ text: 'stack error'
49+ }
50+ return &err
51+}
52+
53+fn direct_local() IError {
54+ err := PtrErr{
55+ text: 'direct error'
56+ }
57+ return &err
58+}
59+
60+fn from_param(err PtrErr) !int {
61+ return &err
62+}
63+
64+fn local_field() !int {
65+ holder := Holder{
66+ err: PtrErr{
67+ text: 'local field error'
68+ }
69+ }
70+ return &holder.err
71+}
72+
73+fn from_param_field(holder Holder) !int {
74+ return &holder.err
75+}
76+
77+fn from_pointer_param_field(holder &Holder) !int {
78+ return &holder.err
79+}
80+
81+fn main() {
82+ local_ptr() or {
83+ assert err.msg() == 'stack error'
84+ assert err.code() == 7
85+ }
86+ direct := direct_local()
87+ assert direct.msg() == 'direct error'
88+ assert direct.code() == 7
89+ from_param(PtrErr{
90+ text: 'param error'
91+ }) or {
92+ assert err.msg() == 'param error'
93+ assert err.code() == 7
94+ }
95+ local_field() or {
96+ assert err.msg() == 'local field error'
97+ assert err.code() == 7
98+ }
99+ from_param_field(Holder{
100+ err: PtrErr{
101+ text: 'param field error'
102+ }
103+ }) or {
104+ assert err.msg() == 'param field error'
105+ assert err.code() == 7
106+ }
107+ stable := Holder{
108+ err: PtrErr{
109+ text: 'pointer field error'
110+ }
111+ }
112+ from_pointer_param_field(&stable) or {
113+ assert err.msg() == 'pointer field error'
114+ assert err.code() == 7
115+ }
116+ println('ok')
117+}
118+") or {
119+ panic(err)
120+ }
121+
122+ bin := os.join_path(os.temp_dir(), 'v3_ierror_local_pointer_payload_out_${os.getpid()}')
123+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
124+ assert compile.exit_code == 0, compile.output
125+ assert !compile.output.contains('C compilation failed'), compile.output
126+
127+ run := os.execute(bin)
128+ assert run.exit_code == 0, run.output
129+ assert run.output.trim_space() == 'ok'
130+
131+ c_code := os.read_file(bin + '.c') or { panic(err) }
132+ local_body := c_fn_body(c_code, 'Optional local_ptr(void) {')
133+ assert local_body.contains('._object = memdup('), local_body
134+ assert local_body.contains('sizeof(PtrErr)'), local_body
135+ assert !local_body.contains('._object = &err'), local_body
136+
137+ direct_body := c_fn_body(c_code, 'IError direct_local(void) {')
138+ assert direct_body.contains('._object = memdup('), direct_body
139+ assert direct_body.contains('sizeof(PtrErr)'), direct_body
140+ assert !direct_body.contains('._object = &err'), direct_body
141+
142+ param_body := c_fn_body(c_code, 'Optional from_param(PtrErr err) {')
143+ assert param_body.contains('._object = memdup('), param_body
144+ assert param_body.contains('sizeof(PtrErr)'), param_body
145+ assert !param_body.contains('._object = &err'), param_body
146+
147+ local_field_body := c_fn_body(c_code, 'Optional local_field(void) {')
148+ assert local_field_body.contains('._object = memdup('), local_field_body
149+ assert local_field_body.contains('sizeof(PtrErr)'), local_field_body
150+ assert !local_field_body.contains('._object = &holder.err'), local_field_body
151+
152+ param_field_body := c_fn_body(c_code, 'Optional from_param_field(Holder holder) {')
153+ assert param_field_body.contains('._object = memdup('), param_field_body
154+ assert param_field_body.contains('sizeof(PtrErr)'), param_field_body
155+ assert !param_field_body.contains('._object = &holder.err'), param_field_body
156+
157+ pointer_param_field_body := c_fn_body(c_code,
158+ 'Optional from_pointer_param_field(Holder* holder) {')
159+ assert !pointer_param_field_body.contains('._object = memdup('), pointer_param_field_body
160+}
@@ -0,0 +1,278 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_promoted_methods_test')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn test_ierror_dispatch_uses_promoted_embedded_methods() {
18+ v3_bin := build_v3()
19+
20+ root := os.join_path(os.temp_dir(), 'v3_ierror_promoted_methods_${os.getpid()}')
21+ os.mkdir_all(root) or { panic(err) }
22+ src := os.join_path(root, 'main.v')
23+ os.write_file(src, "struct BaseErr {
24+ text string
25+}
26+
27+fn helper_msg(s string) string {
28+ return s
29+}
30+
31+fn (err BaseErr) msg() string {
32+ return helper_msg(err.text)
33+}
34+
35+fn (err BaseErr) code() int {
36+ return 42
37+}
38+
39+struct WrapErr {
40+ BaseErr
41+}
42+
43+fn fail() !int {
44+ return WrapErr{
45+ BaseErr: BaseErr{
46+ text: 'promoted'
47+ }
48+ }
49+}
50+
51+fn main() {
52+ fail() or {
53+ println(err.msg() + ':' + err.code().str())
54+ return
55+ }
56+ println('bad')
57+}
58+") or {
59+ panic(err)
60+ }
61+
62+ bin := os.join_path(os.temp_dir(), 'v3_ierror_promoted_methods_out_${os.getpid()}')
63+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
64+ assert compile.exit_code == 0, compile.output
65+ assert !compile.output.contains('C compilation failed'), compile.output
66+
67+ c_code := os.read_file('${bin}.c') or { '' }
68+ assert c_code.contains('BaseErr__msg(((WrapErr*)i->_object)->BaseErr)'), c_code
69+ assert c_code.contains('helper_msg'), c_code
70+
71+ run := os.execute(bin)
72+ assert run.exit_code == 0, run.output
73+ assert run.output.trim_space() == 'promoted:42'
74+}
75+
76+fn test_ierror_dispatch_uses_promoted_embedded_pointer_methods() {
77+ v3_bin := build_v3()
78+
79+ root := os.join_path(os.temp_dir(), 'v3_ierror_promoted_pointer_methods_${os.getpid()}')
80+ os.mkdir_all(root) or { panic(err) }
81+ src := os.join_path(root, 'main.v')
82+ os.write_file(src, "struct PtrErr {
83+ text string
84+}
85+
86+fn (err &PtrErr) msg() string {
87+ return err.text
88+}
89+
90+fn (err &PtrErr) code() int {
91+ return 7
92+}
93+
94+struct PointerWrapErr {
95+ PtrErr &PtrErr
96+}
97+
98+fn fail_pointer() !int {
99+ return PointerWrapErr{
100+ PtrErr: &PtrErr{
101+ text: 'pointer'
102+ }
103+ }
104+}
105+
106+struct InnerErr {
107+ text string
108+}
109+
110+fn (err InnerErr) msg() string {
111+ return err.text
112+}
113+
114+fn (err InnerErr) code() int {
115+ return 8
116+}
117+
118+struct PointerInner {
119+ InnerErr &InnerErr
120+}
121+
122+struct NestedWrapErr {
123+ PointerInner
124+}
125+
126+fn fail_nested() !int {
127+ return NestedWrapErr{
128+ PointerInner: PointerInner{
129+ InnerErr: &InnerErr{
130+ text: 'nested'
131+ }
132+ }
133+ }
134+}
135+
136+fn main() {
137+ fail_pointer() or {
138+ assert err.msg() == 'pointer'
139+ assert err.code() == 7
140+ }
141+ fail_nested() or {
142+ assert err.msg() == 'nested'
143+ assert err.code() == 8
144+ }
145+ println('ok')
146+}
147+") or {
148+ panic(err)
149+ }
150+
151+ bin := os.join_path(os.temp_dir(), 'v3_ierror_promoted_pointer_methods_out_${os.getpid()}')
152+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
153+ assert compile.exit_code == 0, compile.output
154+ assert !compile.output.contains('C compilation failed'), compile.output
155+
156+ c_code := os.read_file('${bin}.c') or { '' }
157+ assert c_code.contains('PtrErr__msg(((PointerWrapErr*)i->_object)->PtrErr)'), c_code
158+ assert c_code.contains('InnerErr__msg(*((((NestedWrapErr*)i->_object)->PointerInner).InnerErr))'), c_code
159+
160+ run := os.execute(bin)
161+ assert run.exit_code == 0, run.output
162+ assert run.output.trim_space() == 'ok'
163+}
164+
165+fn test_ierror_embed_compatible_method_dependencies_are_marked_used() {
166+ v3_bin := build_v3()
167+
168+ root := os.join_path(os.temp_dir(), 'v3_ierror_embed_method_deps_${os.getpid()}')
169+ os.mkdir_all(root) or { panic(err) }
170+ src := os.join_path(root, 'main.v')
171+ os.write_file(src, "struct GhostError {
172+ Error
173+ text string
174+}
175+
176+fn ghost_msg_part(text string) string {
177+ return text
178+}
179+
180+fn (err GhostError) msg() string {
181+ return ghost_msg_part(err.text)
182+}
183+
184+fn fail() !int {
185+ return GhostError{
186+ text: 'ghost'
187+ }
188+}
189+
190+fn main() {
191+ fail() or {
192+ println('ok')
193+ return
194+ }
195+ println('bad')
196+}
197+") or {
198+ panic(err)
199+ }
200+
201+ bin := os.join_path(os.temp_dir(), 'v3_ierror_embed_method_deps_out_${os.getpid()}')
202+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
203+ assert compile.exit_code == 0, compile.output
204+ assert !compile.output.contains('C compilation failed'), compile.output
205+
206+ c_code := os.read_file('${bin}.c') or { '' }
207+ assert c_code.contains('GhostError__msg'), c_code
208+ assert c_code.contains('string ghost_msg_part('), c_code
209+
210+ run := os.execute(bin)
211+ assert run.exit_code == 0, run.output
212+ assert run.output.trim_space() == 'ok'
213+}
214+
215+fn test_ierror_direct_bad_signature_falls_back_to_promoted_method() {
216+ v3_bin := build_v3()
217+
218+ root := os.join_path(os.temp_dir(), 'v3_ierror_bad_direct_promoted_${os.getpid()}')
219+ os.mkdir_all(root) or { panic(err) }
220+ src := os.join_path(root, 'main.v')
221+ os.write_file(src, "struct BaseErr {
222+ text string
223+}
224+
225+fn (err BaseErr) msg() string {
226+ return err.text
227+}
228+
229+fn (err BaseErr) code() int {
230+ return 7
231+}
232+
233+struct WrapErr {
234+ BaseErr
235+}
236+
237+fn (err WrapErr) msg(extra int) string {
238+ return err.BaseErr.text + ':' + extra.str()
239+}
240+
241+fn (err WrapErr) code(extra int) int {
242+ return extra
243+}
244+
245+fn fail() !int {
246+ return WrapErr{
247+ BaseErr: BaseErr{
248+ text: 'embedded'
249+ }
250+ }
251+}
252+
253+fn main() {
254+ fail() or {
255+ println(err.msg() + ':' + err.code().str())
256+ return
257+ }
258+ println('bad')
259+}
260+") or {
261+ panic(err)
262+ }
263+
264+ bin := os.join_path(os.temp_dir(), 'v3_ierror_bad_direct_promoted_out_${os.getpid()}')
265+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
266+ assert compile.exit_code == 0, compile.output
267+ assert !compile.output.contains('C compilation failed'), compile.output
268+
269+ c_code := os.read_file('${bin}.c') or { '' }
270+ assert c_code.contains('BaseErr__msg(((WrapErr*)i->_object)->BaseErr)'), c_code
271+ assert c_code.contains('BaseErr__code(((WrapErr*)i->_object)->BaseErr)'), c_code
272+ assert !c_code.contains('return WrapErr__msg(*(WrapErr*)i->_object)'), c_code
273+ assert !c_code.contains('return WrapErr__code(*(WrapErr*)i->_object)'), c_code
274+
275+ run := os.execute(bin)
276+ assert run.exit_code == 0, run.output
277+ assert run.output.trim_space() == 'embedded:7'
278+}
@@ -0,0 +1,155 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_ierror_result_payload_success_test')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn c_fn_body(c_code string, header string) string {
18+ start := c_code.index(header) or { return '' }
19+ tail := c_code[start..]
20+ end := tail.index('\n}\n') or { return tail }
21+ return tail[..end + 3]
22+}
23+
24+fn test_ierror_shaped_result_payload_returns_success_before_error_boxing() {
25+ v3_bin := build_v3()
26+
27+ root := os.join_path(os.temp_dir(), 'v3_ierror_result_payload_success_${os.getpid()}')
28+ os.mkdir_all(root) or { panic(err) }
29+ src := os.join_path(root, 'main.v')
30+ os.write_file(src, "struct MyErr {
31+ label string
32+}
33+
34+fn (err MyErr) msg() string {
35+ return err.label
36+}
37+
38+fn (err MyErr) code() int {
39+ return 7
40+}
41+
42+fn payload() !MyErr {
43+ return MyErr{
44+ label: 'payload'
45+ }
46+}
47+
48+fn payload_defer() !MyErr {
49+ defer {
50+ assert true
51+ }
52+ return MyErr{
53+ label: 'payload-defer'
54+ }
55+}
56+
57+fn payload_ierror() !IError {
58+ return MyErr{
59+ label: 'payload-ierror'
60+ }
61+}
62+
63+fn payload_ierror_defer() !IError {
64+ defer {
65+ assert true
66+ }
67+ return MyErr{
68+ label: 'payload-ierror-defer'
69+ }
70+}
71+
72+fn fail() !int {
73+ return MyErr{
74+ label: 'real-error'
75+ }
76+}
77+
78+fn make_err() IError {
79+ return error('boom')
80+}
81+
82+fn fail_interface() !int {
83+ return make_err()
84+}
85+
86+fn fail_interface_defer() !int {
87+ defer {
88+ assert true
89+ }
90+ return make_err()
91+}
92+
93+fn main() {
94+ x := payload() or {
95+ println('ERR:' + err.msg())
96+ return
97+ }
98+ println('OK:' + x.msg())
99+
100+ y := payload_defer() or {
101+ println('ERR_DEFER:' + err.msg())
102+ return
103+ }
104+ println('OK:' + y.msg())
105+
106+ i := payload_ierror() or {
107+ println('IERR_PAYLOAD_ERR:' + err.msg())
108+ return
109+ }
110+ println('IERR_OK:' + i.msg())
111+
112+ j := payload_ierror_defer() or {
113+ println('IERR_DEFER_PAYLOAD_ERR:' + err.msg())
114+ return
115+ }
116+ println('IERR_DEFER_OK:' + j.msg())
117+
118+ fail() or {
119+ println('ERR:' + err.msg() + ':' + err.code().str())
120+ }
121+
122+ fail_interface() or {
123+ println('IERR:' + err.msg())
124+ }
125+
126+ fail_interface_defer() or {
127+ println('IERR_DEFER:' + err.msg())
128+ return
129+ }
130+ println('BAD:success')
131+}
132+") or {
133+ panic(err)
134+ }
135+
136+ bin := os.join_path(os.temp_dir(), 'v3_ierror_result_payload_success_out_${os.getpid()}')
137+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
138+ assert compile.exit_code == 0, compile.output
139+ assert !compile.output.contains('C compilation failed'), compile.output
140+
141+ run := os.execute(bin)
142+ assert run.exit_code == 0, run.output
143+ assert run.output.trim_space() == 'OK:payload\nOK:payload-defer\nIERR_OK:payload-ierror\nIERR_DEFER_OK:payload-ierror-defer\nERR:real-error:7\nIERR:boom\nIERR_DEFER:boom'
144+
145+ c_code := os.read_file(bin + '.c') or { panic(err) }
146+ payload_ierror_body := c_fn_body(c_code, 'Optional_IError payload_ierror(void) {')
147+ assert payload_ierror_body.contains('.ok = true, .value = (IError){._typ = '), payload_ierror_body
148+ assert !payload_ierror_body.contains('.ok = false'), payload_ierror_body
149+
150+ payload_ierror_defer_body := c_fn_body(c_code, 'Optional_IError payload_ierror_defer(void) {')
151+ assert payload_ierror_defer_body.contains('= (Optional_IError){.ok = true, .value = '), payload_ierror_defer_body
152+
153+ assert payload_ierror_defer_body.contains('(IError){._typ = '), payload_ierror_defer_body
154+ assert !payload_ierror_defer_body.contains('= (Optional_IError){.ok = false'), payload_ierror_defer_body
155+}
@@ -0,0 +1,240 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_interface_type_pattern_test')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn test_interface_type_patterns_resolve_imported_concrete_types() {
18+ v3_bin := build_v3()
19+
20+ root := os.join_path(os.temp_dir(), 'v3_interface_type_pattern_${os.getpid()}')
21+ mod_dir := os.join_path(root, 'ifacepkg')
22+ os.mkdir_all(mod_dir) or { panic(err) }
23+ os.write_file(os.join_path(mod_dir, 'ifacepkg.v'), "module ifacepkg
24+
25+pub interface Thing {
26+ name() string
27+}
28+
29+pub struct Foo {}
30+
31+pub fn (f Foo) name() string {
32+ return 'foo'
33+}
34+
35+pub struct Bar {}
36+
37+pub fn (b Bar) name() string {
38+ return 'bar'
39+}
40+
41+pub fn make_foo() Thing {
42+ return Foo{}
43+}
44+
45+pub fn make_bar() Thing {
46+ return Bar{}
47+}
48+
49+pub fn is_foo(x Thing) bool {
50+ return x is Foo
51+}
52+
53+pub fn classify(x Thing) string {
54+ match x {
55+ Foo {
56+ return 'foo'
57+ }
58+ Bar {
59+ return 'bar'
60+ }
61+ else {
62+ return 'else'
63+ }
64+ }
65+}
66+") or {
67+ panic(err)
68+ }
69+
70+ src := os.join_path(root, 'main.v')
71+ os.write_file(src, 'import ifacepkg
72+import ifacepkg { Foo }
73+
74+interface Named {
75+ name string
76+}
77+
78+struct User {
79+ name string
80+ age int
81+}
82+
83+fn describe_named(n Named) string {
84+ return match n {
85+ User { n.name + ":" + int_str(n.age) }
86+ else { n.name }
87+ }
88+}
89+
90+interface Labelled {
91+ label string
92+}
93+
94+struct Box {
95+ label string
96+ size int
97+}
98+
99+struct Other {
100+ label string
101+}
102+
103+type BoxSum = Box | Other
104+
105+fn describe_labelled(x Labelled) string {
106+ return match x {
107+ Box { x.label + ":" + int_str(x.size) }
108+ else { x.label }
109+ }
110+}
111+
112+fn main() {
113+ foo := ifacepkg.make_foo()
114+ bar := ifacepkg.make_bar()
115+ println(ifacepkg.is_foo(foo).str())
116+ println(ifacepkg.is_foo(bar).str())
117+ println((foo is ifacepkg.Foo).str())
118+ println((bar is ifacepkg.Foo).str())
119+ println((foo is Foo).str())
120+ println((bar is Foo).str())
121+ println(ifacepkg.classify(foo))
122+ println(ifacepkg.classify(bar))
123+ println(describe_named(User{
124+ name: "Ada"
125+ age: 37
126+ }))
127+ println(describe_labelled(Box{
128+ label: "b"
129+ size: 4
130+ }))
131+}
132+') or {
133+ panic(err)
134+ }
135+
136+ bin := os.join_path(os.temp_dir(), 'v3_interface_type_pattern_out_${os.getpid()}')
137+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
138+ assert compile.exit_code == 0, compile.output
139+ assert !compile.output.contains('C compilation failed'), compile.output
140+
141+ c_code := os.read_file('${bin}.c') or { '' }
142+ assert c_code.contains('._typ =='), c_code
143+ assert !c_code.contains('return x._object != NULL;'), c_code
144+ assert !c_code.contains('x == Foo'), c_code
145+ assert !c_code.contains('x == Bar'), c_code
146+ assert !c_code.contains('n.age'), c_code
147+ assert !c_code.contains('x.Box'), c_code
148+
149+ run := os.execute(bin)
150+ assert run.exit_code == 0, run.output
151+ assert run.output.trim_space() == 'true\nfalse\ntrue\nfalse\ntrue\nfalse\nfoo\nbar\nAda:37\nb:4'
152+}
153+
154+fn test_interface_type_patterns_resolve_import_aliases() {
155+ v3_bin := build_v3()
156+
157+ root := os.join_path(os.temp_dir(), 'v3_interface_type_pattern_alias_${os.getpid()}')
158+ mod_dir := os.join_path(root, 'shapes')
159+ os.mkdir_all(mod_dir) or { panic(err) }
160+ os.write_file(os.join_path(mod_dir, 'shapes.v'), 'module shapes
161+
162+pub interface Shape {
163+ area() int
164+}
165+
166+pub struct Rect {
167+pub:
168+ w int
169+}
170+
171+pub fn (r Rect) area() int {
172+ return r.w
173+}
174+
175+pub struct Circle {
176+pub:
177+ r int
178+}
179+
180+pub fn (c Circle) area() int {
181+ return c.r
182+}
183+
184+pub fn make_rect() Shape {
185+ return Rect{
186+ w: 3
187+ }
188+}
189+
190+pub fn make_circle() Shape {
191+ return Circle{
192+ r: 4
193+ }
194+}
195+') or {
196+ panic(err)
197+ }
198+
199+ src := os.join_path(root, 'main.v')
200+ os.write_file(src, 'import shapes as sh
201+import shapes { Shape }
202+
203+fn is_rect(s Shape) bool {
204+ return s is sh.Rect
205+}
206+
207+fn classify(s Shape) string {
208+ return match s {
209+ sh.Rect { "rect" }
210+ else { "other" }
211+ }
212+}
213+
214+fn main() {
215+ rect := sh.make_rect()
216+ circle := sh.make_circle()
217+ println(is_rect(rect).str())
218+ println(is_rect(circle).str())
219+ println(classify(rect))
220+ println(classify(circle))
221+}
222+') or {
223+ panic(err)
224+ }
225+
226+ bin := os.join_path(os.temp_dir(), 'v3_interface_type_pattern_alias_out_${os.getpid()}')
227+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
228+ assert compile.exit_code == 0, compile.output
229+ assert !compile.output.contains('C compilation failed'), compile.output
230+
231+ c_code := os.read_file('${bin}.c') or { '' }
232+ assert c_code.contains('._typ =='), c_code
233+ assert !c_code.contains('return true;\\n}\\n\\nstring classify'), c_code
234+ assert !c_code.contains('s == shapes__Rect'), c_code
235+ assert !c_code.contains('s == sh__Rect'), c_code
236+
237+ run := os.execute(bin)
238+ assert run.exit_code == 0, run.output
239+ assert run.output.trim_space() == 'true\nfalse\nrect\nother'
240+}
@@ -0,0 +1,44 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_join_path_markused_${os.getpid()}')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn test_join_path_lowering_roots_join_path_single() {
18+ v3_bin := build_v3()
19+ src := os.join_path(os.temp_dir(), 'v3_join_path_markused_input_${os.getpid()}.v')
20+ os.write_file(src, "import os
21+
22+fn main() {
23+ path := os.join_path('a', 'b', 'c')
24+ expected := 'a' + os.path_separator + 'b' + os.path_separator + 'c'
25+ assert path == expected
26+ println(path)
27+}
28+") or {
29+ panic(err)
30+ }
31+
32+ bin := os.join_path(os.temp_dir(), 'v3_join_path_markused_input_${os.getpid()}')
33+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
34+ assert compile.exit_code == 0, compile.output
35+ assert !compile.output.contains('C compilation failed'), compile.output
36+
37+ generated := os.read_file(bin + '.c') or { panic(err) }
38+ assert generated.contains('os__join_path_single'), generated
39+
40+ run := os.execute(bin)
41+ assert run.exit_code == 0, run.output
42+ expected := 'a' + os.path_separator + 'b' + os.path_separator + 'c'
43+ assert run.output.trim_space() == expected
44+}
@@ -0,0 +1,91 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_local_ierror_interface_test')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn test_module_local_ierror_interface_is_not_builtin_ierror() {
18+ v3_bin := build_v3()
19+
20+ root := os.join_path(os.temp_dir(), 'v3_local_ierror_interface_${os.getpid()}')
21+ mod_dir := os.join_path(root, 'pkg')
22+ os.mkdir_all(mod_dir) or { panic(err) }
23+ os.write_file(os.join_path(mod_dir, 'pkg.v'), "module pkg
24+
25+pub interface IError {
26+ name() string
27+}
28+
29+pub struct LocalErr {
30+ text string
31+}
32+
33+pub fn (err LocalErr) name() string {
34+ return err.text
35+}
36+
37+pub fn make() IError {
38+ return LocalErr{
39+ text: 'local'
40+ }
41+}
42+
43+pub fn make_ref() &IError {
44+ return &IError(LocalErr{
45+ text: 'local-ref'
46+ })
47+}
48+
49+pub fn show(err IError) string {
50+ return err.name()
51+}
52+
53+pub fn show_ref(err &IError) string {
54+ return err.name()
55+}
56+ ") or {
57+ panic(err)
58+ }
59+
60+ src := os.join_path(root, 'main.v')
61+ os.write_file(src, 'import pkg
62+
63+fn main() {
64+ err := pkg.make()
65+ println(pkg.show(err))
66+ ref := pkg.make_ref()
67+ println(pkg.show_ref(ref))
68+}
69+ ') or {
70+ panic(err)
71+ }
72+
73+ bin := os.join_path(os.temp_dir(), 'v3_local_ierror_interface_input')
74+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
75+ assert compile.exit_code == 0, compile.output
76+ assert !compile.output.contains('C compilation failed'), compile.output
77+
78+ c_code := os.read_file('${bin}.c') or { '' }
79+ assert c_code.contains('pkg__IError pkg__make'), c_code
80+ assert c_code.contains('string pkg__show(pkg__IError err)'), c_code
81+ assert c_code.contains('pkg__IError* pkg__make_ref'), c_code
82+ assert c_code.contains('string pkg__show_ref(pkg__IError* err)'), c_code
83+ assert !c_code.contains('\nIError pkg__make'), c_code
84+ assert !c_code.contains('string pkg__show(IError err)'), c_code
85+ assert !c_code.contains('\nIError* pkg__make_ref'), c_code
86+ assert !c_code.contains('string pkg__show_ref(IError* err)'), c_code
87+
88+ run := os.execute(bin)
89+ assert run.exit_code == 0, run.output
90+ assert run.output.trim_space() == 'local\nlocal-ref'
91+}
@@ -0,0 +1,99 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn map_keys_reverse_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_map_keys_array_reverse_codegen_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_map_keys_and_array_reverse_keep_concrete_array_types() {
19+ v3_bin := map_keys_reverse_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_map_keys_array_reverse_input_${os.getpid()}.v')
21+ os.write_file(src, "fn first_key(m map[string][]string) string {
22+ keys := m.keys()
23+ key := keys.first()
24+ return key
25+}
26+
27+fn main() {
28+ m := {
29+ 'A': ['B', 'C']
30+ 'DD': ['E']
31+ }
32+ mut key_score := 0
33+ for k in m.keys() {
34+ key_score += k.len
35+ }
36+ assert key_score == 3
37+
38+ keys := m.keys()
39+ mut keys_score := 0
40+ for k in keys {
41+ keys_score += k.len
42+ }
43+ assert keys_score == 3
44+
45+ mut values := ''
46+ for v in m['A'] {
47+ values += v
48+ }
49+ assert values == 'BC'
50+
51+ int_map := {
52+ 1: 'one'
53+ 20: 'twenty'
54+ }
55+ mut int_key_total := 0
56+ for k in int_map.keys() {
57+ int_key_total += k
58+ }
59+ assert int_key_total == 21
60+
61+ mut arr := []string{}
62+ arr << 'one'
63+ arr << 'two'
64+ arr << 'three'
65+ mut rev := ''
66+ for v in arr.reverse() {
67+ rev += v + ';'
68+ }
69+ assert rev == 'three;two;one;'
70+
71+ mut nums := []int{}
72+ nums << 3
73+ nums << 5
74+ nums << 8
75+ mut reversed_number := 0
76+ for v in nums.reverse() {
77+ reversed_number = reversed_number * 10 + v
78+ }
79+ assert reversed_number == 853
80+
81+ single := {
82+ 'z': ['payload']
83+ }
84+ assert first_key(single) == 'z'
85+ println('ok')
86+}
87+") or {
88+ panic(err)
89+ }
90+
91+ bin := os.join_path(os.temp_dir(), 'v3_map_keys_array_reverse_input_${os.getpid()}')
92+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
93+ assert compile.exit_code == 0, compile.output
94+ assert !compile.output.contains('C compilation failed'), compile.output
95+
96+ run := os.execute(bin)
97+ assert run.exit_code == 0, run.output
98+ assert run.output.trim_space() == 'ok'
99+}
@@ -0,0 +1,54 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_map_receiver_method_markused_${os.getpid()}')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn test_map_receiver_method_is_rooted_by_markused() {
18+ v3_bin := build_v3()
19+ src := os.join_path(os.temp_dir(), 'v3_map_receiver_method_markused_input_${os.getpid()}.v')
20+ os.write_file(src, "struct Entry {
21+ kind int
22+}
23+
24+fn (m map[string]Entry) find(name string) ?Entry {
25+ if name in m {
26+ return m[name]
27+ }
28+ return none
29+}
30+
31+fn main() {
32+ mut items := map[string]Entry{}
33+ items['name'] = Entry{
34+ kind: 41
35+ }
36+ got := items.find('name') or { return }
37+ println(got.kind)
38+}
39+") or {
40+ panic(err)
41+ }
42+
43+ bin := os.join_path(os.temp_dir(), 'v3_map_receiver_method_markused_input_${os.getpid()}')
44+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
45+ assert compile.exit_code == 0, compile.output
46+ assert !compile.output.contains('C compilation failed'), compile.output
47+
48+ generated := os.read_file(bin + '.c') or { panic(err) }
49+ assert generated.contains('map_stringEntry__find'), generated
50+
51+ run := os.execute(bin)
52+ assert run.exit_code == 0, run.output
53+ assert run.output.trim_space() == '41'
54+}
@@ -188,6 +188,31 @@ fn main() {
188188 assert used['string__plus']
189189}
190190
191+fn test_map_str_seeds_string_plus_runtime_helper() {
192+ used := mark_used_source('map_str_string_plus', '
193+fn render(m map[string]int) string {
194+ return m.str()
195+}
196+
197+fn main() {
198+ _ := render(map[string]int{})
199+}
200+')
201+ assert used['string__plus']
202+}
203+
204+fn test_print_map_seeds_string_plus_runtime_helper() {
205+ used := mark_used_source('print_map_string_plus', '
206+fn main() {
207+ m := {
208+ "a": 1
209+ }
210+ println(m)
211+}
212+')
213+ assert used['string__plus']
214+}
215+
191216fn test_moduleless_export_after_module_file_is_rooted() {
192217 a, tc := parse_checked_project_in_order('moduleless_export_after_module', [
193218 'helper/helper.v',
@@ -0,0 +1,363 @@
1+import os
2+import v3.flat
3+import v3.parser
4+import v3.pref
5+import v3.types
6+
7+const local_vexe = @VEXE
8+const local_tests_dir = os.dir(@FILE)
9+const local_v3_dir = os.dir(local_tests_dir)
10+const local_vlib_dir = os.dir(local_v3_dir)
11+
12+fn proxy_to_local_v3_if_needed() bool {
13+ if os.getenv('V3_MATCH_GENERIC_PATTERN_INNER') == '1' {
14+ return false
15+ }
16+ cmd := 'V3_MATCH_GENERIC_PATTERN_INNER=1 ${os.quoted_path(local_vexe)} -gc none -path "${local_vlib_dir}|@vlib|@vmodules" ${os.quoted_path(@FILE)}'
17+ result := os.execute(cmd)
18+ assert result.exit_code == 0, result.output
19+ return true
20+}
21+
22+fn parse_match_generic_pattern_source(src string) &flat.FlatAst {
23+ path := os.join_path(os.temp_dir(), 'v3_match_generic_pattern_${os.getpid()}.v')
24+ os.write_file(path, src) or { panic(err) }
25+ prefs := pref.new_preferences()
26+ mut p := parser.Parser.new(prefs)
27+ p.parse_into(path)
28+ return p.a
29+}
30+
31+fn find_fn_node(a &flat.FlatAst, name string) flat.Node {
32+ for node in a.nodes {
33+ if node.kind == .fn_decl && node.value == name {
34+ return node
35+ }
36+ }
37+ assert false, 'missing fn `${name}`'
38+ return flat.Node{}
39+}
40+
41+fn find_match_branch_with_pattern(a &flat.FlatAst, pattern string) flat.Node {
42+ for node in a.nodes {
43+ if node.kind != .match_branch || node.children_count == 0 {
44+ continue
45+ }
46+ cond := a.child_node(&node, 0)
47+ if cond.kind == .ident && cond.value == pattern {
48+ return node
49+ }
50+ if cond.kind == .selector && cond.value == pattern {
51+ return node
52+ }
53+ }
54+ assert false, 'missing match branch pattern `${pattern}`'
55+ return flat.Node{}
56+}
57+
58+fn final_file_node(a &flat.FlatAst) flat.Node {
59+ for i := a.nodes.len - 1; i >= 0; i-- {
60+ node := a.nodes[i]
61+ if node.kind == .file && node.children_count > 0 {
62+ return node
63+ }
64+ }
65+ assert false, 'missing final file node'
66+ return flat.Node{}
67+}
68+
69+fn file_child_fn_names(a &flat.FlatAst) []string {
70+ file_node := final_file_node(a)
71+ mut names := []string{}
72+ for i in 0 .. file_node.children_count {
73+ child := a.child_node(&file_node, i)
74+ if child.kind == .fn_decl {
75+ names << child.value
76+ }
77+ }
78+ return names
79+}
80+
81+fn find_match_branch_with_qualified_pattern(a &flat.FlatAst, module_name string, pattern string) flat.Node {
82+ for node in a.nodes {
83+ if node.kind != .match_branch || node.children_count == 0 {
84+ continue
85+ }
86+ cond := a.child_node(&node, 0)
87+ if cond.kind != .selector || cond.value != pattern || cond.children_count == 0 {
88+ continue
89+ }
90+ base := a.child_node(cond, 0)
91+ if base.kind == .ident && base.value == module_name {
92+ return node
93+ }
94+ }
95+ assert false, 'missing match branch pattern `${module_name}.${pattern}`'
96+ return flat.Node{}
97+}
98+
99+fn branch_has_direct_array_literal(a &flat.FlatAst, branch flat.Node) bool {
100+ for i in 1 .. branch.children_count {
101+ child := a.child_node(&branch, i)
102+ if child.kind == .array_literal {
103+ return true
104+ }
105+ if child.kind == .expr_stmt && child.children_count > 0 {
106+ expr := a.child_node(child, 0)
107+ if expr.kind == .array_literal {
108+ return true
109+ }
110+ }
111+ }
112+ return false
113+}
114+
115+fn test_match_branch_generic_type_patterns_are_consumed() {
116+ if proxy_to_local_v3_if_needed() {
117+ return
118+ }
119+ a := parse_match_generic_pattern_source('
120+struct Empty {}
121+
122+struct Node[T] {
123+ value T
124+}
125+
126+type Tree[T] = Empty | Node[T]
127+
128+fn walk[T](tree Tree[T]) int {
129+ return match tree {
130+ Empty { 0 }
131+ Node[T] { tree.value }
132+ }
133+}
134+
135+fn qualified[T](tree Tree[T]) int {
136+ return match tree {
137+ foo.Node[T] { 1 }
138+ else { 0 }
139+ }
140+}
141+
142+fn nested_arg[T](tree Tree[T]) int {
143+ return match tree {
144+ Node[foo.Bar] { 1 }
145+ else { 0 }
146+ }
147+}
148+
149+fn after() int {
150+ return 7
151+}
152+')
153+
154+ names := file_child_fn_names(a)
155+ assert names == ['walk', 'qualified', 'nested_arg', 'after']
156+ find_fn_node(a, 'after')
157+
158+ node_branch := find_match_branch_with_pattern(a, 'Node[T]')
159+ assert node_branch.children_count == 2
160+ assert !branch_has_direct_array_literal(a, node_branch)
161+
162+ nested_arg_branch := find_match_branch_with_pattern(a, 'Node[foo.Bar]')
163+ nested_cond := a.child_node(&nested_arg_branch, 0)
164+ assert nested_cond.kind == .ident
165+ assert nested_cond.value == 'Node[foo.Bar]'
166+
167+ qualified_branch := find_match_branch_with_qualified_pattern(a, 'foo', 'Node[T]')
168+ cond := a.child_node(&qualified_branch, 0)
169+ base := a.child_node(cond, 0)
170+ assert base.kind == .ident
171+ assert base.value == 'foo'
172+ assert cond.value == 'Node[T]'
173+}
174+
175+fn check_source_errors(name string, src string) []types.TypeError {
176+ path := os.join_path(os.temp_dir(), 'v3_generic_sum_checker_${name}_${os.getpid()}.v')
177+ os.write_file(path, src) or { panic(err) }
178+ prefs := pref.new_preferences()
179+ mut p := parser.Parser.new(prefs)
180+ mut a := p.parse_file(path)
181+ mut tc := types.TypeChecker.new(a)
182+ tc.collect(a)
183+ tc.diagnose_unknown_calls = true
184+ tc.diagnostic_files[path] = true
185+ tc.check_semantics()
186+ return tc.errors.clone()
187+}
188+
189+fn assert_checker_ok(name string, src string) {
190+ errors := check_source_errors(name, src)
191+ assert errors.len == 0, errors.str()
192+}
193+
194+fn assert_checker_error_contains(name string, src string, expected string) {
195+ errors := check_source_errors(name, src)
196+ assert errors.len > 0
197+ mut text := ''
198+ for err in errors {
199+ text += err.msg + '\n'
200+ }
201+ assert text.contains(expected), text
202+}
203+
204+const generic_sum_defs = '
205+struct Empty {}
206+
207+struct Node[T] {
208+ value T
209+ left Tree[T]
210+}
211+
212+struct Other[T] {
213+ value T
214+}
215+
216+type Tree[T] = Empty | Node[T]
217+'
218+
219+fn test_generic_sum_type_checker_accepts_shared_field_on_concrete_sum() {
220+ if proxy_to_local_v3_if_needed() {
221+ return
222+ }
223+ assert_checker_ok('generic_sum_shared_field', '
224+struct A[T] {
225+ x T
226+}
227+
228+struct B[T] {
229+ x T
230+}
231+
232+type S[T] = A[T] | B[T]
233+
234+fn get(s S[int]) int {
235+ return s.x
236+}
237+')
238+}
239+
240+fn test_generic_sum_type_checker_accepts_methods_returns_and_smartcasts() {
241+ if proxy_to_local_v3_if_needed() {
242+ return
243+ }
244+ assert_checker_ok('generic_sum_method_and_returns', generic_sum_defs +
245+ '
246+fn (tree Tree[T]) size[T]() int {
247+ return match tree {
248+ Empty { 0 }
249+ Node[T] { 1 +
250+ tree.left.size() }
251+ }
252+}
253+
254+fn make_int_tree() Tree[int] {
255+ return Node[int]{
256+ value: 1
257+ left: Empty{}
258+ }
259+}
260+
261+fn make_tree[T](value T) Tree[T] {
262+ return Node[T]{
263+ value: value
264+ left: Empty{}
265+ }
266+}
267+
268+fn field_after_is[T](tree Tree[T]) T {
269+ if tree is Node[T] {
270+ return tree.value
271+ }
272+ return T(0)
273+}
274+
275+fn main() {
276+ tree := make_int_tree()
277+ _ := tree.size()
278+}
279+')
280+}
281+
282+fn test_generic_sum_type_checker_rejects_wrong_generic_variant() {
283+ if proxy_to_local_v3_if_needed() {
284+ return
285+ }
286+ assert_checker_error_contains('generic_sum_wrong_variant_arg', generic_sum_defs +
287+ '
288+fn bad() Tree[int] {
289+ return Node[string]{
290+ value: "bad"
291+ left: Empty{}
292+ }
293+}
294+
295+fn main() {}
296+',
297+ 'cannot return `Node[string]` as `Tree[int]`')
298+}
299+
300+fn test_generic_sum_type_checker_rejects_non_variant_pattern() {
301+ if proxy_to_local_v3_if_needed() {
302+ return
303+ }
304+ assert_checker_error_contains('generic_sum_non_variant_pattern', generic_sum_defs +
305+ '
306+fn bad(tree Tree[int]) int {
307+ return match tree {
308+ Other[int] { 1 }
309+ else { 0 }
310+ }
311+}
312+
313+fn main() {}
314+',
315+ '`Other[int]` is not a variant of sum type `Tree[int]`')
316+}
317+
318+fn test_generic_sum_type_checker_accepts_qualified_generic_variant_pattern() {
319+ if proxy_to_local_v3_if_needed() {
320+ return
321+ }
322+ root := os.join_path(os.temp_dir(), 'v3_generic_sum_checker_qualified_${os.getpid()}')
323+ os.rmdir_all(root) or {}
324+ foo_path := os.join_path(root, 'foo', 'foo.v')
325+ main_path := os.join_path(root, 'main.v')
326+ os.mkdir_all(os.dir(foo_path)) or { panic(err) }
327+ os.write_file(foo_path, 'module foo
328+
329+pub struct Node[T] {
330+pub:
331+ value T
332+}
333+') or { panic(err) }
334+ os.write_file(main_path, 'module main
335+
336+import foo
337+
338+struct Empty {}
339+
340+type Tree[T] = Empty | foo.Node[T]
341+
342+fn value(tree Tree[int]) int {
343+ return match tree {
344+ foo.Node[int] { tree.value }
345+ Empty { 0 }
346+ }
347+}
348+
349+fn main() {}
350+') or {
351+ panic(err)
352+ }
353+ prefs := pref.new_preferences()
354+ mut p := parser.Parser.new(prefs)
355+ mut a := p.parse_files([main_path, foo_path])
356+ mut tc := types.TypeChecker.new(a)
357+ tc.collect(a)
358+ tc.diagnose_unknown_calls = true
359+ tc.diagnostic_files[main_path] = true
360+ tc.diagnostic_files[foo_path] = true
361+ tc.check_semantics()
362+ assert tc.errors.len == 0, tc.errors.str()
363+}
@@ -0,0 +1,106 @@
1+import os
2+
3+const module_fn_collision_vexe = @VEXE
4+const module_fn_collision_tests_dir = os.dir(@FILE)
5+const module_fn_collision_v3_dir = os.dir(module_fn_collision_tests_dir)
6+const module_fn_collision_vlib_dir = os.dir(module_fn_collision_v3_dir)
7+const module_fn_collision_v3_src = os.join_path(module_fn_collision_v3_dir, 'v3.v')
8+
9+fn module_fn_collision_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_module_fn_collision_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${module_fn_collision_vexe} -gc none -path "${module_fn_collision_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${module_fn_collision_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn module_fn_collision_write_project() string {
19+ root := os.join_path(os.temp_dir(), 'v3_module_fn_collision_${os.getpid()}')
20+ os.rmdir_all(root) or {}
21+ os.mkdir_all(os.join_path(root, 'collisionmod')) or { panic(err) }
22+ os.mkdir_all(os.join_path(root, 'localmod')) or { panic(err) }
23+ os.write_file(os.join_path(root, 'v.mod'), "Module { name: 'module_fn_collision' }\n") or {
24+ panic(err)
25+ }
26+ os.write_file(os.join_path(root, 'collisionmod/collisionmod.v'), 'module collisionmod
27+
28+struct Node[T] {
29+ value T
30+}
31+
32+pub struct Holder[T] {
33+ node &Node[T]
34+}
35+
36+fn new_node[T](value T) &Node[T] {
37+ return &Node[T]{
38+ value: value
39+ }
40+}
41+
42+pub fn make[T](value T) Holder[T] {
43+ return Holder[T]{
44+ node: new_node(value)
45+ }
46+}
47+
48+pub fn value[T](holder Holder[T]) T {
49+ return holder.node.value
50+}
51+') or {
52+ panic(err)
53+ }
54+ os.write_file(os.join_path(root, 'localmod/localmod.v'), 'module localmod
55+
56+fn helper() int {
57+ return 41
58+}
59+
60+pub fn use_helper() int {
61+ return helper() + 1
62+}
63+') or {
64+ panic(err)
65+ }
66+ os.write_file(os.join_path(root, 'main.v'), "module main
67+
68+import collisionmod
69+import localmod
70+
71+fn main() {
72+ holder := collisionmod.make(7)
73+ assert collisionmod.value(holder) == 7
74+ assert localmod.use_helper() == 42
75+ mut values := map[string]int{}
76+ values['answer'] = 42
77+ assert values['answer'] == 42
78+ println('ok')
79+}
80+") or {
81+ panic(err)
82+ }
83+ return os.join_path(root, 'main.v')
84+}
85+
86+fn test_imported_module_fn_short_name_does_not_pollute_builtin_return_type() {
87+ v3_bin := module_fn_collision_build_v3()
88+ main_path := module_fn_collision_write_project()
89+ out := os.join_path(os.temp_dir(), 'v3_module_fn_collision_out_${os.getpid()}')
90+ os.rm(out) or {}
91+ os.rm(out + '.c') or {}
92+ compile := os.execute('${v3_bin} ${main_path} -b c -o ${out}')
93+ assert compile.exit_code == 0, compile.output
94+ assert !compile.output.contains('C compilation failed'), compile.output
95+
96+ run := os.execute(out)
97+ assert run.exit_code == 0, run.output
98+ assert run.output.trim_space() == 'ok'
99+
100+ generated := os.read_file(out + '.c') or { panic(err) }
101+ assert generated.contains('mapnode* z = new_node();'), generated
102+ assert generated.contains('collisionmod__new_node_T_v_int'), generated
103+ assert generated.contains('localmod__helper()'), generated
104+ assert !generated.contains('collisionmod__Node_T* z = new_node();'), generated
105+ assert !generated.contains('Array_fixed_collisionmod__Node_T* z = new_node();'), generated
106+}
@@ -0,0 +1,70 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_mut_param_array_clone_test_${os.getpid()}')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn test_mut_array_param_clone_reassigns_value_array() {
18+ v3_bin := build_v3()
19+
20+ src := os.join_path(os.temp_dir(), 'v3_mut_param_array_clone_input_${os.getpid()}.v')
21+ os.write_file(src, "fn update(mut xs []int) {
22+ mut ys := xs.clone()
23+ ys[0] = 2
24+ xs = ys.clone()
25+}
26+
27+fn main() {
28+ mut xs := [1, 1]
29+ update(mut xs)
30+ assert xs[0] == 2
31+ assert xs[1] == 1
32+ println('ok')
33+}
34+") or {
35+ panic(err)
36+ }
37+
38+ bin := os.join_path(os.temp_dir(), 'v3_mut_param_array_clone_input_${os.getpid()}')
39+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
40+ assert compile.exit_code == 0, compile.output
41+ assert !compile.output.contains('cannot assign `&[]int` to `[]int`'), compile.output
42+ assert !compile.output.contains('C compilation failed'), compile.output
43+
44+ run := os.execute(bin)
45+ assert run.exit_code == 0, run.output
46+ assert run.output.trim_space() == 'ok'
47+}
48+
49+fn test_mut_array_param_assignment_still_rejects_non_array() {
50+ v3_bin := build_v3()
51+
52+ src := os.join_path(os.temp_dir(), 'v3_mut_array_param_bad_assign_${os.getpid()}.v')
53+ os.write_file(src, 'fn update(mut xs []int) {
54+ xs = 1
55+}
56+
57+fn main() {
58+ mut xs := [1, 2]
59+ update(mut xs)
60+}
61+') or {
62+ panic(err)
63+ }
64+
65+ bin := os.join_path(os.temp_dir(), 'v3_mut_array_param_bad_assign_${os.getpid()}')
66+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
67+ assert compile.exit_code != 0, compile.output
68+ assert compile.output.contains('cannot assign'), compile.output
69+ assert !compile.output.contains('C compilation failed'), compile.output
70+}
@@ -0,0 +1,214 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn mut_param_reassign_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_mut_param_reassign_codegen_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn mut_param_reassign_run_good(v3_bin string, name string, source string) string {
19+ src := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}.v')
20+ os.write_file(src, source) or { panic(err) }
21+ bin := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}')
22+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
23+ assert compile.exit_code == 0, compile.output
24+ assert !compile.output.contains('C compilation failed'), compile.output
25+ run := os.execute(bin)
26+ assert run.exit_code == 0, run.output
27+ return run.output.trim_space()
28+}
29+
30+fn mut_param_reassign_run_bad(v3_bin string, name string, source string, expected string) {
31+ src := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}.v')
32+ os.write_file(src, source) or { panic(err) }
33+ bin := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}')
34+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
35+ assert compile.exit_code != 0, compile.output
36+ assert compile.output.contains(expected), compile.output
37+ assert !compile.output.contains('C compilation failed'), compile.output
38+}
39+
40+fn test_mut_array_param_reassigns_to_base_type() {
41+ v3_bin := mut_param_reassign_build_v3()
42+ out := mut_param_reassign_run_good(v3_bin, 'mut_array_param_reassign', 'fn replace(mut xs []int) {
43+ mut tmp := []int{}
44+ tmp << 4
45+ tmp << 9
46+ xs = tmp.clone()
47+}
48+
49+fn main() {
50+ mut xs := []int{}
51+ xs << 1
52+ replace(mut xs)
53+ assert xs.len == 2
54+ assert xs[0] == 4
55+ assert xs[1] == 9
56+ println("ok")
57+}
58+')
59+ assert out == 'ok'
60+}
61+
62+fn test_mut_string_param_concat_reads_as_string() {
63+ v3_bin := mut_param_reassign_build_v3()
64+ out := mut_param_reassign_run_good(v3_bin, 'mut_string_param_concat', "fn add(mut s string) string {
65+ return s + '!'
66+}
67+
68+fn main() {
69+ mut s := 'hi'
70+ println(add(mut s))
71+}
72+")
73+ assert out == 'hi!'
74+}
75+
76+fn test_generic_mut_array_param_reassigns_to_base_type() {
77+ v3_bin := mut_param_reassign_build_v3()
78+ out := mut_param_reassign_run_good(v3_bin, 'generic_mut_array_param_reassign', 'struct Item {
79+ data int
80+ priority int
81+}
82+
83+fn replace[T](mut xs []T, value T) {
84+ mut tmp := []T{}
85+ tmp << value
86+ xs = tmp.clone()
87+}
88+
89+fn main() {
90+ mut xs := []Item{}
91+ replace(mut xs, Item{data: 7, priority: 3})
92+ assert xs.len == 1
93+ assert xs[0].data == 7
94+ assert xs[0].priority == 3
95+ println("ok")
96+}
97+')
98+ assert out == 'ok'
99+}
100+
101+fn test_mut_map_param_reassigns_to_base_type() {
102+ v3_bin := mut_param_reassign_build_v3()
103+ out := mut_param_reassign_run_good(v3_bin, 'mut_map_param_reassign', "fn replace(mut m map[string]int) {
104+ tmp := {
105+ 'answer': 42
106+ }
107+ m = tmp.clone()
108+}
109+
110+fn main() {
111+ mut m := map[string]int{}
112+ m['old'] = 1
113+ replace(mut m)
114+ assert m.len == 1
115+ assert m['answer'] == 42
116+ println('ok')
117+}
118+")
119+ assert out == 'ok'
120+}
121+
122+fn test_multi_return_assign_to_mut_array_param_uses_base_type() {
123+ v3_bin := mut_param_reassign_build_v3()
124+ out := mut_param_reassign_run_good(v3_bin, 'mut_array_param_multi_return_reassign', 'fn pair() ([]int, int) {
125+ return [1, 2], 7
126+}
127+
128+fn replace(mut xs []int) {
129+ xs, _ = pair()
130+}
131+
132+fn main() {
133+ mut xs := [0]
134+ replace(mut xs)
135+ assert xs.len == 2
136+ assert xs[0] == 1
137+ assert xs[1] == 2
138+ println("ok")
139+}
140+')
141+ assert out == 'ok'
142+}
143+
144+fn test_mut_param_compound_assign_and_postfix_store_through_pointer() {
145+ v3_bin := mut_param_reassign_build_v3()
146+ out := mut_param_reassign_run_good(v3_bin, 'mut_param_compound_assign', 'fn inc(mut n int) {
147+ n += 1
148+ n++
149+}
150+
151+fn main() {
152+ mut x := 1
153+ inc(mut x)
154+ assert x == 3
155+ println("ok")
156+}
157+')
158+ assert out == 'ok'
159+}
160+
161+fn test_mut_param_local_shadow_keeps_local_type() {
162+ v3_bin := mut_param_reassign_build_v3()
163+ out := mut_param_reassign_run_good(v3_bin, 'mut_param_local_shadow', "fn f(mut s string) {
164+ {
165+ mut s := 1
166+ s = 2
167+ assert s + 1 == 3
168+ }
169+ s = 'done'
170+}
171+
172+fn main() {
173+ mut s := 'start'
174+ f(mut s)
175+ assert s == 'done'
176+ println('ok')
177+}
178+")
179+ assert out == 'ok'
180+}
181+
182+fn test_mut_param_reassign_keeps_invalid_assignments_rejected() {
183+ v3_bin := mut_param_reassign_build_v3()
184+ mut_param_reassign_run_bad(v3_bin, 'bad_mut_array_param_reassign_elem', "fn bad(mut xs []int) {
185+ mut ys := []string{}
186+ ys << 'bad'
187+ xs = ys
188+}
189+
190+fn main() {
191+ mut xs := []int{}
192+ bad(mut xs)
193+}
194+",
195+ 'cannot assign `[]string` to `[]int`')
196+ mut_param_reassign_run_bad(v3_bin, 'bad_mut_array_param_reassign_scalar', 'fn bad(mut xs []int) {
197+ xs = 1
198+}
199+
200+fn main() {
201+ mut xs := []int{}
202+ bad(mut xs)
203+}
204+',
205+ 'cannot assign `int` to `[]int`')
206+ mut_param_reassign_run_bad(v3_bin, 'bad_pointer_local_reassign_value', 'fn main() {
207+ mut xs := []int{}
208+ mut p := &xs
209+ mut tmp := []int{}
210+ p = tmp
211+}
212+',
213+ 'cannot assign `[]int` to `&[]int`')
214+}
@@ -0,0 +1,83 @@
1+import os
2+
3+const nested_array_append_vexe = @VEXE
4+const nested_array_append_tests_dir = os.dir(@FILE)
5+const nested_array_append_v3_dir = os.dir(nested_array_append_tests_dir)
6+const nested_array_append_vlib_dir = os.dir(nested_array_append_v3_dir)
7+const nested_array_append_v3_src = os.join_path(nested_array_append_v3_dir, 'v3.v')
8+
9+fn nested_array_append_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_nested_array_append_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${nested_array_append_vexe} -gc none -path "${nested_array_append_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${nested_array_append_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_nested_array_append_keeps_push_and_push_many_semantics() {
19+ v3_bin := nested_array_append_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_nested_array_append_input_${os.getpid()}.v')
21+ os.write_file(src, "fn main() {
22+ mut scalars := []int{}
23+ scalars << [1, 2]
24+ assert scalars.len == 2
25+ assert scalars[1] == 2
26+
27+ mut rows := [][]int{}
28+ row_group := [[1], [2]]
29+ rows << row_group
30+ assert rows.len == 2
31+ assert rows[1][0] == 2
32+
33+ mut row_items := [][]int{}
34+ row := [3, 4]
35+ row_items << row
36+ assert row_items.len == 1
37+ assert row_items[0][1] == 4
38+
39+ mut cube := [][][]int{}
40+ cube_item := [[5], [6]]
41+ cube << cube_item
42+ assert cube.len == 1
43+ assert cube[0].len == 2
44+ assert cube[0][1][0] == 6
45+
46+ mut cube_many := [][][]int{}
47+ cube_group := [[[7]], [[8]]]
48+ cube_many << cube_group
49+ assert cube_many.len == 2
50+ assert cube_many[1][0][0] == 8
51+
52+ mut queue := [][][]string{}
53+ path := []string{}
54+ queue << [['A'], path]
55+ assert queue.len == 1
56+ assert queue[0].len == 2
57+ assert queue[0][0][0] == 'A'
58+ assert queue[0][1].len == 0
59+ println('ok')
60+}
61+") or {
62+ panic(err)
63+ }
64+
65+ bin := os.join_path(os.temp_dir(), 'v3_nested_array_append_input_${os.getpid()}')
66+ os.rm(bin) or {}
67+ os.rm(bin + '.c') or {}
68+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
69+ assert compile.exit_code == 0, compile.output
70+ assert !compile.output.contains('C compilation failed'), compile.output
71+
72+ run := os.execute(bin)
73+ assert run.exit_code == 0, run.output
74+ assert run.output.trim_space() == 'ok'
75+
76+ c_code := os.read_file(bin + '.c') or { panic(err) }
77+ assert c_code.contains('array__push_many(&scalars'), c_code
78+ assert c_code.contains('array__push_many(&rows'), c_code
79+ assert c_code.contains('array_push(&row_items'), c_code
80+ assert c_code.contains('array_push(&cube'), c_code
81+ assert c_code.contains('array__push_many(&cube_many'), c_code
82+ assert c_code.contains('array_push(&queue'), c_code
83+}
@@ -0,0 +1,76 @@
1+import os
2+
3+const numeric_array_literal_vexe = @VEXE
4+const numeric_array_literal_tests_dir = os.dir(@FILE)
5+const numeric_array_literal_v3_dir = os.dir(numeric_array_literal_tests_dir)
6+const numeric_array_literal_vlib_dir = os.dir(numeric_array_literal_v3_dir)
7+const numeric_array_literal_v3_src = os.join_path(numeric_array_literal_v3_dir, 'v3.v')
8+
9+fn numeric_array_literal_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_numeric_array_literal_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${numeric_array_literal_vexe} -gc none -path "${numeric_array_literal_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${numeric_array_literal_v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn test_numeric_array_literals_keep_float_common_type() {
19+ v3_bin := numeric_array_literal_build_v3()
20+ src := os.join_path(os.temp_dir(), 'v3_numeric_array_literal_input_${os.getpid()}.v')
21+ os.write_file(src, "
22+fn main() {
23+ xs := [-0.3, 0.0, 0.3, 0.6, 1.0, 1.5]
24+ mut sum := 0.0
25+ for x in xs {
26+ sum += x
27+ }
28+ assert sum > 3.09 && sum < 3.11
29+
30+ ys := [1, 2, 3]
31+ mut isum := 0
32+ for y in ys {
33+ isum += y
34+ }
35+ assert isum == 6
36+
37+ zs := [f32(1.25), f32(2.5)]
38+ mut zsum := f32(0)
39+ for z in zs {
40+ zsum += z
41+ }
42+ assert zsum > f32(3.74) && zsum < f32(3.76)
43+
44+ ws := [f32(1.25), 2]
45+ mut wsum := f32(0)
46+ for w in ws {
47+ wsum += w
48+ }
49+ assert wsum > f32(3.24) && wsum < f32(3.26)
50+ println('ok')
51+}
52+") or {
53+ panic(err)
54+ }
55+
56+ bin := os.join_path(os.temp_dir(), 'v3_numeric_array_literal_input_${os.getpid()}')
57+ os.rm(bin) or {}
58+ os.rm(bin + '.c') or {}
59+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
60+ assert compile.exit_code == 0, compile.output
61+ assert !compile.output.contains('C compilation failed'), compile.output
62+
63+ run := os.execute(bin)
64+ assert run.exit_code == 0, run.output
65+ assert run.output.trim_space() == 'ok'
66+
67+ c_code := os.read_file(bin + '.c') or { panic(err) }
68+ assert c_code.contains('array_new(sizeof(double), 0, 6)'), c_code
69+ assert c_code.contains('double x = *(double*)(array_get(xs,'), c_code
70+ assert c_code.contains('array_new(sizeof(int), 0, 3)'), c_code
71+ assert !c_code.contains('int x = *(int*)(array_get(xs,'), c_code
72+ assert c_code.contains('array_new(sizeof(float), 0, 2)'), c_code
73+ assert c_code.contains('float z = *(float*)(array_get(zs,'), c_code
74+ assert c_code.contains('float w = *(float*)(array_get(ws,'), c_code
75+ assert !c_code.contains('array_new(sizeof(double), 0, 2)'), c_code
76+}
@@ -9,7 +9,7 @@ const parallel_v3_src = os.join_path(parallel_v3_dir, 'v3.v')
99// build_parallel_v3 builds parallel v3 data for v3 tests.
1010fn build_parallel_v3() string {
1111 vexe := @VEXE
12- v3_bin := os.join_path(os.temp_dir(), 'v3_parallel_cgen_test')
12+ v3_bin := os.join_path(os.temp_dir(), 'v3_parallel_cgen_test_${os.getpid()}')
1313 os.rm(v3_bin) or {}
1414 build :=
1515 os.execute('${vexe} -d parallel -gc none -path "${parallel_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${parallel_v3_src}')
@@ -230,6 +230,68 @@ fn test_parallel_cgen_worker_resolves_generic_struct_method_signature() {
230230 assert !c_code.contains('?T'), c_code
231231}
232232
233+fn write_parallel_ierror_payload_success_project(name string) string {
234+ project_dir := os.join_path(os.temp_dir(), 'v3_${name}')
235+ os.rmdir_all(project_dir) or {}
236+ os.mkdir_all(os.join_path(project_dir, 'payloadmod')) or { panic(err) }
237+
238+ mut main_src := strings.new_builder(128_000)
239+ main_src.writeln('module main')
240+ main_src.writeln('')
241+ main_src.writeln('import payloadmod')
242+ main_src.writeln('')
243+ for i in 0 .. 1300 {
244+ main_src.writeln('fn helper_${i}() int {')
245+ main_src.writeln('\treturn ${i}')
246+ main_src.writeln('}')
247+ main_src.writeln('')
248+ }
249+ main_src.writeln('fn main() {')
250+ main_src.writeln('\tmut total := 0')
251+ for i in 0 .. 1300 {
252+ main_src.writeln('\ttotal += helper_${i}()')
253+ }
254+ main_src.writeln('\tpayload := payloadmod.payload() or {')
255+ main_src.writeln("\t\tprintln('ERR:' + err.msg() + ':' + int_str(err.code()))")
256+ main_src.writeln('\t\treturn')
257+ main_src.writeln('\t}')
258+ main_src.writeln("\tprintln('OK:' + payload.msg() + ':' + int_str(payload.code()))")
259+ main_src.writeln('\tprintln(int_str(total))')
260+ main_src.writeln('}')
261+ os.write_file(os.join_path(project_dir, 'main.v'), main_src.str()) or { panic(err) }
262+
263+ os.write_file(os.join_path(project_dir, 'payloadmod', 'payloadmod.v'), 'module payloadmod
264+
265+pub struct WrappedBuiltinError {
266+ Error
267+}
268+
269+pub fn payload() !IError {
270+ return WrappedBuiltinError{}
271+}
272+') or {
273+ panic(err)
274+ }
275+ return os.join_path(project_dir, 'main.v')
276+}
277+
278+fn test_parallel_cgen_worker_preserves_ierror_payload_success_with_builtin_error_embed() {
279+ v3_bin := build_parallel_v3()
280+ main_path :=
281+ write_parallel_ierror_payload_success_project('parallel_ierror_payload_success_${os.getpid()}')
282+ bin_out := os.join_path(os.temp_dir(), 'v3_parallel_ierror_payload_success_out_${os.getpid()}')
283+ compile := os.execute('VJOBS=2 ${v3_bin} ${main_path} -b c -o ${bin_out}')
284+ assert compile.exit_code == 0, compile.output
285+ assert compile.output.contains('cgen (parallel)'), compile.output
286+ run := os.execute(bin_out)
287+ assert run.exit_code == 0, run.output
288+ assert run.output.trim_space() == 'OK::0\n844350'
289+ c_code := os.read_file(bin_out + '.c') or { panic(err) }
290+ assert c_code.contains('return (Optional_IError){.ok = true, .value = (IError){._typ ='), c_code
291+ assert c_code.contains('= {"", 0, 1};'), c_code
292+ assert c_code.contains('.message = _str_'), c_code
293+}
294+
233295fn write_parallel_top_level_no_main_project(name string) string {
234296 project_dir := os.join_path(os.temp_dir(), 'v3_${name}')
235297 os.rmdir_all(project_dir) or {}
@@ -0,0 +1,119 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_selector_expr_receiver_markused_${os.getpid()}')
11+ build :=
12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
13+ assert build.exit_code == 0, build.output
14+ return v3_bin
15+}
16+
17+fn test_selector_receiver_or_expr_roots_string_methods() {
18+ v3_bin := build_v3()
19+
20+ src := os.join_path(os.temp_dir(), 'v3_selector_expr_receiver_markused_input_${os.getpid()}.v')
21+ os.write_file(src, "fn maybe_input() ?string {
22+ return none
23+}
24+
25+fn main() {
26+ input := maybe_input() or {
27+ ' exit '
28+ }.trim_space()
29+ if input.to_upper() == 'EXIT' {
30+ println('ok')
31+ }
32+}
33+") or {
34+ panic(err)
35+ }
36+
37+ bin := os.join_path(os.temp_dir(), 'v3_selector_expr_receiver_markused_input_${os.getpid()}')
38+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
39+ assert compile.exit_code == 0, compile.output
40+ assert !compile.output.contains('C compilation failed'), compile.output
41+
42+ generated := os.read_file(bin + '.c') or { panic(err) }
43+ assert generated.contains('string__trim_space'), generated
44+ assert generated.contains('string__to_upper'), generated
45+
46+ run := os.execute(bin)
47+ assert run.exit_code == 0, run.output
48+ assert run.output.trim_space() == 'ok'
49+}
50+
51+fn test_top_level_selector_receiver_or_expr_roots_string_methods() {
52+ v3_bin := build_v3()
53+
54+ src := os.join_path(os.temp_dir(),
55+ 'v3_top_level_selector_expr_receiver_markused_${os.getpid()}.v')
56+ os.write_file(src, "fn maybe_input() ?string {
57+ return none
58+}
59+
60+for i := 0; i < 1; i++ {
61+ input := maybe_input() or {
62+ ' exit '
63+ }.trim_space()
64+ if input.to_upper() == 'EXIT' {
65+ println('ok')
66+ }
67+}
68+") or {
69+ panic(err)
70+ }
71+
72+ bin := os.join_path(os.temp_dir(),
73+ 'v3_top_level_selector_expr_receiver_markused_${os.getpid()}')
74+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
75+ assert compile.exit_code == 0, compile.output
76+ assert !compile.output.contains('C compilation failed'), compile.output
77+
78+ generated := os.read_file(bin + '.c') or { panic(err) }
79+ assert generated.contains('string__trim_space'), generated
80+ assert generated.contains('string__to_upper'), generated
81+
82+ run := os.execute(bin)
83+ assert run.exit_code == 0, run.output
84+ assert run.output.trim_space() == 'ok'
85+}
86+
87+fn test_result_selector_receiver_or_expr_roots_string_methods() {
88+ v3_bin := build_v3()
89+
90+ src := os.join_path(os.temp_dir(), 'v3_result_selector_expr_receiver_markused_${os.getpid()}.v')
91+ os.write_file(src, "fn get_value() !string {
92+ return error('x')
93+}
94+
95+fn main() {
96+ input := get_value() or {
97+ ' exit '
98+ }.trim_space().to_upper()
99+ if input == 'EXIT' {
100+ println('ok')
101+ }
102+}
103+") or {
104+ panic(err)
105+ }
106+
107+ bin := os.join_path(os.temp_dir(), 'v3_result_selector_expr_receiver_markused_${os.getpid()}')
108+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
109+ assert compile.exit_code == 0, compile.output
110+ assert !compile.output.contains('C compilation failed'), compile.output
111+
112+ generated := os.read_file(bin + '.c') or { panic(err) }
113+ assert generated.contains('string__trim_space'), generated
114+ assert generated.contains('string__to_upper'), generated
115+
116+ run := os.execute(bin)
117+ assert run.exit_code == 0, run.output
118+ assert run.output.trim_space() == 'ok'
119+}
@@ -0,0 +1,174 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn string_interp_build_v3() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_string_interpolation_stringify_test_${os.getpid()}')
11+ os.rm(v3_bin) or {}
12+ build :=
13+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
14+ assert build.exit_code == 0, build.output
15+ return v3_bin
16+}
17+
18+fn compile_v3_input(v3_bin string, name string, source string, bin string) os.Result {
19+ src := os.join_path(os.temp_dir(), '${name}_${os.getpid()}.v')
20+ os.write_file(src, source) or { panic(err) }
21+ return os.execute('${v3_bin} ${src} -b c -o ${bin}')
22+}
23+
24+fn test_typed_string_interpolation_stringifies_maps_arrays_and_generic_indexes() {
25+ v3_bin := string_interp_build_v3()
26+ src := 'fn generic_first[T](xs []T) string {
27+ return \'\${xs[0]}\'
28+}
29+
30+fn generic_line[T](xs []T) string {
31+ return \'node \${xs[0]}\'
32+}
33+
34+fn main() {
35+ visited := {
36+ \'A\': true
37+ }
38+ visited_line := \'Visited: \${visited} done\'
39+ assert visited_line.contains(\'Visited: {\')
40+ assert visited_line.contains("\'A\': true")
41+ assert visited_line.contains(\'} done\')
42+
43+ graph := {
44+ \'A\': [\'B\', \'C\']
45+ }
46+ graph_line := \'Graph: \${graph}\'
47+ assert graph_line.contains(\'Graph: {\')
48+ assert graph_line.contains("\'A\': [\'B\', \'C\']")
49+
50+ int_names := {
51+ 7: \'seven\'
52+ }
53+ int_names_line := \'Names: \${int_names}\'
54+ assert int_names_line.contains(\'Names: {\')
55+ assert int_names_line.contains("7: \'seven\'")
56+
57+ nums := [1, 2]
58+ assert \'Array: \${nums}\' == \'Array: [1, 2]\'
59+
60+ mut xs := []int{}
61+ xs << 7
62+ assert generic_first(xs) == \'7\'
63+ assert generic_line(xs) == \'node 7\'
64+ println(\'ok\')
65+}
66+'
67+ bin := os.join_path(os.temp_dir(), 'v3_string_interpolation_stringify_positive_${os.getpid()}')
68+ compile := compile_v3_input(v3_bin, 'v3_string_interpolation_stringify_positive', src, bin)
69+ assert compile.exit_code == 0, compile.output
70+ assert !compile.output.contains('C compilation failed'), compile.output
71+ run := os.execute(bin)
72+ assert run.exit_code == 0, run.output
73+ assert run.output.trim_space() == 'ok'
74+}
75+
76+fn test_defer_string_interpolation_with_generic_type_arguments() {
77+ v3_bin := string_interp_build_v3()
78+ src := "struct Box[T] {
79+ value T
80+}
81+
82+fn (b Box[T]) str() string {
83+ return 'box:' + '\${b.value}'
84+}
85+
86+struct PlainBox[T] {
87+ value T
88+}
89+
90+fn show_box[T](b Box[T]) {
91+ defer {
92+ println('box \${b}')
93+ }
94+ println('body-box')
95+}
96+
97+fn show_plain_box[T](b PlainBox[T]) {
98+ list := [b]
99+ mut lookup := map[string]PlainBox[T]{}
100+ lookup['one'] = b
101+ defer {
102+ println('plain \${b} \${list} \${lookup}')
103+ }
104+ println('body-plain')
105+}
106+
107+fn show_value[D](value D) {
108+ defer {
109+ println('value \${value}')
110+ }
111+ println('body-d')
112+}
113+
114+fn main() {
115+ show_box(Box[int]{
116+ value: 7
117+ })
118+ show_plain_box(PlainBox[int]{
119+ value: 8
120+ })
121+ show_value(9)
122+}
123+"
124+ bin := os.join_path(os.temp_dir(), 'v3_defer_generic_string_interp_${os.getpid()}')
125+ compile := compile_v3_input(v3_bin, 'v3_defer_generic_string_interp', src, bin)
126+ assert compile.exit_code == 0, compile.output
127+ assert !compile.output.contains('C compilation failed'), compile.output
128+ run := os.execute(bin)
129+ assert run.exit_code == 0, run.output
130+ assert run.output.trim_space() == "body-box\nbox box:7\nbody-plain\nplain PlainBox[int]{} [PlainBox[int]{}] {'one': PlainBox[int]{}}\nbody-d\nvalue 9"
131+}
132+
133+fn test_string_plus_accepts_string_aliases() {
134+ v3_bin := string_interp_build_v3()
135+ src := "type MyString = string
136+
137+fn main() {
138+ s := MyString('y')
139+ assert 'x' + s == 'xy'
140+ assert s + 'z' == 'yz'
141+ println('ok')
142+}
143+"
144+ bin := os.join_path(os.temp_dir(), 'v3_string_alias_plus_positive_${os.getpid()}')
145+ compile := compile_v3_input(v3_bin, 'v3_string_alias_plus_positive', src, bin)
146+ assert compile.exit_code == 0, compile.output
147+ assert !compile.output.contains('C compilation failed'), compile.output
148+ run := os.execute(bin)
149+ assert run.exit_code == 0, run.output
150+ assert run.output.trim_space() == 'ok'
151+}
152+
153+fn test_string_plus_does_not_stringify_non_strings() {
154+ v3_bin := string_interp_build_v3()
155+ int_bin := os.join_path(os.temp_dir(), 'v3_string_plus_int_negative_${os.getpid()}')
156+ int_result := compile_v3_input(v3_bin, 'v3_string_plus_int_negative', "fn main() {
157+ _ := 'x' + 1
158+}
159+",
160+ int_bin)
161+ assert int_result.exit_code != 0, int_result.output
162+ assert int_result.output.contains('operator `+` cannot concatenate `string` and `int`'), int_result.output
163+ assert !int_result.output.contains('C compilation failed'), int_result.output
164+
165+ map_bin := os.join_path(os.temp_dir(), 'v3_string_plus_map_negative_${os.getpid()}')
166+ map_result := compile_v3_input(v3_bin, 'v3_string_plus_map_negative', "fn main() {
167+ _ := 'x' + map[string]int{}
168+}
169+",
170+ map_bin)
171+ assert map_result.exit_code != 0, map_result.output
172+ assert map_result.output.contains('operator `+` cannot concatenate `string` and `map[string]int`'), map_result.output
173+ assert !map_result.output.contains('C compilation failed'), map_result.output
174+}
@@ -158,6 +158,18 @@ fn test_type_checker_reports_core_semantic_errors() {
158158 run_bad(v3_bin, 'bad_interface_field',
159159 'interface Named {\n\tname string\n}\nstruct Person {}\nfn takes_named(n Named) {}\nfn main() {\n\ttakes_named(Person{})\n}\n',
160160 'cannot use `Person` as argument 1 to `takes_named`; expected `Named`')
161+ run_bad(v3_bin, 'bad_interface_match_unrelated_sum_variant',
162+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nstruct Other {\n\tx int\n}\ntype Unrelated = Other\nfn describe(s Shape) int {\n\treturn match s {\n\t\tOther { 1 }\n\t\telse { s.area() }\n\t}\n}\nfn main() {}\n',
163+ '`Other` is not compatible with interface `Shape`')
164+ run_bad(v3_bin, 'bad_interface_is_unrelated_sum_variant',
165+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nstruct Other {\n\tx int\n}\ntype Unrelated = Other\nfn check(s Shape) bool {\n\treturn s is Other\n}\nfn main() {}\n',
166+ '`Other` is not compatible with interface `Shape`')
167+ run_bad(v3_bin, 'bad_interface_match_unresolved_pattern',
168+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nfn describe(s Shape) int {\n\treturn match s {\n\t\tMissingType { 1 }\n\t\telse { 0 }\n\t}\n}\nfn main() {}\n',
169+ 'unknown type `MissingType`')
170+ run_bad(v3_bin, 'bad_interface_is_unresolved_pattern',
171+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nfn check(s Shape) bool {\n\treturn s is MissingType\n}\nfn main() {}\n',
172+ 'unknown type `MissingType`')
161173 run_bad(v3_bin, 'bad_sum_missing_shared_field',
162174 'struct A {\n\tid int\n}\nstruct B {\n\tname string\n}\ntype Node = A | B\nfn main() {\n\tn := Node(A{\n\t\tid: 1\n\t})\n\t_ := n.id\n}\n',
163175 'unknown field `id` on `Node`')
@@ -170,6 +182,20 @@ fn test_type_checker_reports_core_semantic_errors() {
170182 run_bad(v3_bin, 'bad_sum_match_variant',
171183 'struct Cat {\n\tage int\n}\nstruct Dog {\n\ttricks int\n}\nstruct Bird {\n\twings int\n}\ntype Animal = Cat | Dog\nfn main() {\n\ta := Animal(Cat{\n\t\tage: 2\n\t})\n\tmatch a {\n\t\tBird {}\n\t\telse {}\n\t}\n}\n',
172184 '`Bird` is not a variant of sum type `Animal`')
185+ run_bad(v3_bin, 'bad_sum_constructor_extra_arg',
186+ 'struct Empty {}\nstruct Node[T] {\n\tvalue T\n}\ntype Tree[T] = Empty | Node[T]\nfn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\nfn main() {\n\t_ := Tree[int](Empty{}, side_effect())\n}\n',
187+ 'argument count mismatch for `Tree[int]`: expected 1, got 2')
188+ run_bad(v3_bin, 'bad_sum_constructor_extra_arg_as_call_arg',
189+ 'struct Empty {}\nstruct Node[T] {\n\tvalue T\n}\ntype Tree[T] = Empty | Node[T]\nfn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\nfn use(t Tree[int]) {}\nfn main() {\n\tuse(Tree[int](Empty{}, side_effect()))\n}\n',
190+ 'argument count mismatch for `Tree[int]`: expected 1, got 2')
191+ run_bad_project(v3_bin, 'bad_imported_sum_constructor_extra_arg', {
192+ 'trees/trees.v': 'module trees\n\npub struct Empty {}\n\npub struct Node[T] {\n\tpub:\n\tvalue T\n}\n\npub type Tree[T] = Empty | Node[T]\n\npub fn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\n'
193+ 'main.v': 'import trees { Empty, Tree, side_effect }\n\nfn main() {\n\t_ := Tree[int](Empty{}, side_effect())\n}\n'
194+ }, 'main.v', 'argument count mismatch for `trees.Tree[int]`: expected 1, got 2')
195+ run_bad_project(v3_bin, 'bad_aliased_sum_constructor_extra_arg', {
196+ 'trees/trees.v': 'module trees\n\npub struct Empty {}\n\npub struct Node[T] {\n\tpub:\n\tvalue T\n}\n\npub type Tree[T] = Empty | Node[T]\n\npub fn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\n'
197+ 'main.v': 'import trees as tr\n\nfn main() {\n\t_ := tr.Tree[int](tr.Empty{}, tr.side_effect())\n}\n'
198+ }, 'main.v', 'argument count mismatch for `trees.Tree[int]`: expected 1, got 2')
173199 run_bad(v3_bin, 'bad_unknown_decl_type', 'fn f(x Missing) {}\nfn main() {}\n',
174200 'unknown type `Missing`')
175201 run_bad(v3_bin, 'bad_unknown_generic_application_base',
@@ -45,7 +45,7 @@ pub fn (app &App) show(id int) veb.Result {
4545
4646fn main() {
4747 mut app := &App{}
48- _ := app
48+ _ := app.index()
4949}
5050'
5151 src_file := os.join_path(os.temp_dir(), 'v3_veb_ctx.v')
@@ -422,12 +422,34 @@ fn (t &Transformer) array_append_elem_types_match(rhs_elem_type string, lhs_elem
422422 if rhs_clean == lhs_clean {
423423 return true
424424 }
425+ if array_append_type_is_container_shape(rhs_clean)
426+ || array_append_type_is_container_shape(lhs_clean) {
427+ return false
428+ }
425429 if isnil(t.tc) {
426430 return false
427431 }
428432 return t.array_append_elem_c_type(rhs_clean) == t.array_append_elem_c_type(lhs_clean)
429433}
430434
435+fn array_append_type_is_container_shape(typ string) bool {
436+ clean := typ.trim_space()
437+ if clean.len == 0 {
438+ return false
439+ }
440+ if clean.starts_with('&') {
441+ return array_append_type_is_container_shape(clean[1..])
442+ }
443+ if clean.starts_with('shared ') {
444+ return array_append_type_is_container_shape(clean[7..])
445+ }
446+ if clean.starts_with('atomic ') {
447+ return array_append_type_is_container_shape(clean[7..])
448+ }
449+ return clean.starts_with('[]') || clean.starts_with('map[')
450+ || (clean.starts_with('[') && clean.contains(']'))
451+}
452+
431453// array_append_ident_type supports array append ident type handling for Transformer.
432454fn (t &Transformer) array_append_ident_type(id flat.NodeId) ?string {
433455 if int(id) < 0 {
@@ -226,6 +226,9 @@ fn (t &Transformer) resolve_receiver_method_for_type(receiver_type string, metho
226226 }
227227 }
228228 } else {
229+ if method_name := t.resolve_specialized_generic_receiver_method(clean_type, method) {
230+ return method_name
231+ }
229232 direct := '${clean_type}.${method}'
230233 if t.is_known_fn_name(direct) {
231234 return direct
@@ -271,6 +274,33 @@ fn (t &Transformer) resolve_receiver_method_for_type(receiver_type string, metho
271274 return none
272275}
273276
277+fn (t &Transformer) resolve_specialized_generic_receiver_method(receiver_type string, method string) ?string {
278+ base, args, ok := generic_app_parts(receiver_type)
279+ if !ok || args.len == 0 {
280+ return none
281+ }
282+ short_args := generic_type_args_short(args)
283+ suffix := generic_type_suffixes(args)
284+ mut candidates := []string{}
285+ candidates << '${base}[${short_args}].${method}'
286+ candidates << '${base}_${suffix}.${method}'
287+ candidates << c_name('${base}_${suffix}.${method}')
288+ if base.contains('.') {
289+ module_name := base.all_before_last('.')
290+ short_base := base.all_after_last('.')
291+ candidates << '${short_base}[${short_args}].${method}'
292+ candidates << '${short_base}_${suffix}.${method}'
293+ candidates << '${module_name}.${short_base}_${suffix}.${method}'
294+ candidates << c_name('${module_name}.${short_base}_${suffix}.${method}')
295+ }
296+ for candidate in candidates {
297+ if t.is_known_fn_name(candidate) {
298+ return candidate
299+ }
300+ }
301+ return none
302+}
303+
274304// resolve_alias_receiver_method converts resolve alias receiver method data for transform.
275305fn (t &Transformer) resolve_alias_receiver_method(base_type string, method string) ?string {
276306 if isnil(t.tc) || base_type.len == 0 || method.len == 0 {
@@ -1498,6 +1528,9 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat
14981528 }
14991529 return t.enum_autostr_call(expr, qenum)
15001530 }
1531+ if generic_str := t.generic_receiver_str_call(expr, clean_typ) {
1532+ return generic_str
1533+ }
15011534 if clean_typ in t.structs || clean_typ in t.sum_types {
15021535 mut qualified := clean_typ
15031536 if !clean_typ.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
@@ -1520,6 +1553,8 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat
15201553 return t.wrap_string_conversion(arr, '[]${elem_type}')
15211554 } else if clean_typ.len > 0 && clean_typ.starts_with('[]') {
15221555 return t.lower_array_str(expr, clean_typ)
1556+ } else if clean_typ.len > 0 && clean_typ.starts_with('map[') {
1557+ return t.lower_map_str(expr, clean_typ)
15231558 } else if clean_typ == 'rune' {
15241559 return t.make_call_typed('strconv__format_int', arr2(expr, t.make_int_literal(10)),
15251560 'string')
@@ -1530,6 +1565,24 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat
15301565 }
15311566}
15321567
1568+fn (mut t Transformer) generic_receiver_str_call(expr flat.NodeId, typ string) ?flat.NodeId {
1569+ if isnil(t.tc) || !typ.contains('[') {
1570+ return none
1571+ }
1572+ clean_typ := if typ.starts_with('&') { typ[1..] } else { typ }
1573+ if !clean_typ.ends_with(']') {
1574+ return none
1575+ }
1576+ info := t.tc.resolve_generic_struct_method(clean_typ, 'str') or {
1577+ t.tc.resolve_generic_sum_method(clean_typ, 'str') or { return none }
1578+ }
1579+ if info.return_type.name() != 'string' {
1580+ return none
1581+ }
1582+ selector := t.make_selector(expr, 'str', '')
1583+ return t.make_call_expr_typed(selector, []flat.NodeId{}, 'string')
1584+}
1585+
15331586// append_string builds `result = result + piece` using the runtime string concat helper.
15341587// Using string__plus directly (instead of `+=`) keeps the synthesized node independent of
15351588// type resolution for the freshly-introduced temp.
@@ -1582,6 +1635,73 @@ fn (mut t Transformer) lower_array_str(arr_expr flat.NodeId, base_type string) f
15821635 return t.make_ident(result_name)
15831636}
15841637
1638+// lower_map_str expands `${m}` for `map[K]V` into a runtime loop over keys that formats
1639+// each key/value with the same typed conversions used by array stringification.
1640+fn (mut t Transformer) lower_map_str(map_expr flat.NodeId, base_type string) flat.NodeId {
1641+ src := t.a.nodes[int(map_expr)]
1642+ clean_type := t.clean_map_type(base_type)
1643+ key_type, value_type := t.map_type_parts(clean_type)
1644+ if key_type.len == 0 || value_type.len == 0 {
1645+ return map_expr
1646+ }
1647+ base := t.stable_expr_for_reuse(map_expr)
1648+ mut prefix := []flat.NodeId{}
1649+ t.drain_pending(mut prefix)
1650+ result_name := t.new_temp('map_str')
1651+ keys_name := t.new_temp('map_str_keys')
1652+ idx_name := t.new_temp('map_str_idx')
1653+ key_name := t.new_temp('map_str_key')
1654+ zero_name := t.new_temp('map_str_zero')
1655+ prefix << t.make_decl_assign_typed(result_name, t.make_string_literal('{'), 'string')
1656+ keys := t.make_call_typed('map__keys', arr1(t.runtime_addr(base, clean_type)), '[]${key_type}')
1657+ prefix << t.make_decl_assign_typed(keys_name, keys, '[]${key_type}')
1658+ prefix << t.make_decl_assign_typed(zero_name, t.zero_value_for_type(value_type), value_type)
1659+ init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
1660+ cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(t.make_ident(keys_name),
1661+ 'len', 'int'))
1662+ post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
1663+ key_expr := t.array_get_value(t.make_ident(keys_name), t.make_ident(idx_name), key_type)
1664+ key_decl := t.make_decl_assign_typed(key_name, key_expr, key_type)
1665+ value_name := t.new_temp('map_str_val')
1666+ value_expr := t.make_map_get_expr(base, clean_type, key_name, zero_name, value_type)
1667+ value_decl := t.make_decl_assign_typed(value_name, value_expr, value_type)
1668+ mut loop_body := []flat.NodeId{}
1669+ loop_body << key_decl
1670+ loop_body << value_decl
1671+ sep_cond := t.make_infix(.gt, t.make_ident(idx_name), t.make_int_literal(0))
1672+ sep_stmt := t.append_string(result_name, t.make_string_literal(', '))
1673+ loop_body << t.make_if(sep_cond, t.make_block(arr1(sep_stmt)), t.make_empty())
1674+ t.set_var_type(key_name, key_type)
1675+ key_str := t.wrap_string_conversion(t.make_ident(key_name), key_type)
1676+ t.unset_var_type(key_name)
1677+ t.drain_pending(mut loop_body)
1678+ if key_type == 'string' {
1679+ loop_body << t.append_string(result_name, t.make_string_literal("'"))
1680+ loop_body << t.append_string(result_name, key_str)
1681+ loop_body << t.append_string(result_name, t.make_string_literal("'"))
1682+ } else {
1683+ loop_body << t.append_string(result_name, key_str)
1684+ }
1685+ loop_body << t.append_string(result_name, t.make_string_literal(': '))
1686+ t.set_var_type(value_name, value_type)
1687+ value_str := t.wrap_string_conversion(t.make_ident(value_name), value_type)
1688+ t.unset_var_type(value_name)
1689+ t.drain_pending(mut loop_body)
1690+ if value_type == 'string' {
1691+ loop_body << t.append_string(result_name, t.make_string_literal("'"))
1692+ loop_body << t.append_string(result_name, value_str)
1693+ loop_body << t.append_string(result_name, t.make_string_literal("'"))
1694+ } else {
1695+ loop_body << t.append_string(result_name, value_str)
1696+ }
1697+ prefix << t.make_for_stmt(init, cond, post, loop_body, src)
1698+ prefix << t.append_string(result_name, t.make_string_literal('}'))
1699+ for stmt in prefix {
1700+ t.pending_stmts << stmt
1701+ }
1702+ return t.make_ident(result_name)
1703+}
1704+
15851705// wrap_optional_string_conversion transforms wrap optional string conversion data for transform.
15861706fn (mut t Transformer) wrap_optional_string_conversion(expr flat.NodeId, typ string) flat.NodeId {
15871707 opt_type := t.qualify_optional_type(typ)
@@ -2632,6 +2752,9 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.
26322752 args := t.transform_receiver_method_args(node, base_id, method_name)
26332753 return t.make_call_typed(method_name, args, 'string')
26342754 }
2755+ if method == 'str' && stringify_type_has_generic_placeholder(base_type) {
2756+ return none
2757+ }
26352758 if base_type.starts_with('[]') && method == 'str' {
26362759 return t.wrap_string_conversion(t.transform_expr(base_id), base_type)
26372760 }
@@ -2673,12 +2796,18 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.
26732796 }
26742797 method_name := t.resolve_receiver_method_name(base_id, method)
26752798 if method_name.len > 0 {
2799+ if t.receiver_method_name_is_open_generic(method_name) {
2800+ return none
2801+ }
26762802 args := t.transform_receiver_method_args(node, base_id, method_name)
26772803 ret_type := t.receiver_method_return_type(method_name, node.typ)
26782804 return t.make_call_typed(method_name, args, ret_type)
26792805 }
26802806 if !isnil(t.tc) {
26812807 if resolved_method := t.tc.resolved_call_name(id) {
2808+ if t.receiver_method_name_is_open_generic(resolved_method) {
2809+ return none
2810+ }
26822811 if t.is_known_fn_name(resolved_method) {
26832812 params := t.call_param_types(resolved_method)
26842813 if !t.resolved_call_uses_receiver_type(base_id, base_type, params) {
@@ -2700,6 +2829,20 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.
27002829 return none
27012830}
27022831
2832+fn (t &Transformer) receiver_method_name_is_open_generic(method_name string) bool {
2833+ if method_name.contains('.') {
2834+ receiver := method_name.all_before_last('.')
2835+ _, args, ok := generic_app_parts(receiver)
2836+ return ok && args.len > 0 && t.generic_args_have_placeholders(args)
2837+ }
2838+ for marker in ['_T__', '_U__', '_K__', '_V__', '_A__', '_B__', '_C__', '_X__', '_Y__', '_Z__'] {
2839+ if method_name.contains(marker) {
2840+ return true
2841+ }
2842+ }
2843+ return false
2844+}
2845+
27032846// is_builder_receiver reports whether is builder receiver applies in transform.
27042847fn (t &Transformer) is_builder_receiver(base_id flat.NodeId, base_type string) bool {
27052848 if is_builder_type_name(base_type) {
@@ -1288,6 +1288,9 @@ fn (t &Transformer) extract_is_expr(cond_id flat.NodeId) IsExprInfo {
12881288// sum_type_for_is_expr supports sum type for is expr handling for Transformer.
12891289fn (t &Transformer) sum_type_for_is_expr(expr_type string, variant string) string {
12901290 clean_expr_type := t.trim_pointer_type(expr_type)
1291+ if _ := t.resolve_sum_variant_pattern_for_subject(clean_expr_type, variant) {
1292+ return clean_expr_type
1293+ }
12911294 resolved_expr_sum := t.resolve_sum_name(clean_expr_type)
12921295 if resolved_expr_sum in t.sum_types {
12931296 if _ := t.sum_variant_name(resolved_expr_sum, variant) {
@@ -8,6 +8,11 @@ fn (t &Transformer) is_interface_type(name string) bool {
88 return t.resolve_interface_type_name(name).len > 0
99}
1010
11+fn (t &Transformer) is_builtin_ierror_interface_name(name string) bool {
12+ clean := t.trim_pointer_type(t.normalize_type_alias(name))
13+ return clean == 'IError' || clean == 'builtin.IError'
14+}
15+
1116// resolve_interface_type_name resolves resolve interface type name information for transform.
1217fn (t &Transformer) resolve_interface_type_name(name string) string {
1318 if name.len == 0 || isnil(t.tc) {
@@ -17,6 +22,15 @@ fn (t &Transformer) resolve_interface_type_name(name string) string {
1722 if clean in t.tc.interface_names {
1823 return clean
1924 }
25+ if !clean.contains('.') && t.cur_file.len > 0 {
26+ for candidate in t.tc.file_selective_imports[file_import_key(t.cur_file, clean)] or {
27+ []string{}
28+ } {
29+ if candidate in t.tc.interface_names {
30+ return candidate
31+ }
32+ }
33+ }
2034 if !clean.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
2135 && t.cur_module != 'builtin' {
2236 qname := '${t.cur_module}.${clean}'
@@ -39,7 +53,7 @@ fn (mut t Transformer) transform_interface_value_for_type(id flat.NodeId, target
3953 }
4054 // IError has bespoke handling (built via `error()`, fields accessed directly);
4155 // do not route it through the generic interface boxing.
42- if iface_name.all_after_last('.') == 'IError' {
56+ if t.is_builtin_ierror_interface_name(iface_name) {
4357 return none
4458 }
4559 source_type := t.node_type(id)
@@ -80,7 +94,7 @@ fn (mut t Transformer) transform_global_amp_interface_cast(node flat.Node, targe
8094 return none
8195 }
8296 iface_name := t.resolve_interface_type_name(child.value)
83- if iface_name.len == 0 || iface_name.all_after_last('.') == 'IError' {
97+ if iface_name.len == 0 || t.is_builtin_ierror_interface_name(iface_name) {
8498 return none
8599 }
86100 old_pending := t.pending_stmts.clone()
@@ -11,6 +11,14 @@ struct GenericStructDecl {
1111 key string
1212}
1313
14+struct GenericSumDecl {
15+ id flat.NodeId
16+ node flat.Node
17+ file string
18+ module string
19+ key string
20+}
21+
1422fn (mut t Transformer) is_generic_fn(name string) bool {
1523 decls := t.cached_generic_fn_decls()
1624 return name in decls || transform_qualified_fn_name(t.cur_module, name) in decls
@@ -174,13 +182,65 @@ fn (mut t Transformer) materialize_generic_structs() {
174182 return
175183 }
176184 decls := t.collect_generic_struct_decls()
177- if decls.len == 0 {
185+ sum_decls := t.collect_generic_sum_decls()
186+ if decls.len == 0 && sum_decls.len == 0 {
187+ return
188+ }
189+ specs, sum_specs := t.collect_generic_materialization_specs(decls, sum_decls)
190+ for spec, base in sum_specs {
191+ decl := sum_decls[base] or { continue }
192+ t.materialize_generic_sum_spec(spec, decl)
193+ }
194+ for spec, base in specs {
195+ decl := decls[base] or { continue }
196+ t.materialize_generic_struct_spec(spec, decl)
197+ }
198+ t.erase_generic_sum_templates(sum_decls)
199+ for _, decl in decls {
200+ t.tc.structs.delete(decl.key)
201+ t.tc.unions.delete(decl.key)
202+ t.tc.params_structs.delete(decl.key)
203+ }
204+}
205+
206+fn (mut t Transformer) materialize_generic_sum_types(erase_templates bool) {
207+ if isnil(t.tc) {
178208 return
179209 }
210+ decls := t.collect_generic_struct_decls()
211+ sum_decls := t.collect_generic_sum_decls()
212+ if sum_decls.len == 0 {
213+ return
214+ }
215+ _, sum_specs := t.collect_generic_materialization_specs(decls, sum_decls)
216+ for spec, base in sum_specs {
217+ decl := sum_decls[base] or { continue }
218+ t.materialize_generic_sum_spec(spec, decl)
219+ }
220+ if erase_templates {
221+ t.erase_generic_sum_templates(sum_decls)
222+ }
223+}
224+
225+fn (mut t Transformer) erase_generic_sum_templates(sum_decls map[string]GenericSumDecl) {
226+ for _, decl in sum_decls {
227+ t.tc.sum_types.delete(decl.key)
228+ t.sum_types.delete(decl.key)
229+ t.tc.sum_generic_params.delete(decl.key)
230+ if decl.key != decl.node.value {
231+ t.tc.sum_generic_params.delete(decl.node.value)
232+ }
233+ }
234+}
235+
236+fn (mut t Transformer) collect_generic_materialization_specs(decls map[string]GenericStructDecl, sum_decls map[string]GenericSumDecl) (map[string]string, map[string]string) {
180237 mut specs := map[string]string{}
238+ mut sum_specs := map[string]string{}
181239 t.collect_generic_struct_specs(decls, mut specs)
182- for _ in 0 .. 20 {
240+ t.collect_generic_sum_specs(sum_decls, mut sum_specs)
241+ for _ in 0 .. 40 {
183242 before := specs.len
243+ before_sums := sum_specs.len
184244 current := specs.clone()
185245 for spec, base in current {
186246 decl := decls[base] or { continue }
@@ -197,21 +257,32 @@ fn (mut t Transformer) materialize_generic_structs() {
197257 decl.node.generic_params)
198258 t.collect_generic_struct_spec_from_type(field_type, decl.module, decl.file, decls, mut
199259 specs)
260+ t.collect_generic_sum_spec_from_type(field_type, decl.module, decl.file, sum_decls, mut
261+ sum_specs)
200262 }
201263 }
202- if specs.len == before {
264+ current_sums := sum_specs.clone()
265+ for spec, base in current_sums {
266+ decl := sum_decls[base] or { continue }
267+ _, args, ok := generic_app_parts(spec)
268+ if !ok {
269+ continue
270+ }
271+ for i in 0 .. decl.node.children_count {
272+ variant := t.a.child_node(&decl.node, i)
273+ variant_type := substitute_generic_type_text_with_params(variant.value, args,
274+ decl.node.generic_params)
275+ t.collect_generic_struct_spec_from_type(variant_type, decl.module, decl.file,
276+ decls, mut specs)
277+ t.collect_generic_sum_spec_from_type(variant_type, decl.module, decl.file,
278+ sum_decls, mut sum_specs)
279+ }
280+ }
281+ if specs.len == before && sum_specs.len == before_sums {
203282 break
204283 }
205284 }
206- for spec, base in specs {
207- decl := decls[base] or { continue }
208- t.materialize_generic_struct_spec(spec, decl)
209- }
210- for _, decl in decls {
211- t.tc.structs.delete(decl.key)
212- t.tc.unions.delete(decl.key)
213- t.tc.params_structs.delete(decl.key)
214- }
285+ return specs, sum_specs
215286}
216287
217288fn (mut t Transformer) collect_generic_struct_decls() map[string]GenericStructDecl {
@@ -255,6 +326,43 @@ fn generic_struct_decl_key(name string, module_name string) string {
255326 return '${module_name}.${name}'
256327}
257328
329+fn (mut t Transformer) collect_generic_sum_decls() map[string]GenericSumDecl {
330+ mut decls := map[string]GenericSumDecl{}
331+ t.ensure_node_module_map()
332+ mut cur_file := ''
333+ mut cur_module := ''
334+ for i, node in t.a.nodes {
335+ match node.kind {
336+ .file {
337+ cur_file = node.value
338+ }
339+ .module_decl {
340+ cur_module = node.value
341+ }
342+ .type_decl {
343+ if node.generic_params.len == 0 || node.children_count == 0 {
344+ continue
345+ }
346+ module_name := t.node_module_map_cache[i] or { cur_module }
347+ key := generic_type_decl_key(node.value, module_name)
348+ decls[key] = GenericSumDecl{
349+ id: flat.NodeId(i)
350+ node: node
351+ file: cur_file
352+ module: module_name
353+ key: key
354+ }
355+ }
356+ else {}
357+ }
358+ }
359+ return decls
360+}
361+
362+fn generic_type_decl_key(name string, module_name string) string {
363+ return generic_struct_decl_key(name, module_name)
364+}
365+
258366fn (mut t Transformer) collect_generic_struct_specs(decls map[string]GenericStructDecl, mut specs map[string]string) {
259367 for _, target in t.tc.type_aliases {
260368 t.collect_generic_struct_spec_from_type(target, '', '', decls, mut specs)
@@ -350,6 +458,95 @@ fn (mut t Transformer) collect_generic_struct_spec_from_type(typ string, module_
350458 specs[spec_name] = spec_base
351459}
352460
461+fn (mut t Transformer) collect_generic_sum_specs(decls map[string]GenericSumDecl, mut specs map[string]string) {
462+ for _, target in t.tc.type_aliases {
463+ t.collect_generic_sum_spec_from_type(target, '', '', decls, mut specs)
464+ }
465+ mut file_name := ''
466+ mut module_name := ''
467+ for node in t.a.nodes {
468+ match node.kind {
469+ .file {
470+ file_name = node.value
471+ module_name = ''
472+ }
473+ .module_decl {
474+ module_name = node.value
475+ }
476+ else {}
477+ }
478+
479+ if node.typ.len > 0 {
480+ t.collect_generic_sum_spec_from_type(node.typ, module_name, file_name, decls, mut specs)
481+ }
482+ match node.kind {
483+ .struct_init, .array_init, .cast_expr, .as_expr, .sizeof_expr, .typeof_expr, .is_expr {
484+ t.collect_generic_sum_spec_from_type(node.value, module_name, file_name, decls, mut
485+ specs)
486+ }
487+ else {}
488+ }
489+ }
490+}
491+
492+fn (mut t Transformer) collect_generic_sum_spec_from_type(typ string, module_name string, file_name string, decls map[string]GenericSumDecl, mut specs map[string]string) {
493+ clean := typ.trim_space()
494+ if clean.len == 0 {
495+ return
496+ }
497+ if clean.starts_with('&') {
498+ t.collect_generic_sum_spec_from_type(clean[1..], module_name, file_name, decls, mut specs)
499+ return
500+ }
501+ if clean.starts_with('mut ') {
502+ t.collect_generic_sum_spec_from_type(clean[4..], module_name, file_name, decls, mut specs)
503+ return
504+ }
505+ if clean.starts_with('?') || clean.starts_with('!') {
506+ t.collect_generic_sum_spec_from_type(clean[1..], module_name, file_name, decls, mut specs)
507+ return
508+ }
509+ if clean.starts_with('...') {
510+ t.collect_generic_sum_spec_from_type(clean[3..], module_name, file_name, decls, mut specs)
511+ return
512+ }
513+ if clean.starts_with('[]') {
514+ t.collect_generic_sum_spec_from_type(clean[2..], module_name, file_name, decls, mut specs)
515+ return
516+ }
517+ if clean.starts_with('map[') {
518+ bracket_end := generic_matching_bracket(clean, 3)
519+ if bracket_end < clean.len {
520+ t.collect_generic_sum_spec_from_type(clean[4..bracket_end], module_name, file_name,
521+ decls, mut specs)
522+ t.collect_generic_sum_spec_from_type(clean[bracket_end + 1..], module_name, file_name,
523+ decls, mut specs)
524+ }
525+ return
526+ }
527+ if clean.starts_with('[') {
528+ bracket_end := generic_matching_bracket(clean, 0)
529+ if bracket_end < clean.len {
530+ t.collect_generic_sum_spec_from_type(clean[bracket_end + 1..], module_name, file_name,
531+ decls, mut specs)
532+ }
533+ return
534+ }
535+ base, args, ok := generic_app_parts(clean)
536+ if !ok {
537+ return
538+ }
539+ for arg in args {
540+ t.collect_generic_sum_spec_from_type(arg, module_name, file_name, decls, mut specs)
541+ }
542+ if t.generic_args_have_placeholders(args) {
543+ return
544+ }
545+ spec_base := t.generic_sum_spec_base_name(base, module_name, file_name, decls) or { return }
546+ spec_name := '${spec_base}[${args.join(', ')}]'
547+ specs[spec_name] = spec_base
548+}
549+
353550fn (t &Transformer) generic_struct_spec_base_name(base string, module_name string, file_name string, decls map[string]GenericStructDecl) ?string {
354551 if base in decls {
355552 return base
@@ -381,6 +578,37 @@ fn (t &Transformer) generic_struct_spec_base_name(base string, module_name strin
381578 return none
382579}
383580
581+fn (t &Transformer) generic_sum_spec_base_name(base string, module_name string, file_name string, decls map[string]GenericSumDecl) ?string {
582+ if base in decls {
583+ return base
584+ }
585+ if base.contains('.') {
586+ return none
587+ }
588+ if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' {
589+ qbase := '${module_name}.${base}'
590+ if qbase in decls {
591+ return qbase
592+ }
593+ }
594+ if resolved := t.selective_import_generic_sum_base(base, file_name, decls) {
595+ return resolved
596+ }
597+ mut found := ''
598+ for key, _ in decls {
599+ if key.all_after_last('.') == base {
600+ if found.len > 0 && found != key {
601+ return none
602+ }
603+ found = key
604+ }
605+ }
606+ if found.len > 0 {
607+ return found
608+ }
609+ return none
610+}
611+
384612fn (t &Transformer) selective_import_generic_struct_base(base string, file_name string, decls map[string]GenericStructDecl) ?string {
385613 if isnil(t.tc) || base.contains('.') || file_name.len == 0 {
386614 return none
@@ -394,6 +622,51 @@ fn (t &Transformer) selective_import_generic_struct_base(base string, file_name
394622 return none
395623}
396624
625+fn (t &Transformer) selective_import_generic_sum_base(base string, file_name string, decls map[string]GenericSumDecl) ?string {
626+ if isnil(t.tc) || base.contains('.') || file_name.len == 0 {
627+ return none
628+ }
629+ candidates := t.tc.file_selective_imports[file_import_key(file_name, base)] or { return none }
630+ for candidate in candidates {
631+ if candidate in decls {
632+ return candidate
633+ }
634+ }
635+ return none
636+}
637+
638+fn (mut t Transformer) materialize_generic_sum_spec(spec_name string, decl GenericSumDecl) {
639+ _, args, ok := generic_app_parts(spec_name)
640+ if !ok || args.len == 0 {
641+ return
642+ }
643+ old_module := t.cur_module
644+ old_file := t.cur_file
645+ old_tc_module := t.tc.cur_module
646+ old_tc_file := t.tc.cur_file
647+ t.cur_module = decl.module
648+ t.cur_file = decl.file
649+ t.tc.cur_module = decl.module
650+ t.tc.cur_file = decl.file
651+ mut variants := []string{}
652+ for i in 0 .. decl.node.children_count {
653+ variant := t.a.child_node(&decl.node, i)
654+ variant_type := substitute_generic_type_text_with_params(variant.value, args,
655+ decl.node.generic_params)
656+ parsed := t.tc.parse_type(variant_type)
657+ variants << if parsed is types.Unknown { variant_type } else { parsed.name() }
658+ }
659+ t.tc.sum_types[spec_name] = variants
660+ t.sum_types[spec_name] = variants
661+ if !isnil(t.sum_cache) {
662+ t.sum_cache.entries.clear()
663+ }
664+ t.cur_module = old_module
665+ t.cur_file = old_file
666+ t.tc.cur_module = old_tc_module
667+ t.tc.cur_file = old_tc_file
668+}
669+
397670fn (mut t Transformer) materialize_generic_struct_spec(spec_name string, decl GenericStructDecl) {
398671 _, args, ok := generic_app_parts(spec_name)
399672 if !ok || args.len == 0 {
@@ -542,6 +815,7 @@ fn (mut t Transformer) emit_generic_fn_specialization(decl GenericFnDecl, args [
542815 t.specialize_cloned_fn_signature(clone_id, decl, args)
543816 clone := t.a.nodes[int(clone_id)]
544817 t.register_specialized_fn_signature(decl, clone, args)
818+ t.materialize_generic_sum_types(false)
545819 t.active_generic_params = old_params
546820 t.transform_specialized_fn_body(clone_id, decl.module, decl.file)
547821 t.cur_module = old_module
@@ -1678,6 +1952,18 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is
16781952 for child in children {
16791953 t.a.children << child
16801954 }
1955+ if node.kind == .call && children.len > 0
1956+ && (node.typ.contains('[') || node.value.contains('[')) {
1957+ type_text := if node.typ.contains('[') { node.typ } else { node.value }
1958+ base, _, ok := generic_app_parts(type_text)
1959+ if ok {
1960+ callee_id := children[0]
1961+ mut callee := t.a.nodes[int(callee_id)]
1962+ if callee.kind in [.ident, .selector] && callee.value == base.all_after_last('.') {
1963+ t.a.nodes[int(callee_id)].value = t.subst_type(type_text, args)
1964+ }
1965+ }
1966+ }
16811967 cloned_value := if is_root {
16821968 specialized_generic_fn_value(node.value, args)
16831969 } else {
@@ -1692,6 +1978,7 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is
16921978 children_count: flat.child_count(children.len)
16931979 typ: t.subst_type(node.typ, args)
16941980 value: cloned_value
1981+ is_mut: node.is_mut
16951982 })
16961983}
16971984
@@ -1699,27 +1986,7 @@ fn (mut t Transformer) copy_cloned_resolution(src_id flat.NodeId, dst_id flat.No
16991986 if isnil(t.tc) {
17001987 return
17011988 }
1702- src_idx := int(src_id)
1703- dst_idx := int(dst_id)
1704- if src_idx < 0 || dst_idx < 0 {
1705- return
1706- }
1707- if src_idx < t.tc.resolved_call_set.len && t.tc.resolved_call_set[src_idx] {
1708- for t.tc.resolved_call_names.len <= dst_idx {
1709- t.tc.resolved_call_names << ''
1710- t.tc.resolved_call_set << false
1711- }
1712- t.tc.resolved_call_names[dst_idx] = t.tc.resolved_call_names[src_idx]
1713- t.tc.resolved_call_set[dst_idx] = true
1714- }
1715- if src_idx < t.tc.resolved_fn_value_set.len && t.tc.resolved_fn_value_set[src_idx] {
1716- for t.tc.resolved_fn_value_names.len <= dst_idx {
1717- t.tc.resolved_fn_value_names << ''
1718- t.tc.resolved_fn_value_set << false
1719- }
1720- t.tc.resolved_fn_value_names[dst_idx] = t.tc.resolved_fn_value_names[src_idx]
1721- t.tc.resolved_fn_value_set[dst_idx] = true
1722- }
1989+ t.tc.copy_cloned_resolution(src_id, dst_id)
17231990}
17241991
17251992fn substitute_generic_node_value(node flat.Node, args []string) string {
@@ -2290,6 +2557,32 @@ fn (t &Transformer) subst_type(typ string, args []string) string {
22902557// subst_node_value is the param-aware counterpart of substitute_generic_node_value.
22912558fn (t &Transformer) subst_node_value(node flat.Node, args []string) string {
22922559 match node.kind {
2560+ .ident {
2561+ if node.value.contains('[') {
2562+ return t.subst_type(node.value, args)
2563+ }
2564+ if node.typ.contains('[') {
2565+ base, _, ok := generic_app_parts(node.typ)
2566+ if ok && node.value == base.all_after_last('.') {
2567+ return t.subst_type(node.typ, args)
2568+ }
2569+ }
2570+ return node.value
2571+ }
2572+ .selector {
2573+ if node.value.contains('[') {
2574+ return t.subst_type(node.value, args)
2575+ }
2576+ if node.typ.contains('[') {
2577+ old_field_type := t.trim_pointer_type(node.typ)
2578+ new_field_type := t.trim_pointer_type(t.subst_type(node.typ, args))
2579+ if old_field_type != new_field_type
2580+ && t.sum_field_name(old_field_type) == node.value {
2581+ return t.sum_field_name(new_field_type)
2582+ }
2583+ }
2584+ return node.value
2585+ }
22932586 .call, .array_init, .struct_init, .cast_expr, .as_expr, .sizeof_expr, .typeof_expr,
22942587 .is_expr, .type_decl, .field_decl, .param {
22952588 return t.subst_type(node.value, args)
@@ -347,7 +347,8 @@ fn (mut t Transformer) build_return_match_chain(match_expr_id flat.NodeId, orig_
347347 branch := t.a.nodes[int(branches[idx])]
348348 is_else := branch.value == 'else'
349349 body_start_idx := if is_else { 0 } else { t.count_conds(branch) }
350- if !is_else && t.match_branch_all_type_patterns(branch) && t.count_conds(branch) > 1 {
350+ if !is_else && t.match_branch_all_type_patterns(match_expr_id, branch)
351+ && t.count_conds(branch) > 1 {
351352 return t.build_return_match_type_branch_chain(match_expr_id, orig_expr_id, branch,
352353 branches, idx, 0, ret_typ)
353354 }
@@ -357,16 +358,15 @@ fn (mut t Transformer) build_return_match_chain(match_expr_id flat.NodeId, orig_
357358 n_conds := t.count_conds(branch)
358359 if n_conds == 1 {
359360 cond_val_id := t.a.child(&branch, 0)
360- if variant_name := t.match_type_pattern(cond_val_id) {
361+ if sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) {
361362 subj := t.expr_key(match_expr_id)
362- sum_name := t.find_sum_type_for_variant(variant_name)
363- if subj.len > 0 && sum_name.len > 0 {
364- t.push_smartcast(subj, variant_name, sum_name)
363+ if subj.len > 0 {
364+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)
365365 sc_pushed++
366366 }
367367 orig_subj := t.expr_key(orig_expr_id)
368- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
369- t.push_smartcast(orig_subj, variant_name, sum_name)
368+ if orig_subj.len > 0 && orig_subj != subj {
369+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)
370370 sc_pushed++
371371 }
372372 }
@@ -411,7 +411,7 @@ fn (mut t Transformer) build_return_match_type_branch_chain(match_expr_id flat.N
411411 }
412412 }
413413 cond_val_id := t.a.child(&branch, cond_idx)
414- variant_name := t.match_type_pattern(cond_val_id) or {
414+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or {
415415 return t.build_return_match_chain(match_expr_id, orig_expr_id, branches, idx + 1, ret_typ)
416416 }
417417 is_start := t.a.children.len
@@ -425,15 +425,20 @@ fn (mut t Transformer) build_return_match_type_branch_chain(match_expr_id flat.N
425425 cond_id := t.transform_is_expr(is_id, t.a.nodes[int(is_id)])
426426
427427 mut sc_pushed := 0
428+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {
429+ SmartcastContext{
430+ variant_name: variant_name
431+ sum_type_name: ''
432+ }
433+ }
428434 subj := t.expr_key(match_expr_id)
429- sum_name := t.find_sum_type_for_variant(variant_name)
430- if subj.len > 0 && sum_name.len > 0 {
431- t.push_smartcast(subj, variant_name, sum_name)
435+ if subj.len > 0 && sc.sum_type_name.len > 0 {
436+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)
432437 sc_pushed++
433438 }
434439 orig_subj := t.expr_key(orig_expr_id)
435- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
436- t.push_smartcast(orig_subj, variant_name, sum_name)
440+ if orig_subj.len > 0 && orig_subj != subj && sc.sum_type_name.len > 0 {
441+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)
437442 sc_pushed++
438443 }
439444 body_block := t.match_branch_return_block(branch, n_conds, ret_typ)
@@ -12,6 +12,11 @@ fn (t &Transformer) trim_pointer_type(typ string) string {
1212
1313// resolve_variant resolves resolve variant information for transform.
1414fn (t &Transformer) resolve_variant(sum_name string, variant string) string {
15+ if !isnil(t.tc) {
16+ if resolved := t.tc.sum_variant_type_for_pattern(sum_name, variant) {
17+ return resolved
18+ }
19+ }
1520 if variant.contains('.') {
1621 return variant
1722 }
@@ -54,6 +59,16 @@ fn (t &Transformer) resolve_sum_name_uncached(sum_name string) string {
5459 if sum_name in t.sum_types {
5560 return sum_name
5661 }
62+ generic_base := generic_base_name_text(sum_name)
63+ if generic_base != sum_name {
64+ resolved_base := t.resolve_sum_name_uncached(generic_base)
65+ if resolved_base in t.sum_types {
66+ return resolved_base
67+ }
68+ if !isnil(t.tc) && resolved_base in t.tc.sum_types {
69+ return resolved_base
70+ }
71+ }
5772 if sum_name.contains('.') {
5873 short_sum := sum_name.all_after_last('.')
5974 if short_sum in t.sum_types {
@@ -132,15 +147,7 @@ fn (t &Transformer) is_interface_type_name(name string) bool {
132147 if name.len == 0 || isnil(t.tc) {
133148 return false
134149 }
135- clean := t.trim_pointer_type(t.normalize_type_alias(name))
136- if clean == 'IError' || clean in t.tc.interface_names {
137- return true
138- }
139- if !clean.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
140- && t.cur_module != 'builtin' {
141- return '${t.cur_module}.${clean}' in t.tc.interface_names
142- }
143- return false
150+ return t.resolve_interface_type_name(name).len > 0
144151}
145152
146153// interface_variant_type supports interface variant type handling for Transformer.
@@ -179,19 +186,17 @@ fn (t &Transformer) sum_type_index(sum_name string, variant string) int {
179186 variants := t.sum_types[resolved_sum] or {
180187 if !isnil(t.tc) {
181188 tc_variants := t.tc.sum_types[resolved_sum] or { return 0 }
182- return sum_type_index_in_variants(tc_variants, variant)
189+ return t.sum_type_index_in_variants(tc_variants, variant)
183190 }
184191 return 0
185192 }
186- return sum_type_index_in_variants(variants, variant)
193+ return t.sum_type_index_in_variants(variants, variant)
187194}
188195
189196// sum_type_index_in_variants supports sum type index in variants handling for transform.
190-fn sum_type_index_in_variants(variants []string, variant string) int {
191- short_variant := variant_short_name_text(variant)
197+fn (t &Transformer) sum_type_index_in_variants(variants []string, variant string) int {
192198 for i, v in variants {
193- short_v := variant_short_name_text(v)
194- if v == variant || short_v == short_variant {
199+ if t.variant_names_match(v, variant) {
195200 return i + 1
196201 }
197202 }
@@ -208,11 +213,24 @@ fn (mut t Transformer) transform_is_expr(id flat.NodeId, node flat.Node) flat.No
208213 clean_type0 := t.trim_pointer_type(expr_type)
209214 clean_type := if clean_type0 in t.sum_types {
210215 clean_type0
216+ } else if _ := t.resolve_sum_variant_pattern_for_subject(clean_type0, node.value) {
217+ clean_type0
211218 } else {
212219 t.find_sum_type_for_variant(node.value)
213220 }
214221 if t.is_interface_type_name(clean_type0) {
215222 new_expr := t.transform_expr(expr_id)
223+ if pattern := t.resolve_interface_pattern(node.value, clean_type0) {
224+ is_start := t.a.children.len
225+ t.a.children << new_expr
226+ return t.a.add_node(flat.Node{
227+ kind: .is_expr
228+ value: pattern
229+ children_start: is_start
230+ children_count: 1
231+ typ: 'bool'
232+ })
233+ }
216234 object := t.make_selector_op(new_expr, '_object', 'voidptr', if expr_type.starts_with('&') {
217235 .arrow
218236 } else {
@@ -220,7 +238,8 @@ fn (mut t Transformer) transform_is_expr(id flat.NodeId, node flat.Node) flat.No
220238 })
221239 return t.make_infix(.ne, object, t.a.add(.nil_literal))
222240 }
223- if clean_type.len == 0 || clean_type !in t.sum_types {
241+ resolved_clean_type := t.resolve_sum_name(clean_type)
242+ if clean_type.len == 0 || resolved_clean_type !in t.sum_types {
224243 return t.make_bool_literal(true)
225244 }
226245 new_expr := t.transform_expr(expr_id)
@@ -249,7 +268,13 @@ fn (t &Transformer) sum_variant_path(sum_name string, variant string) []string {
249268
250269// sum_variant_path_inner supports sum variant path inner handling for Transformer.
251270fn (t &Transformer) sum_variant_path_inner(sum_name string, variant string, mut visited map[string]bool) []string {
252- resolved_sum := t.resolve_sum_name(t.trim_pointer_type(sum_name))
271+ clean_sum := t.trim_pointer_type(sum_name)
272+ if !isnil(t.tc) {
273+ if resolved := t.tc.sum_variant_type_for_pattern(clean_sum, variant) {
274+ return [resolved]
275+ }
276+ }
277+ resolved_sum := t.resolve_sum_name(clean_sum)
253278 if resolved_sum.len == 0 || resolved_sum in visited {
254279 return []string{}
255280 }
@@ -277,14 +302,14 @@ fn (t &Transformer) sum_variant_path_inner(sum_name string, variant string, mut
277302
278303// make_sum_type_pattern_check builds make sum type pattern check data for transform.
279304fn (mut t Transformer) make_sum_type_pattern_check(expr flat.NodeId, expr_type string, sum_name string, variant string) ?flat.NodeId {
280- resolved_sum := t.resolve_sum_name(t.trim_pointer_type(sum_name))
281- path := t.sum_variant_path(resolved_sum, variant)
305+ clean_sum := t.trim_pointer_type(sum_name)
306+ path := t.sum_variant_path(clean_sum, variant)
282307 if path.len == 0 {
283308 return none
284309 }
285310 mut current := expr
286311 mut current_type := expr_type
287- mut current_sum := resolved_sum
312+ mut current_sum := clean_sum
288313 mut chain := flat.empty_node
289314 for i, path_variant in path {
290315 cmp := t.make_sum_is_check(current, current_type, current_sum, path_variant)
@@ -305,7 +330,7 @@ fn (mut t Transformer) make_sum_type_pattern_check(expr flat.NodeId, expr_type s
305330 .dot
306331 })
307332 current_type = field_type
308- current_sum = t.resolve_sum_name(t.trim_pointer_type(qv))
333+ current_sum = t.trim_pointer_type(qv)
309334 }
310335 return chain
311336}
@@ -192,7 +192,10 @@ pub fn transform_with_used_opt(mut a flat.FlatAst, tc &types.TypeChecker, used_f
192192 // next power of two, wasting ~40MB, and each doubling briefly holds both the old and
193193 // new arrays — the dominant peak-RSS contributor under -gc none). The serial path is
194194 // latency-sensitive and does not clone worker ASTs, so let it grow naturally.
195- reserve_nodes := a.nodes.len * 7 / 4 - a.nodes.cap
195+ // Nodes grow a bit less than children on large compiler inputs. Keeping their
196+ // reserve below the next power-of-two cliff avoids retaining a mostly-empty
197+ // doubled nodes array through annotate/CGen.
198+ reserve_nodes := a.nodes.len * 5 / 3 - a.nodes.cap
196199 if reserve_nodes > 0 {
197200 unsafe { a.nodes.grow_cap(reserve_nodes) }
198201 }
@@ -1414,6 +1417,7 @@ fn (mut t Transformer) transform_fn_body(fn_idx int) {
14141417 t.smartcast_stack.clear()
14151418 // Collect param types
14161419 mut param_idx := 0
1420+ mut source_mut_params := []string{}
14171421 for i in 0 .. fn_node.children_count {
14181422 child_id := t.a.children[fn_node.children_start + i]
14191423 if int(child_id) < 0 {
@@ -1434,6 +1438,9 @@ fn (mut t Transformer) transform_fn_body(fn_idx int) {
14341438 typ = raw_typ
14351439 }
14361440 t.set_var_type(child.value, typ)
1441+ if child.is_mut {
1442+ source_mut_params << child.value
1443+ }
14371444 param_idx++
14381445 }
14391446 }
@@ -1453,6 +1460,9 @@ fn (mut t Transformer) transform_fn_body(fn_idx int) {
14531460 } else {
14541461 t.mark_escaping_amp_ptrs(body_ids)
14551462 }
1463+ for name in source_mut_params {
1464+ t.pointer_value_lvalues[name] = true
1465+ }
14561466 new_body := t.transform_stmts(body_ids)
14571467 // Rebuild function children: params then new body
14581468 start := t.a.children.len
@@ -2852,7 +2862,7 @@ fn (mut t Transformer) coerce_transformed_expr_to_type(expr flat.NodeId, source_
28522862// is_ierror_type reports whether is ierror type applies in transform.
28532863fn (t &Transformer) is_ierror_type(name string) bool {
28542864 clean := t.trim_pointer_type(t.normalize_type_alias(name))
2855- return clean == 'IError' || clean.ends_with('.IError')
2865+ return clean == 'IError' || clean == 'builtin.IError'
28562866}
28572867
28582868// expr_is_nil_like supports expr is nil like handling for Transformer.
@@ -4257,10 +4267,13 @@ fn (mut t Transformer) transform_index_expr(id flat.NodeId, node flat.Node) flat
42574267}
42584268
42594269// transform_string_interp transforms transform string interp data for transform.
4260-fn (mut t Transformer) transform_string_interp(_id flat.NodeId, node flat.Node) flat.NodeId {
4270+fn (mut t Transformer) transform_string_interp(id flat.NodeId, node flat.Node) flat.NodeId {
42614271 if node.children_count == 0 {
42624272 return t.make_string_literal('')
42634273 }
4274+ if t.string_interp_has_unresolved_generic_part(node) {
4275+ return id
4276+ }
42644277 // Some parts (arrays, optionals) lower into a prelude pushed onto pending_stmts, which
42654278 // runs before the containing statement. If such a part follows a part with side effects,
42664279 // keeping the earlier part inline in the string__plus chain would evaluate it after the
@@ -4287,6 +4300,7 @@ fn (mut t Transformer) transform_string_interp(_id flat.NodeId, node flat.Node)
42874300 if typ.len == 0 {
42884301 typ = 'string'
42894302 }
4303+ t.ensure_stringify_generic_instances_for_type(typ)
42904304 part := t.wrap_string_conversion(transformed, typ)
42914305 mut part_stmts := []flat.NodeId{}
42924306 t.drain_pending(mut part_stmts)
@@ -4329,6 +4343,232 @@ fn (mut t Transformer) transform_string_interp(_id flat.NodeId, node flat.Node)
43294343 return result
43304344}
43314345
4346+fn (t &Transformer) string_interp_has_unresolved_generic_part(node flat.Node) bool {
4347+ for i in 0 .. node.children_count {
4348+ child_id := t.a.child(&node, i)
4349+ if t.string_interp_child_has_unresolved_generic_part(child_id) {
4350+ return true
4351+ }
4352+ }
4353+ return false
4354+}
4355+
4356+fn (t &Transformer) string_interp_child_has_unresolved_generic_part(id flat.NodeId) bool {
4357+ if int(id) < 0 {
4358+ return false
4359+ }
4360+ node := t.a.nodes[int(id)]
4361+ mut candidates := []string{cap: 5}
4362+ candidates << t.node_type(id)
4363+ if node.typ.len > 0 {
4364+ candidates << t.normalize_type_alias(node.typ)
4365+ }
4366+ if node.kind == .ident {
4367+ candidates << t.var_type(node.value)
4368+ }
4369+ candidates << t.lvalue_type(id)
4370+ candidates << t.reliable_stringify_type(id)
4371+ for typ in candidates {
4372+ if t.stringify_type_has_generic_placeholder(typ) {
4373+ return true
4374+ }
4375+ }
4376+ return false
4377+}
4378+
4379+fn (mut t Transformer) ensure_stringify_generic_instances_for_type(typ string) {
4380+ clean := typ.trim_space()
4381+ if clean.len == 0 || t.stringify_type_has_generic_placeholder(clean) {
4382+ return
4383+ }
4384+ if clean.starts_with('&') {
4385+ t.ensure_stringify_generic_instances_for_type(clean[1..])
4386+ return
4387+ }
4388+ if clean.starts_with('mut ') {
4389+ t.ensure_stringify_generic_instances_for_type(clean[4..])
4390+ return
4391+ }
4392+ if clean.starts_with('?') || clean.starts_with('!') {
4393+ t.ensure_stringify_generic_instances_for_type(clean[1..])
4394+ return
4395+ }
4396+ if clean.starts_with('...') {
4397+ t.ensure_stringify_generic_instances_for_type(clean[3..])
4398+ return
4399+ }
4400+ if clean.starts_with('[]') {
4401+ t.ensure_stringify_generic_instances_for_type(clean[2..])
4402+ return
4403+ }
4404+ if clean.starts_with('map[') {
4405+ bracket_end := generic_matching_bracket(clean, 3)
4406+ if bracket_end < clean.len {
4407+ t.ensure_stringify_generic_instances_for_type(clean[4..bracket_end])
4408+ t.ensure_stringify_generic_instances_for_type(clean[bracket_end + 1..])
4409+ }
4410+ return
4411+ }
4412+ if clean.starts_with('[') {
4413+ bracket_end := generic_matching_bracket(clean, 0)
4414+ if bracket_end < clean.len {
4415+ t.ensure_stringify_generic_instances_for_type(clean[bracket_end + 1..])
4416+ }
4417+ return
4418+ }
4419+ base, args, ok := generic_app_parts(clean)
4420+ if !ok {
4421+ return
4422+ }
4423+ for arg in args {
4424+ t.ensure_stringify_generic_instances_for_type(arg)
4425+ }
4426+ if clean in t.structs {
4427+ return
4428+ }
4429+ base_info := t.lookup_struct_info_direct(base) or { return }
4430+ params := t.generic_struct_params_for_stringify(base)
4431+ mut fields := []FieldInfo{cap: base_info.fields.len}
4432+ for field in base_info.fields {
4433+ field_typ := if params.len > 0 {
4434+ substitute_generic_type_text_with_params(field.typ, args, params)
4435+ } else {
4436+ substitute_generic_type_text(field.typ, args)
4437+ }
4438+ fields << FieldInfo{
4439+ name: field.name
4440+ typ: field_typ
4441+ raw_typ: field.raw_typ
4442+ default_expr: field.default_expr
4443+ }
4444+ }
4445+ t.structs[clean] = StructInfo{
4446+ name: clean
4447+ module: base_info.module
4448+ is_params: base_info.is_params
4449+ fields: fields
4450+ }
4451+}
4452+
4453+fn (t &Transformer) generic_struct_params_for_stringify(base string) []string {
4454+ if isnil(t.tc) {
4455+ return []string{}
4456+ }
4457+ if params := t.tc.struct_generic_params[base] {
4458+ return params
4459+ }
4460+ if !base.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
4461+ && t.cur_module != 'builtin' {
4462+ if params := t.tc.struct_generic_params['${t.cur_module}.${base}'] {
4463+ return params
4464+ }
4465+ }
4466+ if base.contains('.') {
4467+ if params := t.tc.struct_generic_params[base.all_after_last('.')] {
4468+ return params
4469+ }
4470+ }
4471+ return []string{}
4472+}
4473+
4474+fn (t &Transformer) stringify_type_has_generic_placeholder(typ string) bool {
4475+ clean := typ.trim_space()
4476+ if clean.len == 0 {
4477+ return false
4478+ }
4479+ if clean == 'generic' {
4480+ return true
4481+ }
4482+ if is_generic_fn_placeholder_name(clean) {
4483+ return !t.is_known_concrete_type_name(clean)
4484+ }
4485+ if clean.starts_with('&') {
4486+ return t.stringify_type_has_generic_placeholder(clean[1..])
4487+ }
4488+ if clean.starts_with('mut ') {
4489+ return t.stringify_type_has_generic_placeholder(clean[4..])
4490+ }
4491+ if clean.starts_with('?') || clean.starts_with('!') {
4492+ return t.stringify_type_has_generic_placeholder(clean[1..])
4493+ }
4494+ if clean.starts_with('...') {
4495+ return t.stringify_type_has_generic_placeholder(clean[3..])
4496+ }
4497+ if clean.starts_with('[]') {
4498+ return t.stringify_type_has_generic_placeholder(clean[2..])
4499+ }
4500+ if clean.starts_with('map[') {
4501+ bracket_end := generic_matching_bracket(clean, 3)
4502+ if bracket_end < clean.len {
4503+ return t.stringify_type_has_generic_placeholder(clean[4..bracket_end])
4504+ || t.stringify_type_has_generic_placeholder(clean[bracket_end + 1..])
4505+ }
4506+ return false
4507+ }
4508+ if clean.starts_with('[') {
4509+ bracket_end := generic_matching_bracket(clean, 0)
4510+ if bracket_end < clean.len {
4511+ return t.stringify_type_has_generic_placeholder(clean[bracket_end + 1..])
4512+ }
4513+ return false
4514+ }
4515+ _, args, ok := generic_app_parts(clean)
4516+ if ok {
4517+ for arg in args {
4518+ if t.stringify_type_has_generic_placeholder(arg) {
4519+ return true
4520+ }
4521+ }
4522+ }
4523+ return false
4524+}
4525+
4526+fn stringify_type_has_generic_placeholder(typ string) bool {
4527+ clean := typ.trim_space()
4528+ if clean.len == 0 {
4529+ return false
4530+ }
4531+ if is_generic_placeholder_type_name(clean) {
4532+ return true
4533+ }
4534+ if clean.starts_with('&') {
4535+ return stringify_type_has_generic_placeholder(clean[1..])
4536+ }
4537+ if clean.starts_with('?') || clean.starts_with('!') {
4538+ return stringify_type_has_generic_placeholder(clean[1..])
4539+ }
4540+ if clean.starts_with('...') {
4541+ return stringify_type_has_generic_placeholder(clean[3..])
4542+ }
4543+ if clean.starts_with('[]') {
4544+ return stringify_type_has_generic_placeholder(clean[2..])
4545+ }
4546+ if clean.starts_with('map[') {
4547+ bracket_end := generic_matching_bracket(clean, 3)
4548+ if bracket_end < clean.len {
4549+ return stringify_type_has_generic_placeholder(clean[4..bracket_end])
4550+ || stringify_type_has_generic_placeholder(clean[bracket_end + 1..])
4551+ }
4552+ return false
4553+ }
4554+ if clean.starts_with('[') {
4555+ bracket_end := generic_matching_bracket(clean, 0)
4556+ if bracket_end < clean.len {
4557+ return stringify_type_has_generic_placeholder(clean[bracket_end + 1..])
4558+ }
4559+ return false
4560+ }
4561+ _, args, ok := generic_app_parts(clean)
4562+ if ok {
4563+ for arg in args {
4564+ if stringify_type_has_generic_placeholder(arg) {
4565+ return true
4566+ }
4567+ }
4568+ }
4569+ return false
4570+}
4571+
43324572// transform_selector_expr transforms transform selector expr data for transform.
43334573fn (mut t Transformer) transform_selector_expr(id flat.NodeId, node flat.Node) flat.NodeId {
43344574 if node.children_count == 0 {
@@ -4722,7 +4962,7 @@ fn (mut t Transformer) transform_prefix_expr(id flat.NodeId, node flat.Node) fla
47224962 // heap-allocated interface so the resulting pointer stays valid, rather
47234963 // than emitting a plain `(Interface*)x` reinterpret cast.
47244964 iface := t.resolve_interface_type_name(child.value)
4725- if iface.len > 0 && iface.all_after_last('.') != 'IError' {
4965+ if iface.len > 0 && !t.is_builtin_ierror_interface_name(iface) {
47264966 if boxed := t.transform_interface_value_for_type(cast_arg_id, '&${child.value}') {
47274967 return boxed
47284968 }
@@ -5680,13 +5920,47 @@ fn (t &Transformer) sum_variant_name(sum_name string, variant string) ?string {
56805920}
56815921
56825922fn (t &Transformer) variant_names_match(a string, b string) bool {
5683- return a == b || t.variant_short_name(a) == t.variant_short_name(b)
5923+ if a == b || t.variant_short_name(a) == t.variant_short_name(b) {
5924+ return true
5925+ }
5926+ a_base, a_args, a_generic := generic_app_parts(a)
5927+ b_base, b_args, b_generic := generic_app_parts(b)
5928+ if a_generic || b_generic {
5929+ a_match_base := if a_generic { a_base } else { a }
5930+ b_match_base := if b_generic { b_base } else { b }
5931+ if a_generic && b_generic && !t.generic_variant_args_are_open(a_args)
5932+ && !t.generic_variant_args_are_open(b_args) {
5933+ return false
5934+ }
5935+ return t.variant_short_name(a_match_base) == t.variant_short_name(b_match_base)
5936+ }
5937+ return false
5938+}
5939+
5940+fn (t &Transformer) generic_variant_args_are_open(args []string) bool {
5941+ for arg in args {
5942+ if t.generic_arg_is_unresolved(arg) {
5943+ return true
5944+ }
5945+ }
5946+ return false
56845947}
56855948
56865949fn (t &Transformer) variant_short_name(name string) string {
56875950 return variant_short_name_text(name)
56885951}
56895952
5953+fn generic_base_name_text(name string) string {
5954+ if name.starts_with('[') {
5955+ return name
5956+ }
5957+ bracket := name.index_u8(`[`)
5958+ if bracket <= 0 {
5959+ return name
5960+ }
5961+ return name[..bracket]
5962+}
5963+
56905964fn variant_short_name_text(name string) string {
56915965 if name.starts_with('&') {
56925966 return '&' + variant_short_name_text(name[1..])
@@ -5972,7 +6246,7 @@ fn (t &Transformer) resolve_expr_type(id flat.NodeId) string {
59726246 }
59736247 }
59746248 if node.children_count > 0 {
5975- elem_type := t.node_type(t.a.child(&node, 0))
6249+ elem_type := t.array_literal_elem_type(node)
59766250 if elem_type.len > 0 {
59776251 return '[]${elem_type}'
59786252 }
@@ -6049,6 +6323,9 @@ fn (t &Transformer) resolve_expr_type(id flat.NodeId) string {
60496323 if node.op == .not {
60506324 return 'bool'
60516325 }
6326+ if node.op in [.minus, .plus] {
6327+ return child_type
6328+ }
60526329 }
60536330 return ''
60546331 }
@@ -6140,6 +6417,51 @@ fn (t &Transformer) resolve_expr_type(id flat.NodeId) string {
61406417 }
61416418}
61426419
6420+fn (t &Transformer) array_literal_elem_type(node flat.Node) string {
6421+ if node.children_count == 0 {
6422+ return 'int'
6423+ }
6424+ mut elem_type := t.node_type(t.a.child(&node, 0))
6425+ if !is_numeric_type_name(elem_type) {
6426+ return elem_type
6427+ }
6428+ mut has_f32 := elem_type == 'f32'
6429+ mut has_f64 := elem_type == 'f64'
6430+ for i in 1 .. node.children_count {
6431+ child_type := t.node_type(t.a.child(&node, i))
6432+ if !is_numeric_type_name(child_type) {
6433+ return elem_type
6434+ }
6435+ if child_type == 'f32' {
6436+ has_f32 = true
6437+ }
6438+ if child_type == 'f64' {
6439+ has_f64 = true
6440+ }
6441+ }
6442+ if has_f64 {
6443+ return 'f64'
6444+ }
6445+ if has_f32 {
6446+ return 'f32'
6447+ }
6448+ return elem_type
6449+}
6450+
6451+fn is_numeric_type_name(name string) bool {
6452+ return is_integer_type_name(name) || is_float_type_name(name)
6453+}
6454+
6455+fn is_integer_type_name(name string) bool {
6456+ return name == 'int' || name == 'i8' || name == 'i16' || name == 'i64' || name == 'u8'
6457+ || name == 'byte' || name == 'u16' || name == 'u32' || name == 'u64' || name == 'isize'
6458+ || name == 'usize' || name == 'rune'
6459+}
6460+
6461+fn is_float_type_name(name string) bool {
6462+ return name == 'f32' || name == 'f64'
6463+}
6464+
61436465fn (t &Transformer) current_call_return_type(node flat.Node) string {
61446466 if node.children_count > 0 {
61456467 fn_node := t.a.child_node(&node, 0)
@@ -6299,17 +6621,18 @@ fn (t &Transformer) match_branch_type_contexts(match_expr_id flat.NodeId, branch
62996621 return []SmartcastContext{}
63006622 }
63016623 cond_val_id := t.a.child(&branch, 0)
6302- variant_name := t.match_type_pattern(cond_val_id) or { return []SmartcastContext{} }
63036624 subj := t.expr_key(match_expr_id)
6304- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id), variant_name)
6305- if subj.len == 0 || sum_name.len == 0 {
6625+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {
6626+ return []SmartcastContext{}
6627+ }
6628+ if subj.len == 0 {
63066629 return []SmartcastContext{}
63076630 }
63086631 return [
63096632 SmartcastContext{
63106633 expr_name: subj
6311- variant_name: variant_name
6312- sum_type_name: sum_name
6634+ variant_name: sc.variant_name
6635+ sum_type_name: sc.sum_type_name
63136636 },
63146637 ]
63156638}
@@ -6433,7 +6756,7 @@ fn (mut t Transformer) build_match_chain(match_expr_id flat.NodeId, orig_expr_id
64336756 // (build_match_chain runs per branch, and the compiler has very large matches).
64346757 n_conds := if is_else { 0 } else { t.count_conds(branch) }
64356758 body_start_idx := n_conds
6436- if !is_else && n_conds > 1 && t.match_branch_all_type_patterns(branch) {
6759+ if !is_else && n_conds > 1 && t.match_branch_all_type_patterns(match_expr_id, branch) {
64376760 return t.build_match_type_branch_chain(match_expr_id, orig_expr_id, branch, branches, idx,
64386761 0)
64396762 }
@@ -6443,17 +6766,15 @@ fn (mut t Transformer) build_match_chain(match_expr_id flat.NodeId, orig_expr_id
64436766 if !is_else {
64446767 if n_conds == 1 {
64456768 cond_val_id := t.a.child(&branch, 0)
6446- if variant_name := t.match_type_pattern(cond_val_id) {
6769+ if sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) {
64476770 subj := t.expr_key(match_expr_id)
6448- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id),
6449- variant_name)
6450- if subj.len > 0 && sum_name.len > 0 {
6451- t.push_smartcast(subj, variant_name, sum_name)
6771+ if subj.len > 0 {
6772+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)
64526773 sc_pushed++
64536774 }
64546775 orig_subj := t.expr_key(orig_expr_id)
6455- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
6456- t.push_smartcast(orig_subj, variant_name, sum_name)
6776+ if orig_subj.len > 0 && orig_subj != subj {
6777+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)
64576778 sc_pushed++
64586779 }
64596780 }
@@ -6542,7 +6863,8 @@ fn (mut t Transformer) build_match_value_chain(match_expr_id flat.NodeId, orig_e
65426863 branch := t.a.nodes[int(branches[idx])]
65436864 is_else := branch.value == 'else'
65446865 body_start_idx := if is_else { 0 } else { t.count_conds(branch) }
6545- if !is_else && t.match_branch_all_type_patterns(branch) && t.count_conds(branch) > 1 {
6866+ if !is_else && t.match_branch_all_type_patterns(match_expr_id, branch)
6867+ && t.count_conds(branch) > 1 {
65466868 return t.build_match_value_type_branch_chain(match_expr_id, orig_expr_id, branch, branches,
65476869 idx, 0, target_name, target_type)
65486870 }
@@ -6552,17 +6874,15 @@ fn (mut t Transformer) build_match_value_chain(match_expr_id flat.NodeId, orig_e
65526874 n_conds := t.count_conds(branch)
65536875 if n_conds == 1 {
65546876 cond_val_id := t.a.child(&branch, 0)
6555- if variant_name := t.match_type_pattern(cond_val_id) {
6877+ if sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) {
65566878 subj := t.expr_key(match_expr_id)
6557- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id),
6558- variant_name)
6559- if subj.len > 0 && sum_name.len > 0 {
6560- t.push_smartcast(subj, variant_name, sum_name)
6879+ if subj.len > 0 {
6880+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)
65616881 sc_pushed++
65626882 }
65636883 orig_subj := t.expr_key(orig_expr_id)
6564- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
6565- t.push_smartcast(orig_subj, variant_name, sum_name)
6884+ if orig_subj.len > 0 && orig_subj != subj {
6885+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)
65666886 sc_pushed++
65676887 }
65686888 }
@@ -6624,7 +6944,7 @@ fn (mut t Transformer) build_match_value_type_branch_chain(match_expr_id flat.No
66246944 }
66256945 }
66266946 cond_val_id := t.a.child(&branch, cond_idx)
6627- variant_name := t.match_type_pattern(cond_val_id) or {
6947+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or {
66286948 return t.build_match_value_chain(match_expr_id, orig_expr_id, branches, idx + 1,
66296949 target_name, target_type)
66306950 }
@@ -6639,15 +6959,20 @@ fn (mut t Transformer) build_match_value_type_branch_chain(match_expr_id flat.No
66396959 cond_id := t.transform_is_expr(is_id, t.a.nodes[int(is_id)])
66406960
66416961 mut sc_pushed := 0
6962+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {
6963+ SmartcastContext{
6964+ variant_name: variant_name
6965+ sum_type_name: ''
6966+ }
6967+ }
66426968 subj := t.expr_key(match_expr_id)
6643- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id), variant_name)
6644- if subj.len > 0 && sum_name.len > 0 {
6645- t.push_smartcast(subj, variant_name, sum_name)
6969+ if subj.len > 0 && sc.sum_type_name.len > 0 {
6970+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)
66466971 sc_pushed++
66476972 }
66486973 orig_subj := t.expr_key(orig_expr_id)
6649- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
6650- t.push_smartcast(orig_subj, variant_name, sum_name)
6974+ if orig_subj.len > 0 && orig_subj != subj && sc.sum_type_name.len > 0 {
6975+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)
66516976 sc_pushed++
66526977 }
66536978
@@ -6685,7 +7010,7 @@ fn (mut t Transformer) build_match_type_branch_chain(match_expr_id flat.NodeId,
66857010 }
66867011 }
66877012 cond_val_id := t.a.child(&branch, cond_idx)
6688- variant_name := t.match_type_pattern(cond_val_id) or {
7013+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or {
66897014 return t.build_match_chain(match_expr_id, orig_expr_id, branches, idx + 1)
66907015 }
66917016 is_start := t.a.children.len
@@ -6699,15 +7024,20 @@ fn (mut t Transformer) build_match_type_branch_chain(match_expr_id flat.NodeId,
66997024 cond_id := t.transform_is_expr(is_id, t.a.nodes[int(is_id)])
67007025
67017026 mut sc_pushed := 0
7027+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {
7028+ SmartcastContext{
7029+ variant_name: variant_name
7030+ sum_type_name: ''
7031+ }
7032+ }
67027033 subj := t.expr_key(match_expr_id)
6703- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id), variant_name)
6704- if subj.len > 0 && sum_name.len > 0 {
6705- t.push_smartcast(subj, variant_name, sum_name)
7034+ if subj.len > 0 && sc.sum_type_name.len > 0 {
7035+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)
67067036 sc_pushed++
67077037 }
67087038 orig_subj := t.expr_key(orig_expr_id)
6709- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
6710- t.push_smartcast(orig_subj, variant_name, sum_name)
7039+ if orig_subj.len > 0 && orig_subj != subj && sc.sum_type_name.len > 0 {
7040+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)
67117041 sc_pushed++
67127042 }
67137043
@@ -6781,7 +7111,7 @@ fn (mut t Transformer) build_match_cond(match_expr_id flat.NodeId, branch flat.N
67817111 if n_conds == 1 {
67827112 cond_val_id := t.a.child(&branch, 0)
67837113 cond_val := t.a.nodes[int(cond_val_id)]
6784- if variant_name := t.match_type_pattern(cond_val_id) {
7114+ if variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) {
67857115 is_start := t.a.children.len
67867116 t.a.children << match_expr_id
67877117 is_id := t.a.add_node(flat.Node{
@@ -6801,7 +7131,7 @@ fn (mut t Transformer) build_match_cond(match_expr_id flat.NodeId, branch flat.N
68017131 for i in 0 .. n_conds {
68027132 cond_val_id := t.a.child(&branch, i)
68037133 cond_val := t.a.nodes[int(cond_val_id)]
6804- variant_name := t.match_type_pattern(cond_val_id) or { '' }
7134+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or { '' }
68057135 cmp := if variant_name.len > 0 {
68067136 is_start := t.a.children.len
68077137 t.a.children << match_expr_id
@@ -6846,15 +7176,158 @@ fn (t &Transformer) match_type_pattern(cond_val_id flat.NodeId) ?string {
68467176 return none
68477177}
68487178
7179+fn (t &Transformer) match_type_pattern_for_subject(match_expr_id flat.NodeId, cond_val_id flat.NodeId) ?string {
7180+ if int(cond_val_id) < 0 {
7181+ return none
7182+ }
7183+ pattern := t.type_pattern_name(cond_val_id)
7184+ if pattern.len == 0 {
7185+ return none
7186+ }
7187+ subject_type := t.trim_pointer_type(t.original_expr_type(match_expr_id))
7188+ if t.is_interface_type_name(subject_type) {
7189+ return t.resolve_interface_pattern(pattern, subject_type)
7190+ }
7191+ if resolved_variant := t.resolve_sum_variant_pattern_for_subject(subject_type, pattern) {
7192+ return resolved_variant
7193+ }
7194+ if t.is_sum_variant(pattern) {
7195+ return pattern
7196+ }
7197+ return none
7198+}
7199+
7200+fn (t &Transformer) resolve_sum_variant_pattern_for_subject(subject_type string, pattern string) ?string {
7201+ if pattern.len == 0 {
7202+ return none
7203+ }
7204+ if !isnil(t.tc) {
7205+ if resolved := t.tc.sum_variant_type_for_pattern(subject_type, pattern) {
7206+ return resolved
7207+ }
7208+ }
7209+ resolved_sum := t.resolve_sum_name(subject_type)
7210+ return t.sum_variant_name(resolved_sum, pattern)
7211+}
7212+
7213+fn (t &Transformer) match_type_smartcast_context(match_expr_id flat.NodeId, cond_val_id flat.NodeId) ?SmartcastContext {
7214+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or { return none }
7215+ subject_type := t.trim_pointer_type(t.original_expr_type(match_expr_id))
7216+ sum_name := if t.is_interface_type_name(subject_type) {
7217+ resolved := t.resolve_interface_type_name(subject_type)
7218+ if resolved.len > 0 {
7219+ resolved
7220+ } else {
7221+ subject_type
7222+ }
7223+ } else {
7224+ t.sum_type_for_is_expr(subject_type, variant_name)
7225+ }
7226+ if sum_name.len == 0 {
7227+ return none
7228+ }
7229+ return SmartcastContext{
7230+ variant_name: variant_name
7231+ sum_type_name: sum_name
7232+ }
7233+}
7234+
7235+fn (t &Transformer) match_pattern_implements_interface(pattern string, subject_type string) bool {
7236+ return t.resolve_interface_pattern(pattern, subject_type) != none
7237+}
7238+
7239+fn (t &Transformer) resolve_interface_pattern(pattern string, subject_type string) ?string {
7240+ if !t.is_interface_type_name(subject_type) {
7241+ return none
7242+ }
7243+ resolved_iface := t.resolve_interface_type_name(subject_type)
7244+ iface := if resolved_iface.len > 0 { resolved_iface } else { subject_type }
7245+ for candidate in t.interface_pattern_candidates(pattern) {
7246+ if t.is_builtin_ierror_interface_name(iface) {
7247+ if t.tc.named_type_compatible_with_ierror(candidate) {
7248+ return candidate
7249+ }
7250+ } else if t.tc.named_type_implements_interface(candidate, iface) {
7251+ return candidate
7252+ }
7253+ }
7254+ return none
7255+}
7256+
7257+fn (t &Transformer) interface_pattern_candidates(pattern string) []string {
7258+ mut candidates := []string{}
7259+ if !pattern.contains('.') {
7260+ mut has_scoped_candidate := false
7261+ if t.cur_file.len > 0 {
7262+ for candidate in t.tc.file_selective_imports[file_import_key(t.cur_file, pattern)] or {
7263+ []string{}
7264+ } {
7265+ if t.interface_pattern_candidate_known(candidate) {
7266+ candidates << candidate
7267+ has_scoped_candidate = true
7268+ }
7269+ }
7270+ }
7271+ if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
7272+ local := '${t.cur_module}.${pattern}'
7273+ if t.interface_pattern_candidate_known(local) {
7274+ candidates << local
7275+ has_scoped_candidate = true
7276+ }
7277+ }
7278+ if !has_scoped_candidate {
7279+ candidates << pattern
7280+ }
7281+ } else if resolved := t.resolve_import_alias_pattern(pattern) {
7282+ candidates << resolved
7283+ candidates << pattern
7284+ } else {
7285+ candidates << pattern
7286+ }
7287+ qpattern := t.tc.qualify_name(pattern)
7288+ if qpattern != pattern {
7289+ candidates << qpattern
7290+ }
7291+ mut result := []string{}
7292+ mut seen := map[string]bool{}
7293+ for candidate in candidates {
7294+ if candidate.len == 0 || candidate in seen {
7295+ continue
7296+ }
7297+ seen[candidate] = true
7298+ result << candidate
7299+ }
7300+ return result
7301+}
7302+
7303+fn (t &Transformer) interface_pattern_candidate_known(candidate string) bool {
7304+ return candidate in t.tc.type_aliases || candidate in t.tc.structs
7305+ || candidate in t.tc.interface_names || candidate in t.tc.flag_enums
7306+ || candidate in t.tc.enum_names || candidate in t.tc.sum_types
7307+}
7308+
7309+fn (t &Transformer) resolve_import_alias_pattern(pattern string) ?string {
7310+ if t.cur_file.len == 0 {
7311+ return none
7312+ }
7313+ dot := pattern.index_u8(`.`)
7314+ if dot <= 0 {
7315+ return none
7316+ }
7317+ alias := pattern[..dot]
7318+ resolved := t.tc.file_imports[file_import_key(t.cur_file, alias)] or { return none }
7319+ return '${resolved}.${pattern[dot + 1..]}'
7320+}
7321+
68497322// match_branch_all_type_patterns supports match branch all type patterns handling for Transformer.
6850-fn (t &Transformer) match_branch_all_type_patterns(branch flat.Node) bool {
7323+fn (t &Transformer) match_branch_all_type_patterns(match_expr_id flat.NodeId, branch flat.Node) bool {
68517324 n_conds := t.count_conds(branch)
68527325 if n_conds == 0 {
68537326 return false
68547327 }
68557328 for i in 0 .. n_conds {
68567329 cond_val_id := t.a.child(&branch, i)
6857- if _ := t.match_type_pattern(cond_val_id) {
7330+ if _ := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) {
68587331 continue
68597332 }
68607333 return false
@@ -114,6 +114,7 @@ fn (mut t Transformer) run_parallel_transform(items []FnWorkItem, base_nodes int
114114 for ci in 0 .. thread_count {
115115 ww := unsafe { &Transformer(workers[ci]) }
116116 t.merge_worker(ww, chunks[ci + 1], base_nodes, base_children)
117+ ww.tc.free_parallel_transform_caches()
117118 // The worker's cloned base AST is no longer needed after merge.
118119 unsafe {
119120 mut wast := &flat.FlatAst(worker_asts[ci])
@@ -773,11 +773,17 @@ fn (t &Transformer) node_type(id flat.NodeId) string {
773773 if int(id) < 0 {
774774 return ''
775775 }
776+ node := t.a.nodes[int(id)]
776777 resolved := t.resolve_expr_type(id)
777778 if resolved.len > 0 {
779+ if t.generic_arg_is_unresolved(resolved) && node.typ.len > 0 {
780+ node_typ := t.normalize_type_alias(node.typ)
781+ if node_typ.len > 0 && !t.generic_arg_is_unresolved(node_typ) {
782+ return node_typ
783+ }
784+ }
778785 return resolved
779786 }
780- node := t.a.nodes[int(id)]
781787 mut deferred_call_typ := ''
782788 if node.typ.len > 0 {
783789 node_typ := t.normalize_type_alias(node.typ)
@@ -158,29 +158,33 @@ struct LocalBinding {
158158// TypeCache represents type cache data used by types.
159159struct TypeCache {
160160mut:
161- parse_enabled bool
162- parse_entries map[string]Type
163- c_entries map[string]string
161+ parse_enabled bool
162+ parse_entries map[string]Type
163+ c_entries map[string]string
164+ ierror_compat_entries map[string]int
164165}
165166
166167// TypeChecker represents type checker data used by types.
167168@[heap]
168169pub struct TypeChecker {
169170pub mut:
170- a &flat.FlatAst = unsafe { nil }
171- fn_ret_types map[string]Type
172- fn_param_types map[string][]Type
173- fn_ret_type_texts map[string]string // generic struct method key -> original return type text (e.g. `Box[T].clone` -> `Box[T]`)
174- fn_param_type_texts map[string][]string // generic struct method key -> original param type texts (receiver first)
175- fn_type_files map[string]string
176- fn_type_modules map[string]string
177- fn_generic_params map[string][]string
178- fn_variadic map[string]bool
179- c_variadic_fns map[string]bool
180- fn_implicit_veb_ctx map[string]bool
181- structs map[string][]StructField
182- struct_generic_params map[string][]string // generic struct base name -> type-param names (e.g. Vec4 -> [T])
183- struct_field_c_abi_fns map[string]string
171+ a &flat.FlatAst = unsafe { nil }
172+ fn_ret_types map[string]Type
173+ fn_param_types map[string][]Type
174+ fn_ret_type_texts map[string]string // generic struct method key -> original return type text (e.g. `Box[T].clone` -> `Box[T]`)
175+ fn_param_type_texts map[string][]string // generic struct method key -> original param type texts (receiver first)
176+ fn_type_files map[string]string
177+ fn_type_modules map[string]string
178+ fn_generic_params map[string][]string
179+ fn_variadic map[string]bool
180+ c_variadic_fns map[string]bool
181+ fn_implicit_veb_ctx map[string]bool
182+ structs map[string][]StructField
183+ struct_modules map[string]string
184+ struct_files map[string]string
185+ struct_error_embeds_shadow_builtin map[string]bool
186+ struct_generic_params map[string][]string // generic struct base name -> type-param names (e.g. Vec4 -> [T])
187+ struct_field_c_abi_fns map[string]string
184188 // concrete `Box[int].method` -> substituted CallInfo for a method *value* on a
185189 // generic receiver. The open `Box[T].method` registration is gone by cgen time, so
186190 // the checker stashes the resolved signature here for gen_method_value_closure.
@@ -190,6 +194,7 @@ pub mut:
190194 type_aliases map[string]string
191195 type_alias_c_abi_fns map[string]string
192196 sum_types map[string][]string
197+ sum_generic_params map[string][]string // generic sum type base name -> type-param names (e.g. Tree -> [T])
193198 enum_names map[string]bool
194199 enum_fields map[string][]string
195200 flag_enums map[string]bool
@@ -217,8 +222,7 @@ pub mut:
217222 errors []TypeError
218223 resolved_call_names []string // node_id -> resolved function name
219224 resolved_call_set []bool
220- resolved_fn_value_names []string // node_id -> resolved function value name
221- resolved_fn_value_set []bool
225+ resolved_fn_value_names map[int]string // node_id -> resolved function value name
222226 // Methods used as *values* (`recv.method` passed as a callback), recorded per enclosing
223227 // function during semantic checking — which has full scope/type info, runs before
224228 // markused, and (unlike a call) routes a value-context selector through check_selector.
@@ -235,6 +239,8 @@ pub mut:
235239 // scope); a reassignment in a deeper conditional/loop scope keeps the maybe-method marker.
236240 method_value_local_depth map[string]int
237241 cur_fn_node_id int = -1
242+ cur_fn_mut_param_base_types map[string]Type
243+ cur_fn_mut_param_scope_depths map[string]int
238244 expr_type_values []Type // node_id -> complex/contextual resolved type
239245 expr_type_set []bool
240246 checking_nodes []bool
@@ -253,59 +259,65 @@ mut:
253259pub fn TypeChecker.new(a &flat.FlatAst) TypeChecker {
254260 fs := new_scope(unsafe { nil })
255261 return TypeChecker{
256- a: a
257- fn_ret_types: map[string]Type{}
258- fn_param_types: map[string][]Type{}
259- fn_ret_type_texts: map[string]string{}
260- fn_param_type_texts: map[string][]string{}
261- fn_type_files: map[string]string{}
262- fn_type_modules: map[string]string{}
263- fn_generic_params: map[string][]string{}
264- fn_variadic: map[string]bool{}
265- c_variadic_fns: map[string]bool{}
266- fn_implicit_veb_ctx: map[string]bool{}
267- structs: map[string][]StructField{}
268- struct_generic_params: map[string][]string{}
269- struct_field_c_abi_fns: map[string]string{}
270- generic_method_value_info: map[string]CallInfo{}
271- params_structs: map[string]bool{}
272- unions: map[string]bool{}
273- type_aliases: map[string]string{}
274- type_alias_c_abi_fns: map[string]string{}
275- sum_types: map[string][]string{}
276- enum_names: map[string]bool{}
277- enum_fields: map[string][]string{}
278- flag_enums: map[string]bool{}
279- interface_names: map[string]bool{}
280- interface_fields: map[string][]StructField{}
281- interface_embeds: map[string][]string{}
282- interface_abstract_methods: map[string][]string{}
283- c_globals: map[string]Type{}
284- const_types: map[string]Type{}
285- const_exprs: map[string]flat.NodeId{}
286- const_modules: map[string]string{}
287- const_suffixes: map[string]string{}
288- imports: map[string]string{}
289- file_imports: map[string]string{}
290- file_selective_imports: map[string][]string{}
291- file_modules: map[string]string{}
292- file_scope: fs
293- cur_scope: fs
294- resolved_call_names: []string{len: a.nodes.len}
295- resolved_call_set: []bool{len: a.nodes.len}
296- resolved_fn_value_names: []string{len: a.nodes.len}
297- resolved_fn_value_set: []bool{len: a.nodes.len}
298- method_values_by_fn: map[int][]string{}
299- method_value_locals: map[string]bool{}
300- method_value_local_depth: map[string]int{}
301- expr_type_values: []Type{len: a.nodes.len, init: Type(void_)}
302- expr_type_set: []bool{len: a.nodes.len}
303- checking_nodes: []bool{len: a.nodes.len}
304- diagnostic_files: map[string]bool{}
305- smartcasts: map[string]Type{}
306- type_cache: &TypeCache{
307- parse_entries: map[string]Type{}
308- c_entries: map[string]string{}
262+ a: a
263+ fn_ret_types: map[string]Type{}
264+ fn_param_types: map[string][]Type{}
265+ fn_ret_type_texts: map[string]string{}
266+ fn_param_type_texts: map[string][]string{}
267+ fn_type_files: map[string]string{}
268+ fn_type_modules: map[string]string{}
269+ fn_generic_params: map[string][]string{}
270+ fn_variadic: map[string]bool{}
271+ c_variadic_fns: map[string]bool{}
272+ fn_implicit_veb_ctx: map[string]bool{}
273+ structs: map[string][]StructField{}
274+ struct_modules: map[string]string{}
275+ struct_files: map[string]string{}
276+ struct_error_embeds_shadow_builtin: map[string]bool{}
277+ struct_generic_params: map[string][]string{}
278+ struct_field_c_abi_fns: map[string]string{}
279+ generic_method_value_info: map[string]CallInfo{}
280+ params_structs: map[string]bool{}
281+ unions: map[string]bool{}
282+ type_aliases: map[string]string{}
283+ type_alias_c_abi_fns: map[string]string{}
284+ sum_types: map[string][]string{}
285+ sum_generic_params: map[string][]string{}
286+ enum_names: map[string]bool{}
287+ enum_fields: map[string][]string{}
288+ flag_enums: map[string]bool{}
289+ interface_names: map[string]bool{}
290+ interface_fields: map[string][]StructField{}
291+ interface_embeds: map[string][]string{}
292+ interface_abstract_methods: map[string][]string{}
293+ c_globals: map[string]Type{}
294+ const_types: map[string]Type{}
295+ const_exprs: map[string]flat.NodeId{}
296+ const_modules: map[string]string{}
297+ const_suffixes: map[string]string{}
298+ imports: map[string]string{}
299+ file_imports: map[string]string{}
300+ file_selective_imports: map[string][]string{}
301+ file_modules: map[string]string{}
302+ file_scope: fs
303+ cur_scope: fs
304+ resolved_call_names: []string{len: a.nodes.len}
305+ resolved_call_set: []bool{len: a.nodes.len}
306+ resolved_fn_value_names: map[int]string{}
307+ method_values_by_fn: map[int][]string{}
308+ method_value_locals: map[string]bool{}
309+ method_value_local_depth: map[string]int{}
310+ cur_fn_mut_param_base_types: map[string]Type{}
311+ cur_fn_mut_param_scope_depths: map[string]int{}
312+ expr_type_values: []Type{len: a.nodes.len, init: Type(void_)}
313+ expr_type_set: []bool{len: a.nodes.len}
314+ checking_nodes: []bool{len: a.nodes.len}
315+ diagnostic_files: map[string]bool{}
316+ smartcasts: map[string]Type{}
317+ type_cache: &TypeCache{
318+ parse_entries: map[string]Type{}
319+ c_entries: map[string]string{}
320+ ierror_compat_entries: map[string]int{}
309321 }
310322 }
311323}
@@ -321,38 +333,59 @@ pub fn TypeChecker.new(a &flat.FlatAst) TypeChecker {
321333pub fn (tc &TypeChecker) fork_for_parallel_transform(ast &flat.FlatAst) &TypeChecker {
322334 mut forked := *tc
323335 forked.a = ast
336+ forked.resolved_fn_value_names = tc.resolved_fn_value_names.clone()
324337 forked.type_cache = &TypeCache{
325- parse_enabled: if tc.type_cache != unsafe { nil } {
338+ parse_enabled: if tc.type_cache != unsafe { nil } {
326339 tc.type_cache.parse_enabled
327340 } else {
328341 false
329342 }
330- parse_entries: map[string]Type{}
331- c_entries: map[string]string{}
343+ parse_entries: map[string]Type{}
344+ c_entries: map[string]string{}
345+ ierror_compat_entries: map[string]int{}
332346 }
333347 return &forked
334348}
335349
350+// free_parallel_transform_caches releases private memoization maps owned by a forked
351+// transform checker and leaves it valid if it is accidentally read again.
352+pub fn (mut tc TypeChecker) free_parallel_transform_caches() {
353+ parse_enabled := if tc.type_cache != unsafe { nil } {
354+ tc.type_cache.parse_enabled
355+ } else {
356+ false
357+ }
358+ if tc.type_cache != unsafe { nil } {
359+ unsafe {
360+ tc.type_cache.parse_entries.free()
361+ tc.type_cache.c_entries.free()
362+ tc.type_cache.ierror_compat_entries.free()
363+ }
364+ }
365+ tc.type_cache = &TypeCache{
366+ parse_enabled: parse_enabled
367+ parse_entries: map[string]Type{}
368+ c_entries: map[string]string{}
369+ ierror_compat_entries: map[string]int{}
370+ }
371+}
372+
336373// reset_node_caches updates reset node caches state for types.
337374fn (mut tc TypeChecker) reset_node_caches(n int) {
338375 tc.resolved_call_names = []string{len: n}
339376 tc.resolved_call_set = []bool{len: n}
340- tc.resolved_fn_value_names = []string{len: n}
341- tc.resolved_fn_value_set = []bool{len: n}
377+ tc.resolved_fn_value_names = map[int]string{}
342378 tc.expr_type_values = []Type{len: n, init: Type(void_)}
343379 tc.expr_type_set = []bool{len: n}
344380 tc.checking_nodes = []bool{len: n}
345381}
346382
347383fn (mut tc TypeChecker) extend_node_caches(n int) {
348- if n <= tc.resolved_call_names.len && n <= tc.resolved_fn_value_names.len
349- && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len {
384+ if n <= tc.resolved_call_names.len && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len {
350385 return
351386 }
352387 extend_string_cache(mut tc.resolved_call_names, n)
353388 extend_bool_cache(mut tc.resolved_call_set, n)
354- extend_string_cache(mut tc.resolved_fn_value_names, n)
355- extend_bool_cache(mut tc.resolved_fn_value_set, n)
356389 extend_type_cache(mut tc.expr_type_values, n)
357390 extend_bool_cache(mut tc.expr_type_set, n)
358391 extend_bool_cache(mut tc.checking_nodes, n)
@@ -449,8 +482,9 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {
449482 tc.scope_pool_index = 0
450483 tc.reset_node_caches(a.nodes.len)
451484 tc.type_cache = &TypeCache{
452- parse_entries: map[string]Type{}
453- c_entries: map[string]string{}
485+ parse_entries: map[string]Type{}
486+ c_entries: map[string]string{}
487+ ierror_compat_entries: map[string]int{}
454488 }
455489 for node in a.nodes {
456490 if node.kind == .struct_decl && node.value == 'string' {
@@ -492,6 +526,8 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {
492526 if qname !in tc.structs {
493527 tc.structs[qname] = []StructField{}
494528 }
529+ tc.struct_modules[qname] = tc.cur_module
530+ tc.struct_files[qname] = tc.cur_file
495531 if node.generic_params.len > 0 {
496532 tc.struct_generic_params[qname] = node.generic_params.clone()
497533 if qname != node.value {
@@ -512,7 +548,14 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {
512548 v := a.child_node(&node, i)
513549 variants << tc.qualify_name(v.value)
514550 }
515- tc.sum_types[tc.qualify_name(node.value)] = variants
551+ qname := tc.qualify_name(node.value)
552+ tc.sum_types[qname] = variants
553+ if node.generic_params.len > 0 {
554+ tc.sum_generic_params[qname] = node.generic_params.clone()
555+ if qname != node.value {
556+ tc.sum_generic_params[node.value] = node.generic_params.clone()
557+ }
558+ }
516559 } else if node.typ.len > 0 {
517560 qname := tc.qualify_name(node.value)
518561 tc.type_aliases[qname] = tc.qualify_type_text(node.typ)
@@ -587,14 +630,22 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {
587630 .struct_decl {
588631 mut fields := []StructField{}
589632 mut field_c_abi_fns := map[string]string{}
633+ mut shadows_builtin_error_embed := false
590634 for i in 0 .. node.children_count {
591635 f := a.child_node(&node, i)
592636 if f.kind != .field_decl {
593637 continue
594638 }
595639 field_typ := if f.typ.len > 0 { f.typ } else { f.value }
640+ field_is_embed := source_field_decl_is_embed(f, field_typ)
641+ shadows_builtin_error := field_is_embed
642+ && field_typ in ['Error', 'MessageError']
643+ && tc.unqualified_type_name_shadows_builtin_in_scope(field_typ, tc.cur_file, tc.cur_module)
644+ if shadows_builtin_error {
645+ shadows_builtin_error_embed = true
646+ }
596647 mut typ := tc.parse_type(field_typ)
597- if f.value == field_typ {
648+ if f.value == field_typ || shadows_builtin_error {
598649 typ = tc.resolve_known_field_type(field_typ, typ)
599650 }
600651 if c_abi_fn := tc.c_abi_fn_ptr_type_for_type_text(field_typ) {
@@ -607,6 +658,11 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {
607658 }
608659 qname := tc.qualify_name(node.value)
609660 tc.structs[qname] = fields
661+ tc.struct_modules[qname] = tc.cur_module
662+ tc.struct_files[qname] = tc.cur_file
663+ if shadows_builtin_error_embed {
664+ tc.struct_error_embeds_shadow_builtin[qname] = true
665+ }
610666 for field_name, c_abi_fn in field_c_abi_fns {
611667 tc.struct_field_c_abi_fns[struct_field_c_abi_key(qname, field_name)] = c_abi_fn
612668 }
@@ -1161,6 +1217,25 @@ fn (mut tc TypeChecker) register_c_variadic_fn(name string) {
11611217 }
11621218}
11631219
1220+fn (mut tc TypeChecker) insert_fn_param_binding(p flat.Node) {
1221+ if p.kind != .param || p.value.len == 0 {
1222+ return
1223+ }
1224+ typ := tc.parse_type(p.typ)
1225+ tc.cur_scope.insert(p.value, typ)
1226+ if p.is_mut {
1227+ tc.cur_fn_mut_param_base_types[p.value] = mut_param_base_type(typ)
1228+ tc.cur_fn_mut_param_scope_depths[p.value] = tc.cur_scope_depth()
1229+ }
1230+}
1231+
1232+fn mut_param_base_type(typ Type) Type {
1233+ if typ is Pointer {
1234+ return typ.base_type
1235+ }
1236+ return typ
1237+}
1238+
11641239// annotate_types performs a scope-aware walk over every function body, tracking
11651240// local variable types as they are declared, and records complex/contextual
11661241// expression types. This mirrors what the v2 transformer relies on: the type
@@ -1190,13 +1265,15 @@ pub fn (mut tc TypeChecker) annotate_types_with_used(used_fns map[string]bool) {
11901265 if !tc.should_annotate_fn(node, used_fns) {
11911266 continue
11921267 }
1268+ mut saved_mut_params := tc.cur_fn_mut_param_base_types.move()
1269+ mut saved_mut_param_depths := tc.cur_fn_mut_param_scope_depths.move()
1270+ tc.cur_fn_mut_param_base_types = map[string]Type{}
1271+ tc.cur_fn_mut_param_scope_depths = map[string]int{}
11931272 tc.cur_scope = tc.file_scope
11941273 tc.push_scope()
11951274 for pi in 0 .. node.children_count {
11961275 p := tc.a.child_node(&node, pi)
1197- if p.kind == .param && p.value.len > 0 {
1198- tc.cur_scope.insert(p.value, tc.parse_type(p.typ))
1199- }
1276+ tc.insert_fn_param_binding(p)
12001277 }
12011278 tc.insert_implicit_veb_ctx(node)
12021279 for i in 0 .. node.children_count {
@@ -1206,6 +1283,8 @@ pub fn (mut tc TypeChecker) annotate_types_with_used(used_fns map[string]bool) {
12061283 }
12071284 }
12081285 tc.pop_scope()
1286+ tc.cur_fn_mut_param_base_types = saved_mut_params.move()
1287+ tc.cur_fn_mut_param_scope_depths = saved_mut_param_depths.move()
12091288 }
12101289 }
12111290}
@@ -1528,12 +1607,26 @@ pub fn (tc &TypeChecker) resolved_call_name(id flat.NodeId) ?string {
15281607// resolved_fn_value_name returns the checker-resolved function name for a function value node.
15291608pub fn (tc &TypeChecker) resolved_fn_value_name(id flat.NodeId) ?string {
15301609 idx := int(id)
1531- if idx >= 0 && idx < tc.resolved_fn_value_set.len && tc.resolved_fn_value_set[idx] {
1532- return tc.resolved_fn_value_names[idx]
1610+ if idx >= 0 {
1611+ if name := tc.resolved_fn_value_names[idx] {
1612+ return name
1613+ }
15331614 }
15341615 return none
15351616}
15361617
1618+// copy_cloned_resolution copies checker-owned call/function-value resolution metadata
1619+// from an original node to a transform-created clone. The call cache remains dense; the
1620+// function-value cache is intentionally sparse because only a tiny subset of nodes use it.
1621+pub fn (mut tc TypeChecker) copy_cloned_resolution(src_id flat.NodeId, dst_id flat.NodeId) {
1622+ if name := tc.resolved_call_name(src_id) {
1623+ tc.remember_resolved_call(dst_id, name)
1624+ }
1625+ if name := tc.resolved_fn_value_name(src_id) {
1626+ tc.remember_resolved_fn_value(dst_id, name)
1627+ }
1628+}
1629+
15371630// resolve_fn_value_name_for_expected resolves and records a function value in an expected FnType context.
15381631fn (mut tc TypeChecker) resolve_fn_value_name_for_expected(id flat.NodeId, expected Type) ?string {
15391632 if name := tc.resolved_fn_value_name(id) {
@@ -1572,13 +1665,7 @@ fn (mut tc TypeChecker) remember_resolved_fn_value(id flat.NodeId, name string)
15721665 if idx < 0 {
15731666 return
15741667 }
1575- if idx >= tc.resolved_fn_value_names.len {
1576- tc.extend_node_caches(tc.a.nodes.len)
1577- }
1578- if idx < tc.resolved_fn_value_names.len {
1579- tc.resolved_fn_value_names[idx] = name
1580- tc.resolved_fn_value_set[idx] = true
1581- }
1668+ tc.resolved_fn_value_names[idx] = name
15821669}
15831670
15841671fn (mut tc TypeChecker) remember_resolved_fn_value_chain(id flat.NodeId, name string) {
@@ -1657,6 +1744,10 @@ pub fn (mut tc TypeChecker) check_semantics() {
16571744 tc.check_const_field_values(node)
16581745 }
16591746 .fn_decl {
1747+ mut saved_mut_params := tc.cur_fn_mut_param_base_types.move()
1748+ mut saved_mut_param_depths := tc.cur_fn_mut_param_scope_depths.move()
1749+ tc.cur_fn_mut_param_base_types = map[string]Type{}
1750+ tc.cur_fn_mut_param_scope_depths = map[string]int{}
16601751 tc.check_decl_type_strings(flat.NodeId(i), node)
16611752 tc.cur_fn_ret_type = tc.parse_type(node.typ)
16621753 tc.cur_fn_node_id = i
@@ -1665,9 +1756,7 @@ pub fn (mut tc TypeChecker) check_semantics() {
16651756 tc.push_scope()
16661757 for pi in 0 .. node.children_count {
16671758 p := tc.a.child_node(&node, pi)
1668- if p.kind == .param && p.value.len > 0 {
1669- tc.cur_scope.insert(p.value, tc.parse_type(p.typ))
1670- }
1759+ tc.insert_fn_param_binding(p)
16711760 }
16721761 tc.insert_implicit_veb_ctx(node)
16731762 tc.check_fn_body(node)
@@ -1682,6 +1771,8 @@ pub fn (mut tc TypeChecker) check_semantics() {
16821771 }
16831772 tc.pop_scope()
16841773 tc.cur_fn_ret_type = Type(void_)
1774+ tc.cur_fn_mut_param_base_types = saved_mut_params.move()
1775+ tc.cur_fn_mut_param_scope_depths = saved_mut_param_depths.move()
16851776 }
16861777 .c_fn_decl {
16871778 if tc.reject_unsupported_generics {
@@ -2992,6 +3083,10 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) {
29923083 tc.check_select_stmt(node)
29933084 return
29943085 }
3086+ if node.kind == .infix {
3087+ tc.check_infix(id, node)
3088+ return
3089+ }
29953090 // A method value stored in a container escapes the single-use guarantee of its per-site
29963091 // static receiver, so reject `[obj.method]` / `arr << obj.method` / `{'k': obj.method}`.
29973092 if node.kind == .array_literal {
@@ -3003,15 +3098,92 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) {
30033098 for j := 1; j < node.children_count; j += 2 {
30043099 tc.reject_stored_method_value(tc.a.child(&node, j))
30053100 }
3006- } else if node.kind == .infix && node.op == .left_shift && node.children_count >= 2 {
3101+ }
3102+
3103+ for i in 0 .. node.children_count {
3104+ tc.check_node(tc.a.child(&node, i))
3105+ }
3106+}
3107+
3108+// check_infix validates type-sensitive infix operations that would otherwise reach CGen
3109+// as raw helper calls with incompatible arguments.
3110+fn (mut tc TypeChecker) check_infix(id flat.NodeId, node flat.Node) {
3111+ for i in 0 .. node.children_count {
3112+ tc.check_node(tc.a.child(&node, i))
3113+ }
3114+ if node.op == .left_shift && node.children_count >= 2 {
30073115 if unwrap_pointer(tc.resolve_type(tc.a.child(&node, 0))) is Array {
30083116 tc.reject_stored_method_value(tc.a.child(&node, 1))
30093117 }
30103118 }
3119+ if node.op != .plus || node.children_count < 2 || !tc.should_diagnose(id) {
3120+ return
3121+ }
3122+ lhs_id := tc.a.child(&node, 0)
3123+ rhs_id := tc.a.child(&node, 1)
3124+ lhs_type := tc.infix_read_type(lhs_id)
3125+ rhs_type := tc.infix_read_type(rhs_id)
3126+ lhs_is_string := type_is_string_like(lhs_type)
3127+ rhs_is_string := type_is_string_like(rhs_type)
3128+ if lhs_is_string == rhs_is_string {
3129+ return
3130+ }
3131+ if lhs_type is Unknown || rhs_type is Unknown {
3132+ return
3133+ }
3134+ tc.record_error(.assignment_mismatch,
3135+ 'operator `+` cannot concatenate `${lhs_type.name()}` and `${rhs_type.name()}`', id)
3136+}
30113137
3012- for i in 0 .. node.children_count {
3013- tc.check_node(tc.a.child(&node, i))
3138+fn (tc &TypeChecker) infix_read_type(id flat.NodeId) Type {
3139+ typ := tc.resolve_type(id)
3140+ if int(id) < 0 {
3141+ return typ
3142+ }
3143+ node := tc.a.nodes[int(id)]
3144+ if node.kind == .ident {
3145+ if base := tc.mut_param_base_for_current_ident(node.value, typ) {
3146+ return base
3147+ }
3148+ }
3149+ return typ
3150+}
3151+
3152+fn (tc &TypeChecker) mut_param_base_for_current_ident(name string, typ Type) ?Type {
3153+ if typ !is Pointer {
3154+ return none
3155+ }
3156+ param_depth := tc.cur_fn_mut_param_scope_depths[name] or { return none }
3157+ binding_depth := tc.current_binding_depth(name) or { return none }
3158+ if binding_depth != param_depth {
3159+ return none
3160+ }
3161+ return tc.cur_fn_mut_param_base_types[name] or { return none }
3162+}
3163+
3164+fn (tc &TypeChecker) current_binding_depth(name string) ?int {
3165+ mut depth := tc.cur_scope_depth()
3166+ mut scope := tc.cur_scope
3167+ for scope != unsafe { nil } {
3168+ for i := scope.names.len - 1; i >= 0; i-- {
3169+ if scope.names[i] == name {
3170+ return depth
3171+ }
3172+ }
3173+ scope = scope.parent
3174+ depth--
3175+ }
3176+ return none
3177+}
3178+
3179+fn type_is_string_like(typ Type) bool {
3180+ if typ is String {
3181+ return true
30143182 }
3183+ if typ is Alias {
3184+ return type_is_string_like(typ.base_type)
3185+ }
3186+ return false
30153187}
30163188
30173189// check_select_stmt validates a `select { ... }` statement. A receive branch
@@ -3088,13 +3260,15 @@ fn (mut tc TypeChecker) check_or_expr(node flat.Node) {
30883260// check_fn_literal validates check fn literal state for types.
30893261fn (mut tc TypeChecker) check_fn_literal(node flat.Node) {
30903262 saved_ret := tc.cur_fn_ret_type
3263+ mut saved_mut_params := tc.cur_fn_mut_param_base_types.move()
3264+ mut saved_mut_param_depths := tc.cur_fn_mut_param_scope_depths.move()
3265+ tc.cur_fn_mut_param_base_types = map[string]Type{}
3266+ tc.cur_fn_mut_param_scope_depths = map[string]int{}
30913267 tc.cur_fn_ret_type = tc.parse_type(node.typ)
30923268 tc.push_scope()
30933269 for i in 0 .. node.children_count {
30943270 child := tc.a.child_node(&node, i)
3095- if child.kind == .param && child.value.len > 0 {
3096- tc.cur_scope.insert(child.value, tc.parse_type(child.typ))
3097- }
3271+ tc.insert_fn_param_binding(child)
30983272 }
30993273 for i in 0 .. node.children_count {
31003274 child_id := tc.a.child(&node, i)
@@ -3106,6 +3280,8 @@ fn (mut tc TypeChecker) check_fn_literal(node flat.Node) {
31063280 }
31073281 tc.pop_scope()
31083282 tc.cur_fn_ret_type = saved_ret
3283+ tc.cur_fn_mut_param_base_types = saved_mut_params.move()
3284+ tc.cur_fn_mut_param_scope_depths = saved_mut_param_depths.move()
31093285}
31103286
31113287// check_lambda_expr validates check lambda expr state for types.
@@ -3229,6 +3405,9 @@ fn (mut tc TypeChecker) check_decl_assign(id flat.NodeId, node flat.Node) {
32293405 if tc.check_multi_return_decl_assign(id, node) {
32303406 return
32313407 }
3408+ if tc.check_assignment_marker(id, node) {
3409+ return
3410+ }
32323411 mut i := 0
32333412 for i + 1 < node.children_count {
32343413 lhs_id := tc.a.child(&node, i)
@@ -3470,16 +3649,20 @@ fn (mut tc TypeChecker) check_assign(id flat.NodeId, node flat.Node) {
34703649 if tc.check_multi_return_assign(id, node) {
34713650 return
34723651 }
3652+ if tc.check_assignment_marker(id, node) {
3653+ return
3654+ }
34733655 mut i := 0
34743656 for i + 1 < node.children_count {
34753657 lhs_id := tc.a.child(&node, i)
34763658 rhs_id := tc.a.child(&node, i + 1)
34773659 lhs_type := tc.resolve_lvalue_type(lhs_id)
3660+ expected_type := tc.assignment_expected_type(lhs_id, lhs_type)
34783661 tc.check_node(rhs_id)
3479- rhs_type := tc.resolve_expr(rhs_id, lhs_type)
3480- if !tc.type_compatible(rhs_type, lhs_type) {
3662+ rhs_type := tc.resolve_expr(rhs_id, expected_type)
3663+ if !tc.type_compatible(rhs_type, expected_type) {
34813664 tc.type_mismatch(.assignment_mismatch,
3482- 'cannot assign `${rhs_type.name()}` to `${lhs_type.name()}`', id)
3665+ 'cannot assign `${rhs_type.name()}` to `${expected_type.name()}`', id)
34833666 }
34843667 if node.kind in [.assign, .selector_assign, .index_assign] {
34853668 if tc.expr_is_method_value(rhs_id) && !tc.lvalue_is_local_var(lhs_id) {
@@ -3496,6 +3679,14 @@ fn (mut tc TypeChecker) check_assign(id flat.NodeId, node flat.Node) {
34963679 }
34973680}
34983681
3682+fn (mut tc TypeChecker) check_assignment_marker(id flat.NodeId, node flat.Node) bool {
3683+ if node.value.starts_with('for init assignment mismatch:') {
3684+ tc.record_error(.assignment_mismatch, node.value, id)
3685+ return true
3686+ }
3687+ return false
3688+}
3689+
34993690// index_assign_lhs_is_map supports index assign lhs is map handling for TypeChecker.
35003691fn (tc &TypeChecker) index_assign_lhs_is_map(node flat.Node) bool {
35013692 if node.children_count == 0 {
@@ -3534,9 +3725,10 @@ fn (mut tc TypeChecker) check_multi_return_assign(id flat.NodeId, node flat.Node
35343725 }
35353726 for i, lhs_id in lhs_ids {
35363727 lhs_type := tc.resolve_lvalue_type(lhs_id)
3537- if !tc.type_compatible(rhs_type.types[i], lhs_type) {
3728+ expected_type := tc.assignment_expected_type(lhs_id, lhs_type)
3729+ if !tc.type_compatible(rhs_type.types[i], expected_type) {
35383730 tc.type_mismatch(.assignment_mismatch,
3539- 'cannot assign `${rhs_type.types[i].name()}` to `${lhs_type.name()}`', id)
3731+ 'cannot assign `${rhs_type.types[i].name()}` to `${expected_type.name()}`', id)
35403732 }
35413733 }
35423734 return true
@@ -3563,6 +3755,19 @@ fn (mut tc TypeChecker) check_postfix(id flat.NodeId, node flat.Node) {
35633755 }
35643756}
35653757
3758+fn (tc &TypeChecker) assignment_expected_type(lhs_id flat.NodeId, lhs_type Type) Type {
3759+ if int(lhs_id) < 0 {
3760+ return lhs_type
3761+ }
3762+ lhs := tc.a.nodes[int(lhs_id)]
3763+ if lhs.kind == .ident {
3764+ if base := tc.mut_param_base_for_current_ident(lhs.value, lhs_type) {
3765+ return base
3766+ }
3767+ }
3768+ return lhs_type
3769+}
3770+
35663771// resolve_lvalue_type resolves resolve lvalue type information for types.
35673772fn (mut tc TypeChecker) resolve_lvalue_type(lhs_id flat.NodeId) Type {
35683773 if int(lhs_id) < 0 {
@@ -3657,13 +3862,104 @@ fn (mut tc TypeChecker) check_return(id flat.NodeId, node flat.Node) {
36573862 }
36583863 child_id := tc.a.child(&node, 0)
36593864 tc.check_node(child_id)
3865+ if expected is ResultType {
3866+ if bad_type := tc.invalid_ierror_return_expr_type_name(child_id, expected) {
3867+ tc.record_error(.return_mismatch,
3868+ 'cannot return `${bad_type}` as `${Type(expected).name()}`', id)
3869+ return
3870+ }
3871+ }
36603872 actual := tc.resolve_expr(child_id, expected)
36613873 if !tc.type_compatible(actual, expected) {
3874+ if expected is ResultType {
3875+ if is_ierror_type(actual) || tc.type_compatible_with_ierror_payload(actual) {
3876+ return
3877+ }
3878+ }
36623879 tc.type_mismatch(.return_mismatch,
36633880 'cannot return `${actual.name()}` as `${expected.name()}`', id)
36643881 }
36653882}
36663883
3884+fn (tc &TypeChecker) invalid_ierror_return_expr_type_name(id flat.NodeId, expected ResultType) ?string {
3885+ if !tc.valid_node_id(id) {
3886+ return none
3887+ }
3888+ raw_type := tc.resolve_type(id)
3889+ if tc.type_compatible(raw_type, expected) {
3890+ return none
3891+ }
3892+ if tc.type_compatible(raw_type, expected.base_type) {
3893+ return none
3894+ }
3895+ node := tc.a.nodes[int(id)]
3896+ match node.kind {
3897+ .expr_stmt, .paren {
3898+ if node.children_count > 0 {
3899+ return tc.invalid_ierror_return_expr_type_name(tc.a.child(&node, 0), expected)
3900+ }
3901+ }
3902+ .prefix {
3903+ if node.op == .amp && node.children_count > 0 {
3904+ return tc.invalid_ierror_return_expr_type_name(tc.a.child(&node, 0), expected)
3905+ }
3906+ }
3907+ .struct_init {
3908+ concrete := tc.resolve_unqualified_builtin_error_struct_name(node.value) or {
3909+ tc.resolve_selective_import_type_symbol(node.value) or {
3910+ tc.qualify_name(node.value)
3911+ }
3912+ }
3913+ if !tc.named_type_compatible_with_ierror(concrete) {
3914+ return concrete
3915+ }
3916+ }
3917+ .match_stmt {
3918+ for i in 1 .. node.children_count {
3919+ branch_id := tc.a.child(&node, i)
3920+ if !tc.valid_node_id(branch_id) || tc.a.nodes[int(branch_id)].kind != .match_branch {
3921+ continue
3922+ }
3923+ tail := tc.branch_tail_expr_id(branch_id)
3924+ if bad_type := tc.invalid_ierror_return_expr_type_name(tail, expected) {
3925+ return bad_type
3926+ }
3927+ }
3928+ }
3929+ .if_expr {
3930+ if node.children_count > 1 {
3931+ then_tail := tc.branch_tail_expr_id(tc.a.child(&node, 1))
3932+ if bad_type := tc.invalid_ierror_return_expr_type_name(then_tail, expected) {
3933+ return bad_type
3934+ }
3935+ }
3936+ if node.children_count > 2 {
3937+ else_id := tc.a.child(&node, 2)
3938+ else_tail := if tc.valid_node_id(else_id)
3939+ && tc.a.nodes[int(else_id)].kind == .if_expr {
3940+ else_id
3941+ } else {
3942+ tc.branch_tail_expr_id(else_id)
3943+ }
3944+ if bad_type := tc.invalid_ierror_return_expr_type_name(else_tail, expected) {
3945+ return bad_type
3946+ }
3947+ }
3948+ }
3949+ else {
3950+ if is_ierror_type(raw_type) || tc.type_compatible_with_ierror_payload(raw_type) {
3951+ return none
3952+ }
3953+ if raw_type is Unknown || raw_type is Void {
3954+ return none
3955+ }
3956+ return raw_type.name()
3957+ }
3958+ }
3959+
3960+ return none
3961+}
3962+
36673963fn multi_return_payload_type(typ Type) ?MultiReturn {
36683964 if typ is MultiReturn {
36693965 return typ
@@ -3685,6 +3981,10 @@ fn multi_return_payload_type(typ Type) ?MultiReturn {
36853981
36863982// check_call validates check call state for types.
36873983fn (mut tc TypeChecker) check_call(id flat.NodeId, node flat.Node) {
3984+ if sum_name := tc.sum_constructor_call_name(node) {
3985+ tc.check_sum_constructor_call(id, node, sum_name)
3986+ return
3987+ }
36883988 if info := tc.resolve_call_info(id, node) {
36893989 if info.name.len > 0 && !is_array_dsl_call_name(info.name) {
36903990 tc.remember_resolved_call(id, info.name)
@@ -3708,99 +4008,250 @@ fn (mut tc TypeChecker) check_call(id flat.NodeId, node flat.Node) {
37084008 }
37094009}
37104010
3711-// should_diagnose reports whether should diagnose applies in types.
3712-fn (tc &TypeChecker) should_diagnose(id flat.NodeId) bool {
3713- if int(id) < 0 || int(id) < tc.a.user_code_start {
3714- return false
3715- }
3716- if int(id) < tc.a.nodes.len && !tc.a.nodes[int(id)].pos.is_valid() && !tc.diagnose_unknown_calls {
3717- return false
3718- }
3719- if tc.diagnostic_files.len == 0 {
3720- return true
3721- }
3722- return tc.cur_file in tc.diagnostic_files
3723-}
3724-
3725-fn (tc &TypeChecker) should_diagnose_unsupported_generic(id flat.NodeId) bool {
3726- if tc.should_diagnose(id) {
3727- return true
3728- }
3729- if int(id) < 0 || int(id) < tc.a.user_code_start {
3730- return false
4011+fn (mut tc TypeChecker) check_sum_constructor_call(id flat.NodeId, node flat.Node, sum_name string) {
4012+ expected := Type(SumType{
4013+ name: sum_name
4014+ })
4015+ tc.remember_expr_type(id, expected)
4016+ actual_count := node.children_count - 1
4017+ if actual_count != 1 {
4018+ if tc.should_diagnose(id) {
4019+ tc.record_error(.call_arg_mismatch,
4020+ 'argument count mismatch for `${tc.call_display_name(node)}`: expected 1, got ${actual_count}',
4021+ id)
4022+ }
4023+ for i in 1 .. node.children_count {
4024+ tc.check_node(tc.call_arg_value(tc.a.child(&node, i)))
4025+ }
4026+ return
37314027 }
3732- if tc.diagnostic_files.len == 0 {
3733- return false
4028+ arg_id := tc.call_arg_value(tc.a.child(&node, 1))
4029+ tc.check_node(arg_id)
4030+ arg_type := tc.resolve_expr(arg_id, expected)
4031+ if !tc.type_compatible(arg_type, expected) {
4032+ tc.type_mismatch(.call_arg_mismatch,
4033+ 'cannot use `${arg_type.name()}` as sum constructor payload; expected variant of `${sum_name}`',
4034+ id)
37344035 }
3735- return tc.diagnostic_files['generic:' + tc.cur_file]
3736-}
3737-
3738-// should_diagnose_unknown_call reports whether should diagnose unknown call applies in types.
3739-fn (tc &TypeChecker) should_diagnose_unknown_call(id flat.NodeId) bool {
3740- return tc.diagnose_unknown_calls && tc.should_diagnose(id)
37414036}
37424037
3743-// resolve_call_info resolves resolve call info information for types.
3744-fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallInfo {
4038+fn (tc &TypeChecker) sum_constructor_call_name(node flat.Node) ?string {
37454039 if node.children_count == 0 {
37464040 return none
37474041 }
3748- fn_node := tc.a.child_node(&node, 0)
3749- if info := tc.resolve_generic_call_info(fn_node) {
3750- return info
3751- }
3752- if fn_node.kind == .selector {
3753- base_id := tc.a.child(fn_node, 0)
3754- base_node := tc.a.nodes[int(base_id)]
3755- if base_node.kind == .ident && base_node.value == 'C' {
3756- return none
3757- }
3758- if base_node.kind == .ident {
3759- if resolved_mod := tc.resolve_import_alias(base_node.value) {
3760- mod_name := '${resolved_mod}.${fn_node.value}'
3761- if mod_name in tc.fn_ret_types {
3762- return tc.call_info(mod_name, false)
3763- }
3764- }
3765- qbase := tc.qualify_name(base_node.value)
3766- static_name := '${qbase}.${fn_node.value}'
3767- if static_name in tc.fn_ret_types && (qbase in tc.structs
3768- || qbase in tc.enum_names || qbase in tc.sum_types
3769- || qbase in tc.interface_names || qbase in tc.type_aliases) {
3770- // `qbase in tc.type_aliases` covers static methods on a type alias,
3771- // e.g. `fn SimdFloat4.new()` for `type SimdFloat4 = vec.Vec4[f32]`.
3772- return tc.call_info(static_name, false)
3773- }
3774- if fn_node.value == 'zero' && qbase in tc.flag_enums {
3775- return CallInfo{
3776- name: ''
3777- params: []Type{}
3778- return_type: Type(Enum{
3779- name: qbase
3780- is_flag: true
3781- })
3782- params_known: true
4042+ callee_id := tc.a.child(&node, 0)
4043+ callee := tc.a.nodes[int(callee_id)]
4044+ match callee.kind {
4045+ .ident {
4046+ if resolved := tc.resolve_selective_import_type_symbol(callee.value) {
4047+ if sum_name := tc.known_sum_constructor_name(resolved) {
4048+ return sum_name
37834049 }
37844050 }
3785- } else if base_node.kind == .selector {
3786- if method_name := tc.module_const_receiver_method_name(base_node, fn_node.value) {
3787- return tc.call_info(method_name, true)
3788- }
3789- inner := tc.a.child_node(base_node, 0)
3790- if inner.kind == .ident {
3791- mod_name := tc.resolve_import_alias(inner.value) or { inner.value }
3792- full_name := '${mod_name}.${base_node.value}.${fn_node.value}'
3793- if full_name in tc.fn_ret_types {
3794- return tc.call_info(full_name, false)
3795- }
4051+ return tc.known_sum_constructor_name(callee.value)
4052+ }
4053+ .selector {
4054+ base := tc.a.child_node(callee, 0)
4055+ if base.kind == .ident {
4056+ mod_name := tc.resolve_import_alias(base.value) or { base.value }
4057+ return tc.known_sum_constructor_name('${mod_name}.${callee.value}')
37964058 }
37974059 }
3798- if fn_typ := tc.selector_fn_type(fn_node) {
3799- return CallInfo{
3800- name: ''
3801- params: fn_typ.params.clone()
3802- return_type: fn_typ.return_type
3803- params_known: true
4060+ .index, .prefix, .array_init {
4061+ type_name := tc.type_expr_name(callee_id)
4062+ if type_name.len > 0 {
4063+ return tc.known_sum_constructor_name(type_name)
4064+ }
4065+ }
4066+ else {}
4067+ }
4068+
4069+ return none
4070+}
4071+
4072+fn (tc &TypeChecker) type_expr_name(id flat.NodeId) string {
4073+ if int(id) < 0 {
4074+ return ''
4075+ }
4076+ node := tc.a.nodes[int(id)]
4077+ match node.kind {
4078+ .ident {
4079+ if resolved := tc.resolve_selective_import_type_symbol(node.value) {
4080+ return resolved
4081+ }
4082+ return node.value
4083+ }
4084+ .selector {
4085+ if node.children_count == 0 {
4086+ return node.value
4087+ }
4088+ base_id := tc.a.child(&node, 0)
4089+ base_node := tc.a.nodes[int(base_id)]
4090+ base := if base_node.kind == .ident {
4091+ tc.resolve_import_alias(base_node.value) or { tc.type_expr_name(base_id) }
4092+ } else {
4093+ tc.type_expr_name(base_id)
4094+ }
4095+ if base.len == 0 {
4096+ return node.value
4097+ }
4098+ return '${base}.${node.value}'
4099+ }
4100+ .index {
4101+ if node.children_count < 2 || node.value == 'range' {
4102+ return ''
4103+ }
4104+ base := tc.type_expr_name(tc.a.child(&node, 0))
4105+ if base.len == 0 {
4106+ return ''
4107+ }
4108+ mut args := []string{}
4109+ for i in 1 .. node.children_count {
4110+ arg := tc.type_expr_name(tc.a.child(&node, i))
4111+ if arg.len == 0 {
4112+ return ''
4113+ }
4114+ args << arg
4115+ }
4116+ return '${base}[${args.join(', ')}]'
4117+ }
4118+ .array_init {
4119+ if node.value.len == 0 {
4120+ return ''
4121+ }
4122+ return '[]${node.value}'
4123+ }
4124+ .prefix {
4125+ if node.children_count == 0 {
4126+ return ''
4127+ }
4128+ child := tc.type_expr_name(tc.a.child(&node, 0))
4129+ if child.len == 0 {
4130+ return ''
4131+ }
4132+ if node.op == .amp {
4133+ return '&${child}'
4134+ }
4135+ return child
4136+ }
4137+ else {
4138+ return ''
4139+ }
4140+ }
4141+}
4142+
4143+fn (tc &TypeChecker) known_sum_constructor_name(name string) ?string {
4144+ if name in tc.sum_types {
4145+ return name
4146+ }
4147+ base_name := strip_generic_args_name(name)
4148+ if base_name in tc.sum_types {
4149+ return name
4150+ }
4151+ qname := tc.qualify_name(name)
4152+ if qname in tc.sum_types {
4153+ return qname
4154+ }
4155+ qbase := strip_generic_args_name(qname)
4156+ if qbase in tc.sum_types {
4157+ return qname
4158+ }
4159+ return none
4160+}
4161+
4162+// should_diagnose reports whether should diagnose applies in types.
4163+fn (tc &TypeChecker) should_diagnose(id flat.NodeId) bool {
4164+ if int(id) < 0 || int(id) < tc.a.user_code_start {
4165+ return false
4166+ }
4167+ if int(id) < tc.a.nodes.len && !tc.a.nodes[int(id)].pos.is_valid() && !tc.diagnose_unknown_calls {
4168+ return false
4169+ }
4170+ if tc.diagnostic_files.len == 0 {
4171+ return true
4172+ }
4173+ return tc.cur_file in tc.diagnostic_files
4174+}
4175+
4176+fn (tc &TypeChecker) should_diagnose_unsupported_generic(id flat.NodeId) bool {
4177+ if tc.should_diagnose(id) {
4178+ return true
4179+ }
4180+ if int(id) < 0 || int(id) < tc.a.user_code_start {
4181+ return false
4182+ }
4183+ if tc.diagnostic_files.len == 0 {
4184+ return false
4185+ }
4186+ return tc.diagnostic_files['generic:' + tc.cur_file]
4187+}
4188+
4189+// should_diagnose_unknown_call reports whether should diagnose unknown call applies in types.
4190+fn (tc &TypeChecker) should_diagnose_unknown_call(id flat.NodeId) bool {
4191+ return tc.diagnose_unknown_calls && tc.should_diagnose(id)
4192+}
4193+
4194+// resolve_call_info resolves resolve call info information for types.
4195+fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallInfo {
4196+ if node.children_count == 0 {
4197+ return none
4198+ }
4199+ fn_node := tc.a.child_node(&node, 0)
4200+ if info := tc.resolve_generic_call_info(fn_node) {
4201+ return info
4202+ }
4203+ if fn_node.kind == .selector {
4204+ base_id := tc.a.child(fn_node, 0)
4205+ base_node := tc.a.nodes[int(base_id)]
4206+ if base_node.kind == .ident && base_node.value == 'C' {
4207+ return none
4208+ }
4209+ if base_node.kind == .ident {
4210+ if resolved_mod := tc.resolve_import_alias(base_node.value) {
4211+ mod_name := '${resolved_mod}.${fn_node.value}'
4212+ if mod_name in tc.fn_ret_types {
4213+ return tc.call_info(mod_name, false)
4214+ }
4215+ }
4216+ qbase := tc.qualify_name(base_node.value)
4217+ static_name := '${qbase}.${fn_node.value}'
4218+ if static_name in tc.fn_ret_types && (qbase in tc.structs
4219+ || qbase in tc.enum_names || qbase in tc.sum_types
4220+ || qbase in tc.interface_names || qbase in tc.type_aliases) {
4221+ // `qbase in tc.type_aliases` covers static methods on a type alias,
4222+ // e.g. `fn SimdFloat4.new()` for `type SimdFloat4 = vec.Vec4[f32]`.
4223+ return tc.call_info(static_name, false)
4224+ }
4225+ if fn_node.value == 'zero' && qbase in tc.flag_enums {
4226+ return CallInfo{
4227+ name: ''
4228+ params: []Type{}
4229+ return_type: Type(Enum{
4230+ name: qbase
4231+ is_flag: true
4232+ })
4233+ params_known: true
4234+ }
4235+ }
4236+ } else if base_node.kind == .selector {
4237+ if method_name := tc.module_const_receiver_method_name(base_node, fn_node.value) {
4238+ return tc.call_info(method_name, true)
4239+ }
4240+ inner := tc.a.child_node(base_node, 0)
4241+ if inner.kind == .ident {
4242+ mod_name := tc.resolve_import_alias(inner.value) or { inner.value }
4243+ full_name := '${mod_name}.${base_node.value}.${fn_node.value}'
4244+ if full_name in tc.fn_ret_types {
4245+ return tc.call_info(full_name, false)
4246+ }
4247+ }
4248+ }
4249+ if fn_typ := tc.selector_fn_type(fn_node) {
4250+ return CallInfo{
4251+ name: ''
4252+ params: fn_typ.params.clone()
4253+ return_type: fn_typ.return_type
4254+ params_known: true
38044255 }
38054256 }
38064257 base_type := tc.resolve_type(base_id)
@@ -3825,7 +4276,16 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI
38254276 return CallInfo{
38264277 name: 'array_clone'
38274278 params: tarr1(base_type)
3828- return_type: base_type
4279+ return_type: clean
4280+ has_receiver: true
4281+ params_known: true
4282+ }
4283+ }
4284+ if clean is Array && fn_node.value == 'reverse' {
4285+ return CallInfo{
4286+ name: 'array.reverse'
4287+ params: tarr1(base_type)
4288+ return_type: clean
38294289 has_receiver: true
38304290 params_known: true
38314291 }
@@ -3842,7 +4302,7 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI
38424302 if clean is Map {
38434303 if fn_node.value == 'keys' {
38444304 return CallInfo{
3845- name: 'map.keys'
4305+ name: ''
38464306 params: tarr1(base_type)
38474307 return_type: Type(Array{
38484308 elem_type: clean.key_type
@@ -3853,7 +4313,7 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI
38534313 }
38544314 if fn_node.value == 'values' {
38554315 return CallInfo{
3856- name: 'map.values'
4316+ name: ''
38574317 params: tarr1(base_type)
38584318 return_type: Type(Array{
38594319 elem_type: clean.value_type
@@ -3862,10 +4322,6 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI
38624322 params_known: true
38634323 }
38644324 }
3865- map_method := 'map.${fn_node.value}'
3866- if map_method in tc.fn_ret_types {
3867- return tc.call_info(map_method, true)
3868- }
38694325 }
38704326 if clean is Array {
38714327 match fn_node.value {
@@ -4073,6 +4529,9 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI
40734529 }
40744530 }
40754531 if clean is SumType {
4532+ if info := tc.resolve_generic_sum_method(clean.name, fn_node.value) {
4533+ return info
4534+ }
40764535 mname := '${clean.name}.${fn_node.value}'
40774536 if mname in tc.fn_ret_types {
40784537 return tc.call_info(mname, true)
@@ -4162,7 +4621,17 @@ fn (mut tc TypeChecker) resolve_generic_call_info(fn_node flat.Node) ?CallInfo {
41624621 return none
41634622 }
41644623 base_node := tc.a.nodes[int(base_id)]
4165- call_name := tc.generic_call_base_name(base_node) or { return none }
4624+ call_name := tc.generic_call_base_name(base_node) or {
4625+ if type_name := tc.generic_call_base_type_name(base_node) {
4626+ return CallInfo{
4627+ name: ''
4628+ params: []Type{}
4629+ return_type: tc.parse_type('${type_name}[${type_arg}]')
4630+ params_known: false
4631+ }
4632+ }
4633+ return none
4634+ }
41664635 if is_veb_run_at_call_name(call_name) {
41674636 return CallInfo{
41684637 name: call_name
@@ -4192,6 +4661,32 @@ fn (mut tc TypeChecker) resolve_generic_call_info(fn_node flat.Node) ?CallInfo {
41924661 return tc.call_info(call_name, false)
41934662}
41944663
4664+fn (tc &TypeChecker) generic_call_base_type_name(base_node flat.Node) ?string {
4665+ if base_node.kind == .ident {
4666+ qname := tc.qualify_name(base_node.value)
4667+ if tc.type_symbol_known(qname) {
4668+ return qname
4669+ }
4670+ if tc.type_symbol_known(base_node.value) {
4671+ return base_node.value
4672+ }
4673+ if resolved := tc.resolve_selective_import_type_symbol(base_node.value) {
4674+ return resolved
4675+ }
4676+ }
4677+ if base_node.kind == .selector && base_node.children_count > 0 {
4678+ inner := tc.a.child_node(&base_node, 0)
4679+ if inner.kind == .ident {
4680+ mod_name := tc.resolve_import_alias(inner.value) or { inner.value }
4681+ full_name := '${mod_name}.${base_node.value}'
4682+ if tc.type_symbol_known(full_name) {
4683+ return full_name
4684+ }
4685+ }
4686+ }
4687+ return none
4688+}
4689+
41954690fn is_decode_call_name(name string) bool {
41964691 return name in ['json.decode', 'json2.decode']
41974692}
@@ -4690,13 +5185,14 @@ fn (tc &TypeChecker) parse_fn_signature_type(name string, typ string) Type {
46905185 tc.file_modules[decl_file] or { tc.cur_module }
46915186 }
46925187 scoped.type_cache = &TypeCache{
4693- parse_enabled: if tc.type_cache != unsafe { nil } {
5188+ parse_enabled: if tc.type_cache != unsafe { nil } {
46945189 tc.type_cache.parse_enabled
46955190 } else {
46965191 false
46975192 }
4698- parse_entries: map[string]Type{}
4699- c_entries: map[string]string{}
5193+ parse_entries: map[string]Type{}
5194+ c_entries: map[string]string{}
5195+ ierror_compat_entries: map[string]int{}
47005196 }
47015197 return scoped.parse_type(typ)
47025198}
@@ -5528,6 +6024,12 @@ fn (tc &TypeChecker) call_display_name(node flat.Node) string {
55286024 return '${base.value}.${fn_node.value}'
55296025 }
55306026 }
6027+ if fn_node.kind in [.index, .prefix, .array_init] {
6028+ type_name := tc.type_expr_name(tc.a.child(&node, 0))
6029+ if type_name.len > 0 {
6030+ return type_name
6031+ }
6032+ }
55316033 return fn_node.value
55326034}
55336035
@@ -5705,13 +6207,43 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {
57056207 for j in 0 .. n_conds {
57066208 cond := tc.a.node(tc.a.child(branch, j))
57076209 if pattern := tc.match_type_pattern(cond) {
5708- if !tc.sum_has_variant(subject_type.name, pattern) {
6210+ if _ := tc.sum_variant_type_for_pattern(subject_type.name, pattern) {
6211+ } else {
57096212 tc.record_error(.condition_mismatch,
57106213 '`${pattern}` is not a variant of sum type `${subject_type.name}`', tc.a.child(branch,
57116214 j))
57126215 }
57136216 }
57146217 }
6218+ } else if is_ierror_type(subject_type) {
6219+ for j in 0 .. n_conds {
6220+ cond_id := tc.a.child(branch, j)
6221+ cond := tc.a.node(cond_id)
6222+ if pattern := tc.match_type_pattern(cond) {
6223+ if _ := tc.resolve_ierror_match_pattern(pattern) {
6224+ } else if tc.should_diagnose(cond_id) {
6225+ tc.record_error(.condition_mismatch,
6226+ '`${pattern}` is not compatible with `IError`', cond_id)
6227+ }
6228+ }
6229+ }
6230+ } else if subject_type is Interface {
6231+ for j in 0 .. n_conds {
6232+ cond_id := tc.a.child(branch, j)
6233+ cond := tc.a.node(cond_id)
6234+ if pattern := tc.match_type_pattern(cond) {
6235+ if concrete := tc.resolve_interface_match_pattern(pattern) {
6236+ if !tc.named_type_implements_interface(concrete, subject_type.name)
6237+ && tc.should_diagnose(cond_id) {
6238+ tc.record_error(.condition_mismatch,
6239+ '`${pattern}` is not compatible with interface `${subject_type.name}`',
6240+ cond_id)
6241+ }
6242+ } else if tc.should_diagnose(cond_id) {
6243+ tc.record_error(.condition_mismatch, 'unknown type `${pattern}`', cond_id)
6244+ }
6245+ }
6246+ }
57156247 }
57166248 saved_smartcasts := clone_smartcasts(tc.smartcasts)
57176249 if subject_key.len > 0 && valid_string_data(subject_key) && n_conds == 1
@@ -5719,7 +6251,16 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {
57196251 cond_id := tc.a.child(branch, 0)
57206252 cond := tc.a.node(cond_id)
57216253 if pattern := tc.match_type_pattern(cond) {
5722- tc.smartcasts[subject_key] = tc.parse_type(pattern)
6254+ smartcast_type := if subject_type is SumType {
6255+ tc.sum_variant_type_for_pattern(subject_type.name, pattern) or { pattern }
6256+ } else if is_ierror_type(subject_type) {
6257+ tc.resolve_ierror_match_pattern(pattern) or { pattern }
6258+ } else if subject_type is Interface {
6259+ tc.resolve_interface_match_pattern(pattern) or { pattern }
6260+ } else {
6261+ pattern
6262+ }
6263+ tc.smartcasts[subject_key] = tc.parse_type(smartcast_type)
57236264 }
57246265 }
57256266 tc.push_scope()
@@ -5731,40 +6272,136 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {
57316272 }
57326273}
57336274
5734-// check_is_expr validates check is expr state for types.
5735-fn (mut tc TypeChecker) check_is_expr(id flat.NodeId, node flat.Node) {
5736- if node.children_count == 0 {
5737- return
5738- }
5739- expr_id := tc.a.child(&node, 0)
5740- tc.check_node(expr_id)
5741- expr_type := unwrap_pointer(tc.resolve_type(expr_id))
5742- if expr_type is SumType {
5743- if node.value.len > 0 && !tc.sum_has_variant(expr_type.name, node.value)
5744- && tc.should_diagnose(id) {
5745- tc.record_error(.condition_mismatch,
5746- '`${node.value}` is not a variant of sum type `${expr_type.name}`', id)
6275+fn (tc &TypeChecker) resolve_interface_match_pattern(pattern string) ?string {
6276+ for candidate in tc.interface_match_pattern_candidates(pattern) {
6277+ if tc.type_symbol_known(candidate) {
6278+ return candidate
57476279 }
5748- return
5749- }
5750- if expr_type is Interface || expr_type is Unknown {
5751- return
5752- }
5753- if tc.should_diagnose(id) {
5754- tc.record_error(.condition_mismatch,
5755- '`is` can only be used with sum type or interface values, not `${expr_type.name()}`',
5756- id)
57576280 }
6281+ return none
57586282}
57596283
5760-// branch_tail_type supports branch tail type handling for TypeChecker.
5761-fn (tc &TypeChecker) branch_tail_type(id flat.NodeId) Type {
5762- if !tc.valid_node_id(id) {
5763- return Type(void_)
6284+fn (tc &TypeChecker) resolve_ierror_match_pattern(pattern string) ?string {
6285+ for candidate in tc.interface_match_pattern_candidates(pattern) {
6286+ if tc.named_type_compatible_with_ierror(candidate) {
6287+ return candidate
6288+ }
57646289 }
5765- node := tc.a.nodes[int(id)]
5766- if node.kind == .if_expr {
5767- return tc.if_expr_tail_type(id)
6290+ return none
6291+}
6292+
6293+fn (tc &TypeChecker) interface_match_pattern_candidates(pattern string) []string {
6294+ mut candidates := []string{}
6295+ if !pattern.contains('.') {
6296+ mut has_scoped_candidate := false
6297+ if resolved := tc.resolve_selective_import_type_symbol(pattern) {
6298+ candidates << resolved
6299+ has_scoped_candidate = true
6300+ }
6301+ if tc.source_declares_type_in_scope(pattern, tc.cur_file, tc.cur_module) {
6302+ candidates << pattern
6303+ has_scoped_candidate = true
6304+ }
6305+ if tc.cur_module.len > 0 && tc.cur_module != 'main' && tc.cur_module != 'builtin' {
6306+ local := '${tc.cur_module}.${pattern}'
6307+ if tc.type_symbol_known(local) {
6308+ candidates << local
6309+ has_scoped_candidate = true
6310+ }
6311+ }
6312+ if !has_scoped_candidate {
6313+ candidates << pattern
6314+ }
6315+ } else if resolved := tc.resolve_import_alias_pattern(pattern) {
6316+ candidates << resolved
6317+ candidates << pattern
6318+ } else {
6319+ candidates << pattern
6320+ }
6321+ qpattern := tc.qualify_name(pattern)
6322+ if qpattern != pattern {
6323+ candidates << qpattern
6324+ }
6325+ mut result := []string{}
6326+ mut seen := map[string]bool{}
6327+ for candidate in candidates {
6328+ if candidate.len == 0 || candidate in seen {
6329+ continue
6330+ }
6331+ seen[candidate] = true
6332+ result << candidate
6333+ }
6334+ return result
6335+}
6336+
6337+fn (tc &TypeChecker) resolve_import_alias_pattern(pattern string) ?string {
6338+ dot := pattern.index_u8(`.`)
6339+ if dot <= 0 {
6340+ return none
6341+ }
6342+ alias := pattern[..dot]
6343+ resolved := tc.resolve_import_alias(alias) or { return none }
6344+ return '${resolved}.${pattern[dot + 1..]}'
6345+}
6346+
6347+// check_is_expr validates check is expr state for types.
6348+fn (mut tc TypeChecker) check_is_expr(id flat.NodeId, node flat.Node) {
6349+ if node.children_count == 0 {
6350+ return
6351+ }
6352+ expr_id := tc.a.child(&node, 0)
6353+ tc.check_node(expr_id)
6354+ expr_type := unwrap_pointer(tc.resolve_type(expr_id))
6355+ if expr_type is SumType {
6356+ if node.value.len > 0 && tc.sum_variant_type_for_pattern(expr_type.name, node.value) == none
6357+ && tc.should_diagnose(id) {
6358+ tc.record_error(.condition_mismatch,
6359+ '`${node.value}` is not a variant of sum type `${expr_type.name}`', id)
6360+ }
6361+ return
6362+ }
6363+ if is_ierror_type(expr_type) {
6364+ if node.value.len > 0 {
6365+ if _ := tc.resolve_ierror_match_pattern(node.value) {
6366+ } else if tc.should_diagnose(id) {
6367+ tc.record_error(.condition_mismatch,
6368+ '`${node.value}` is not compatible with `IError`', id)
6369+ }
6370+ }
6371+ return
6372+ }
6373+ if expr_type is Interface {
6374+ if node.value.len > 0 {
6375+ if concrete := tc.resolve_interface_match_pattern(node.value) {
6376+ if !tc.named_type_implements_interface(concrete, expr_type.name)
6377+ && tc.should_diagnose(id) {
6378+ tc.record_error(.condition_mismatch,
6379+ '`${node.value}` is not compatible with interface `${expr_type.name}`', id)
6380+ }
6381+ } else if tc.should_diagnose(id) {
6382+ tc.record_error(.condition_mismatch, 'unknown type `${node.value}`', id)
6383+ }
6384+ }
6385+ return
6386+ }
6387+ if expr_type is Unknown {
6388+ return
6389+ }
6390+ if tc.should_diagnose(id) {
6391+ tc.record_error(.condition_mismatch,
6392+ '`is` can only be used with sum type or interface values, not `${expr_type.name()}`',
6393+ id)
6394+ }
6395+}
6396+
6397+// branch_tail_type supports branch tail type handling for TypeChecker.
6398+fn (tc &TypeChecker) branch_tail_type(id flat.NodeId) Type {
6399+ if !tc.valid_node_id(id) {
6400+ return Type(void_)
6401+ }
6402+ node := tc.a.nodes[int(id)]
6403+ if node.kind == .if_expr {
6404+ return tc.if_expr_tail_type(id)
57686405 }
57696406 if node.kind == .block {
57706407 if node.children_count == 0 {
@@ -6201,7 +6838,7 @@ fn (tc &TypeChecker) selector_type(_id flat.NodeId, node flat.Node) ?Type {
62016838 return typ
62026839 }
62036840 }
6204- if clean_name == 'IError' || clean_name.ends_with('.IError') {
6841+ if is_builtin_ierror_name(clean_name) {
62056842 if node.value == 'message' {
62066843 return Type(String{})
62076844 }
@@ -6266,14 +6903,16 @@ fn (tc &TypeChecker) lowered_sum_selector_type(sum SumType, field string) ?Type
62666903
62676904// sum_shared_field_type supports sum shared field type handling for TypeChecker.
62686905fn (tc &TypeChecker) sum_shared_field_type(sum SumType, field string) ?Type {
6269- variants := tc.sum_types[sum.name] or { return none }
6906+ base := tc.sum_base_name(sum.name)
6907+ variants := tc.sum_types[base] or { return none }
62706908 if variants.len == 0 {
62716909 return none
62726910 }
62736911 mut has_common := false
62746912 mut common_typ := Type(void_)
62756913 for variant in variants {
6276- variant_field := tc.struct_field_type(variant, field) or { return none }
6914+ concrete := tc.concrete_sum_variant_name(sum.name, variant)
6915+ variant_field := tc.struct_field_type(concrete, field) or { return none }
62776916 if !has_common {
62786917 common_typ = variant_field
62796918 has_common = true
@@ -6847,9 +7486,6 @@ fn (tc &TypeChecker) type_compatible(actual Type, expected Type) bool {
68477486 return tc.type_compatible(actual, expected.base_type)
68487487 }
68497488 if expected is ResultType {
6850- if is_ierror_type(actual) {
6851- return true
6852- }
68537489 if actual is ResultType {
68547490 return tc.type_compatible(actual.base_type, expected.base_type)
68557491 }
@@ -7004,18 +7640,22 @@ fn fn_return_canonical_type_name(typ Type) string {
70047640}
70057641
70067642// is_ierror_type reports whether is ierror type applies in types.
7643+fn is_builtin_ierror_name(name string) bool {
7644+ return name == 'IError' || name == 'builtin.IError'
7645+}
7646+
70077647fn is_ierror_type(t Type) bool {
70087648 if t is Alias {
7009- return t.name == 'IError' || t.name.ends_with('.IError') || is_ierror_type(t.base_type)
7649+ return is_builtin_ierror_name(t.name) || is_ierror_type(t.base_type)
70107650 }
70117651 if t is Pointer {
70127652 return is_ierror_type(t.base_type)
70137653 }
70147654 if t is Struct {
7015- return t.name == 'IError' || t.name.ends_with('.IError')
7655+ return is_builtin_ierror_name(t.name)
70167656 }
70177657 if t is Interface {
7018- return t.name == 'IError' || t.name.ends_with('.IError')
7658+ return is_builtin_ierror_name(t.name)
70197659 }
70207660 return false
70217661}
@@ -7289,75 +7929,408 @@ fn (tc &TypeChecker) const_int_expr(id flat.NodeId, seen []string) ?int {
72897929 return none
72907930}
72917931
7292-// type_implements_interface returns type implements interface data for TypeChecker.
7293-fn (tc &TypeChecker) type_implements_interface(actual Type, expected Interface) bool {
7294- clean := unwrap_pointer(actual)
7295- if clean is Unknown {
7296- return true
7297- }
7298- if clean is Interface {
7299- return tc.interface_implements_interface(clean.name, expected.name)
7300- }
7301- concrete_name := method_type_name(clean)
7302- if concrete_name.len == 0 {
7303- return false
7932+// type_implements_interface returns type implements interface data for TypeChecker.
7933+fn (tc &TypeChecker) type_implements_interface(actual Type, expected Interface) bool {
7934+ clean := unwrap_pointer(actual)
7935+ if clean is Unknown {
7936+ return true
7937+ }
7938+ if clean is Interface {
7939+ return tc.interface_implements_interface(clean.name, expected.name)
7940+ }
7941+ concrete_name := method_type_name(clean)
7942+ if concrete_name.len == 0 {
7943+ return false
7944+ }
7945+ return tc.named_type_implements_interface(concrete_name, expected.name)
7946+}
7947+
7948+// interface_implements_interface supports interface implements interface handling for TypeChecker.
7949+fn (tc &TypeChecker) interface_implements_interface(actual_name string, expected_name string) bool {
7950+ if actual_name == expected_name {
7951+ return true
7952+ }
7953+ for method in tc.interface_method_names(expected_name) {
7954+ actual_key := tc.interface_method_signature_key(actual_name, method) or {
7955+ '${actual_name}.${method}'
7956+ }
7957+ expected_key := tc.interface_method_signature_key(expected_name, method) or {
7958+ '${expected_name}.${method}'
7959+ }
7960+ if actual_key !in tc.fn_param_types
7961+ || !tc.method_signature_compatible(actual_key, expected_key) {
7962+ return false
7963+ }
7964+ }
7965+ for field in tc.interface_field_list(expected_name) {
7966+ actual_field := tc.interface_field_type(actual_name, field.name) or { return false }
7967+ if !tc.type_compatible(actual_field, field.typ)
7968+ || !tc.type_compatible(field.typ, actual_field) {
7969+ return false
7970+ }
7971+ }
7972+ return true
7973+}
7974+
7975+// named_type_implements_interface
7976+// supports helper handling in types.
7977+pub fn (tc &TypeChecker) named_type_implements_interface(concrete_name string, iface_name string) bool {
7978+ // Only the abstract (declared) methods must be provided by the concrete type.
7979+ // Methods defined directly on the interface (default implementations) are
7980+ // inherited and need not be reimplemented.
7981+ for method in tc.interface_abstract_method_names(iface_name) {
7982+ concrete_key := '${concrete_name}.${method}'
7983+ if concrete_key !in tc.fn_param_types {
7984+ return false
7985+ }
7986+ expected_key := tc.interface_method_signature_key(iface_name, method) or {
7987+ '${iface_name}.${method}'
7988+ }
7989+ if !tc.method_signature_compatible(concrete_key, expected_key) {
7990+ return false
7991+ }
7992+ }
7993+ for field in tc.interface_field_list(iface_name) {
7994+ actual_field := tc.struct_field_type(concrete_name, field.name) or { return false }
7995+ if !tc.type_compatible(actual_field, field.typ)
7996+ || !tc.type_compatible(field.typ, actual_field) {
7997+ return false
7998+ }
7999+ }
8000+ return true
8001+}
8002+
8003+pub fn (tc &TypeChecker) named_type_compatible_with_ierror(concrete_name string) bool {
8004+ cache_key := tc.ierror_compat_cache_key(concrete_name)
8005+ if tc.type_cache != unsafe { nil } {
8006+ mut cache := unsafe { tc.type_cache }
8007+ if cached := cache.ierror_compat_entries[cache_key] {
8008+ return cached > 0
8009+ }
8010+ }
8011+ mut seen := map[string]bool{}
8012+ result := tc.named_type_compatible_with_ierror_inner(concrete_name, mut seen)
8013+ if tc.type_cache != unsafe { nil } {
8014+ mut cache := unsafe { tc.type_cache }
8015+ cache.ierror_compat_entries[cache_key] = if result { 1 } else { -1 }
8016+ }
8017+ return result
8018+}
8019+
8020+fn (tc &TypeChecker) ierror_compat_cache_key(concrete_name string) string {
8021+ if concrete_name in ['Error', 'MessageError'] {
8022+ return '${tc.cur_file}\n${tc.cur_module}\n${concrete_name}'
8023+ }
8024+ if concrete_name in tc.structs {
8025+ return concrete_name
8026+ }
8027+ qname := tc.qualify_name(concrete_name)
8028+ if qname in tc.structs {
8029+ return qname
8030+ }
8031+ return concrete_name
8032+}
8033+
8034+fn (tc &TypeChecker) type_compatible_with_ierror_payload(actual Type) bool {
8035+ concrete_name := method_type_name(unwrap_pointer(actual))
8036+ if concrete_name.len == 0 {
8037+ return false
8038+ }
8039+ return tc.named_type_compatible_with_ierror(concrete_name)
8040+}
8041+
8042+fn (tc &TypeChecker) named_type_compatible_with_ierror_inner(concrete_name string, mut seen map[string]bool) bool {
8043+ mut lookup := concrete_name
8044+ if lookup !in tc.structs {
8045+ qname := tc.qualify_name(lookup)
8046+ if qname in tc.structs {
8047+ lookup = qname
8048+ }
8049+ }
8050+ if lookup in ['Error', 'MessageError'] && tc.unqualified_type_name_shadows_builtin(lookup) {
8051+ return false
8052+ }
8053+ if lookup in seen {
8054+ return false
8055+ }
8056+ seen[lookup] = true
8057+ if tc.is_builtin_error_struct_name(lookup) {
8058+ return true
8059+ }
8060+ if tc.named_type_has_non_builtin_error_embed(lookup) {
8061+ return false
8062+ }
8063+ if tc.named_type_implements_ierror_methods(lookup) {
8064+ return true
8065+ }
8066+ struct_module := tc.struct_modules[lookup] or { tc.cur_module }
8067+ struct_file := tc.struct_files[lookup] or { tc.cur_file }
8068+ for field in tc.structs[lookup] or { []StructField{} } {
8069+ embedded_type := embedded_field_type(field) or { continue }
8070+ embedded_name := method_type_name(unwrap_pointer(embedded_type))
8071+ if embedded_name.len == 0 {
8072+ continue
8073+ }
8074+ if tc.embedded_field_is_scoped_builtin_error(field, embedded_name, struct_file,
8075+ struct_module)
8076+ {
8077+ return true
8078+ }
8079+ if embedded_name in ['Error', 'MessageError'] && field.name == embedded_name {
8080+ continue
8081+ }
8082+ if tc.is_builtin_error_struct_name(embedded_name) {
8083+ return true
8084+ }
8085+ if tc.named_type_compatible_with_ierror_inner(embedded_name, mut seen) {
8086+ return true
8087+ }
8088+ }
8089+ return false
8090+}
8091+
8092+fn (tc &TypeChecker) named_type_implements_ierror_methods(concrete_name string) bool {
8093+ iface_name := if 'builtin.IError' in tc.interface_names { 'builtin.IError' } else { 'IError' }
8094+ return tc.named_type_implements_interface(concrete_name, iface_name)
8095+}
8096+
8097+fn (tc &TypeChecker) named_type_has_non_builtin_error_embed(concrete_name string) bool {
8098+ mut lookup := concrete_name
8099+ if lookup !in tc.structs {
8100+ qname := tc.qualify_name(lookup)
8101+ if qname in tc.structs {
8102+ lookup = qname
8103+ }
8104+ }
8105+ if tc.struct_error_embeds_shadow_builtin[lookup] {
8106+ return true
8107+ }
8108+ struct_module := tc.struct_modules[lookup] or { tc.cur_module }
8109+ struct_file := tc.struct_files[lookup] or { tc.cur_file }
8110+ if tc.source_struct_has_non_builtin_error_embed(lookup, struct_file, struct_module) {
8111+ return true
8112+ }
8113+ for field in tc.structs[lookup] or { []StructField{} } {
8114+ if field.name in ['Error', 'MessageError']
8115+ && tc.unqualified_type_name_shadows_builtin_in_scope(field.name, struct_file, struct_module) {
8116+ return true
8117+ }
8118+ embedded_type := embedded_field_type(field) or { continue }
8119+ embedded_name := method_type_name(unwrap_pointer(embedded_type))
8120+ if embedded_name.len == 0 {
8121+ continue
8122+ }
8123+ if tc.embedded_field_is_scoped_builtin_error(field, embedded_name, struct_file,
8124+ struct_module)
8125+ {
8126+ continue
8127+ }
8128+ if embedded_name in ['Error', 'MessageError'] && field.name == embedded_name {
8129+ return true
8130+ }
8131+ if embedded_name.all_after_last('.') in ['Error', 'MessageError']
8132+ && !tc.is_builtin_error_struct_name(embedded_name) {
8133+ return true
8134+ }
8135+ }
8136+ return false
8137+}
8138+
8139+fn (tc &TypeChecker) source_struct_has_non_builtin_error_embed(concrete_name string, file string, mod_name string) bool {
8140+ if isnil(tc.a) {
8141+ return false
8142+ }
8143+ target := concrete_name.all_after_last('.')
8144+ target_module := if concrete_name.contains('.') {
8145+ concrete_name.all_before_last('.')
8146+ } else {
8147+ mod_name
8148+ }
8149+ mut cur_file := ''
8150+ mut cur_module := ''
8151+ for node in tc.a.nodes {
8152+ match node.kind {
8153+ .file {
8154+ cur_file = node.value
8155+ cur_module = ''
8156+ }
8157+ .module_decl {
8158+ cur_module = node.value
8159+ }
8160+ .struct_decl {
8161+ if node.value != target {
8162+ continue
8163+ }
8164+ if target_module.len > 0 {
8165+ if !module_names_match(cur_module, target_module) {
8166+ continue
8167+ }
8168+ } else if file.len == 0 || cur_file != file
8169+ || !module_names_match(cur_module, mod_name) {
8170+ continue
8171+ }
8172+ for i in 0 .. node.children_count {
8173+ field := tc.a.child_node(&node, i)
8174+ if field.kind != .field_decl {
8175+ continue
8176+ }
8177+ field_typ := if field.typ.len > 0 { field.typ } else { field.value }
8178+ if !source_field_decl_is_embed(field, field_typ) {
8179+ continue
8180+ }
8181+ if field_typ in ['Error', 'MessageError']
8182+ && tc.unqualified_type_name_shadows_builtin_in_scope(field_typ, cur_file, cur_module) {
8183+ return true
8184+ }
8185+ resolved := tc.resolve_imported_type_text_in_file(field_typ, cur_file)
8186+ if resolved.all_after_last('.') in ['Error', 'MessageError']
8187+ && !tc.is_builtin_error_struct_name(resolved) {
8188+ return true
8189+ }
8190+ }
8191+ }
8192+ else {}
8193+ }
8194+ }
8195+ return false
8196+}
8197+
8198+fn source_field_decl_is_embed(field flat.Node, field_typ string) bool {
8199+ return field.typ.len == 0 || field.value.len == 0 || field.value == field_typ
8200+ || (field_typ.contains('.') && field.value == field_typ.all_after_last('.'))
8201+}
8202+
8203+fn (tc &TypeChecker) embedded_field_is_scoped_builtin_error(field StructField, embedded_name string, struct_file string, struct_module string) bool {
8204+ if embedded_name !in ['Error', 'MessageError'] || field.name != embedded_name {
8205+ return false
8206+ }
8207+ return !tc.unqualified_type_name_shadows_builtin_in_scope(embedded_name, struct_file,
8208+ struct_module)
8209+}
8210+
8211+fn (tc &TypeChecker) resolve_unqualified_builtin_error_struct_name(name string) ?string {
8212+ if name !in ['Error', 'MessageError'] {
8213+ return none
8214+ }
8215+ if tc.unqualified_type_name_shadows_builtin(name) {
8216+ return none
8217+ }
8218+ if mod_name := tc.struct_modules[name] {
8219+ if mod_name == 'builtin' {
8220+ return name
8221+ }
8222+ }
8223+ if tc.has_builtins {
8224+ return name
8225+ }
8226+ return none
8227+}
8228+
8229+fn (tc &TypeChecker) unqualified_type_name_shadows_builtin(name string) bool {
8230+ return tc.unqualified_type_name_shadows_builtin_in_scope(name, tc.cur_file, tc.cur_module)
8231+}
8232+
8233+fn (tc &TypeChecker) unqualified_type_name_shadows_builtin_in_scope(name string, file string, mod_name string) bool {
8234+ local_name := if mod_name.len > 0 && mod_name !in ['', 'main', 'builtin'] {
8235+ '${mod_name}.${name}'
8236+ } else {
8237+ name
8238+ }
8239+ if local_name == name && mod_name != 'builtin'
8240+ && tc.source_declares_type_in_scope(name, file, mod_name) {
8241+ return true
8242+ }
8243+ if local_name != name && tc.type_symbol_known(local_name) {
8244+ return true
8245+ }
8246+ if file_import_key(file, name) in tc.file_selective_imports {
8247+ if resolved := tc.resolve_selective_import_type_symbol_in_file(name, file) {
8248+ return !tc.is_builtin_error_struct_name(resolved)
8249+ }
8250+ return true
8251+ }
8252+ if local_name != name {
8253+ return false
8254+ }
8255+ if name in tc.structs {
8256+ return tc.struct_modules[name] or { '' } != 'builtin'
8257+ }
8258+ if name in tc.type_aliases || name in tc.sum_types || name in tc.interface_names
8259+ || name in tc.enum_names {
8260+ return true
8261+ }
8262+ return false
8263+}
8264+
8265+fn (tc &TypeChecker) source_declares_type_in_scope(name string, file string, mod_name string) bool {
8266+ if file.len == 0 || isnil(tc.a) {
8267+ return false
8268+ }
8269+ mut cur_file := ''
8270+ mut cur_module := ''
8271+ for node in tc.a.nodes {
8272+ match node.kind {
8273+ .file {
8274+ cur_file = node.value
8275+ cur_module = ''
8276+ }
8277+ .module_decl {
8278+ cur_module = node.value
8279+ }
8280+ .struct_decl, .type_decl, .interface_decl, .enum_decl {
8281+ if cur_file == file && module_names_match(cur_module, mod_name)
8282+ && node.value == name {
8283+ return true
8284+ }
8285+ }
8286+ else {}
8287+ }
8288+ }
8289+ return false
8290+}
8291+
8292+fn module_names_match(a string, b string) bool {
8293+ return a == b || (a in ['', 'main'] && b in ['', 'main'])
8294+}
8295+
8296+fn (tc &TypeChecker) resolve_selective_import_type_symbol_in_file(name string, file string) ?string {
8297+ candidates := tc.file_selective_imports[file_import_key(file, name)] or { return none }
8298+ for candidate in candidates {
8299+ if tc.type_symbol_known(candidate) {
8300+ return candidate
8301+ }
73048302 }
7305- return tc.named_type_implements_interface(concrete_name, expected.name)
8303+ return none
73068304}
73078305
7308-// interface_implements_interface supports interface implements interface handling for TypeChecker.
7309-fn (tc &TypeChecker) interface_implements_interface(actual_name string, expected_name string) bool {
7310- if actual_name == expected_name {
7311- return true
8306+fn (tc &TypeChecker) resolve_imported_type_text_in_file(typ string, file string) string {
8307+ if !typ.contains('.') || typ.starts_with('C.') {
8308+ return typ
73128309 }
7313- for method in tc.interface_method_names(expected_name) {
7314- actual_key := tc.interface_method_signature_key(actual_name, method) or {
7315- '${actual_name}.${method}'
7316- }
7317- expected_key := tc.interface_method_signature_key(expected_name, method) or {
7318- '${expected_name}.${method}'
7319- }
7320- if actual_key !in tc.fn_param_types
7321- || !tc.method_signature_compatible(actual_key, expected_key) {
7322- return false
7323- }
8310+ dot := typ.index_u8(`.`)
8311+ if dot <= 0 {
8312+ return typ
73248313 }
7325- for field in tc.interface_field_list(expected_name) {
7326- actual_field := tc.interface_field_type(actual_name, field.name) or { return false }
7327- if !tc.type_compatible(actual_field, field.typ)
7328- || !tc.type_compatible(field.typ, actual_field) {
7329- return false
8314+ alias := typ[..dot]
8315+ if resolved := tc.file_imports[file_import_key(file, alias)] {
8316+ if resolved != alias {
8317+ return resolved + typ[dot..]
73308318 }
73318319 }
7332- return true
8320+ return typ
73338321}
73348322
7335-// named_type_implements_interface
7336-// supports helper handling in types.
7337-pub fn (tc &TypeChecker) named_type_implements_interface(concrete_name string, iface_name string) bool {
7338- // Only the abstract (declared) methods must be provided by the concrete type.
7339- // Methods defined directly on the interface (default implementations) are
7340- // inherited and need not be reimplemented.
7341- for method in tc.interface_abstract_method_names(iface_name) {
7342- concrete_key := '${concrete_name}.${method}'
7343- if concrete_key !in tc.fn_param_types {
7344- return false
7345- }
7346- expected_key := tc.interface_method_signature_key(iface_name, method) or {
7347- '${iface_name}.${method}'
7348- }
7349- if !tc.method_signature_compatible(concrete_key, expected_key) {
7350- return false
7351- }
8323+fn (tc &TypeChecker) is_builtin_error_struct_name(name string) bool {
8324+ if name in ['builtin.Error', 'builtin.MessageError'] {
8325+ return true
73528326 }
7353- for field in tc.interface_field_list(iface_name) {
7354- actual_field := tc.struct_field_type(concrete_name, field.name) or { return false }
7355- if !tc.type_compatible(actual_field, field.typ)
7356- || !tc.type_compatible(field.typ, actual_field) {
8327+ if name in ['Error', 'MessageError'] {
8328+ if tc.unqualified_type_name_shadows_builtin(name) {
73578329 return false
73588330 }
8331+ return tc.struct_modules[name] or { '' } == 'builtin'
73598332 }
7360- return true
8333+ return false
73618334}
73628335
73638336// interface_method_names supports interface method names handling for TypeChecker.
@@ -7563,6 +8536,12 @@ fn (tc &TypeChecker) substitute_generic_type(typ Type, args []string, param_name
75638536 base_type: tc.substitute_generic_type(typ.base_type, args, param_names)
75648537 })
75658538 }
8539+ if typ is SumType {
8540+ if typ.name.contains('[') {
8541+ return tc.parse_type(subst_generic_text(typ.name, args, param_names))
8542+ }
8543+ return typ
8544+ }
75668545 if typ is FnType {
75678546 mut params := []Type{}
75688547 for param in typ.params {
@@ -7679,6 +8658,9 @@ fn embedded_field_type(field StructField) ?Type {
76798658 if field_type_name.len == 0 {
76808659 return none
76818660 }
8661+ if field.name.len == 0 {
8662+ return field.typ
8663+ }
76828664 mut names := [field_type_name]
76838665 base_name, _, is_generic := generic_type_application_parts(field_type_name)
76848666 if is_generic {
@@ -7746,33 +8728,256 @@ fn method_type_name(t Type) string {
77468728 return ''
77478729}
77488730
8731+fn (tc &TypeChecker) sum_base_name(sum_name string) string {
8732+ base, _, ok := generic_type_application_parts(sum_name)
8733+ if ok {
8734+ return base
8735+ }
8736+ if sum_name in tc.sum_types {
8737+ return sum_name
8738+ }
8739+ qname := tc.qualify_name(sum_name)
8740+ if qname in tc.sum_types {
8741+ return qname
8742+ }
8743+ return sum_name
8744+}
8745+
8746+fn (tc &TypeChecker) sum_params_for_base(base string) []string {
8747+ if params := tc.sum_generic_params[base] {
8748+ return params
8749+ }
8750+ short := base.all_after_last('.')
8751+ if params := tc.sum_generic_params[short] {
8752+ return params
8753+ }
8754+ return []string{}
8755+}
8756+
8757+fn (tc &TypeChecker) concrete_sum_variant_name(sum_name string, variant string) string {
8758+ base, args, ok := generic_type_application_parts(sum_name)
8759+ if !ok {
8760+ return variant
8761+ }
8762+ params := tc.sum_params_for_base(base)
8763+ if params.len == 0 || params.len != args.len {
8764+ return variant
8765+ }
8766+ return subst_generic_text(variant, args, params)
8767+}
8768+
8769+fn (tc &TypeChecker) generic_type_name_matches(a string, b string) bool {
8770+ if a == b {
8771+ return true
8772+ }
8773+ a_base, a_args, a_ok := generic_type_application_parts(a)
8774+ b_base, b_args, b_ok := generic_type_application_parts(b)
8775+ if a_ok || b_ok {
8776+ if !a_ok || !b_ok || a_args.len != b_args.len {
8777+ return false
8778+ }
8779+ if !tc.generic_type_base_matches(a_base, b_base) {
8780+ return false
8781+ }
8782+ for i in 0 .. a_args.len {
8783+ if !tc.generic_type_arg_matches(a_args[i], b_args[i]) {
8784+ return false
8785+ }
8786+ }
8787+ return true
8788+ }
8789+ return tc.generic_type_base_matches(a, b)
8790+}
8791+
8792+fn (tc &TypeChecker) generic_type_base_matches(a string, b string) bool {
8793+ a_clean := a.trim_space()
8794+ b_clean := b.trim_space()
8795+ if a_clean == b_clean {
8796+ return true
8797+ }
8798+ a_resolved := tc.resolve_generic_match_base(a_clean)
8799+ b_resolved := tc.resolve_generic_match_base(b_clean)
8800+ if a_resolved == b_resolved {
8801+ return true
8802+ }
8803+ if a_clean.contains('.') || b_clean.contains('.') || a_resolved.contains('.')
8804+ || b_resolved.contains('.') {
8805+ return false
8806+ }
8807+ return short_type_name(a_clean) == short_type_name(b_clean)
8808+}
8809+
8810+fn (tc &TypeChecker) resolve_generic_match_base(base string) string {
8811+ if base.len == 0 {
8812+ return base
8813+ }
8814+ if base.contains('.') {
8815+ return tc.resolve_imported_type_text(base)
8816+ }
8817+ if resolved := tc.resolve_selective_import_type_symbol(base) {
8818+ return resolved
8819+ }
8820+ qbase := tc.qualify_name(base)
8821+ if qbase != base && tc.type_symbol_known(qbase) {
8822+ return qbase
8823+ }
8824+ return base
8825+}
8826+
8827+fn (tc &TypeChecker) generic_type_arg_matches(a string, b string) bool {
8828+ a_clean := a.trim_space()
8829+ b_clean := b.trim_space()
8830+ if a_clean == b_clean {
8831+ return true
8832+ }
8833+ if tc.generic_match_arg_is_open_param(a_clean) || tc.generic_match_arg_is_open_param(b_clean) {
8834+ return true
8835+ }
8836+ if a_clean.starts_with('&') || b_clean.starts_with('&') {
8837+ return a_clean.starts_with('&') && b_clean.starts_with('&')
8838+ && tc.generic_type_arg_matches(a_clean[1..], b_clean[1..])
8839+ }
8840+ if a_clean.starts_with('mut ') || b_clean.starts_with('mut ') {
8841+ return a_clean.starts_with('mut ') && b_clean.starts_with('mut ')
8842+ && tc.generic_type_arg_matches(a_clean[4..], b_clean[4..])
8843+ }
8844+ if a_clean.starts_with('?') || b_clean.starts_with('?') {
8845+ return a_clean.starts_with('?') && b_clean.starts_with('?')
8846+ && tc.generic_type_arg_matches(a_clean[1..], b_clean[1..])
8847+ }
8848+ if a_clean.starts_with('!') || b_clean.starts_with('!') {
8849+ return a_clean.starts_with('!') && b_clean.starts_with('!')
8850+ && tc.generic_type_arg_matches(a_clean[1..], b_clean[1..])
8851+ }
8852+ if a_clean.starts_with('...') || b_clean.starts_with('...') {
8853+ return a_clean.starts_with('...') && b_clean.starts_with('...')
8854+ && tc.generic_type_arg_matches(a_clean[3..], b_clean[3..])
8855+ }
8856+ if a_clean.starts_with('[]') || b_clean.starts_with('[]') {
8857+ return a_clean.starts_with('[]') && b_clean.starts_with('[]')
8858+ && tc.generic_type_arg_matches(a_clean[2..], b_clean[2..])
8859+ }
8860+ if a_clean.starts_with('map[') || b_clean.starts_with('map[') {
8861+ if !a_clean.starts_with('map[') || !b_clean.starts_with('map[') {
8862+ return false
8863+ }
8864+ a_bracket_end := find_matching_bracket(a_clean, 3)
8865+ b_bracket_end := find_matching_bracket(b_clean, 3)
8866+ if a_bracket_end >= a_clean.len || b_bracket_end >= b_clean.len {
8867+ return false
8868+ }
8869+ return tc.generic_type_arg_matches(a_clean[4..a_bracket_end], b_clean[4..b_bracket_end])
8870+ && tc.generic_type_arg_matches(a_clean[a_bracket_end + 1..], b_clean[b_bracket_end + 1..])
8871+ }
8872+ if a_clean.starts_with('[') || b_clean.starts_with('[') {
8873+ if !a_clean.starts_with('[') || !b_clean.starts_with('[') {
8874+ return false
8875+ }
8876+ a_bracket_end := find_matching_bracket(a_clean, 0)
8877+ b_bracket_end := find_matching_bracket(b_clean, 0)
8878+ if a_bracket_end >= a_clean.len || b_bracket_end >= b_clean.len
8879+ || a_clean[..a_bracket_end + 1] != b_clean[..b_bracket_end + 1] {
8880+ return false
8881+ }
8882+ return tc.generic_type_arg_matches(a_clean[a_bracket_end + 1..],
8883+ b_clean[b_bracket_end + 1..])
8884+ }
8885+ a_base, a_args, a_ok := generic_type_application_parts(a_clean)
8886+ b_base, b_args, b_ok := generic_type_application_parts(b_clean)
8887+ if a_ok || b_ok {
8888+ if !a_ok || !b_ok || a_args.len != b_args.len {
8889+ return false
8890+ }
8891+ if !tc.generic_type_base_matches(a_base, b_base) {
8892+ return false
8893+ }
8894+ for i in 0 .. a_args.len {
8895+ if !tc.generic_type_arg_matches(a_args[i], b_args[i]) {
8896+ return false
8897+ }
8898+ }
8899+ return true
8900+ }
8901+ a_resolved := tc.resolve_generic_match_arg(a_clean)
8902+ b_resolved := tc.resolve_generic_match_arg(b_clean)
8903+ if a_resolved == b_resolved {
8904+ return true
8905+ }
8906+ return false
8907+}
8908+
8909+fn (tc &TypeChecker) generic_match_arg_is_open_param(arg string) bool {
8910+ return is_bare_generic_param(arg) && !tc.is_known_type_text(arg)
8911+}
8912+
8913+fn (tc &TypeChecker) resolve_generic_match_arg(arg string) string {
8914+ if arg.len == 0 {
8915+ return arg
8916+ }
8917+ if !arg.contains('.') {
8918+ if resolved := tc.resolve_selective_import_type_symbol(arg) {
8919+ return resolved
8920+ }
8921+ }
8922+ qarg := tc.qualify_type_text(arg)
8923+ if qarg.len > 0 {
8924+ return qarg
8925+ }
8926+ return arg
8927+}
8928+
8929+fn (tc &TypeChecker) bare_variant_name_matches(candidate string, declared string, concrete string) bool {
8930+ if generic_type_application(candidate) {
8931+ return false
8932+ }
8933+ candidate_base := candidate.trim_space()
8934+ declared_base := strip_generic_args_name(declared).trim_space()
8935+ concrete_base := strip_generic_args_name(concrete).trim_space()
8936+ candidate_resolved := tc.resolve_generic_match_base(candidate_base)
8937+ declared_resolved := tc.resolve_generic_match_base(declared_base)
8938+ concrete_resolved := tc.resolve_generic_match_base(concrete_base)
8939+ if candidate_resolved == declared_resolved || candidate_resolved == concrete_resolved {
8940+ return true
8941+ }
8942+ if candidate_base.contains('.') || candidate_resolved.contains('.') {
8943+ return false
8944+ }
8945+ return (!declared_base.contains('.') && candidate_base == short_type_name(declared_base))
8946+ || (!concrete_base.contains('.') && candidate_base == short_type_name(concrete_base))
8947+}
8948+
77498949// type_matches_sum returns type matches sum data for TypeChecker.
77508950fn (tc &TypeChecker) type_matches_sum(actual Type, expected Type) bool {
77518951 if expected is SumType {
77528952 actual_name := actual.name()
7753- return tc.sum_has_variant(expected.name, actual_name)
8953+ return tc.sum_variant_type_for_pattern(expected.name, actual_name) != none
77548954 }
77558955 return false
77568956}
77578957
77588958// sum_has_variant converts sum has variant data for types.
77598959fn (tc &TypeChecker) sum_has_variant(sum_name string, variant_name string) bool {
7760- variants := tc.sum_types[sum_name] or { return false }
7761- variant_short := short_type_name(variant_name)
7762- for variant in variants {
7763- if variant == variant_name || short_type_name(variant) == variant_short {
7764- return true
7765- }
7766- }
8960+ return tc.sum_variant_type_for_pattern(sum_name, variant_name) != none
8961+}
8962+
8963+pub fn (tc &TypeChecker) sum_variant_type_for_pattern(sum_name string, variant_name string) ?string {
8964+ base := tc.sum_base_name(sum_name)
8965+ variants := tc.sum_types[base] or { return none }
8966+ mut candidates := [variant_name]
77678967 qvariant := tc.qualify_name(variant_name)
77688968 if qvariant != variant_name {
7769- for variant in variants {
7770- if variant == qvariant || short_type_name(variant) == variant_short {
7771- return true
8969+ candidates << qvariant
8970+ }
8971+ for variant in variants {
8972+ concrete := tc.concrete_sum_variant_name(sum_name, variant)
8973+ for candidate in candidates {
8974+ if tc.generic_type_name_matches(candidate, concrete)
8975+ || tc.bare_variant_name_matches(candidate, variant, concrete) {
8976+ return concrete
77728977 }
77738978 }
77748979 }
7775- return false
8980+ return none
77768981}
77778982
77788983// match_type_pattern supports match type pattern handling for TypeChecker.
@@ -7980,6 +9185,18 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {
79809185 reason: 'unknown'
79819186 })
79829187 }
9188+ if !typ.contains('.') {
9189+ if resolved := tc.resolve_selective_import_type_symbol(typ) {
9190+ if resolved_type := tc.type_from_known_symbol(resolved) {
9191+ return resolved_type
9192+ }
9193+ }
9194+ }
9195+ if builtin_error_name := tc.resolve_unqualified_builtin_error_struct_name(typ) {
9196+ return Type(Struct{
9197+ name: builtin_error_name
9198+ })
9199+ }
79839200 if typ.starts_with('C.') {
79849201 return Type(Struct{
79859202 name: typ
@@ -8002,14 +9219,14 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {
80029219 base_type: tc.parse_type(tc.type_aliases[typ])
80039220 })
80049221 }
8005- if typ in tc.interface_names {
9222+ if qtyp in tc.interface_names {
80069223 return Type(Interface{
8007- name: typ
9224+ name: qtyp
80089225 })
80099226 }
8010- if qtyp in tc.interface_names {
9227+ if typ in tc.interface_names {
80119228 return Type(Interface{
8012- name: qtyp
9229+ name: typ
80139230 })
80149231 }
80159232 if typ in tc.structs {
@@ -8122,7 +9339,7 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {
81229339 }
81239340 if qbase in tc.sum_types {
81249341 return Type(SumType{
8125- name: qbase
9342+ name: qbase + generic_suffix
81269343 })
81279344 }
81289345 if !base.contains('.') {
@@ -8145,7 +9362,7 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {
81459362 }
81469363 if resolved in tc.sum_types {
81479364 return Type(SumType{
8148- name: resolved
9365+ name: resolved + generic_suffix
81499366 })
81509367 }
81519368 }
@@ -8168,7 +9385,7 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {
81689385 }
81699386 if base in tc.sum_types {
81709387 return Type(SumType{
8171- name: base
9388+ name: base + generic_suffix
81729389 })
81739390 }
81749391 if is_concrete_generic && !is_builtin_type_name(base) {
@@ -8221,6 +9438,35 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {
82219438 })
82229439}
82239440
9441+fn (tc &TypeChecker) array_literal_elem_type(node flat.Node) Type {
9442+ if node.children_count == 0 {
9443+ return Type(int_)
9444+ }
9445+ mut elem_type := tc.resolve_type(tc.a.child(&node, 0))
9446+ mut all_numeric := elem_type.is_integer() || elem_type.is_float()
9447+ mut has_f32 := elem_type.name() == 'f32'
9448+ mut has_f64 := elem_type.name() == 'f64'
9449+ for i in 1 .. node.children_count {
9450+ child_type := tc.resolve_type(tc.a.child(&node, i))
9451+ if !(child_type.is_integer() || child_type.is_float()) {
9452+ all_numeric = false
9453+ }
9454+ if child_type.name() == 'f32' {
9455+ has_f32 = true
9456+ }
9457+ if child_type.name() == 'f64' {
9458+ has_f64 = true
9459+ }
9460+ }
9461+ if all_numeric && has_f64 {
9462+ return Type(f64_)
9463+ }
9464+ if all_numeric && has_f32 {
9465+ return tc.parse_type('f32')
9466+ }
9467+ return elem_type
9468+}
9469+
82249470fn (tc &TypeChecker) is_known_type_text(typ string) bool {
82259471 qtyp := tc.qualify_name(typ)
82269472 if !typ.contains('.') {
@@ -8680,7 +9926,7 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {
86809926 clean_type := unwrap_pointer(base_type)
86819927 if clean_type is Array {
86829928 if fn_node.value == 'clone' || fn_node.value == 'reverse' {
8683- return base_type
9929+ return clean_type
86849930 }
86859931 if fn_node.value == 'filter' || fn_node.value == 'sorted' {
86869932 return base_type
@@ -8736,7 +9982,7 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {
87369982 return unknown_type('`.wait()` requires an array of threads')
87379983 }
87389984 if fn_node.value == 'clone' {
8739- return base_type
9985+ return clean_type
87409986 }
87419987 elem_type := array_elem_type(clean_type)
87429988 elem_name := elem_type.name()
@@ -9081,7 +10327,7 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {
908110327 }
908210328 .array_literal {
908310329 if node.children_count > 0 {
9084- elem_type := tc.resolve_type(tc.a.child(&node, 0))
10330+ elem_type := tc.array_literal_elem_type(node)
908510331 return Type(ArrayFixed{
908610332 elem_type: elem_type
908710333 len: node.children_count
@@ -9501,6 +10747,54 @@ pub fn (tc &TypeChecker) resolve_generic_struct_method(type_name string, method
950110747 }
950210748}
950310749
10750+pub fn (tc &TypeChecker) resolve_generic_sum_method(type_name string, method string) ?CallInfo {
10751+ mut base := type_name
10752+ mut concrete_args := []string{}
10753+ parsed_base, parsed_args, is_generic := generic_type_application_parts(type_name)
10754+ if is_generic {
10755+ base = parsed_base
10756+ for arg in parsed_args {
10757+ concrete_args << arg.trim_space()
10758+ }
10759+ }
10760+ params := tc.sum_params_for_base(base)
10761+ if params.len == 0 {
10762+ return none
10763+ }
10764+ if !is_generic {
10765+ concrete_args = params.clone()
10766+ }
10767+ if params.len != concrete_args.len {
10768+ return none
10769+ }
10770+ generic_key := '${base}[${params.join(', ')}].${method}'
10771+ ret := tc.fn_ret_types[generic_key] or { return none }
10772+ mut sub_ret := tc.substitute_generic_type(ret, concrete_args, params)
10773+ if ret_text := tc.fn_ret_type_texts[generic_key] {
10774+ sub_ret = tc.parse_fn_signature_type(generic_key, subst_generic_text(ret_text,
10775+ concrete_args, params))
10776+ }
10777+ mut sub_params := []Type{}
10778+ if param_texts := tc.fn_param_type_texts[generic_key] {
10779+ for pt in param_texts {
10780+ sub_params << tc.parse_fn_signature_type(generic_key, subst_generic_text(pt,
10781+ concrete_args, params))
10782+ }
10783+ } else if ptypes := tc.fn_param_types[generic_key] {
10784+ for pt in ptypes {
10785+ sub_params << tc.substitute_generic_type(pt, concrete_args, params)
10786+ }
10787+ }
10788+ return CallInfo{
10789+ name: generic_key
10790+ params: sub_params
10791+ return_type: sub_ret
10792+ has_receiver: true
10793+ is_variadic: tc.fn_variadic[generic_key] or { false }
10794+ params_known: true
10795+ }
10796+}
10797+
950410798// subst_generic_text textually substitutes the generic parameter names `params` with the
950510799// concrete argument texts `args` inside a type text, preserving the generic application
950610800// form so a method signature mentioning the receiver type (`Box[T]`) becomes the concrete