medvednikov

/

vqPublic
0 commits21 issue12 pull requests0 contributorsDiscussionsProjectsCI

v3: extend baseline part2 #9

OpenGGReiwants to mergepr/27583intomaster· last Jun 27
52 files changed+9176-576
vlib/v3/flat/flat.v+2-0

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

vlib/v3/gen/c/cleanc.v+85-3

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

vlib/v3/gen/c/fn.v+3-0

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

vlib/v3/gen/c/fn_d_parallel.v+76-68

@@ -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.

vlib/v3/gen/c/interface.v+302-5

@@ -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;')

vlib/v3/gen/c/stmt.v+78-1

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

vlib/v3/gen/c/str_intp.v+6-2

@@ -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 {

vlib/v3/markused/markused.v+358-15

@@ -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.') {

vlib/v3/parser/parser.v+216-38

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

vlib/v3/test_all.vsh+283-7

@@ -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}')

vlib/v3/tests/builtin_const_imported_ref_codegen_test.vnew file+50-0

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

vlib/v3/tests/builtin_const_local_collision_codegen_test.vnew file+60-0

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

vlib/v3/tests/checker_diagnostic_files_test.vnew file+77-0

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

vlib/v3/tests/filelock_c_helpers_codegen_test.vnew file+59-0

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

vlib/v3/tests/for_multi_init_codegen_test.vnew file+209-0

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

vlib/v3/tests/generic_receiver_nested_call_codegen_test.vnew file+113-0

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

vlib/v3/tests/generic_struct_str_string_interp_codegen_test.vnew file+94-0

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

vlib/v3/tests/generic_sum_str_string_interp_codegen_test.vnew file+60-0

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

vlib/v3/tests/generic_sum_type_codegen_test.vnew file+509-0

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

vlib/v3/tests/ierror_match_codegen_test.vnew file+624-0

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

vlib/v3/tests/ierror_payload_compat_codegen_test.vnew file+757-0

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

vlib/v3/tests/ierror_pointer_payload_codegen_test.vnew file+160-0

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

vlib/v3/tests/ierror_promoted_methods_codegen_test.vnew file+278-0

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

vlib/v3/tests/ierror_result_payload_success_codegen_test.vnew file+155-0

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

vlib/v3/tests/interface_type_pattern_codegen_test.vnew file+240-0

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

vlib/v3/tests/join_path_markused_codegen_test.vnew file+44-0

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

vlib/v3/tests/local_ierror_interface_codegen_test.vnew file+91-0

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

vlib/v3/tests/map_keys_array_reverse_codegen_test.vnew file+99-0

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

vlib/v3/tests/map_receiver_method_markused_codegen_test.vnew file+54-0

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