@@ -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+}