medvednikov

/

vxx2Public
0 commits0 issues4 pull requests0 contributorsDiscussionsProjectsCI

v3: extend baseline part2 #5

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

16+

17+fn test_map_receiver_method_is_rooted_by_markused() {

18+ v3_bin := build_v3()

19+ src := os.join_path(os.temp_dir(), 'v3_map_receiver_method_markused_input_${os.getpid()}.v')

20+ os.write_file(src, "struct Entry {

21+ kind int

22+}

23+

24+fn (m map[string]Entry) find(name string) ?Entry {

25+ if name in m {

26+ return m[name]

27+ }

28+ return none

29+}

30+

31+fn main() {

32+ mut items := map[string]Entry{}

33+ items['name'] = Entry{

34+ kind: 41

35+ }

36+ got := items.find('name') or { return }

37+ println(got.kind)

38+}

39+") or {

40+ panic(err)

41+ }

42+

43+ bin := os.join_path(os.temp_dir(), 'v3_map_receiver_method_markused_input_${os.getpid()}')

44+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

45+ assert compile.exit_code == 0, compile.output

46+ assert !compile.output.contains('C compilation failed'), compile.output

47+

48+ generated := os.read_file(bin + '.c') or { panic(err) }

49+ assert generated.contains('map_stringEntry__find'), generated

50+

51+ run := os.execute(bin)

52+ assert run.exit_code == 0, run.output

53+ assert run.output.trim_space() == '41'

54+}

vlib/v3/tests/markused_test.v+25-0

@@ -188,6 +188,31 @@ fn main() {

188188 assert used['string__plus']

189189}

190190

191+fn test_map_str_seeds_string_plus_runtime_helper() {

192+ used := mark_used_source('map_str_string_plus', '

193+fn render(m map[string]int) string {

194+ return m.str()

195+}

196+

197+fn main() {

198+ _ := render(map[string]int{})

199+}

200+')

201+ assert used['string__plus']

202+}

203+

204+fn test_print_map_seeds_string_plus_runtime_helper() {

205+ used := mark_used_source('print_map_string_plus', '

206+fn main() {

207+ m := {

208+ "a": 1

209+ }

210+ println(m)

211+}

212+')

213+ assert used['string__plus']

214+}

215+

191216fn test_moduleless_export_after_module_file_is_rooted() {

192217 a, tc := parse_checked_project_in_order('moduleless_export_after_module', [

193218 'helper/helper.v',

vlib/v3/tests/match_generic_pattern_parser_test.vnew file+363-0

@@ -0,0 +1,363 @@

1+import os

2+import v3.flat

3+import v3.parser

4+import v3.pref

5+import v3.types

6+

7+const local_vexe = @VEXE

8+const local_tests_dir = os.dir(@FILE)

9+const local_v3_dir = os.dir(local_tests_dir)

10+const local_vlib_dir = os.dir(local_v3_dir)

11+

12+fn proxy_to_local_v3_if_needed() bool {

13+ if os.getenv('V3_MATCH_GENERIC_PATTERN_INNER') == '1' {

14+ return false

15+ }

16+ cmd := 'V3_MATCH_GENERIC_PATTERN_INNER=1 ${os.quoted_path(local_vexe)} -gc none -path "${local_vlib_dir}|@vlib|@vmodules" ${os.quoted_path(@FILE)}'

17+ result := os.execute(cmd)

18+ assert result.exit_code == 0, result.output

19+ return true

20+}

21+

22+fn parse_match_generic_pattern_source(src string) &flat.FlatAst {

23+ path := os.join_path(os.temp_dir(), 'v3_match_generic_pattern_${os.getpid()}.v')

24+ os.write_file(path, src) or { panic(err) }

25+ prefs := pref.new_preferences()

26+ mut p := parser.Parser.new(prefs)

27+ p.parse_into(path)

28+ return p.a

29+}

30+

31+fn find_fn_node(a &flat.FlatAst, name string) flat.Node {

32+ for node in a.nodes {

33+ if node.kind == .fn_decl && node.value == name {

34+ return node

35+ }

36+ }

37+ assert false, 'missing fn `${name}`'

38+ return flat.Node{}

39+}

40+

41+fn find_match_branch_with_pattern(a &flat.FlatAst, pattern string) flat.Node {

42+ for node in a.nodes {

43+ if node.kind != .match_branch || node.children_count == 0 {

44+ continue

45+ }

46+ cond := a.child_node(&node, 0)

47+ if cond.kind == .ident && cond.value == pattern {

48+ return node

49+ }

50+ if cond.kind == .selector && cond.value == pattern {

51+ return node

52+ }

53+ }

54+ assert false, 'missing match branch pattern `${pattern}`'

55+ return flat.Node{}

56+}

57+

58+fn final_file_node(a &flat.FlatAst) flat.Node {

59+ for i := a.nodes.len - 1; i >= 0; i-- {

60+ node := a.nodes[i]

61+ if node.kind == .file && node.children_count > 0 {

62+ return node

63+ }

64+ }

65+ assert false, 'missing final file node'

66+ return flat.Node{}

67+}

68+

69+fn file_child_fn_names(a &flat.FlatAst) []string {

70+ file_node := final_file_node(a)

71+ mut names := []string{}

72+ for i in 0 .. file_node.children_count {

73+ child := a.child_node(&file_node, i)

74+ if child.kind == .fn_decl {

75+ names << child.value

76+ }

77+ }

78+ return names

79+}

80+

81+fn find_match_branch_with_qualified_pattern(a &flat.FlatAst, module_name string, pattern string) flat.Node {

82+ for node in a.nodes {

83+ if node.kind != .match_branch || node.children_count == 0 {

84+ continue

85+ }

86+ cond := a.child_node(&node, 0)

87+ if cond.kind != .selector || cond.value != pattern || cond.children_count == 0 {

88+ continue

89+ }

90+ base := a.child_node(cond, 0)

91+ if base.kind == .ident && base.value == module_name {

92+ return node

93+ }

94+ }

95+ assert false, 'missing match branch pattern `${module_name}.${pattern}`'

96+ return flat.Node{}

97+}

98+

99+fn branch_has_direct_array_literal(a &flat.FlatAst, branch flat.Node) bool {

100+ for i in 1 .. branch.children_count {

101+ child := a.child_node(&branch, i)

102+ if child.kind == .array_literal {

103+ return true

104+ }

105+ if child.kind == .expr_stmt && child.children_count > 0 {

106+ expr := a.child_node(child, 0)

107+ if expr.kind == .array_literal {

108+ return true

109+ }

110+ }

111+ }

112+ return false

113+}

114+

115+fn test_match_branch_generic_type_patterns_are_consumed() {

116+ if proxy_to_local_v3_if_needed() {

117+ return

118+ }

119+ a := parse_match_generic_pattern_source('

120+struct Empty {}

121+

122+struct Node[T] {

123+ value T

124+}

125+

126+type Tree[T] = Empty | Node[T]

127+

128+fn walk[T](tree Tree[T]) int {

129+ return match tree {

130+ Empty { 0 }

131+ Node[T] { tree.value }

132+ }

133+}

134+

135+fn qualified[T](tree Tree[T]) int {

136+ return match tree {

137+ foo.Node[T] { 1 }

138+ else { 0 }

139+ }

140+}

141+

142+fn nested_arg[T](tree Tree[T]) int {

143+ return match tree {

144+ Node[foo.Bar] { 1 }

145+ else { 0 }

146+ }

147+}

148+

149+fn after() int {

150+ return 7

151+}

152+')

153+

154+ names := file_child_fn_names(a)

155+ assert names == ['walk', 'qualified', 'nested_arg', 'after']

156+ find_fn_node(a, 'after')

157+

158+ node_branch := find_match_branch_with_pattern(a, 'Node[T]')

159+ assert node_branch.children_count == 2

160+ assert !branch_has_direct_array_literal(a, node_branch)

161+

162+ nested_arg_branch := find_match_branch_with_pattern(a, 'Node[foo.Bar]')

163+ nested_cond := a.child_node(&nested_arg_branch, 0)

164+ assert nested_cond.kind == .ident

165+ assert nested_cond.value == 'Node[foo.Bar]'

166+

167+ qualified_branch := find_match_branch_with_qualified_pattern(a, 'foo', 'Node[T]')

168+ cond := a.child_node(&qualified_branch, 0)

169+ base := a.child_node(cond, 0)

170+ assert base.kind == .ident

171+ assert base.value == 'foo'

172+ assert cond.value == 'Node[T]'

173+}

174+

175+fn check_source_errors(name string, src string) []types.TypeError {

176+ path := os.join_path(os.temp_dir(), 'v3_generic_sum_checker_${name}_${os.getpid()}.v')

177+ os.write_file(path, src) or { panic(err) }

178+ prefs := pref.new_preferences()

179+ mut p := parser.Parser.new(prefs)

180+ mut a := p.parse_file(path)

181+ mut tc := types.TypeChecker.new(a)

182+ tc.collect(a)

183+ tc.diagnose_unknown_calls = true

184+ tc.diagnostic_files[path] = true

185+ tc.check_semantics()

186+ return tc.errors.clone()

187+}

188+

189+fn assert_checker_ok(name string, src string) {

190+ errors := check_source_errors(name, src)

191+ assert errors.len == 0, errors.str()

192+}

193+

194+fn assert_checker_error_contains(name string, src string, expected string) {

195+ errors := check_source_errors(name, src)

196+ assert errors.len > 0

197+ mut text := ''

198+ for err in errors {

199+ text += err.msg + '\n'

200+ }

201+ assert text.contains(expected), text

202+}

203+

204+const generic_sum_defs = '

205+struct Empty {}

206+

207+struct Node[T] {

208+ value T

209+ left Tree[T]

210+}

211+

212+struct Other[T] {

213+ value T

214+}

215+

216+type Tree[T] = Empty | Node[T]

217+'

218+

219+fn test_generic_sum_type_checker_accepts_shared_field_on_concrete_sum() {

220+ if proxy_to_local_v3_if_needed() {

221+ return

222+ }

223+ assert_checker_ok('generic_sum_shared_field', '

224+struct A[T] {

225+ x T

226+}

227+

228+struct B[T] {

229+ x T

230+}

231+

232+type S[T] = A[T] | B[T]

233+

234+fn get(s S[int]) int {

235+ return s.x

236+}

237+')

238+}

239+

240+fn test_generic_sum_type_checker_accepts_methods_returns_and_smartcasts() {

241+ if proxy_to_local_v3_if_needed() {

242+ return

243+ }

244+ assert_checker_ok('generic_sum_method_and_returns', generic_sum_defs +

245+ '

246+fn (tree Tree[T]) size[T]() int {

247+ return match tree {

248+ Empty { 0 }

249+ Node[T] { 1 +

250+ tree.left.size() }

251+ }

252+}

253+

254+fn make_int_tree() Tree[int] {

255+ return Node[int]{

256+ value: 1

257+ left: Empty{}

258+ }

259+}

260+

261+fn make_tree[T](value T) Tree[T] {

262+ return Node[T]{

263+ value: value

264+ left: Empty{}

265+ }

266+}

267+

268+fn field_after_is[T](tree Tree[T]) T {

269+ if tree is Node[T] {

270+ return tree.value

271+ }

272+ return T(0)

273+}

274+

275+fn main() {

276+ tree := make_int_tree()

277+ _ := tree.size()

278+}

279+')

280+}

281+

282+fn test_generic_sum_type_checker_rejects_wrong_generic_variant() {

283+ if proxy_to_local_v3_if_needed() {

284+ return

285+ }

286+ assert_checker_error_contains('generic_sum_wrong_variant_arg', generic_sum_defs +

287+ '

288+fn bad() Tree[int] {

289+ return Node[string]{

290+ value: "bad"

291+ left: Empty{}

292+ }

293+}

294+

295+fn main() {}

296+',

297+ 'cannot return `Node[string]` as `Tree[int]`')

298+}

299+

300+fn test_generic_sum_type_checker_rejects_non_variant_pattern() {

301+ if proxy_to_local_v3_if_needed() {

302+ return

303+ }

304+ assert_checker_error_contains('generic_sum_non_variant_pattern', generic_sum_defs +

305+ '

306+fn bad(tree Tree[int]) int {

307+ return match tree {

308+ Other[int] { 1 }

309+ else { 0 }

310+ }

311+}

312+

313+fn main() {}

314+',

315+ '`Other[int]` is not a variant of sum type `Tree[int]`')

316+}

317+

318+fn test_generic_sum_type_checker_accepts_qualified_generic_variant_pattern() {

319+ if proxy_to_local_v3_if_needed() {

320+ return

321+ }

322+ root := os.join_path(os.temp_dir(), 'v3_generic_sum_checker_qualified_${os.getpid()}')

323+ os.rmdir_all(root) or {}

324+ foo_path := os.join_path(root, 'foo', 'foo.v')

325+ main_path := os.join_path(root, 'main.v')

326+ os.mkdir_all(os.dir(foo_path)) or { panic(err) }

327+ os.write_file(foo_path, 'module foo

328+

329+pub struct Node[T] {

330+pub:

331+ value T

332+}

333+') or { panic(err) }

334+ os.write_file(main_path, 'module main

335+

336+import foo

337+

338+struct Empty {}

339+

340+type Tree[T] = Empty | foo.Node[T]

341+

342+fn value(tree Tree[int]) int {

343+ return match tree {

344+ foo.Node[int] { tree.value }

345+ Empty { 0 }

346+ }

347+}

348+

349+fn main() {}

350+') or {

351+ panic(err)

352+ }

353+ prefs := pref.new_preferences()

354+ mut p := parser.Parser.new(prefs)

355+ mut a := p.parse_files([main_path, foo_path])

356+ mut tc := types.TypeChecker.new(a)

357+ tc.collect(a)

358+ tc.diagnose_unknown_calls = true

359+ tc.diagnostic_files[main_path] = true

360+ tc.diagnostic_files[foo_path] = true

361+ tc.check_semantics()

362+ assert tc.errors.len == 0, tc.errors.str()

363+}

vlib/v3/tests/module_fn_short_name_collision_codegen_test.vnew file+106-0

@@ -0,0 +1,106 @@

1+import os

2+

3+const module_fn_collision_vexe = @VEXE

4+const module_fn_collision_tests_dir = os.dir(@FILE)

5+const module_fn_collision_v3_dir = os.dir(module_fn_collision_tests_dir)

6+const module_fn_collision_vlib_dir = os.dir(module_fn_collision_v3_dir)

7+const module_fn_collision_v3_src = os.join_path(module_fn_collision_v3_dir, 'v3.v')

8+

9+fn module_fn_collision_build_v3() string {

10+ v3_bin := os.join_path(os.temp_dir(), 'v3_module_fn_collision_test_${os.getpid()}')

11+ os.rm(v3_bin) or {}

12+ build :=

13+ os.execute('${module_fn_collision_vexe} -gc none -path "${module_fn_collision_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${module_fn_collision_v3_src}')

14+ assert build.exit_code == 0, build.output

15+ return v3_bin

16+}

17+

18+fn module_fn_collision_write_project() string {

19+ root := os.join_path(os.temp_dir(), 'v3_module_fn_collision_${os.getpid()}')

20+ os.rmdir_all(root) or {}

21+ os.mkdir_all(os.join_path(root, 'collisionmod')) or { panic(err) }

22+ os.mkdir_all(os.join_path(root, 'localmod')) or { panic(err) }

23+ os.write_file(os.join_path(root, 'v.mod'), "Module { name: 'module_fn_collision' }\n") or {

24+ panic(err)

25+ }

26+ os.write_file(os.join_path(root, 'collisionmod/collisionmod.v'), 'module collisionmod

27+

28+struct Node[T] {

29+ value T

30+}

31+

32+pub struct Holder[T] {

33+ node &Node[T]

34+}

35+

36+fn new_node[T](value T) &Node[T] {

37+ return &Node[T]{

38+ value: value

39+ }

40+}

41+

42+pub fn make[T](value T) Holder[T] {

43+ return Holder[T]{

44+ node: new_node(value)

45+ }

46+}

47+

48+pub fn value[T](holder Holder[T]) T {

49+ return holder.node.value

50+}

51+') or {

52+ panic(err)

53+ }

54+ os.write_file(os.join_path(root, 'localmod/localmod.v'), 'module localmod

55+

56+fn helper() int {

57+ return 41

58+}

59+

60+pub fn use_helper() int {

61+ return helper() + 1

62+}

63+') or {

64+ panic(err)

65+ }

66+ os.write_file(os.join_path(root, 'main.v'), "module main

67+

68+import collisionmod

69+import localmod

70+

71+fn main() {

72+ holder := collisionmod.make(7)

73+ assert collisionmod.value(holder) == 7

74+ assert localmod.use_helper() == 42

75+ mut values := map[string]int{}

76+ values['answer'] = 42

77+ assert values['answer'] == 42

78+ println('ok')

79+}

80+") or {

81+ panic(err)

82+ }

83+ return os.join_path(root, 'main.v')

84+}

85+

86+fn test_imported_module_fn_short_name_does_not_pollute_builtin_return_type() {

87+ v3_bin := module_fn_collision_build_v3()

88+ main_path := module_fn_collision_write_project()

89+ out := os.join_path(os.temp_dir(), 'v3_module_fn_collision_out_${os.getpid()}')

90+ os.rm(out) or {}

91+ os.rm(out + '.c') or {}

92+ compile := os.execute('${v3_bin} ${main_path} -b c -o ${out}')

93+ assert compile.exit_code == 0, compile.output

94+ assert !compile.output.contains('C compilation failed'), compile.output

95+

96+ run := os.execute(out)

97+ assert run.exit_code == 0, run.output

98+ assert run.output.trim_space() == 'ok'

99+

100+ generated := os.read_file(out + '.c') or { panic(err) }

101+ assert generated.contains('mapnode* z = new_node();'), generated

102+ assert generated.contains('collisionmod__new_node_T_v_int'), generated

103+ assert generated.contains('localmod__helper()'), generated

104+ assert !generated.contains('collisionmod__Node_T* z = new_node();'), generated

105+ assert !generated.contains('Array_fixed_collisionmod__Node_T* z = new_node();'), generated

106+}

vlib/v3/tests/mut_param_array_clone_codegen_test.vnew file+70-0

@@ -0,0 +1,70 @@

1+import os

2+

3+const vexe = @VEXE

4+const tests_dir = os.dir(@FILE)

5+const v3_dir = os.dir(tests_dir)

6+const vlib_dir = os.dir(v3_dir)

7+const v3_src = os.join_path(v3_dir, 'v3.v')

8+

9+fn build_v3() string {

10+ v3_bin := os.join_path(os.temp_dir(), 'v3_mut_param_array_clone_test_${os.getpid()}')

11+ build :=

12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')

13+ assert build.exit_code == 0, build.output

14+ return v3_bin

15+}

16+

17+fn test_mut_array_param_clone_reassigns_value_array() {

18+ v3_bin := build_v3()

19+

20+ src := os.join_path(os.temp_dir(), 'v3_mut_param_array_clone_input_${os.getpid()}.v')

21+ os.write_file(src, "fn update(mut xs []int) {

22+ mut ys := xs.clone()

23+ ys[0] = 2

24+ xs = ys.clone()

25+}

26+

27+fn main() {

28+ mut xs := [1, 1]

29+ update(mut xs)

30+ assert xs[0] == 2

31+ assert xs[1] == 1

32+ println('ok')

33+}

34+") or {

35+ panic(err)

36+ }

37+

38+ bin := os.join_path(os.temp_dir(), 'v3_mut_param_array_clone_input_${os.getpid()}')

39+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

40+ assert compile.exit_code == 0, compile.output

41+ assert !compile.output.contains('cannot assign `&[]int` to `[]int`'), compile.output

42+ assert !compile.output.contains('C compilation failed'), compile.output

43+

44+ run := os.execute(bin)

45+ assert run.exit_code == 0, run.output

46+ assert run.output.trim_space() == 'ok'

47+}

48+

49+fn test_mut_array_param_assignment_still_rejects_non_array() {

50+ v3_bin := build_v3()

51+

52+ src := os.join_path(os.temp_dir(), 'v3_mut_array_param_bad_assign_${os.getpid()}.v')

53+ os.write_file(src, 'fn update(mut xs []int) {

54+ xs = 1

55+}

56+

57+fn main() {

58+ mut xs := [1, 2]

59+ update(mut xs)

60+}

61+') or {

62+ panic(err)

63+ }

64+

65+ bin := os.join_path(os.temp_dir(), 'v3_mut_array_param_bad_assign_${os.getpid()}')

66+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

67+ assert compile.exit_code != 0, compile.output

68+ assert compile.output.contains('cannot assign'), compile.output

69+ assert !compile.output.contains('C compilation failed'), compile.output

70+}

vlib/v3/tests/mut_param_reassign_codegen_test.vnew file+214-0

@@ -0,0 +1,214 @@

1+import os

2+

3+const vexe = @VEXE

4+const tests_dir = os.dir(@FILE)

5+const v3_dir = os.dir(tests_dir)

6+const vlib_dir = os.dir(v3_dir)

7+const v3_src = os.join_path(v3_dir, 'v3.v')

8+

9+fn mut_param_reassign_build_v3() string {

10+ v3_bin := os.join_path(os.temp_dir(), 'v3_mut_param_reassign_codegen_test_${os.getpid()}')

11+ os.rm(v3_bin) or {}

12+ build :=

13+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')

14+ assert build.exit_code == 0, build.output

15+ return v3_bin

16+}

17+

18+fn mut_param_reassign_run_good(v3_bin string, name string, source string) string {

19+ src := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}.v')

20+ os.write_file(src, source) or { panic(err) }

21+ bin := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}')

22+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

23+ assert compile.exit_code == 0, compile.output

24+ assert !compile.output.contains('C compilation failed'), compile.output

25+ run := os.execute(bin)

26+ assert run.exit_code == 0, run.output

27+ return run.output.trim_space()

28+}

29+

30+fn mut_param_reassign_run_bad(v3_bin string, name string, source string, expected string) {

31+ src := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}.v')

32+ os.write_file(src, source) or { panic(err) }

33+ bin := os.join_path(os.temp_dir(), 'v3_${name}_${os.getpid()}')

34+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

35+ assert compile.exit_code != 0, compile.output

36+ assert compile.output.contains(expected), compile.output

37+ assert !compile.output.contains('C compilation failed'), compile.output

38+}

39+

40+fn test_mut_array_param_reassigns_to_base_type() {

41+ v3_bin := mut_param_reassign_build_v3()

42+ out := mut_param_reassign_run_good(v3_bin, 'mut_array_param_reassign', 'fn replace(mut xs []int) {

43+ mut tmp := []int{}

44+ tmp << 4

45+ tmp << 9

46+ xs = tmp.clone()

47+}

48+

49+fn main() {

50+ mut xs := []int{}

51+ xs << 1

52+ replace(mut xs)

53+ assert xs.len == 2

54+ assert xs[0] == 4

55+ assert xs[1] == 9

56+ println("ok")

57+}

58+')

59+ assert out == 'ok'

60+}

61+

62+fn test_mut_string_param_concat_reads_as_string() {

63+ v3_bin := mut_param_reassign_build_v3()

64+ out := mut_param_reassign_run_good(v3_bin, 'mut_string_param_concat', "fn add(mut s string) string {

65+ return s + '!'

66+}

67+

68+fn main() {

69+ mut s := 'hi'

70+ println(add(mut s))

71+}

72+")

73+ assert out == 'hi!'

74+}

75+

76+fn test_generic_mut_array_param_reassigns_to_base_type() {

77+ v3_bin := mut_param_reassign_build_v3()

78+ out := mut_param_reassign_run_good(v3_bin, 'generic_mut_array_param_reassign', 'struct Item {

79+ data int

80+ priority int

81+}

82+

83+fn replace[T](mut xs []T, value T) {

84+ mut tmp := []T{}

85+ tmp << value

86+ xs = tmp.clone()

87+}

88+

89+fn main() {

90+ mut xs := []Item{}

91+ replace(mut xs, Item{data: 7, priority: 3})

92+ assert xs.len == 1

93+ assert xs[0].data == 7

94+ assert xs[0].priority == 3

95+ println("ok")

96+}

97+')

98+ assert out == 'ok'

99+}

100+

101+fn test_mut_map_param_reassigns_to_base_type() {

102+ v3_bin := mut_param_reassign_build_v3()

103+ out := mut_param_reassign_run_good(v3_bin, 'mut_map_param_reassign', "fn replace(mut m map[string]int) {

104+ tmp := {

105+ 'answer': 42

106+ }

107+ m = tmp.clone()

108+}

109+

110+fn main() {

111+ mut m := map[string]int{}

112+ m['old'] = 1

113+ replace(mut m)

114+ assert m.len == 1

115+ assert m['answer'] == 42

116+ println('ok')

117+}

118+")

119+ assert out == 'ok'

120+}

121+

122+fn test_multi_return_assign_to_mut_array_param_uses_base_type() {

123+ v3_bin := mut_param_reassign_build_v3()

124+ out := mut_param_reassign_run_good(v3_bin, 'mut_array_param_multi_return_reassign', 'fn pair() ([]int, int) {

125+ return [1, 2], 7

126+}

127+

128+fn replace(mut xs []int) {

129+ xs, _ = pair()

130+}

131+

132+fn main() {

133+ mut xs := [0]

134+ replace(mut xs)

135+ assert xs.len == 2

136+ assert xs[0] == 1

137+ assert xs[1] == 2

138+ println("ok")

139+}

140+')

141+ assert out == 'ok'

142+}

143+

144+fn test_mut_param_compound_assign_and_postfix_store_through_pointer() {

145+ v3_bin := mut_param_reassign_build_v3()

146+ out := mut_param_reassign_run_good(v3_bin, 'mut_param_compound_assign', 'fn inc(mut n int) {

147+ n += 1

148+ n++

149+}

150+

151+fn main() {

152+ mut x := 1

153+ inc(mut x)

154+ assert x == 3

155+ println("ok")

156+}

157+')

158+ assert out == 'ok'

159+}

160+

161+fn test_mut_param_local_shadow_keeps_local_type() {

162+ v3_bin := mut_param_reassign_build_v3()

163+ out := mut_param_reassign_run_good(v3_bin, 'mut_param_local_shadow', "fn f(mut s string) {

164+ {

165+ mut s := 1

166+ s = 2

167+ assert s + 1 == 3

168+ }

169+ s = 'done'

170+}

171+

172+fn main() {

173+ mut s := 'start'

174+ f(mut s)

175+ assert s == 'done'

176+ println('ok')

177+}

178+")

179+ assert out == 'ok'

180+}

181+

182+fn test_mut_param_reassign_keeps_invalid_assignments_rejected() {

183+ v3_bin := mut_param_reassign_build_v3()

184+ mut_param_reassign_run_bad(v3_bin, 'bad_mut_array_param_reassign_elem', "fn bad(mut xs []int) {

185+ mut ys := []string{}

186+ ys << 'bad'

187+ xs = ys

188+}

189+

190+fn main() {

191+ mut xs := []int{}

192+ bad(mut xs)

193+}

194+",

195+ 'cannot assign `[]string` to `[]int`')

196+ mut_param_reassign_run_bad(v3_bin, 'bad_mut_array_param_reassign_scalar', 'fn bad(mut xs []int) {

197+ xs = 1

198+}

199+

200+fn main() {

201+ mut xs := []int{}

202+ bad(mut xs)

203+}

204+',

205+ 'cannot assign `int` to `[]int`')

206+ mut_param_reassign_run_bad(v3_bin, 'bad_pointer_local_reassign_value', 'fn main() {

207+ mut xs := []int{}

208+ mut p := &xs

209+ mut tmp := []int{}

210+ p = tmp

211+}

212+',

213+ 'cannot assign `[]int` to `&[]int`')

214+}

vlib/v3/tests/nested_array_append_codegen_test.vnew file+83-0

@@ -0,0 +1,83 @@

1+import os

2+

3+const nested_array_append_vexe = @VEXE

4+const nested_array_append_tests_dir = os.dir(@FILE)

5+const nested_array_append_v3_dir = os.dir(nested_array_append_tests_dir)

6+const nested_array_append_vlib_dir = os.dir(nested_array_append_v3_dir)

7+const nested_array_append_v3_src = os.join_path(nested_array_append_v3_dir, 'v3.v')

8+

9+fn nested_array_append_build_v3() string {

10+ v3_bin := os.join_path(os.temp_dir(), 'v3_nested_array_append_test_${os.getpid()}')

11+ os.rm(v3_bin) or {}

12+ build :=

13+ os.execute('${nested_array_append_vexe} -gc none -path "${nested_array_append_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${nested_array_append_v3_src}')

14+ assert build.exit_code == 0, build.output

15+ return v3_bin

16+}

17+

18+fn test_nested_array_append_keeps_push_and_push_many_semantics() {

19+ v3_bin := nested_array_append_build_v3()

20+ src := os.join_path(os.temp_dir(), 'v3_nested_array_append_input_${os.getpid()}.v')

21+ os.write_file(src, "fn main() {

22+ mut scalars := []int{}

23+ scalars << [1, 2]

24+ assert scalars.len == 2

25+ assert scalars[1] == 2

26+

27+ mut rows := [][]int{}

28+ row_group := [[1], [2]]

29+ rows << row_group

30+ assert rows.len == 2

31+ assert rows[1][0] == 2

32+

33+ mut row_items := [][]int{}

34+ row := [3, 4]

35+ row_items << row

36+ assert row_items.len == 1

37+ assert row_items[0][1] == 4

38+

39+ mut cube := [][][]int{}

40+ cube_item := [[5], [6]]

41+ cube << cube_item

42+ assert cube.len == 1

43+ assert cube[0].len == 2

44+ assert cube[0][1][0] == 6

45+

46+ mut cube_many := [][][]int{}

47+ cube_group := [[[7]], [[8]]]

48+ cube_many << cube_group

49+ assert cube_many.len == 2

50+ assert cube_many[1][0][0] == 8

51+

52+ mut queue := [][][]string{}

53+ path := []string{}

54+ queue << [['A'], path]

55+ assert queue.len == 1

56+ assert queue[0].len == 2

57+ assert queue[0][0][0] == 'A'

58+ assert queue[0][1].len == 0

59+ println('ok')

60+}

61+") or {

62+ panic(err)

63+ }

64+

65+ bin := os.join_path(os.temp_dir(), 'v3_nested_array_append_input_${os.getpid()}')

66+ os.rm(bin) or {}

67+ os.rm(bin + '.c') or {}

68+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

69+ assert compile.exit_code == 0, compile.output

70+ assert !compile.output.contains('C compilation failed'), compile.output

71+

72+ run := os.execute(bin)

73+ assert run.exit_code == 0, run.output

74+ assert run.output.trim_space() == 'ok'

75+

76+ c_code := os.read_file(bin + '.c') or { panic(err) }

77+ assert c_code.contains('array__push_many(&scalars'), c_code

78+ assert c_code.contains('array__push_many(&rows'), c_code

79+ assert c_code.contains('array_push(&row_items'), c_code

80+ assert c_code.contains('array_push(&cube'), c_code

81+ assert c_code.contains('array__push_many(&cube_many'), c_code

82+ assert c_code.contains('array_push(&queue'), c_code

83+}

vlib/v3/tests/numeric_array_literal_type_codegen_test.vnew file+76-0

@@ -0,0 +1,76 @@

1+import os

2+

3+const numeric_array_literal_vexe = @VEXE

4+const numeric_array_literal_tests_dir = os.dir(@FILE)

5+const numeric_array_literal_v3_dir = os.dir(numeric_array_literal_tests_dir)

6+const numeric_array_literal_vlib_dir = os.dir(numeric_array_literal_v3_dir)

7+const numeric_array_literal_v3_src = os.join_path(numeric_array_literal_v3_dir, 'v3.v')

8+

9+fn numeric_array_literal_build_v3() string {

10+ v3_bin := os.join_path(os.temp_dir(), 'v3_numeric_array_literal_test_${os.getpid()}')

11+ os.rm(v3_bin) or {}

12+ build :=

13+ os.execute('${numeric_array_literal_vexe} -gc none -path "${numeric_array_literal_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${numeric_array_literal_v3_src}')

14+ assert build.exit_code == 0, build.output

15+ return v3_bin

16+}

17+

18+fn test_numeric_array_literals_keep_float_common_type() {

19+ v3_bin := numeric_array_literal_build_v3()

20+ src := os.join_path(os.temp_dir(), 'v3_numeric_array_literal_input_${os.getpid()}.v')

21+ os.write_file(src, "

22+fn main() {

23+ xs := [-0.3, 0.0, 0.3, 0.6, 1.0, 1.5]

24+ mut sum := 0.0

25+ for x in xs {

26+ sum += x

27+ }

28+ assert sum > 3.09 && sum < 3.11

29+

30+ ys := [1, 2, 3]

31+ mut isum := 0

32+ for y in ys {

33+ isum += y

34+ }

35+ assert isum == 6

36+

37+ zs := [f32(1.25), f32(2.5)]

38+ mut zsum := f32(0)

39+ for z in zs {

40+ zsum += z

41+ }

42+ assert zsum > f32(3.74) && zsum < f32(3.76)

43+

44+ ws := [f32(1.25), 2]

45+ mut wsum := f32(0)

46+ for w in ws {

47+ wsum += w

48+ }

49+ assert wsum > f32(3.24) && wsum < f32(3.26)

50+ println('ok')

51+}

52+") or {

53+ panic(err)

54+ }

55+

56+ bin := os.join_path(os.temp_dir(), 'v3_numeric_array_literal_input_${os.getpid()}')

57+ os.rm(bin) or {}

58+ os.rm(bin + '.c') or {}

59+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

60+ assert compile.exit_code == 0, compile.output

61+ assert !compile.output.contains('C compilation failed'), compile.output

62+

63+ run := os.execute(bin)

64+ assert run.exit_code == 0, run.output

65+ assert run.output.trim_space() == 'ok'

66+

67+ c_code := os.read_file(bin + '.c') or { panic(err) }

68+ assert c_code.contains('array_new(sizeof(double), 0, 6)'), c_code

69+ assert c_code.contains('double x = *(double*)(array_get(xs,'), c_code

70+ assert c_code.contains('array_new(sizeof(int), 0, 3)'), c_code

71+ assert !c_code.contains('int x = *(int*)(array_get(xs,'), c_code

72+ assert c_code.contains('array_new(sizeof(float), 0, 2)'), c_code

73+ assert c_code.contains('float z = *(float*)(array_get(zs,'), c_code

74+ assert c_code.contains('float w = *(float*)(array_get(ws,'), c_code

75+ assert !c_code.contains('array_new(sizeof(double), 0, 2)'), c_code

76+}

vlib/v3/tests/parallel_cgen_test.v+63-1

@@ -9,7 +9,7 @@ const parallel_v3_src = os.join_path(parallel_v3_dir, 'v3.v')

99// build_parallel_v3 builds parallel v3 data for v3 tests.

1010fn build_parallel_v3() string {

1111 vexe := @VEXE

12- v3_bin := os.join_path(os.temp_dir(), 'v3_parallel_cgen_test')

12+ v3_bin := os.join_path(os.temp_dir(), 'v3_parallel_cgen_test_${os.getpid()}')

1313 os.rm(v3_bin) or {}

1414 build :=

1515 os.execute('${vexe} -d parallel -gc none -path "${parallel_vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${parallel_v3_src}')

@@ -230,6 +230,68 @@ fn test_parallel_cgen_worker_resolves_generic_struct_method_signature() {

230230 assert !c_code.contains('?T'), c_code

231231}

232232

233+fn write_parallel_ierror_payload_success_project(name string) string {

234+ project_dir := os.join_path(os.temp_dir(), 'v3_${name}')

235+ os.rmdir_all(project_dir) or {}

236+ os.mkdir_all(os.join_path(project_dir, 'payloadmod')) or { panic(err) }

237+

238+ mut main_src := strings.new_builder(128_000)

239+ main_src.writeln('module main')

240+ main_src.writeln('')

241+ main_src.writeln('import payloadmod')

242+ main_src.writeln('')

243+ for i in 0 .. 1300 {

244+ main_src.writeln('fn helper_${i}() int {')

245+ main_src.writeln('\treturn ${i}')

246+ main_src.writeln('}')

247+ main_src.writeln('')

248+ }

249+ main_src.writeln('fn main() {')

250+ main_src.writeln('\tmut total := 0')

251+ for i in 0 .. 1300 {

252+ main_src.writeln('\ttotal += helper_${i}()')

253+ }

254+ main_src.writeln('\tpayload := payloadmod.payload() or {')

255+ main_src.writeln("\t\tprintln('ERR:' + err.msg() + ':' + int_str(err.code()))")

256+ main_src.writeln('\t\treturn')

257+ main_src.writeln('\t}')

258+ main_src.writeln("\tprintln('OK:' + payload.msg() + ':' + int_str(payload.code()))")

259+ main_src.writeln('\tprintln(int_str(total))')

260+ main_src.writeln('}')

261+ os.write_file(os.join_path(project_dir, 'main.v'), main_src.str()) or { panic(err) }

262+

263+ os.write_file(os.join_path(project_dir, 'payloadmod', 'payloadmod.v'), 'module payloadmod

264+

265+pub struct WrappedBuiltinError {

266+ Error

267+}

268+

269+pub fn payload() !IError {

270+ return WrappedBuiltinError{}

271+}

272+') or {

273+ panic(err)

274+ }

275+ return os.join_path(project_dir, 'main.v')

276+}

277+

278+fn test_parallel_cgen_worker_preserves_ierror_payload_success_with_builtin_error_embed() {

279+ v3_bin := build_parallel_v3()

280+ main_path :=

281+ write_parallel_ierror_payload_success_project('parallel_ierror_payload_success_${os.getpid()}')

282+ bin_out := os.join_path(os.temp_dir(), 'v3_parallel_ierror_payload_success_out_${os.getpid()}')

283+ compile := os.execute('VJOBS=2 ${v3_bin} ${main_path} -b c -o ${bin_out}')

284+ assert compile.exit_code == 0, compile.output

285+ assert compile.output.contains('cgen (parallel)'), compile.output

286+ run := os.execute(bin_out)

287+ assert run.exit_code == 0, run.output

288+ assert run.output.trim_space() == 'OK::0\n844350'

289+ c_code := os.read_file(bin_out + '.c') or { panic(err) }

290+ assert c_code.contains('return (Optional_IError){.ok = true, .value = (IError){._typ ='), c_code

291+ assert c_code.contains('= {"", 0, 1};'), c_code

292+ assert c_code.contains('.message = _str_'), c_code

293+}

294+

233295fn write_parallel_top_level_no_main_project(name string) string {

234296 project_dir := os.join_path(os.temp_dir(), 'v3_${name}')

235297 os.rmdir_all(project_dir) or {}

vlib/v3/tests/selector_expr_receiver_markused_codegen_test.vnew file+119-0

@@ -0,0 +1,119 @@

1+import os

2+

3+const vexe = @VEXE

4+const tests_dir = os.dir(@FILE)

5+const v3_dir = os.dir(tests_dir)

6+const vlib_dir = os.dir(v3_dir)

7+const v3_src = os.join_path(v3_dir, 'v3.v')

8+

9+fn build_v3() string {

10+ v3_bin := os.join_path(os.temp_dir(), 'v3_selector_expr_receiver_markused_${os.getpid()}')

11+ build :=

12+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')

13+ assert build.exit_code == 0, build.output

14+ return v3_bin

15+}

16+

17+fn test_selector_receiver_or_expr_roots_string_methods() {

18+ v3_bin := build_v3()

19+

20+ src := os.join_path(os.temp_dir(), 'v3_selector_expr_receiver_markused_input_${os.getpid()}.v')

21+ os.write_file(src, "fn maybe_input() ?string {

22+ return none

23+}

24+

25+fn main() {

26+ input := maybe_input() or {

27+ ' exit '

28+ }.trim_space()

29+ if input.to_upper() == 'EXIT' {

30+ println('ok')

31+ }

32+}

33+") or {

34+ panic(err)

35+ }

36+

37+ bin := os.join_path(os.temp_dir(), 'v3_selector_expr_receiver_markused_input_${os.getpid()}')

38+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

39+ assert compile.exit_code == 0, compile.output

40+ assert !compile.output.contains('C compilation failed'), compile.output

41+

42+ generated := os.read_file(bin + '.c') or { panic(err) }

43+ assert generated.contains('string__trim_space'), generated

44+ assert generated.contains('string__to_upper'), generated

45+

46+ run := os.execute(bin)

47+ assert run.exit_code == 0, run.output

48+ assert run.output.trim_space() == 'ok'

49+}

50+

51+fn test_top_level_selector_receiver_or_expr_roots_string_methods() {

52+ v3_bin := build_v3()

53+

54+ src := os.join_path(os.temp_dir(),

55+ 'v3_top_level_selector_expr_receiver_markused_${os.getpid()}.v')

56+ os.write_file(src, "fn maybe_input() ?string {

57+ return none

58+}

59+

60+for i := 0; i < 1; i++ {

61+ input := maybe_input() or {

62+ ' exit '

63+ }.trim_space()

64+ if input.to_upper() == 'EXIT' {

65+ println('ok')

66+ }

67+}

68+") or {

69+ panic(err)

70+ }

71+

72+ bin := os.join_path(os.temp_dir(),

73+ 'v3_top_level_selector_expr_receiver_markused_${os.getpid()}')

74+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

75+ assert compile.exit_code == 0, compile.output

76+ assert !compile.output.contains('C compilation failed'), compile.output

77+

78+ generated := os.read_file(bin + '.c') or { panic(err) }

79+ assert generated.contains('string__trim_space'), generated

80+ assert generated.contains('string__to_upper'), generated

81+

82+ run := os.execute(bin)

83+ assert run.exit_code == 0, run.output

84+ assert run.output.trim_space() == 'ok'

85+}

86+

87+fn test_result_selector_receiver_or_expr_roots_string_methods() {

88+ v3_bin := build_v3()

89+

90+ src := os.join_path(os.temp_dir(), 'v3_result_selector_expr_receiver_markused_${os.getpid()}.v')

91+ os.write_file(src, "fn get_value() !string {

92+ return error('x')

93+}

94+

95+fn main() {

96+ input := get_value() or {

97+ ' exit '

98+ }.trim_space().to_upper()

99+ if input == 'EXIT' {

100+ println('ok')

101+ }

102+}

103+") or {

104+ panic(err)

105+ }

106+

107+ bin := os.join_path(os.temp_dir(), 'v3_result_selector_expr_receiver_markused_${os.getpid()}')

108+ compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')

109+ assert compile.exit_code == 0, compile.output

110+ assert !compile.output.contains('C compilation failed'), compile.output

111+

112+ generated := os.read_file(bin + '.c') or { panic(err) }

113+ assert generated.contains('string__trim_space'), generated

114+ assert generated.contains('string__to_upper'), generated

115+

116+ run := os.execute(bin)

117+ assert run.exit_code == 0, run.output

118+ assert run.output.trim_space() == 'ok'

119+}

vlib/v3/tests/string_interpolation_stringify_codegen_test.vnew file+174-0

@@ -0,0 +1,174 @@

1+import os

2+

3+const vexe = @VEXE

4+const tests_dir = os.dir(@FILE)

5+const v3_dir = os.dir(tests_dir)

6+const vlib_dir = os.dir(v3_dir)

7+const v3_src = os.join_path(v3_dir, 'v3.v')

8+

9+fn string_interp_build_v3() string {

10+ v3_bin := os.join_path(os.temp_dir(), 'v3_string_interpolation_stringify_test_${os.getpid()}')

11+ os.rm(v3_bin) or {}

12+ build :=

13+ os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')

14+ assert build.exit_code == 0, build.output

15+ return v3_bin

16+}

17+

18+fn compile_v3_input(v3_bin string, name string, source string, bin string) os.Result {

19+ src := os.join_path(os.temp_dir(), '${name}_${os.getpid()}.v')

20+ os.write_file(src, source) or { panic(err) }

21+ return os.execute('${v3_bin} ${src} -b c -o ${bin}')

22+}

23+

24+fn test_typed_string_interpolation_stringifies_maps_arrays_and_generic_indexes() {

25+ v3_bin := string_interp_build_v3()

26+ src := 'fn generic_first[T](xs []T) string {

27+ return \'\${xs[0]}\'

28+}

29+

30+fn generic_line[T](xs []T) string {

31+ return \'node \${xs[0]}\'

32+}

33+

34+fn main() {

35+ visited := {

36+ \'A\': true

37+ }

38+ visited_line := \'Visited: \${visited} done\'

39+ assert visited_line.contains(\'Visited: {\')

40+ assert visited_line.contains("\'A\': true")

41+ assert visited_line.contains(\'} done\')

42+

43+ graph := {

44+ \'A\': [\'B\', \'C\']

45+ }

46+ graph_line := \'Graph: \${graph}\'

47+ assert graph_line.contains(\'Graph: {\')

48+ assert graph_line.contains("\'A\': [\'B\', \'C\']")

49+

50+ int_names := {

51+ 7: \'seven\'

52+ }

53+ int_names_line := \'Names: \${int_names}\'

54+ assert int_names_line.contains(\'Names: {\')

55+ assert int_names_line.contains("7: \'seven\'")

56+

57+ nums := [1, 2]

58+ assert \'Array: \${nums}\' == \'Array: [1, 2]\'

59+

60+ mut xs := []int{}

61+ xs << 7

62+ assert generic_first(xs) == \'7\'

63+ assert generic_line(xs) == \'node 7\'

64+ println(\'ok\')

65+}

66+'

67+ bin := os.join_path(os.temp_dir(), 'v3_string_interpolation_stringify_positive_${os.getpid()}')

68+ compile := compile_v3_input(v3_bin, 'v3_string_interpolation_stringify_positive', src, bin)

69+ assert compile.exit_code == 0, compile.output

70+ assert !compile.output.contains('C compilation failed'), compile.output

71+ run := os.execute(bin)

72+ assert run.exit_code == 0, run.output

73+ assert run.output.trim_space() == 'ok'

74+}

75+

76+fn test_defer_string_interpolation_with_generic_type_arguments() {

77+ v3_bin := string_interp_build_v3()

78+ src := "struct Box[T] {

79+ value T

80+}

81+

82+fn (b Box[T]) str() string {

83+ return 'box:' + '\${b.value}'

84+}

85+

86+struct PlainBox[T] {

87+ value T

88+}

89+

90+fn show_box[T](b Box[T]) {

91+ defer {

92+ println('box \${b}')

93+ }

94+ println('body-box')

95+}

96+

97+fn show_plain_box[T](b PlainBox[T]) {

98+ list := [b]

99+ mut lookup := map[string]PlainBox[T]{}

100+ lookup['one'] = b

101+ defer {

102+ println('plain \${b} \${list} \${lookup}')

103+ }

104+ println('body-plain')

105+}

106+

107+fn show_value[D](value D) {

108+ defer {

109+ println('value \${value}')

110+ }

111+ println('body-d')

112+}

113+

114+fn main() {

115+ show_box(Box[int]{

116+ value: 7

117+ })

118+ show_plain_box(PlainBox[int]{

119+ value: 8

120+ })

121+ show_value(9)

122+}

123+"

124+ bin := os.join_path(os.temp_dir(), 'v3_defer_generic_string_interp_${os.getpid()}')

125+ compile := compile_v3_input(v3_bin, 'v3_defer_generic_string_interp', src, bin)

126+ assert compile.exit_code == 0, compile.output

127+ assert !compile.output.contains('C compilation failed'), compile.output

128+ run := os.execute(bin)

129+ assert run.exit_code == 0, run.output

130+ assert run.output.trim_space() == "body-box\nbox box:7\nbody-plain\nplain PlainBox[int]{} [PlainBox[int]{}] {'one': PlainBox[int]{}}\nbody-d\nvalue 9"

131+}

132+

133+fn test_string_plus_accepts_string_aliases() {

134+ v3_bin := string_interp_build_v3()

135+ src := "type MyString = string

136+

137+fn main() {

138+ s := MyString('y')

139+ assert 'x' + s == 'xy'

140+ assert s + 'z' == 'yz'

141+ println('ok')

142+}

143+"

144+ bin := os.join_path(os.temp_dir(), 'v3_string_alias_plus_positive_${os.getpid()}')

145+ compile := compile_v3_input(v3_bin, 'v3_string_alias_plus_positive', src, bin)

146+ assert compile.exit_code == 0, compile.output

147+ assert !compile.output.contains('C compilation failed'), compile.output

148+ run := os.execute(bin)

149+ assert run.exit_code == 0, run.output

150+ assert run.output.trim_space() == 'ok'

151+}

152+

153+fn test_string_plus_does_not_stringify_non_strings() {

154+ v3_bin := string_interp_build_v3()

155+ int_bin := os.join_path(os.temp_dir(), 'v3_string_plus_int_negative_${os.getpid()}')

156+ int_result := compile_v3_input(v3_bin, 'v3_string_plus_int_negative', "fn main() {

157+ _ := 'x' + 1

158+}

159+",

160+ int_bin)

161+ assert int_result.exit_code != 0, int_result.output

162+ assert int_result.output.contains('operator `+` cannot concatenate `string` and `int`'), int_result.output

163+ assert !int_result.output.contains('C compilation failed'), int_result.output

164+

165+ map_bin := os.join_path(os.temp_dir(), 'v3_string_plus_map_negative_${os.getpid()}')

166+ map_result := compile_v3_input(v3_bin, 'v3_string_plus_map_negative', "fn main() {

167+ _ := 'x' + map[string]int{}

168+}

169+",

170+ map_bin)

171+ assert map_result.exit_code != 0, map_result.output

172+ assert map_result.output.contains('operator `+` cannot concatenate `string` and `map[string]int`'), map_result.output

173+ assert !map_result.output.contains('C compilation failed'), map_result.output

174+}

vlib/v3/tests/type_checker_errors_test.v+26-0

@@ -158,6 +158,18 @@ fn test_type_checker_reports_core_semantic_errors() {

158158 run_bad(v3_bin, 'bad_interface_field',

159159 'interface Named {\n\tname string\n}\nstruct Person {}\nfn takes_named(n Named) {}\nfn main() {\n\ttakes_named(Person{})\n}\n',

160160 'cannot use `Person` as argument 1 to `takes_named`; expected `Named`')

161+ run_bad(v3_bin, 'bad_interface_match_unrelated_sum_variant',

162+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nstruct Other {\n\tx int\n}\ntype Unrelated = Other\nfn describe(s Shape) int {\n\treturn match s {\n\t\tOther { 1 }\n\t\telse { s.area() }\n\t}\n}\nfn main() {}\n',

163+ '`Other` is not compatible with interface `Shape`')

164+ run_bad(v3_bin, 'bad_interface_is_unrelated_sum_variant',

165+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nstruct Other {\n\tx int\n}\ntype Unrelated = Other\nfn check(s Shape) bool {\n\treturn s is Other\n}\nfn main() {}\n',

166+ '`Other` is not compatible with interface `Shape`')

167+ run_bad(v3_bin, 'bad_interface_match_unresolved_pattern',

168+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nfn describe(s Shape) int {\n\treturn match s {\n\t\tMissingType { 1 }\n\t\telse { 0 }\n\t}\n}\nfn main() {}\n',

169+ 'unknown type `MissingType`')

170+ run_bad(v3_bin, 'bad_interface_is_unresolved_pattern',

171+ 'interface Shape {\n\tarea() int\n}\nstruct Rect {\n\tw int\n}\nfn (r Rect) area() int {\n\treturn r.w\n}\nfn check(s Shape) bool {\n\treturn s is MissingType\n}\nfn main() {}\n',

172+ 'unknown type `MissingType`')

161173 run_bad(v3_bin, 'bad_sum_missing_shared_field',

162174 'struct A {\n\tid int\n}\nstruct B {\n\tname string\n}\ntype Node = A | B\nfn main() {\n\tn := Node(A{\n\t\tid: 1\n\t})\n\t_ := n.id\n}\n',

163175 'unknown field `id` on `Node`')

@@ -170,6 +182,20 @@ fn test_type_checker_reports_core_semantic_errors() {

170182 run_bad(v3_bin, 'bad_sum_match_variant',

171183 'struct Cat {\n\tage int\n}\nstruct Dog {\n\ttricks int\n}\nstruct Bird {\n\twings int\n}\ntype Animal = Cat | Dog\nfn main() {\n\ta := Animal(Cat{\n\t\tage: 2\n\t})\n\tmatch a {\n\t\tBird {}\n\t\telse {}\n\t}\n}\n',

172184 '`Bird` is not a variant of sum type `Animal`')

185+ run_bad(v3_bin, 'bad_sum_constructor_extra_arg',

186+ 'struct Empty {}\nstruct Node[T] {\n\tvalue T\n}\ntype Tree[T] = Empty | Node[T]\nfn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\nfn main() {\n\t_ := Tree[int](Empty{}, side_effect())\n}\n',

187+ 'argument count mismatch for `Tree[int]`: expected 1, got 2')

188+ run_bad(v3_bin, 'bad_sum_constructor_extra_arg_as_call_arg',

189+ 'struct Empty {}\nstruct Node[T] {\n\tvalue T\n}\ntype Tree[T] = Empty | Node[T]\nfn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\nfn use(t Tree[int]) {}\nfn main() {\n\tuse(Tree[int](Empty{}, side_effect()))\n}\n',

190+ 'argument count mismatch for `Tree[int]`: expected 1, got 2')

191+ run_bad_project(v3_bin, 'bad_imported_sum_constructor_extra_arg', {

192+ 'trees/trees.v': 'module trees\n\npub struct Empty {}\n\npub struct Node[T] {\n\tpub:\n\tvalue T\n}\n\npub type Tree[T] = Empty | Node[T]\n\npub fn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\n'

193+ 'main.v': 'import trees { Empty, Tree, side_effect }\n\nfn main() {\n\t_ := Tree[int](Empty{}, side_effect())\n}\n'

194+ }, 'main.v', 'argument count mismatch for `trees.Tree[int]`: expected 1, got 2')

195+ run_bad_project(v3_bin, 'bad_aliased_sum_constructor_extra_arg', {

196+ 'trees/trees.v': 'module trees\n\npub struct Empty {}\n\npub struct Node[T] {\n\tpub:\n\tvalue T\n}\n\npub type Tree[T] = Empty | Node[T]\n\npub fn side_effect() Node[int] {\n\treturn Node[int]{\n\t\tvalue: 1\n\t}\n}\n'

197+ 'main.v': 'import trees as tr\n\nfn main() {\n\t_ := tr.Tree[int](tr.Empty{}, tr.side_effect())\n}\n'

198+ }, 'main.v', 'argument count mismatch for `trees.Tree[int]`: expected 1, got 2')

173199 run_bad(v3_bin, 'bad_unknown_decl_type', 'fn f(x Missing) {}\nfn main() {}\n',

174200 'unknown type `Missing`')

175201 run_bad(v3_bin, 'bad_unknown_generic_application_base',

vlib/v3/tests/veb_implicit_ctx_test.v+1-1

@@ -45,7 +45,7 @@ pub fn (app &App) show(id int) veb.Result {

4545

4646fn main() {

4747 mut app := &App{}

48- _ := app

48+ _ := app.index()

4949}

5050'

5151 src_file := os.join_path(os.temp_dir(), 'v3_veb_ctx.v')

vlib/v3/transform/array.v+22-0

@@ -422,12 +422,34 @@ fn (t &Transformer) array_append_elem_types_match(rhs_elem_type string, lhs_elem

422422 if rhs_clean == lhs_clean {

423423 return true

424424 }

425+ if array_append_type_is_container_shape(rhs_clean)

426+ || array_append_type_is_container_shape(lhs_clean) {

427+ return false

428+ }

425429 if isnil(t.tc) {

426430 return false

427431 }

428432 return t.array_append_elem_c_type(rhs_clean) == t.array_append_elem_c_type(lhs_clean)

429433}

430434

435+fn array_append_type_is_container_shape(typ string) bool {

436+ clean := typ.trim_space()

437+ if clean.len == 0 {

438+ return false

439+ }

440+ if clean.starts_with('&') {

441+ return array_append_type_is_container_shape(clean[1..])

442+ }

443+ if clean.starts_with('shared ') {

444+ return array_append_type_is_container_shape(clean[7..])

445+ }

446+ if clean.starts_with('atomic ') {

447+ return array_append_type_is_container_shape(clean[7..])

448+ }

449+ return clean.starts_with('[]') || clean.starts_with('map[')

450+ || (clean.starts_with('[') && clean.contains(']'))

451+}

452+

431453// array_append_ident_type supports array append ident type handling for Transformer.

432454fn (t &Transformer) array_append_ident_type(id flat.NodeId) ?string {

433455 if int(id) < 0 {

vlib/v3/transform/fn.v+143-0

@@ -226,6 +226,9 @@ fn (t &Transformer) resolve_receiver_method_for_type(receiver_type string, metho

226226 }

227227 }

228228 } else {

229+ if method_name := t.resolve_specialized_generic_receiver_method(clean_type, method) {

230+ return method_name

231+ }

229232 direct := '${clean_type}.${method}'

230233 if t.is_known_fn_name(direct) {

231234 return direct

@@ -271,6 +274,33 @@ fn (t &Transformer) resolve_receiver_method_for_type(receiver_type string, metho

271274 return none

272275}

273276

277+fn (t &Transformer) resolve_specialized_generic_receiver_method(receiver_type string, method string) ?string {

278+ base, args, ok := generic_app_parts(receiver_type)

279+ if !ok || args.len == 0 {

280+ return none

281+ }

282+ short_args := generic_type_args_short(args)

283+ suffix := generic_type_suffixes(args)

284+ mut candidates := []string{}

285+ candidates << '${base}[${short_args}].${method}'

286+ candidates << '${base}_${suffix}.${method}'

287+ candidates << c_name('${base}_${suffix}.${method}')

288+ if base.contains('.') {

289+ module_name := base.all_before_last('.')

290+ short_base := base.all_after_last('.')

291+ candidates << '${short_base}[${short_args}].${method}'

292+ candidates << '${short_base}_${suffix}.${method}'

293+ candidates << '${module_name}.${short_base}_${suffix}.${method}'

294+ candidates << c_name('${module_name}.${short_base}_${suffix}.${method}')

295+ }

296+ for candidate in candidates {

297+ if t.is_known_fn_name(candidate) {

298+ return candidate

299+ }

300+ }

301+ return none

302+}

303+

274304// resolve_alias_receiver_method converts resolve alias receiver method data for transform.

275305fn (t &Transformer) resolve_alias_receiver_method(base_type string, method string) ?string {

276306 if isnil(t.tc) || base_type.len == 0 || method.len == 0 {

@@ -1498,6 +1528,9 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat

14981528 }

14991529 return t.enum_autostr_call(expr, qenum)

15001530 }

1531+ if generic_str := t.generic_receiver_str_call(expr, clean_typ) {

1532+ return generic_str

1533+ }

15011534 if clean_typ in t.structs || clean_typ in t.sum_types {

15021535 mut qualified := clean_typ

15031536 if !clean_typ.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'

@@ -1520,6 +1553,8 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat

15201553 return t.wrap_string_conversion(arr, '[]${elem_type}')

15211554 } else if clean_typ.len > 0 && clean_typ.starts_with('[]') {

15221555 return t.lower_array_str(expr, clean_typ)

1556+ } else if clean_typ.len > 0 && clean_typ.starts_with('map[') {

1557+ return t.lower_map_str(expr, clean_typ)

15231558 } else if clean_typ == 'rune' {

15241559 return t.make_call_typed('strconv__format_int', arr2(expr, t.make_int_literal(10)),

15251560 'string')

@@ -1530,6 +1565,24 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat

15301565 }

15311566}

15321567

1568+fn (mut t Transformer) generic_receiver_str_call(expr flat.NodeId, typ string) ?flat.NodeId {

1569+ if isnil(t.tc) || !typ.contains('[') {

1570+ return none

1571+ }

1572+ clean_typ := if typ.starts_with('&') { typ[1..] } else { typ }

1573+ if !clean_typ.ends_with(']') {

1574+ return none

1575+ }

1576+ info := t.tc.resolve_generic_struct_method(clean_typ, 'str') or {

1577+ t.tc.resolve_generic_sum_method(clean_typ, 'str') or { return none }

1578+ }

1579+ if info.return_type.name() != 'string' {

1580+ return none

1581+ }

1582+ selector := t.make_selector(expr, 'str', '')

1583+ return t.make_call_expr_typed(selector, []flat.NodeId{}, 'string')

1584+}

1585+

15331586// append_string builds `result = result + piece` using the runtime string concat helper.

15341587// Using string__plus directly (instead of `+=`) keeps the synthesized node independent of

15351588// type resolution for the freshly-introduced temp.

@@ -1582,6 +1635,73 @@ fn (mut t Transformer) lower_array_str(arr_expr flat.NodeId, base_type string) f

15821635 return t.make_ident(result_name)

15831636}

15841637

1638+// lower_map_str expands `${m}` for `map[K]V` into a runtime loop over keys that formats

1639+// each key/value with the same typed conversions used by array stringification.

1640+fn (mut t Transformer) lower_map_str(map_expr flat.NodeId, base_type string) flat.NodeId {

1641+ src := t.a.nodes[int(map_expr)]

1642+ clean_type := t.clean_map_type(base_type)

1643+ key_type, value_type := t.map_type_parts(clean_type)

1644+ if key_type.len == 0 || value_type.len == 0 {

1645+ return map_expr

1646+ }

1647+ base := t.stable_expr_for_reuse(map_expr)

1648+ mut prefix := []flat.NodeId{}

1649+ t.drain_pending(mut prefix)

1650+ result_name := t.new_temp('map_str')

1651+ keys_name := t.new_temp('map_str_keys')

1652+ idx_name := t.new_temp('map_str_idx')

1653+ key_name := t.new_temp('map_str_key')

1654+ zero_name := t.new_temp('map_str_zero')

1655+ prefix << t.make_decl_assign_typed(result_name, t.make_string_literal('{'), 'string')

1656+ keys := t.make_call_typed('map__keys', arr1(t.runtime_addr(base, clean_type)), '[]${key_type}')

1657+ prefix << t.make_decl_assign_typed(keys_name, keys, '[]${key_type}')

1658+ prefix << t.make_decl_assign_typed(zero_name, t.zero_value_for_type(value_type), value_type)

1659+ init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')

1660+ cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(t.make_ident(keys_name),

1661+ 'len', 'int'))

1662+ post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))

1663+ key_expr := t.array_get_value(t.make_ident(keys_name), t.make_ident(idx_name), key_type)

1664+ key_decl := t.make_decl_assign_typed(key_name, key_expr, key_type)

1665+ value_name := t.new_temp('map_str_val')

1666+ value_expr := t.make_map_get_expr(base, clean_type, key_name, zero_name, value_type)

1667+ value_decl := t.make_decl_assign_typed(value_name, value_expr, value_type)

1668+ mut loop_body := []flat.NodeId{}

1669+ loop_body << key_decl

1670+ loop_body << value_decl

1671+ sep_cond := t.make_infix(.gt, t.make_ident(idx_name), t.make_int_literal(0))

1672+ sep_stmt := t.append_string(result_name, t.make_string_literal(', '))

1673+ loop_body << t.make_if(sep_cond, t.make_block(arr1(sep_stmt)), t.make_empty())

1674+ t.set_var_type(key_name, key_type)

1675+ key_str := t.wrap_string_conversion(t.make_ident(key_name), key_type)

1676+ t.unset_var_type(key_name)

1677+ t.drain_pending(mut loop_body)

1678+ if key_type == 'string' {

1679+ loop_body << t.append_string(result_name, t.make_string_literal("'"))

1680+ loop_body << t.append_string(result_name, key_str)

1681+ loop_body << t.append_string(result_name, t.make_string_literal("'"))

1682+ } else {

1683+ loop_body << t.append_string(result_name, key_str)

1684+ }

1685+ loop_body << t.append_string(result_name, t.make_string_literal(': '))

1686+ t.set_var_type(value_name, value_type)

1687+ value_str := t.wrap_string_conversion(t.make_ident(value_name), value_type)

1688+ t.unset_var_type(value_name)

1689+ t.drain_pending(mut loop_body)

1690+ if value_type == 'string' {

1691+ loop_body << t.append_string(result_name, t.make_string_literal("'"))

1692+ loop_body << t.append_string(result_name, value_str)

1693+ loop_body << t.append_string(result_name, t.make_string_literal("'"))

1694+ } else {

1695+ loop_body << t.append_string(result_name, value_str)

1696+ }

1697+ prefix << t.make_for_stmt(init, cond, post, loop_body, src)

1698+ prefix << t.append_string(result_name, t.make_string_literal('}'))

1699+ for stmt in prefix {

1700+ t.pending_stmts << stmt

1701+ }

1702+ return t.make_ident(result_name)

1703+}

1704+

15851705// wrap_optional_string_conversion transforms wrap optional string conversion data for transform.

15861706fn (mut t Transformer) wrap_optional_string_conversion(expr flat.NodeId, typ string) flat.NodeId {

15871707 opt_type := t.qualify_optional_type(typ)

@@ -2632,6 +2752,9 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.

26322752 args := t.transform_receiver_method_args(node, base_id, method_name)

26332753 return t.make_call_typed(method_name, args, 'string')

26342754 }

2755+ if method == 'str' && stringify_type_has_generic_placeholder(base_type) {

2756+ return none

2757+ }

26352758 if base_type.starts_with('[]') && method == 'str' {

26362759 return t.wrap_string_conversion(t.transform_expr(base_id), base_type)

26372760 }

@@ -2673,12 +2796,18 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.

26732796 }

26742797 method_name := t.resolve_receiver_method_name(base_id, method)

26752798 if method_name.len > 0 {

2799+ if t.receiver_method_name_is_open_generic(method_name) {

2800+ return none

2801+ }

26762802 args := t.transform_receiver_method_args(node, base_id, method_name)

26772803 ret_type := t.receiver_method_return_type(method_name, node.typ)

26782804 return t.make_call_typed(method_name, args, ret_type)

26792805 }

26802806 if !isnil(t.tc) {

26812807 if resolved_method := t.tc.resolved_call_name(id) {

2808+ if t.receiver_method_name_is_open_generic(resolved_method) {

2809+ return none

2810+ }

26822811 if t.is_known_fn_name(resolved_method) {

26832812 params := t.call_param_types(resolved_method)

26842813 if !t.resolved_call_uses_receiver_type(base_id, base_type, params) {

@@ -2700,6 +2829,20 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.

27002829 return none

27012830}

27022831

2832+fn (t &Transformer) receiver_method_name_is_open_generic(method_name string) bool {

2833+ if method_name.contains('.') {

2834+ receiver := method_name.all_before_last('.')

2835+ _, args, ok := generic_app_parts(receiver)

2836+ return ok && args.len > 0 && t.generic_args_have_placeholders(args)

2837+ }

2838+ for marker in ['_T__', '_U__', '_K__', '_V__', '_A__', '_B__', '_C__', '_X__', '_Y__', '_Z__'] {

2839+ if method_name.contains(marker) {

2840+ return true

2841+ }

2842+ }

2843+ return false

2844+}

2845+

27032846// is_builder_receiver reports whether is builder receiver applies in transform.

27042847fn (t &Transformer) is_builder_receiver(base_id flat.NodeId, base_type string) bool {

27052848 if is_builder_type_name(base_type) {

vlib/v3/transform/if.v+3-0

@@ -1288,6 +1288,9 @@ fn (t &Transformer) extract_is_expr(cond_id flat.NodeId) IsExprInfo {

12881288// sum_type_for_is_expr supports sum type for is expr handling for Transformer.

12891289fn (t &Transformer) sum_type_for_is_expr(expr_type string, variant string) string {

12901290 clean_expr_type := t.trim_pointer_type(expr_type)

1291+ if _ := t.resolve_sum_variant_pattern_for_subject(clean_expr_type, variant) {

1292+ return clean_expr_type

1293+ }

12911294 resolved_expr_sum := t.resolve_sum_name(clean_expr_type)

12921295 if resolved_expr_sum in t.sum_types {

12931296 if _ := t.sum_variant_name(resolved_expr_sum, variant) {

vlib/v3/transform/interface.v+16-2

@@ -8,6 +8,11 @@ fn (t &Transformer) is_interface_type(name string) bool {

88 return t.resolve_interface_type_name(name).len > 0

99}

1010

11+fn (t &Transformer) is_builtin_ierror_interface_name(name string) bool {

12+ clean := t.trim_pointer_type(t.normalize_type_alias(name))

13+ return clean == 'IError' || clean == 'builtin.IError'

14+}

15+

1116// resolve_interface_type_name resolves resolve interface type name information for transform.

1217fn (t &Transformer) resolve_interface_type_name(name string) string {

1318 if name.len == 0 || isnil(t.tc) {

@@ -17,6 +22,15 @@ fn (t &Transformer) resolve_interface_type_name(name string) string {

1722 if clean in t.tc.interface_names {

1823 return clean

1924 }

25+ if !clean.contains('.') && t.cur_file.len > 0 {

26+ for candidate in t.tc.file_selective_imports[file_import_key(t.cur_file, clean)] or {

27+ []string{}

28+ } {

29+ if candidate in t.tc.interface_names {

30+ return candidate

31+ }

32+ }

33+ }

2034 if !clean.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'

2135 && t.cur_module != 'builtin' {

2236 qname := '${t.cur_module}.${clean}'

@@ -39,7 +53,7 @@ fn (mut t Transformer) transform_interface_value_for_type(id flat.NodeId, target

3953 }

4054 // IError has bespoke handling (built via `error()`, fields accessed directly);

4155 // do not route it through the generic interface boxing.

42- if iface_name.all_after_last('.') == 'IError' {

56+ if t.is_builtin_ierror_interface_name(iface_name) {

4357 return none

4458 }

4559 source_type := t.node_type(id)

@@ -80,7 +94,7 @@ fn (mut t Transformer) transform_global_amp_interface_cast(node flat.Node, targe

8094 return none

8195 }

8296 iface_name := t.resolve_interface_type_name(child.value)

83- if iface_name.len == 0 || iface_name.all_after_last('.') == 'IError' {

97+ if iface_name.len == 0 || t.is_builtin_ierror_interface_name(iface_name) {

8498 return none

8599 }

86100 old_pending := t.pending_stmts.clone()

vlib/v3/transform/monomorphize.v+326-33

@@ -11,6 +11,14 @@ struct GenericStructDecl {

1111 key string

1212}

1313

14+struct GenericSumDecl {

15+ id flat.NodeId

16+ node flat.Node

17+ file string

18+ module string

19+ key string

20+}

21+

1422fn (mut t Transformer) is_generic_fn(name string) bool {

1523 decls := t.cached_generic_fn_decls()

1624 return name in decls || transform_qualified_fn_name(t.cur_module, name) in decls

@@ -174,13 +182,65 @@ fn (mut t Transformer) materialize_generic_structs() {

174182 return

175183 }

176184 decls := t.collect_generic_struct_decls()

177- if decls.len == 0 {

185+ sum_decls := t.collect_generic_sum_decls()

186+ if decls.len == 0 && sum_decls.len == 0 {

187+ return

188+ }

189+ specs, sum_specs := t.collect_generic_materialization_specs(decls, sum_decls)

190+ for spec, base in sum_specs {

191+ decl := sum_decls[base] or { continue }

192+ t.materialize_generic_sum_spec(spec, decl)

193+ }

194+ for spec, base in specs {

195+ decl := decls[base] or { continue }

196+ t.materialize_generic_struct_spec(spec, decl)

197+ }

198+ t.erase_generic_sum_templates(sum_decls)

199+ for _, decl in decls {

200+ t.tc.structs.delete(decl.key)

201+ t.tc.unions.delete(decl.key)

202+ t.tc.params_structs.delete(decl.key)

203+ }

204+}

205+

206+fn (mut t Transformer) materialize_generic_sum_types(erase_templates bool) {

207+ if isnil(t.tc) {

178208 return

179209 }

210+ decls := t.collect_generic_struct_decls()

211+ sum_decls := t.collect_generic_sum_decls()

212+ if sum_decls.len == 0 {

213+ return

214+ }

215+ _, sum_specs := t.collect_generic_materialization_specs(decls, sum_decls)

216+ for spec, base in sum_specs {

217+ decl := sum_decls[base] or { continue }

218+ t.materialize_generic_sum_spec(spec, decl)

219+ }

220+ if erase_templates {

221+ t.erase_generic_sum_templates(sum_decls)

222+ }

223+}

224+

225+fn (mut t Transformer) erase_generic_sum_templates(sum_decls map[string]GenericSumDecl) {

226+ for _, decl in sum_decls {

227+ t.tc.sum_types.delete(decl.key)

228+ t.sum_types.delete(decl.key)

229+ t.tc.sum_generic_params.delete(decl.key)

230+ if decl.key != decl.node.value {

231+ t.tc.sum_generic_params.delete(decl.node.value)

232+ }

233+ }

234+}

235+

236+fn (mut t Transformer) collect_generic_materialization_specs(decls map[string]GenericStructDecl, sum_decls map[string]GenericSumDecl) (map[string]string, map[string]string) {

180237 mut specs := map[string]string{}

238+ mut sum_specs := map[string]string{}

181239 t.collect_generic_struct_specs(decls, mut specs)

182- for _ in 0 .. 20 {

240+ t.collect_generic_sum_specs(sum_decls, mut sum_specs)

241+ for _ in 0 .. 40 {

183242 before := specs.len

243+ before_sums := sum_specs.len

184244 current := specs.clone()

185245 for spec, base in current {

186246 decl := decls[base] or { continue }

@@ -197,21 +257,32 @@ fn (mut t Transformer) materialize_generic_structs() {

197257 decl.node.generic_params)

198258 t.collect_generic_struct_spec_from_type(field_type, decl.module, decl.file, decls, mut

199259 specs)

260+ t.collect_generic_sum_spec_from_type(field_type, decl.module, decl.file, sum_decls, mut

261+ sum_specs)

200262 }

201263 }

202- if specs.len == before {

264+ current_sums := sum_specs.clone()

265+ for spec, base in current_sums {

266+ decl := sum_decls[base] or { continue }

267+ _, args, ok := generic_app_parts(spec)

268+ if !ok {

269+ continue

270+ }

271+ for i in 0 .. decl.node.children_count {

272+ variant := t.a.child_node(&decl.node, i)

273+ variant_type := substitute_generic_type_text_with_params(variant.value, args,

274+ decl.node.generic_params)

275+ t.collect_generic_struct_spec_from_type(variant_type, decl.module, decl.file,

276+ decls, mut specs)

277+ t.collect_generic_sum_spec_from_type(variant_type, decl.module, decl.file,

278+ sum_decls, mut sum_specs)

279+ }

280+ }

281+ if specs.len == before && sum_specs.len == before_sums {

203282 break

204283 }

205284 }

206- for spec, base in specs {

207- decl := decls[base] or { continue }

208- t.materialize_generic_struct_spec(spec, decl)

209- }

210- for _, decl in decls {

211- t.tc.structs.delete(decl.key)

212- t.tc.unions.delete(decl.key)

213- t.tc.params_structs.delete(decl.key)

214- }

285+ return specs, sum_specs

215286}

216287

217288fn (mut t Transformer) collect_generic_struct_decls() map[string]GenericStructDecl {

@@ -255,6 +326,43 @@ fn generic_struct_decl_key(name string, module_name string) string {

255326 return '${module_name}.${name}'

256327}

257328

329+fn (mut t Transformer) collect_generic_sum_decls() map[string]GenericSumDecl {

330+ mut decls := map[string]GenericSumDecl{}

331+ t.ensure_node_module_map()

332+ mut cur_file := ''

333+ mut cur_module := ''

334+ for i, node in t.a.nodes {

335+ match node.kind {

336+ .file {

337+ cur_file = node.value

338+ }

339+ .module_decl {

340+ cur_module = node.value

341+ }

342+ .type_decl {

343+ if node.generic_params.len == 0 || node.children_count == 0 {

344+ continue

345+ }

346+ module_name := t.node_module_map_cache[i] or { cur_module }

347+ key := generic_type_decl_key(node.value, module_name)

348+ decls[key] = GenericSumDecl{

349+ id: flat.NodeId(i)

350+ node: node

351+ file: cur_file

352+ module: module_name

353+ key: key

354+ }

355+ }

356+ else {}

357+ }

358+ }

359+ return decls

360+}

361+

362+fn generic_type_decl_key(name string, module_name string) string {

363+ return generic_struct_decl_key(name, module_name)

364+}

365+

258366fn (mut t Transformer) collect_generic_struct_specs(decls map[string]GenericStructDecl, mut specs map[string]string) {

259367 for _, target in t.tc.type_aliases {

260368 t.collect_generic_struct_spec_from_type(target, '', '', decls, mut specs)

@@ -350,6 +458,95 @@ fn (mut t Transformer) collect_generic_struct_spec_from_type(typ string, module_

350458 specs[spec_name] = spec_base

351459}

352460

461+fn (mut t Transformer) collect_generic_sum_specs(decls map[string]GenericSumDecl, mut specs map[string]string) {

462+ for _, target in t.tc.type_aliases {

463+ t.collect_generic_sum_spec_from_type(target, '', '', decls, mut specs)

464+ }

465+ mut file_name := ''

466+ mut module_name := ''

467+ for node in t.a.nodes {

468+ match node.kind {

469+ .file {

470+ file_name = node.value

471+ module_name = ''

472+ }

473+ .module_decl {

474+ module_name = node.value

475+ }

476+ else {}

477+ }

478+

479+ if node.typ.len > 0 {

480+ t.collect_generic_sum_spec_from_type(node.typ, module_name, file_name, decls, mut specs)

481+ }

482+ match node.kind {

483+ .struct_init, .array_init, .cast_expr, .as_expr, .sizeof_expr, .typeof_expr, .is_expr {

484+ t.collect_generic_sum_spec_from_type(node.value, module_name, file_name, decls, mut

485+ specs)

486+ }

487+ else {}

488+ }

489+ }

490+}

491+

492+fn (mut t Transformer) collect_generic_sum_spec_from_type(typ string, module_name string, file_name string, decls map[string]GenericSumDecl, mut specs map[string]string) {

493+ clean := typ.trim_space()

494+ if clean.len == 0 {

495+ return

496+ }

497+ if clean.starts_with('&') {

498+ t.collect_generic_sum_spec_from_type(clean[1..], module_name, file_name, decls, mut specs)

499+ return

500+ }

501+ if clean.starts_with('mut ') {

502+ t.collect_generic_sum_spec_from_type(clean[4..], module_name, file_name, decls, mut specs)

503+ return

504+ }

505+ if clean.starts_with('?') || clean.starts_with('!') {

506+ t.collect_generic_sum_spec_from_type(clean[1..], module_name, file_name, decls, mut specs)

507+ return

508+ }

509+ if clean.starts_with('...') {

510+ t.collect_generic_sum_spec_from_type(clean[3..], module_name, file_name, decls, mut specs)

511+ return

512+ }

513+ if clean.starts_with('[]') {

514+ t.collect_generic_sum_spec_from_type(clean[2..], module_name, file_name, decls, mut specs)

515+ return

516+ }

517+ if clean.starts_with('map[') {

518+ bracket_end := generic_matching_bracket(clean, 3)

519+ if bracket_end < clean.len {

520+ t.collect_generic_sum_spec_from_type(clean[4..bracket_end], module_name, file_name,

521+ decls, mut specs)

522+ t.collect_generic_sum_spec_from_type(clean[bracket_end + 1..], module_name, file_name,

523+ decls, mut specs)

524+ }

525+ return

526+ }

527+ if clean.starts_with('[') {

528+ bracket_end := generic_matching_bracket(clean, 0)

529+ if bracket_end < clean.len {

530+ t.collect_generic_sum_spec_from_type(clean[bracket_end + 1..], module_name, file_name,

531+ decls, mut specs)

532+ }

533+ return

534+ }

535+ base, args, ok := generic_app_parts(clean)

536+ if !ok {

537+ return

538+ }

539+ for arg in args {

540+ t.collect_generic_sum_spec_from_type(arg, module_name, file_name, decls, mut specs)

541+ }

542+ if t.generic_args_have_placeholders(args) {

543+ return

544+ }

545+ spec_base := t.generic_sum_spec_base_name(base, module_name, file_name, decls) or { return }

546+ spec_name := '${spec_base}[${args.join(', ')}]'

547+ specs[spec_name] = spec_base

548+}

549+

353550fn (t &Transformer) generic_struct_spec_base_name(base string, module_name string, file_name string, decls map[string]GenericStructDecl) ?string {

354551 if base in decls {

355552 return base

@@ -381,6 +578,37 @@ fn (t &Transformer) generic_struct_spec_base_name(base string, module_name strin

381578 return none

382579}

383580

581+fn (t &Transformer) generic_sum_spec_base_name(base string, module_name string, file_name string, decls map[string]GenericSumDecl) ?string {

582+ if base in decls {

583+ return base

584+ }

585+ if base.contains('.') {

586+ return none

587+ }

588+ if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' {

589+ qbase := '${module_name}.${base}'

590+ if qbase in decls {

591+ return qbase

592+ }

593+ }

594+ if resolved := t.selective_import_generic_sum_base(base, file_name, decls) {

595+ return resolved

596+ }

597+ mut found := ''

598+ for key, _ in decls {

599+ if key.all_after_last('.') == base {

600+ if found.len > 0 && found != key {

601+ return none

602+ }

603+ found = key

604+ }

605+ }

606+ if found.len > 0 {

607+ return found

608+ }

609+ return none

610+}

611+

384612fn (t &Transformer) selective_import_generic_struct_base(base string, file_name string, decls map[string]GenericStructDecl) ?string {

385613 if isnil(t.tc) || base.contains('.') || file_name.len == 0 {

386614 return none

@@ -394,6 +622,51 @@ fn (t &Transformer) selective_import_generic_struct_base(base string, file_name

394622 return none

395623}

396624

625+fn (t &Transformer) selective_import_generic_sum_base(base string, file_name string, decls map[string]GenericSumDecl) ?string {

626+ if isnil(t.tc) || base.contains('.') || file_name.len == 0 {

627+ return none

628+ }

629+ candidates := t.tc.file_selective_imports[file_import_key(file_name, base)] or { return none }

630+ for candidate in candidates {

631+ if candidate in decls {

632+ return candidate

633+ }

634+ }

635+ return none

636+}

637+

638+fn (mut t Transformer) materialize_generic_sum_spec(spec_name string, decl GenericSumDecl) {

639+ _, args, ok := generic_app_parts(spec_name)

640+ if !ok || args.len == 0 {

641+ return

642+ }

643+ old_module := t.cur_module

644+ old_file := t.cur_file

645+ old_tc_module := t.tc.cur_module

646+ old_tc_file := t.tc.cur_file

647+ t.cur_module = decl.module

648+ t.cur_file = decl.file

649+ t.tc.cur_module = decl.module

650+ t.tc.cur_file = decl.file

651+ mut variants := []string{}

652+ for i in 0 .. decl.node.children_count {

653+ variant := t.a.child_node(&decl.node, i)

654+ variant_type := substitute_generic_type_text_with_params(variant.value, args,

655+ decl.node.generic_params)

656+ parsed := t.tc.parse_type(variant_type)

657+ variants << if parsed is types.Unknown { variant_type } else { parsed.name() }

658+ }

659+ t.tc.sum_types[spec_name] = variants

660+ t.sum_types[spec_name] = variants

661+ if !isnil(t.sum_cache) {

662+ t.sum_cache.entries.clear()

663+ }

664+ t.cur_module = old_module

665+ t.cur_file = old_file

666+ t.tc.cur_module = old_tc_module

667+ t.tc.cur_file = old_tc_file

668+}

669+

397670fn (mut t Transformer) materialize_generic_struct_spec(spec_name string, decl GenericStructDecl) {

398671 _, args, ok := generic_app_parts(spec_name)

399672 if !ok || args.len == 0 {

@@ -542,6 +815,7 @@ fn (mut t Transformer) emit_generic_fn_specialization(decl GenericFnDecl, args [

542815 t.specialize_cloned_fn_signature(clone_id, decl, args)

543816 clone := t.a.nodes[int(clone_id)]

544817 t.register_specialized_fn_signature(decl, clone, args)

818+ t.materialize_generic_sum_types(false)

545819 t.active_generic_params = old_params

546820 t.transform_specialized_fn_body(clone_id, decl.module, decl.file)

547821 t.cur_module = old_module

@@ -1678,6 +1952,18 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is

16781952 for child in children {

16791953 t.a.children << child

16801954 }

1955+ if node.kind == .call && children.len > 0

1956+ && (node.typ.contains('[') || node.value.contains('[')) {

1957+ type_text := if node.typ.contains('[') { node.typ } else { node.value }

1958+ base, _, ok := generic_app_parts(type_text)

1959+ if ok {

1960+ callee_id := children[0]

1961+ mut callee := t.a.nodes[int(callee_id)]

1962+ if callee.kind in [.ident, .selector] && callee.value == base.all_after_last('.') {

1963+ t.a.nodes[int(callee_id)].value = t.subst_type(type_text, args)

1964+ }

1965+ }

1966+ }

16811967 cloned_value := if is_root {

16821968 specialized_generic_fn_value(node.value, args)

16831969 } else {

@@ -1692,6 +1978,7 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is

16921978 children_count: flat.child_count(children.len)

16931979 typ: t.subst_type(node.typ, args)

16941980 value: cloned_value

1981+ is_mut: node.is_mut

16951982 })

16961983}

16971984

@@ -1699,27 +1986,7 @@ fn (mut t Transformer) copy_cloned_resolution(src_id flat.NodeId, dst_id flat.No

16991986 if isnil(t.tc) {

17001987 return

17011988 }

1702- src_idx := int(src_id)

1703- dst_idx := int(dst_id)

1704- if src_idx < 0 || dst_idx < 0 {

1705- return

1706- }

1707- if src_idx < t.tc.resolved_call_set.len && t.tc.resolved_call_set[src_idx] {

1708- for t.tc.resolved_call_names.len <= dst_idx {

1709- t.tc.resolved_call_names << ''

1710- t.tc.resolved_call_set << false

1711- }

1712- t.tc.resolved_call_names[dst_idx] = t.tc.resolved_call_names[src_idx]

1713- t.tc.resolved_call_set[dst_idx] = true

1714- }

1715- if src_idx < t.tc.resolved_fn_value_set.len && t.tc.resolved_fn_value_set[src_idx] {

1716- for t.tc.resolved_fn_value_names.len <= dst_idx {

1717- t.tc.resolved_fn_value_names << ''

1718- t.tc.resolved_fn_value_set << false

1719- }

1720- t.tc.resolved_fn_value_names[dst_idx] = t.tc.resolved_fn_value_names[src_idx]

1721- t.tc.resolved_fn_value_set[dst_idx] = true

1722- }

1989+ t.tc.copy_cloned_resolution(src_id, dst_id)

17231990}

17241991

17251992fn substitute_generic_node_value(node flat.Node, args []string) string {

@@ -2290,6 +2557,32 @@ fn (t &Transformer) subst_type(typ string, args []string) string {

22902557// subst_node_value is the param-aware counterpart of substitute_generic_node_value.

22912558fn (t &Transformer) subst_node_value(node flat.Node, args []string) string {

22922559 match node.kind {

2560+ .ident {

2561+ if node.value.contains('[') {

2562+ return t.subst_type(node.value, args)

2563+ }

2564+ if node.typ.contains('[') {

2565+ base, _, ok := generic_app_parts(node.typ)

2566+ if ok && node.value == base.all_after_last('.') {

2567+ return t.subst_type(node.typ, args)

2568+ }

2569+ }

2570+ return node.value

2571+ }

2572+ .selector {

2573+ if node.value.contains('[') {

2574+ return t.subst_type(node.value, args)

2575+ }

2576+ if node.typ.contains('[') {

2577+ old_field_type := t.trim_pointer_type(node.typ)

2578+ new_field_type := t.trim_pointer_type(t.subst_type(node.typ, args))

2579+ if old_field_type != new_field_type

2580+ && t.sum_field_name(old_field_type) == node.value {

2581+ return t.sum_field_name(new_field_type)

2582+ }

2583+ }

2584+ return node.value

2585+ }

22932586 .call, .array_init, .struct_init, .cast_expr, .as_expr, .sizeof_expr, .typeof_expr,

22942587 .is_expr, .type_decl, .field_decl, .param {

22952588 return t.subst_type(node.value, args)

vlib/v3/transform/return.v+18-13

@@ -347,7 +347,8 @@ fn (mut t Transformer) build_return_match_chain(match_expr_id flat.NodeId, orig_

347347 branch := t.a.nodes[int(branches[idx])]

348348 is_else := branch.value == 'else'

349349 body_start_idx := if is_else { 0 } else { t.count_conds(branch) }

350- if !is_else && t.match_branch_all_type_patterns(branch) && t.count_conds(branch) > 1 {

350+ if !is_else && t.match_branch_all_type_patterns(match_expr_id, branch)

351+ && t.count_conds(branch) > 1 {

351352 return t.build_return_match_type_branch_chain(match_expr_id, orig_expr_id, branch,

352353 branches, idx, 0, ret_typ)

353354 }

@@ -357,16 +358,15 @@ fn (mut t Transformer) build_return_match_chain(match_expr_id flat.NodeId, orig_

357358 n_conds := t.count_conds(branch)

358359 if n_conds == 1 {

359360 cond_val_id := t.a.child(&branch, 0)

360- if variant_name := t.match_type_pattern(cond_val_id) {

361+ if sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) {

361362 subj := t.expr_key(match_expr_id)

362- sum_name := t.find_sum_type_for_variant(variant_name)

363- if subj.len > 0 && sum_name.len > 0 {

364- t.push_smartcast(subj, variant_name, sum_name)

363+ if subj.len > 0 {

364+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)

365365 sc_pushed++

366366 }

367367 orig_subj := t.expr_key(orig_expr_id)

368- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {

369- t.push_smartcast(orig_subj, variant_name, sum_name)

368+ if orig_subj.len > 0 && orig_subj != subj {

369+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)

370370 sc_pushed++

371371 }

372372 }

@@ -411,7 +411,7 @@ fn (mut t Transformer) build_return_match_type_branch_chain(match_expr_id flat.N

411411 }

412412 }

413413 cond_val_id := t.a.child(&branch, cond_idx)

414- variant_name := t.match_type_pattern(cond_val_id) or {

414+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or {

415415 return t.build_return_match_chain(match_expr_id, orig_expr_id, branches, idx + 1, ret_typ)

416416 }

417417 is_start := t.a.children.len

@@ -425,15 +425,20 @@ fn (mut t Transformer) build_return_match_type_branch_chain(match_expr_id flat.N

425425 cond_id := t.transform_is_expr(is_id, t.a.nodes[int(is_id)])

426426

427427 mut sc_pushed := 0

428+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {

429+ SmartcastContext{

430+ variant_name: variant_name

431+ sum_type_name: ''

432+ }

433+ }

428434 subj := t.expr_key(match_expr_id)

429- sum_name := t.find_sum_type_for_variant(variant_name)

430- if subj.len > 0 && sum_name.len > 0 {

431- t.push_smartcast(subj, variant_name, sum_name)

435+ if subj.len > 0 && sc.sum_type_name.len > 0 {

436+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)

432437 sc_pushed++

433438 }

434439 orig_subj := t.expr_key(orig_expr_id)

435- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {

436- t.push_smartcast(orig_subj, variant_name, sum_name)

440+ if orig_subj.len > 0 && orig_subj != subj && sc.sum_type_name.len > 0 {

441+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)

437442 sc_pushed++

438443 }

439444 body_block := t.match_branch_return_block(branch, n_conds, ret_typ)

vlib/v3/transform/sum.v+46-21

@@ -12,6 +12,11 @@ fn (t &Transformer) trim_pointer_type(typ string) string {

1212

1313// resolve_variant resolves resolve variant information for transform.

1414fn (t &Transformer) resolve_variant(sum_name string, variant string) string {

15+ if !isnil(t.tc) {

16+ if resolved := t.tc.sum_variant_type_for_pattern(sum_name, variant) {

17+ return resolved

18+ }

19+ }

1520 if variant.contains('.') {

1621 return variant

1722 }

@@ -54,6 +59,16 @@ fn (t &Transformer) resolve_sum_name_uncached(sum_name string) string {

5459 if sum_name in t.sum_types {

5560 return sum_name

5661 }

62+ generic_base := generic_base_name_text(sum_name)

63+ if generic_base != sum_name {

64+ resolved_base := t.resolve_sum_name_uncached(generic_base)

65+ if resolved_base in t.sum_types {

66+ return resolved_base

67+ }

68+ if !isnil(t.tc) && resolved_base in t.tc.sum_types {

69+ return resolved_base

70+ }

71+ }

5772 if sum_name.contains('.') {

5873 short_sum := sum_name.all_after_last('.')

5974 if short_sum in t.sum_types {

@@ -132,15 +147,7 @@ fn (t &Transformer) is_interface_type_name(name string) bool {

132147 if name.len == 0 || isnil(t.tc) {

133148 return false

134149 }

135- clean := t.trim_pointer_type(t.normalize_type_alias(name))

136- if clean == 'IError' || clean in t.tc.interface_names {

137- return true

138- }

139- if !clean.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'

140- && t.cur_module != 'builtin' {

141- return '${t.cur_module}.${clean}' in t.tc.interface_names

142- }

143- return false

150+ return t.resolve_interface_type_name(name).len > 0

144151}

145152

146153// interface_variant_type supports interface variant type handling for Transformer.

@@ -179,19 +186,17 @@ fn (t &Transformer) sum_type_index(sum_name string, variant string) int {

179186 variants := t.sum_types[resolved_sum] or {

180187 if !isnil(t.tc) {

181188 tc_variants := t.tc.sum_types[resolved_sum] or { return 0 }

182- return sum_type_index_in_variants(tc_variants, variant)

189+ return t.sum_type_index_in_variants(tc_variants, variant)

183190 }

184191 return 0

185192 }

186- return sum_type_index_in_variants(variants, variant)

193+ return t.sum_type_index_in_variants(variants, variant)

187194}

188195

189196// sum_type_index_in_variants supports sum type index in variants handling for transform.

190-fn sum_type_index_in_variants(variants []string, variant string) int {

191- short_variant := variant_short_name_text(variant)

197+fn (t &Transformer) sum_type_index_in_variants(variants []string, variant string) int {

192198 for i, v in variants {

193- short_v := variant_short_name_text(v)

194- if v == variant || short_v == short_variant {

199+ if t.variant_names_match(v, variant) {

195200 return i + 1

196201 }

197202 }

@@ -208,11 +213,24 @@ fn (mut t Transformer) transform_is_expr(id flat.NodeId, node flat.Node) flat.No

208213 clean_type0 := t.trim_pointer_type(expr_type)

209214 clean_type := if clean_type0 in t.sum_types {

210215 clean_type0

216+ } else if _ := t.resolve_sum_variant_pattern_for_subject(clean_type0, node.value) {

217+ clean_type0

211218 } else {

212219 t.find_sum_type_for_variant(node.value)

213220 }

214221 if t.is_interface_type_name(clean_type0) {

215222 new_expr := t.transform_expr(expr_id)

223+ if pattern := t.resolve_interface_pattern(node.value, clean_type0) {

224+ is_start := t.a.children.len

225+ t.a.children << new_expr

226+ return t.a.add_node(flat.Node{

227+ kind: .is_expr

228+ value: pattern

229+ children_start: is_start

230+ children_count: 1

231+ typ: 'bool'

232+ })

233+ }

216234 object := t.make_selector_op(new_expr, '_object', 'voidptr', if expr_type.starts_with('&') {

217235 .arrow

218236 } else {

@@ -220,7 +238,8 @@ fn (mut t Transformer) transform_is_expr(id flat.NodeId, node flat.Node) flat.No

220238 })

221239 return t.make_infix(.ne, object, t.a.add(.nil_literal))

222240 }

223- if clean_type.len == 0 || clean_type !in t.sum_types {

241+ resolved_clean_type := t.resolve_sum_name(clean_type)

242+ if clean_type.len == 0 || resolved_clean_type !in t.sum_types {

224243 return t.make_bool_literal(true)

225244 }

226245 new_expr := t.transform_expr(expr_id)

@@ -249,7 +268,13 @@ fn (t &Transformer) sum_variant_path(sum_name string, variant string) []string {

249268

250269// sum_variant_path_inner supports sum variant path inner handling for Transformer.

251270fn (t &Transformer) sum_variant_path_inner(sum_name string, variant string, mut visited map[string]bool) []string {

252- resolved_sum := t.resolve_sum_name(t.trim_pointer_type(sum_name))

271+ clean_sum := t.trim_pointer_type(sum_name)

272+ if !isnil(t.tc) {

273+ if resolved := t.tc.sum_variant_type_for_pattern(clean_sum, variant) {

274+ return [resolved]

275+ }

276+ }

277+ resolved_sum := t.resolve_sum_name(clean_sum)

253278 if resolved_sum.len == 0 || resolved_sum in visited {

254279 return []string{}

255280 }

@@ -277,14 +302,14 @@ fn (t &Transformer) sum_variant_path_inner(sum_name string, variant string, mut

277302

278303// make_sum_type_pattern_check builds make sum type pattern check data for transform.

279304fn (mut t Transformer) make_sum_type_pattern_check(expr flat.NodeId, expr_type string, sum_name string, variant string) ?flat.NodeId {

280- resolved_sum := t.resolve_sum_name(t.trim_pointer_type(sum_name))

281- path := t.sum_variant_path(resolved_sum, variant)

305+ clean_sum := t.trim_pointer_type(sum_name)

306+ path := t.sum_variant_path(clean_sum, variant)

282307 if path.len == 0 {

283308 return none

284309 }

285310 mut current := expr

286311 mut current_type := expr_type

287- mut current_sum := resolved_sum

312+ mut current_sum := clean_sum

288313 mut chain := flat.empty_node

289314 for i, path_variant in path {

290315 cmp := t.make_sum_is_check(current, current_type, current_sum, path_variant)

@@ -305,7 +330,7 @@ fn (mut t Transformer) make_sum_type_pattern_check(expr flat.NodeId, expr_type s

305330 .dot

306331 })

307332 current_type = field_type

308- current_sum = t.resolve_sum_name(t.trim_pointer_type(qv))

333+ current_sum = t.trim_pointer_type(qv)

309334 }

310335 return chain

311336}

vlib/v3/transform/transform.v+516-43

@@ -192,7 +192,10 @@ pub fn transform_with_used_opt(mut a flat.FlatAst, tc &types.TypeChecker, used_f

192192 // next power of two, wasting ~40MB, and each doubling briefly holds both the old and

193193 // new arrays — the dominant peak-RSS contributor under -gc none). The serial path is

194194 // latency-sensitive and does not clone worker ASTs, so let it grow naturally.

195- reserve_nodes := a.nodes.len * 7 / 4 - a.nodes.cap

195+ // Nodes grow a bit less than children on large compiler inputs. Keeping their

196+ // reserve below the next power-of-two cliff avoids retaining a mostly-empty

197+ // doubled nodes array through annotate/CGen.

198+ reserve_nodes := a.nodes.len * 5 / 3 - a.nodes.cap

196199 if reserve_nodes > 0 {

197200 unsafe { a.nodes.grow_cap(reserve_nodes) }

198201 }

@@ -1414,6 +1417,7 @@ fn (mut t Transformer) transform_fn_body(fn_idx int) {

14141417 t.smartcast_stack.clear()

14151418 // Collect param types

14161419 mut param_idx := 0

1420+ mut source_mut_params := []string{}

14171421 for i in 0 .. fn_node.children_count {

14181422 child_id := t.a.children[fn_node.children_start + i]

14191423 if int(child_id) < 0 {

@@ -1434,6 +1438,9 @@ fn (mut t Transformer) transform_fn_body(fn_idx int) {

14341438 typ = raw_typ

14351439 }

14361440 t.set_var_type(child.value, typ)

1441+ if child.is_mut {

1442+ source_mut_params << child.value

1443+ }

14371444 param_idx++

14381445 }

14391446 }

@@ -1453,6 +1460,9 @@ fn (mut t Transformer) transform_fn_body(fn_idx int) {

14531460 } else {

14541461 t.mark_escaping_amp_ptrs(body_ids)

14551462 }

1463+ for name in source_mut_params {

1464+ t.pointer_value_lvalues[name] = true

1465+ }

14561466 new_body := t.transform_stmts(body_ids)

14571467 // Rebuild function children: params then new body

14581468 start := t.a.children.len

@@ -2852,7 +2862,7 @@ fn (mut t Transformer) coerce_transformed_expr_to_type(expr flat.NodeId, source_

28522862// is_ierror_type reports whether is ierror type applies in transform.

28532863fn (t &Transformer) is_ierror_type(name string) bool {

28542864 clean := t.trim_pointer_type(t.normalize_type_alias(name))

2855- return clean == 'IError' || clean.ends_with('.IError')

2865+ return clean == 'IError' || clean == 'builtin.IError'

28562866}

28572867

28582868// expr_is_nil_like supports expr is nil like handling for Transformer.

@@ -4257,10 +4267,13 @@ fn (mut t Transformer) transform_index_expr(id flat.NodeId, node flat.Node) flat

42574267}

42584268

42594269// transform_string_interp transforms transform string interp data for transform.

4260-fn (mut t Transformer) transform_string_interp(_id flat.NodeId, node flat.Node) flat.NodeId {

4270+fn (mut t Transformer) transform_string_interp(id flat.NodeId, node flat.Node) flat.NodeId {

42614271 if node.children_count == 0 {

42624272 return t.make_string_literal('')

42634273 }

4274+ if t.string_interp_has_unresolved_generic_part(node) {

4275+ return id

4276+ }

42644277 // Some parts (arrays, optionals) lower into a prelude pushed onto pending_stmts, which

42654278 // runs before the containing statement. If such a part follows a part with side effects,

42664279 // keeping the earlier part inline in the string__plus chain would evaluate it after the

@@ -4287,6 +4300,7 @@ fn (mut t Transformer) transform_string_interp(_id flat.NodeId, node flat.Node)

42874300 if typ.len == 0 {

42884301 typ = 'string'

42894302 }

4303+ t.ensure_stringify_generic_instances_for_type(typ)

42904304 part := t.wrap_string_conversion(transformed, typ)

42914305 mut part_stmts := []flat.NodeId{}

42924306 t.drain_pending(mut part_stmts)

@@ -4329,6 +4343,232 @@ fn (mut t Transformer) transform_string_interp(_id flat.NodeId, node flat.Node)

43294343 return result

43304344}

43314345

4346+fn (t &Transformer) string_interp_has_unresolved_generic_part(node flat.Node) bool {

4347+ for i in 0 .. node.children_count {

4348+ child_id := t.a.child(&node, i)

4349+ if t.string_interp_child_has_unresolved_generic_part(child_id) {

4350+ return true

4351+ }

4352+ }

4353+ return false

4354+}

4355+

4356+fn (t &Transformer) string_interp_child_has_unresolved_generic_part(id flat.NodeId) bool {

4357+ if int(id) < 0 {

4358+ return false

4359+ }

4360+ node := t.a.nodes[int(id)]

4361+ mut candidates := []string{cap: 5}

4362+ candidates << t.node_type(id)

4363+ if node.typ.len > 0 {

4364+ candidates << t.normalize_type_alias(node.typ)

4365+ }

4366+ if node.kind == .ident {

4367+ candidates << t.var_type(node.value)

4368+ }

4369+ candidates << t.lvalue_type(id)

4370+ candidates << t.reliable_stringify_type(id)

4371+ for typ in candidates {

4372+ if t.stringify_type_has_generic_placeholder(typ) {

4373+ return true

4374+ }

4375+ }

4376+ return false

4377+}

4378+

4379+fn (mut t Transformer) ensure_stringify_generic_instances_for_type(typ string) {

4380+ clean := typ.trim_space()

4381+ if clean.len == 0 || t.stringify_type_has_generic_placeholder(clean) {

4382+ return

4383+ }

4384+ if clean.starts_with('&') {

4385+ t.ensure_stringify_generic_instances_for_type(clean[1..])

4386+ return

4387+ }

4388+ if clean.starts_with('mut ') {

4389+ t.ensure_stringify_generic_instances_for_type(clean[4..])

4390+ return

4391+ }

4392+ if clean.starts_with('?') || clean.starts_with('!') {

4393+ t.ensure_stringify_generic_instances_for_type(clean[1..])

4394+ return

4395+ }

4396+ if clean.starts_with('...') {

4397+ t.ensure_stringify_generic_instances_for_type(clean[3..])

4398+ return

4399+ }

4400+ if clean.starts_with('[]') {

4401+ t.ensure_stringify_generic_instances_for_type(clean[2..])

4402+ return

4403+ }

4404+ if clean.starts_with('map[') {

4405+ bracket_end := generic_matching_bracket(clean, 3)

4406+ if bracket_end < clean.len {

4407+ t.ensure_stringify_generic_instances_for_type(clean[4..bracket_end])

4408+ t.ensure_stringify_generic_instances_for_type(clean[bracket_end + 1..])

4409+ }

4410+ return

4411+ }

4412+ if clean.starts_with('[') {

4413+ bracket_end := generic_matching_bracket(clean, 0)

4414+ if bracket_end < clean.len {

4415+ t.ensure_stringify_generic_instances_for_type(clean[bracket_end + 1..])

4416+ }

4417+ return

4418+ }

4419+ base, args, ok := generic_app_parts(clean)

4420+ if !ok {

4421+ return

4422+ }

4423+ for arg in args {

4424+ t.ensure_stringify_generic_instances_for_type(arg)

4425+ }

4426+ if clean in t.structs {

4427+ return

4428+ }

4429+ base_info := t.lookup_struct_info_direct(base) or { return }

4430+ params := t.generic_struct_params_for_stringify(base)

4431+ mut fields := []FieldInfo{cap: base_info.fields.len}

4432+ for field in base_info.fields {

4433+ field_typ := if params.len > 0 {

4434+ substitute_generic_type_text_with_params(field.typ, args, params)

4435+ } else {

4436+ substitute_generic_type_text(field.typ, args)

4437+ }

4438+ fields << FieldInfo{

4439+ name: field.name

4440+ typ: field_typ

4441+ raw_typ: field.raw_typ

4442+ default_expr: field.default_expr

4443+ }

4444+ }

4445+ t.structs[clean] = StructInfo{

4446+ name: clean

4447+ module: base_info.module

4448+ is_params: base_info.is_params

4449+ fields: fields

4450+ }

4451+}

4452+

4453+fn (t &Transformer) generic_struct_params_for_stringify(base string) []string {

4454+ if isnil(t.tc) {

4455+ return []string{}

4456+ }

4457+ if params := t.tc.struct_generic_params[base] {

4458+ return params

4459+ }

4460+ if !base.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'

4461+ && t.cur_module != 'builtin' {

4462+ if params := t.tc.struct_generic_params['${t.cur_module}.${base}'] {

4463+ return params

4464+ }

4465+ }

4466+ if base.contains('.') {

4467+ if params := t.tc.struct_generic_params[base.all_after_last('.')] {

4468+ return params

4469+ }

4470+ }

4471+ return []string{}

4472+}

4473+

4474+fn (t &Transformer) stringify_type_has_generic_placeholder(typ string) bool {

4475+ clean := typ.trim_space()

4476+ if clean.len == 0 {

4477+ return false

4478+ }

4479+ if clean == 'generic' {

4480+ return true

4481+ }

4482+ if is_generic_fn_placeholder_name(clean) {

4483+ return !t.is_known_concrete_type_name(clean)

4484+ }

4485+ if clean.starts_with('&') {

4486+ return t.stringify_type_has_generic_placeholder(clean[1..])

4487+ }

4488+ if clean.starts_with('mut ') {

4489+ return t.stringify_type_has_generic_placeholder(clean[4..])

4490+ }

4491+ if clean.starts_with('?') || clean.starts_with('!') {

4492+ return t.stringify_type_has_generic_placeholder(clean[1..])

4493+ }

4494+ if clean.starts_with('...') {

4495+ return t.stringify_type_has_generic_placeholder(clean[3..])

4496+ }

4497+ if clean.starts_with('[]') {

4498+ return t.stringify_type_has_generic_placeholder(clean[2..])

4499+ }

4500+ if clean.starts_with('map[') {

4501+ bracket_end := generic_matching_bracket(clean, 3)

4502+ if bracket_end < clean.len {

4503+ return t.stringify_type_has_generic_placeholder(clean[4..bracket_end])

4504+ || t.stringify_type_has_generic_placeholder(clean[bracket_end + 1..])

4505+ }

4506+ return false

4507+ }

4508+ if clean.starts_with('[') {

4509+ bracket_end := generic_matching_bracket(clean, 0)

4510+ if bracket_end < clean.len {

4511+ return t.stringify_type_has_generic_placeholder(clean[bracket_end + 1..])

4512+ }

4513+ return false

4514+ }

4515+ _, args, ok := generic_app_parts(clean)

4516+ if ok {

4517+ for arg in args {

4518+ if t.stringify_type_has_generic_placeholder(arg) {

4519+ return true

4520+ }

4521+ }

4522+ }

4523+ return false

4524+}

4525+

4526+fn stringify_type_has_generic_placeholder(typ string) bool {

4527+ clean := typ.trim_space()

4528+ if clean.len == 0 {

4529+ return false

4530+ }

4531+ if is_generic_placeholder_type_name(clean) {

4532+ return true

4533+ }

4534+ if clean.starts_with('&') {

4535+ return stringify_type_has_generic_placeholder(clean[1..])

4536+ }

4537+ if clean.starts_with('?') || clean.starts_with('!') {

4538+ return stringify_type_has_generic_placeholder(clean[1..])

4539+ }

4540+ if clean.starts_with('...') {

4541+ return stringify_type_has_generic_placeholder(clean[3..])

4542+ }

4543+ if clean.starts_with('[]') {

4544+ return stringify_type_has_generic_placeholder(clean[2..])

4545+ }

4546+ if clean.starts_with('map[') {

4547+ bracket_end := generic_matching_bracket(clean, 3)

4548+ if bracket_end < clean.len {

4549+ return stringify_type_has_generic_placeholder(clean[4..bracket_end])

4550+ || stringify_type_has_generic_placeholder(clean[bracket_end + 1..])

4551+ }

4552+ return false

4553+ }

4554+ if clean.starts_with('[') {

4555+ bracket_end := generic_matching_bracket(clean, 0)

4556+ if bracket_end < clean.len {

4557+ return stringify_type_has_generic_placeholder(clean[bracket_end + 1..])

4558+ }

4559+ return false

4560+ }

4561+ _, args, ok := generic_app_parts(clean)

4562+ if ok {

4563+ for arg in args {

4564+ if stringify_type_has_generic_placeholder(arg) {

4565+ return true

4566+ }

4567+ }

4568+ }

4569+ return false

4570+}

4571+

43324572// transform_selector_expr transforms transform selector expr data for transform.

43334573fn (mut t Transformer) transform_selector_expr(id flat.NodeId, node flat.Node) flat.NodeId {

43344574 if node.children_count == 0 {

@@ -4722,7 +4962,7 @@ fn (mut t Transformer) transform_prefix_expr(id flat.NodeId, node flat.Node) fla

47224962 // heap-allocated interface so the resulting pointer stays valid, rather

47234963 // than emitting a plain `(Interface*)x` reinterpret cast.

47244964 iface := t.resolve_interface_type_name(child.value)

4725- if iface.len > 0 && iface.all_after_last('.') != 'IError' {

4965+ if iface.len > 0 && !t.is_builtin_ierror_interface_name(iface) {

47264966 if boxed := t.transform_interface_value_for_type(cast_arg_id, '&${child.value}') {

47274967 return boxed

47284968 }

@@ -5680,13 +5920,47 @@ fn (t &Transformer) sum_variant_name(sum_name string, variant string) ?string {

56805920}

56815921

56825922fn (t &Transformer) variant_names_match(a string, b string) bool {

5683- return a == b || t.variant_short_name(a) == t.variant_short_name(b)

5923+ if a == b || t.variant_short_name(a) == t.variant_short_name(b) {

5924+ return true

5925+ }

5926+ a_base, a_args, a_generic := generic_app_parts(a)

5927+ b_base, b_args, b_generic := generic_app_parts(b)

5928+ if a_generic || b_generic {

5929+ a_match_base := if a_generic { a_base } else { a }

5930+ b_match_base := if b_generic { b_base } else { b }

5931+ if a_generic && b_generic && !t.generic_variant_args_are_open(a_args)

5932+ && !t.generic_variant_args_are_open(b_args) {

5933+ return false

5934+ }

5935+ return t.variant_short_name(a_match_base) == t.variant_short_name(b_match_base)

5936+ }

5937+ return false

5938+}

5939+

5940+fn (t &Transformer) generic_variant_args_are_open(args []string) bool {

5941+ for arg in args {

5942+ if t.generic_arg_is_unresolved(arg) {

5943+ return true

5944+ }

5945+ }

5946+ return false

56845947}

56855948

56865949fn (t &Transformer) variant_short_name(name string) string {

56875950 return variant_short_name_text(name)

56885951}

56895952

5953+fn generic_base_name_text(name string) string {

5954+ if name.starts_with('[') {

5955+ return name

5956+ }

5957+ bracket := name.index_u8(`[`)

5958+ if bracket <= 0 {

5959+ return name

5960+ }

5961+ return name[..bracket]

5962+}

5963+

56905964fn variant_short_name_text(name string) string {

56915965 if name.starts_with('&') {

56925966 return '&' + variant_short_name_text(name[1..])

@@ -5972,7 +6246,7 @@ fn (t &Transformer) resolve_expr_type(id flat.NodeId) string {

59726246 }

59736247 }

59746248 if node.children_count > 0 {

5975- elem_type := t.node_type(t.a.child(&node, 0))

6249+ elem_type := t.array_literal_elem_type(node)

59766250 if elem_type.len > 0 {

59776251 return '[]${elem_type}'

59786252 }

@@ -6049,6 +6323,9 @@ fn (t &Transformer) resolve_expr_type(id flat.NodeId) string {

60496323 if node.op == .not {

60506324 return 'bool'

60516325 }

6326+ if node.op in [.minus, .plus] {

6327+ return child_type

6328+ }

60526329 }

60536330 return ''

60546331 }

@@ -6140,6 +6417,51 @@ fn (t &Transformer) resolve_expr_type(id flat.NodeId) string {

61406417 }

61416418}

61426419

6420+fn (t &Transformer) array_literal_elem_type(node flat.Node) string {

6421+ if node.children_count == 0 {

6422+ return 'int'

6423+ }

6424+ mut elem_type := t.node_type(t.a.child(&node, 0))

6425+ if !is_numeric_type_name(elem_type) {

6426+ return elem_type

6427+ }

6428+ mut has_f32 := elem_type == 'f32'

6429+ mut has_f64 := elem_type == 'f64'

6430+ for i in 1 .. node.children_count {

6431+ child_type := t.node_type(t.a.child(&node, i))

6432+ if !is_numeric_type_name(child_type) {

6433+ return elem_type

6434+ }

6435+ if child_type == 'f32' {

6436+ has_f32 = true

6437+ }

6438+ if child_type == 'f64' {

6439+ has_f64 = true

6440+ }

6441+ }

6442+ if has_f64 {

6443+ return 'f64'

6444+ }

6445+ if has_f32 {

6446+ return 'f32'

6447+ }

6448+ return elem_type

6449+}

6450+

6451+fn is_numeric_type_name(name string) bool {

6452+ return is_integer_type_name(name) || is_float_type_name(name)

6453+}

6454+

6455+fn is_integer_type_name(name string) bool {

6456+ return name == 'int' || name == 'i8' || name == 'i16' || name == 'i64' || name == 'u8'

6457+ || name == 'byte' || name == 'u16' || name == 'u32' || name == 'u64' || name == 'isize'

6458+ || name == 'usize' || name == 'rune'

6459+}

6460+

6461+fn is_float_type_name(name string) bool {

6462+ return name == 'f32' || name == 'f64'

6463+}

6464+

61436465fn (t &Transformer) current_call_return_type(node flat.Node) string {

61446466 if node.children_count > 0 {

61456467 fn_node := t.a.child_node(&node, 0)

@@ -6299,17 +6621,18 @@ fn (t &Transformer) match_branch_type_contexts(match_expr_id flat.NodeId, branch

62996621 return []SmartcastContext{}

63006622 }

63016623 cond_val_id := t.a.child(&branch, 0)

6302- variant_name := t.match_type_pattern(cond_val_id) or { return []SmartcastContext{} }

63036624 subj := t.expr_key(match_expr_id)

6304- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id), variant_name)

6305- if subj.len == 0 || sum_name.len == 0 {

6625+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {

6626+ return []SmartcastContext{}

6627+ }

6628+ if subj.len == 0 {

63066629 return []SmartcastContext{}

63076630 }

63086631 return [

63096632 SmartcastContext{

63106633 expr_name: subj

6311- variant_name: variant_name

6312- sum_type_name: sum_name

6634+ variant_name: sc.variant_name

6635+ sum_type_name: sc.sum_type_name

63136636 },

63146637 ]

63156638}

@@ -6433,7 +6756,7 @@ fn (mut t Transformer) build_match_chain(match_expr_id flat.NodeId, orig_expr_id

64336756 // (build_match_chain runs per branch, and the compiler has very large matches).

64346757 n_conds := if is_else { 0 } else { t.count_conds(branch) }

64356758 body_start_idx := n_conds

6436- if !is_else && n_conds > 1 && t.match_branch_all_type_patterns(branch) {

6759+ if !is_else && n_conds > 1 && t.match_branch_all_type_patterns(match_expr_id, branch) {

64376760 return t.build_match_type_branch_chain(match_expr_id, orig_expr_id, branch, branches, idx,

64386761 0)

64396762 }

@@ -6443,17 +6766,15 @@ fn (mut t Transformer) build_match_chain(match_expr_id flat.NodeId, orig_expr_id

64436766 if !is_else {

64446767 if n_conds == 1 {

64456768 cond_val_id := t.a.child(&branch, 0)

6446- if variant_name := t.match_type_pattern(cond_val_id) {

6769+ if sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) {

64476770 subj := t.expr_key(match_expr_id)

6448- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id),

6449- variant_name)

6450- if subj.len > 0 && sum_name.len > 0 {

6451- t.push_smartcast(subj, variant_name, sum_name)

6771+ if subj.len > 0 {

6772+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)

64526773 sc_pushed++

64536774 }

64546775 orig_subj := t.expr_key(orig_expr_id)

6455- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {

6456- t.push_smartcast(orig_subj, variant_name, sum_name)

6776+ if orig_subj.len > 0 && orig_subj != subj {

6777+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)

64576778 sc_pushed++

64586779 }

64596780 }

@@ -6542,7 +6863,8 @@ fn (mut t Transformer) build_match_value_chain(match_expr_id flat.NodeId, orig_e

65426863 branch := t.a.nodes[int(branches[idx])]

65436864 is_else := branch.value == 'else'

65446865 body_start_idx := if is_else { 0 } else { t.count_conds(branch) }

6545- if !is_else && t.match_branch_all_type_patterns(branch) && t.count_conds(branch) > 1 {

6866+ if !is_else && t.match_branch_all_type_patterns(match_expr_id, branch)

6867+ && t.count_conds(branch) > 1 {

65466868 return t.build_match_value_type_branch_chain(match_expr_id, orig_expr_id, branch, branches,

65476869 idx, 0, target_name, target_type)

65486870 }

@@ -6552,17 +6874,15 @@ fn (mut t Transformer) build_match_value_chain(match_expr_id flat.NodeId, orig_e

65526874 n_conds := t.count_conds(branch)

65536875 if n_conds == 1 {

65546876 cond_val_id := t.a.child(&branch, 0)

6555- if variant_name := t.match_type_pattern(cond_val_id) {

6877+ if sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) {

65566878 subj := t.expr_key(match_expr_id)

6557- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id),

6558- variant_name)

6559- if subj.len > 0 && sum_name.len > 0 {

6560- t.push_smartcast(subj, variant_name, sum_name)

6879+ if subj.len > 0 {

6880+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)

65616881 sc_pushed++

65626882 }

65636883 orig_subj := t.expr_key(orig_expr_id)

6564- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {

6565- t.push_smartcast(orig_subj, variant_name, sum_name)

6884+ if orig_subj.len > 0 && orig_subj != subj {

6885+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)

65666886 sc_pushed++

65676887 }

65686888 }

@@ -6624,7 +6944,7 @@ fn (mut t Transformer) build_match_value_type_branch_chain(match_expr_id flat.No

66246944 }

66256945 }

66266946 cond_val_id := t.a.child(&branch, cond_idx)

6627- variant_name := t.match_type_pattern(cond_val_id) or {

6947+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or {

66286948 return t.build_match_value_chain(match_expr_id, orig_expr_id, branches, idx + 1,

66296949 target_name, target_type)

66306950 }

@@ -6639,15 +6959,20 @@ fn (mut t Transformer) build_match_value_type_branch_chain(match_expr_id flat.No

66396959 cond_id := t.transform_is_expr(is_id, t.a.nodes[int(is_id)])

66406960

66416961 mut sc_pushed := 0

6962+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {

6963+ SmartcastContext{

6964+ variant_name: variant_name

6965+ sum_type_name: ''

6966+ }

6967+ }

66426968 subj := t.expr_key(match_expr_id)

6643- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id), variant_name)

6644- if subj.len > 0 && sum_name.len > 0 {

6645- t.push_smartcast(subj, variant_name, sum_name)

6969+ if subj.len > 0 && sc.sum_type_name.len > 0 {

6970+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)

66466971 sc_pushed++

66476972 }

66486973 orig_subj := t.expr_key(orig_expr_id)

6649- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {

6650- t.push_smartcast(orig_subj, variant_name, sum_name)

6974+ if orig_subj.len > 0 && orig_subj != subj && sc.sum_type_name.len > 0 {

6975+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)

66516976 sc_pushed++

66526977 }

66536978

@@ -6685,7 +7010,7 @@ fn (mut t Transformer) build_match_type_branch_chain(match_expr_id flat.NodeId,

66857010 }

66867011 }

66877012 cond_val_id := t.a.child(&branch, cond_idx)

6688- variant_name := t.match_type_pattern(cond_val_id) or {

7013+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or {

66897014 return t.build_match_chain(match_expr_id, orig_expr_id, branches, idx + 1)

66907015 }

66917016 is_start := t.a.children.len

@@ -6699,15 +7024,20 @@ fn (mut t Transformer) build_match_type_branch_chain(match_expr_id flat.NodeId,

66997024 cond_id := t.transform_is_expr(is_id, t.a.nodes[int(is_id)])

67007025

67017026 mut sc_pushed := 0

7027+ sc := t.match_type_smartcast_context(match_expr_id, cond_val_id) or {

7028+ SmartcastContext{

7029+ variant_name: variant_name

7030+ sum_type_name: ''

7031+ }

7032+ }

67027033 subj := t.expr_key(match_expr_id)

6703- sum_name := t.sum_type_for_is_expr(t.original_expr_type(match_expr_id), variant_name)

6704- if subj.len > 0 && sum_name.len > 0 {

6705- t.push_smartcast(subj, variant_name, sum_name)

7034+ if subj.len > 0 && sc.sum_type_name.len > 0 {

7035+ t.push_smartcast(subj, sc.variant_name, sc.sum_type_name)

67067036 sc_pushed++

67077037 }

67087038 orig_subj := t.expr_key(orig_expr_id)

6709- if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {

6710- t.push_smartcast(orig_subj, variant_name, sum_name)

7039+ if orig_subj.len > 0 && orig_subj != subj && sc.sum_type_name.len > 0 {

7040+ t.push_smartcast(orig_subj, sc.variant_name, sc.sum_type_name)

67117041 sc_pushed++

67127042 }

67137043

@@ -6781,7 +7111,7 @@ fn (mut t Transformer) build_match_cond(match_expr_id flat.NodeId, branch flat.N

67817111 if n_conds == 1 {

67827112 cond_val_id := t.a.child(&branch, 0)

67837113 cond_val := t.a.nodes[int(cond_val_id)]

6784- if variant_name := t.match_type_pattern(cond_val_id) {

7114+ if variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) {

67857115 is_start := t.a.children.len

67867116 t.a.children << match_expr_id

67877117 is_id := t.a.add_node(flat.Node{

@@ -6801,7 +7131,7 @@ fn (mut t Transformer) build_match_cond(match_expr_id flat.NodeId, branch flat.N

68017131 for i in 0 .. n_conds {

68027132 cond_val_id := t.a.child(&branch, i)

68037133 cond_val := t.a.nodes[int(cond_val_id)]

6804- variant_name := t.match_type_pattern(cond_val_id) or { '' }

7134+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or { '' }

68057135 cmp := if variant_name.len > 0 {

68067136 is_start := t.a.children.len

68077137 t.a.children << match_expr_id

@@ -6846,15 +7176,158 @@ fn (t &Transformer) match_type_pattern(cond_val_id flat.NodeId) ?string {

68467176 return none

68477177}

68487178

7179+fn (t &Transformer) match_type_pattern_for_subject(match_expr_id flat.NodeId, cond_val_id flat.NodeId) ?string {

7180+ if int(cond_val_id) < 0 {

7181+ return none

7182+ }

7183+ pattern := t.type_pattern_name(cond_val_id)

7184+ if pattern.len == 0 {

7185+ return none

7186+ }

7187+ subject_type := t.trim_pointer_type(t.original_expr_type(match_expr_id))

7188+ if t.is_interface_type_name(subject_type) {

7189+ return t.resolve_interface_pattern(pattern, subject_type)

7190+ }

7191+ if resolved_variant := t.resolve_sum_variant_pattern_for_subject(subject_type, pattern) {

7192+ return resolved_variant

7193+ }

7194+ if t.is_sum_variant(pattern) {

7195+ return pattern

7196+ }

7197+ return none

7198+}

7199+

7200+fn (t &Transformer) resolve_sum_variant_pattern_for_subject(subject_type string, pattern string) ?string {

7201+ if pattern.len == 0 {

7202+ return none

7203+ }

7204+ if !isnil(t.tc) {

7205+ if resolved := t.tc.sum_variant_type_for_pattern(subject_type, pattern) {

7206+ return resolved

7207+ }

7208+ }

7209+ resolved_sum := t.resolve_sum_name(subject_type)

7210+ return t.sum_variant_name(resolved_sum, pattern)

7211+}

7212+

7213+fn (t &Transformer) match_type_smartcast_context(match_expr_id flat.NodeId, cond_val_id flat.NodeId) ?SmartcastContext {

7214+ variant_name := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) or { return none }

7215+ subject_type := t.trim_pointer_type(t.original_expr_type(match_expr_id))

7216+ sum_name := if t.is_interface_type_name(subject_type) {

7217+ resolved := t.resolve_interface_type_name(subject_type)

7218+ if resolved.len > 0 {

7219+ resolved

7220+ } else {

7221+ subject_type

7222+ }

7223+ } else {

7224+ t.sum_type_for_is_expr(subject_type, variant_name)

7225+ }

7226+ if sum_name.len == 0 {

7227+ return none

7228+ }

7229+ return SmartcastContext{

7230+ variant_name: variant_name

7231+ sum_type_name: sum_name

7232+ }

7233+}

7234+

7235+fn (t &Transformer) match_pattern_implements_interface(pattern string, subject_type string) bool {

7236+ return t.resolve_interface_pattern(pattern, subject_type) != none

7237+}

7238+

7239+fn (t &Transformer) resolve_interface_pattern(pattern string, subject_type string) ?string {

7240+ if !t.is_interface_type_name(subject_type) {

7241+ return none

7242+ }

7243+ resolved_iface := t.resolve_interface_type_name(subject_type)

7244+ iface := if resolved_iface.len > 0 { resolved_iface } else { subject_type }

7245+ for candidate in t.interface_pattern_candidates(pattern) {

7246+ if t.is_builtin_ierror_interface_name(iface) {

7247+ if t.tc.named_type_compatible_with_ierror(candidate) {

7248+ return candidate

7249+ }

7250+ } else if t.tc.named_type_implements_interface(candidate, iface) {

7251+ return candidate

7252+ }

7253+ }

7254+ return none

7255+}

7256+

7257+fn (t &Transformer) interface_pattern_candidates(pattern string) []string {

7258+ mut candidates := []string{}

7259+ if !pattern.contains('.') {

7260+ mut has_scoped_candidate := false

7261+ if t.cur_file.len > 0 {

7262+ for candidate in t.tc.file_selective_imports[file_import_key(t.cur_file, pattern)] or {

7263+ []string{}

7264+ } {

7265+ if t.interface_pattern_candidate_known(candidate) {

7266+ candidates << candidate

7267+ has_scoped_candidate = true

7268+ }

7269+ }

7270+ }

7271+ if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {

7272+ local := '${t.cur_module}.${pattern}'

7273+ if t.interface_pattern_candidate_known(local) {

7274+ candidates << local

7275+ has_scoped_candidate = true

7276+ }

7277+ }

7278+ if !has_scoped_candidate {

7279+ candidates << pattern

7280+ }

7281+ } else if resolved := t.resolve_import_alias_pattern(pattern) {

7282+ candidates << resolved

7283+ candidates << pattern

7284+ } else {

7285+ candidates << pattern

7286+ }

7287+ qpattern := t.tc.qualify_name(pattern)

7288+ if qpattern != pattern {

7289+ candidates << qpattern

7290+ }

7291+ mut result := []string{}

7292+ mut seen := map[string]bool{}

7293+ for candidate in candidates {

7294+ if candidate.len == 0 || candidate in seen {

7295+ continue

7296+ }

7297+ seen[candidate] = true

7298+ result << candidate

7299+ }

7300+ return result

7301+}

7302+

7303+fn (t &Transformer) interface_pattern_candidate_known(candidate string) bool {

7304+ return candidate in t.tc.type_aliases || candidate in t.tc.structs

7305+ || candidate in t.tc.interface_names || candidate in t.tc.flag_enums

7306+ || candidate in t.tc.enum_names || candidate in t.tc.sum_types

7307+}

7308+

7309+fn (t &Transformer) resolve_import_alias_pattern(pattern string) ?string {

7310+ if t.cur_file.len == 0 {

7311+ return none

7312+ }

7313+ dot := pattern.index_u8(`.`)

7314+ if dot <= 0 {

7315+ return none

7316+ }

7317+ alias := pattern[..dot]

7318+ resolved := t.tc.file_imports[file_import_key(t.cur_file, alias)] or { return none }

7319+ return '${resolved}.${pattern[dot + 1..]}'

7320+}

7321+

68497322// match_branch_all_type_patterns supports match branch all type patterns handling for Transformer.

6850-fn (t &Transformer) match_branch_all_type_patterns(branch flat.Node) bool {

7323+fn (t &Transformer) match_branch_all_type_patterns(match_expr_id flat.NodeId, branch flat.Node) bool {

68517324 n_conds := t.count_conds(branch)

68527325 if n_conds == 0 {

68537326 return false

68547327 }

68557328 for i in 0 .. n_conds {

68567329 cond_val_id := t.a.child(&branch, i)

6857- if _ := t.match_type_pattern(cond_val_id) {

7330+ if _ := t.match_type_pattern_for_subject(match_expr_id, cond_val_id) {

68587331 continue

68597332 }

68607333 return false

vlib/v3/transform/transform_d_parallel.v+1-0

@@ -114,6 +114,7 @@ fn (mut t Transformer) run_parallel_transform(items []FnWorkItem, base_nodes int

114114 for ci in 0 .. thread_count {

115115 ww := unsafe { &Transformer(workers[ci]) }

116116 t.merge_worker(ww, chunks[ci + 1], base_nodes, base_children)

117+ ww.tc.free_parallel_transform_caches()

117118 // The worker's cloned base AST is no longer needed after merge.

118119 unsafe {

119120 mut wast := &flat.FlatAst(worker_asts[ci])

vlib/v3/transform/type_propagation.v+7-1

@@ -773,11 +773,17 @@ fn (t &Transformer) node_type(id flat.NodeId) string {

773773 if int(id) < 0 {

774774 return ''

775775 }

776+ node := t.a.nodes[int(id)]

776777 resolved := t.resolve_expr_type(id)

777778 if resolved.len > 0 {

779+ if t.generic_arg_is_unresolved(resolved) && node.typ.len > 0 {

780+ node_typ := t.normalize_type_alias(node.typ)

781+ if node_typ.len > 0 && !t.generic_arg_is_unresolved(node_typ) {

782+ return node_typ

783+ }

784+ }

778785 return resolved

779786 }

780- node := t.a.nodes[int(id)]

781787 mut deferred_call_typ := ''

782788 if node.typ.len > 0 {

783789 node_typ := t.normalize_type_alias(node.typ)

vlib/v3/types/checker.v+1616-322

@@ -158,29 +158,33 @@ struct LocalBinding {

158158// TypeCache represents type cache data used by types.

159159struct TypeCache {

160160mut:

161- parse_enabled bool

162- parse_entries map[string]Type

163- c_entries map[string]string

161+ parse_enabled bool

162+ parse_entries map[string]Type

163+ c_entries map[string]string

164+ ierror_compat_entries map[string]int

164165}

165166

166167// TypeChecker represents type checker data used by types.

167168@[heap]

168169pub struct TypeChecker {

169170pub mut:

170- a &flat.FlatAst = unsafe { nil }

171- fn_ret_types map[string]Type

172- fn_param_types map[string][]Type

173- fn_ret_type_texts map[string]string // generic struct method key -> original return type text (e.g. `Box[T].clone` -> `Box[T]`)

174- fn_param_type_texts map[string][]string // generic struct method key -> original param type texts (receiver first)

175- fn_type_files map[string]string

176- fn_type_modules map[string]string

177- fn_generic_params map[string][]string

178- fn_variadic map[string]bool

179- c_variadic_fns map[string]bool

180- fn_implicit_veb_ctx map[string]bool

181- structs map[string][]StructField

182- struct_generic_params map[string][]string // generic struct base name -> type-param names (e.g. Vec4 -> [T])

183- struct_field_c_abi_fns map[string]string

171+ a &flat.FlatAst = unsafe { nil }

172+ fn_ret_types map[string]Type

173+ fn_param_types map[string][]Type

174+ fn_ret_type_texts map[string]string // generic struct method key -> original return type text (e.g. `Box[T].clone` -> `Box[T]`)

175+ fn_param_type_texts map[string][]string // generic struct method key -> original param type texts (receiver first)

176+ fn_type_files map[string]string

177+ fn_type_modules map[string]string

178+ fn_generic_params map[string][]string

179+ fn_variadic map[string]bool

180+ c_variadic_fns map[string]bool

181+ fn_implicit_veb_ctx map[string]bool

182+ structs map[string][]StructField

183+ struct_modules map[string]string

184+ struct_files map[string]string

185+ struct_error_embeds_shadow_builtin map[string]bool

186+ struct_generic_params map[string][]string // generic struct base name -> type-param names (e.g. Vec4 -> [T])

187+ struct_field_c_abi_fns map[string]string

184188 // concrete `Box[int].method` -> substituted CallInfo for a method *value* on a

185189 // generic receiver. The open `Box[T].method` registration is gone by cgen time, so

186190 // the checker stashes the resolved signature here for gen_method_value_closure.

@@ -190,6 +194,7 @@ pub mut:

190194 type_aliases map[string]string

191195 type_alias_c_abi_fns map[string]string

192196 sum_types map[string][]string

197+ sum_generic_params map[string][]string // generic sum type base name -> type-param names (e.g. Tree -> [T])

193198 enum_names map[string]bool

194199 enum_fields map[string][]string

195200 flag_enums map[string]bool

@@ -217,8 +222,7 @@ pub mut:

217222 errors []TypeError

218223 resolved_call_names []string // node_id -> resolved function name

219224 resolved_call_set []bool

220- resolved_fn_value_names []string // node_id -> resolved function value name

221- resolved_fn_value_set []bool

225+ resolved_fn_value_names map[int]string // node_id -> resolved function value name

222226 // Methods used as *values* (`recv.method` passed as a callback), recorded per enclosing

223227 // function during semantic checking — which has full scope/type info, runs before

224228 // markused, and (unlike a call) routes a value-context selector through check_selector.

@@ -235,6 +239,8 @@ pub mut:

235239 // scope); a reassignment in a deeper conditional/loop scope keeps the maybe-method marker.

236240 method_value_local_depth map[string]int

237241 cur_fn_node_id int = -1

242+ cur_fn_mut_param_base_types map[string]Type

243+ cur_fn_mut_param_scope_depths map[string]int

238244 expr_type_values []Type // node_id -> complex/contextual resolved type

239245 expr_type_set []bool

240246 checking_nodes []bool

@@ -253,59 +259,65 @@ mut:

253259pub fn TypeChecker.new(a &flat.FlatAst) TypeChecker {

254260 fs := new_scope(unsafe { nil })

255261 return TypeChecker{

256- a: a

257- fn_ret_types: map[string]Type{}

258- fn_param_types: map[string][]Type{}

259- fn_ret_type_texts: map[string]string{}

260- fn_param_type_texts: map[string][]string{}

261- fn_type_files: map[string]string{}

262- fn_type_modules: map[string]string{}

263- fn_generic_params: map[string][]string{}

264- fn_variadic: map[string]bool{}

265- c_variadic_fns: map[string]bool{}

266- fn_implicit_veb_ctx: map[string]bool{}

267- structs: map[string][]StructField{}

268- struct_generic_params: map[string][]string{}

269- struct_field_c_abi_fns: map[string]string{}

270- generic_method_value_info: map[string]CallInfo{}

271- params_structs: map[string]bool{}

272- unions: map[string]bool{}

273- type_aliases: map[string]string{}

274- type_alias_c_abi_fns: map[string]string{}

275- sum_types: map[string][]string{}

276- enum_names: map[string]bool{}

277- enum_fields: map[string][]string{}

278- flag_enums: map[string]bool{}

279- interface_names: map[string]bool{}

280- interface_fields: map[string][]StructField{}

281- interface_embeds: map[string][]string{}

282- interface_abstract_methods: map[string][]string{}

283- c_globals: map[string]Type{}

284- const_types: map[string]Type{}

285- const_exprs: map[string]flat.NodeId{}

286- const_modules: map[string]string{}

287- const_suffixes: map[string]string{}

288- imports: map[string]string{}

289- file_imports: map[string]string{}

290- file_selective_imports: map[string][]string{}

291- file_modules: map[string]string{}

292- file_scope: fs

293- cur_scope: fs

294- resolved_call_names: []string{len: a.nodes.len}

295- resolved_call_set: []bool{len: a.nodes.len}

296- resolved_fn_value_names: []string{len: a.nodes.len}

297- resolved_fn_value_set: []bool{len: a.nodes.len}

298- method_values_by_fn: map[int][]string{}

299- method_value_locals: map[string]bool{}

300- method_value_local_depth: map[string]int{}

301- expr_type_values: []Type{len: a.nodes.len, init: Type(void_)}

302- expr_type_set: []bool{len: a.nodes.len}

303- checking_nodes: []bool{len: a.nodes.len}

304- diagnostic_files: map[string]bool{}

305- smartcasts: map[string]Type{}

306- type_cache: &TypeCache{

307- parse_entries: map[string]Type{}

308- c_entries: map[string]string{}

262+ a: a

263+ fn_ret_types: map[string]Type{}

264+ fn_param_types: map[string][]Type{}

265+ fn_ret_type_texts: map[string]string{}

266+ fn_param_type_texts: map[string][]string{}

267+ fn_type_files: map[string]string{}

268+ fn_type_modules: map[string]string{}

269+ fn_generic_params: map[string][]string{}

270+ fn_variadic: map[string]bool{}

271+ c_variadic_fns: map[string]bool{}

272+ fn_implicit_veb_ctx: map[string]bool{}

273+ structs: map[string][]StructField{}

274+ struct_modules: map[string]string{}

275+ struct_files: map[string]string{}

276+ struct_error_embeds_shadow_builtin: map[string]bool{}

277+ struct_generic_params: map[string][]string{}

278+ struct_field_c_abi_fns: map[string]string{}

279+ generic_method_value_info: map[string]CallInfo{}

280+ params_structs: map[string]bool{}

281+ unions: map[string]bool{}

282+ type_aliases: map[string]string{}

283+ type_alias_c_abi_fns: map[string]string{}

284+ sum_types: map[string][]string{}

285+ sum_generic_params: map[string][]string{}

286+ enum_names: map[string]bool{}

287+ enum_fields: map[string][]string{}

288+ flag_enums: map[string]bool{}

289+ interface_names: map[string]bool{}

290+ interface_fields: map[string][]StructField{}

291+ interface_embeds: map[string][]string{}

292+ interface_abstract_methods: map[string][]string{}

293+ c_globals: map[string]Type{}

294+ const_types: map[string]Type{}

295+ const_exprs: map[string]flat.NodeId{}

296+ const_modules: map[string]string{}

297+ const_suffixes: map[string]string{}

298+ imports: map[string]string{}

299+ file_imports: map[string]string{}

300+ file_selective_imports: map[string][]string{}

301+ file_modules: map[string]string{}

302+ file_scope: fs

303+ cur_scope: fs

304+ resolved_call_names: []string{len: a.nodes.len}

305+ resolved_call_set: []bool{len: a.nodes.len}

306+ resolved_fn_value_names: map[int]string{}

307+ method_values_by_fn: map[int][]string{}

308+ method_value_locals: map[string]bool{}

309+ method_value_local_depth: map[string]int{}

310+ cur_fn_mut_param_base_types: map[string]Type{}

311+ cur_fn_mut_param_scope_depths: map[string]int{}

312+ expr_type_values: []Type{len: a.nodes.len, init: Type(void_)}

313+ expr_type_set: []bool{len: a.nodes.len}

314+ checking_nodes: []bool{len: a.nodes.len}

315+ diagnostic_files: map[string]bool{}

316+ smartcasts: map[string]Type{}

317+ type_cache: &TypeCache{

318+ parse_entries: map[string]Type{}

319+ c_entries: map[string]string{}

320+ ierror_compat_entries: map[string]int{}

309321 }

310322 }

311323}

@@ -321,38 +333,59 @@ pub fn TypeChecker.new(a &flat.FlatAst) TypeChecker {

321333pub fn (tc &TypeChecker) fork_for_parallel_transform(ast &flat.FlatAst) &TypeChecker {

322334 mut forked := *tc

323335 forked.a = ast

336+ forked.resolved_fn_value_names = tc.resolved_fn_value_names.clone()

324337 forked.type_cache = &TypeCache{

325- parse_enabled: if tc.type_cache != unsafe { nil } {

338+ parse_enabled: if tc.type_cache != unsafe { nil } {

326339 tc.type_cache.parse_enabled

327340 } else {

328341 false

329342 }

330- parse_entries: map[string]Type{}

331- c_entries: map[string]string{}

343+ parse_entries: map[string]Type{}

344+ c_entries: map[string]string{}

345+ ierror_compat_entries: map[string]int{}

332346 }

333347 return &forked

334348}

335349

350+// free_parallel_transform_caches releases private memoization maps owned by a forked

351+// transform checker and leaves it valid if it is accidentally read again.

352+pub fn (mut tc TypeChecker) free_parallel_transform_caches() {

353+ parse_enabled := if tc.type_cache != unsafe { nil } {

354+ tc.type_cache.parse_enabled

355+ } else {

356+ false

357+ }

358+ if tc.type_cache != unsafe { nil } {

359+ unsafe {

360+ tc.type_cache.parse_entries.free()

361+ tc.type_cache.c_entries.free()

362+ tc.type_cache.ierror_compat_entries.free()

363+ }

364+ }

365+ tc.type_cache = &TypeCache{

366+ parse_enabled: parse_enabled

367+ parse_entries: map[string]Type{}

368+ c_entries: map[string]string{}

369+ ierror_compat_entries: map[string]int{}

370+ }

371+}

372+

336373// reset_node_caches updates reset node caches state for types.

337374fn (mut tc TypeChecker) reset_node_caches(n int) {

338375 tc.resolved_call_names = []string{len: n}

339376 tc.resolved_call_set = []bool{len: n}

340- tc.resolved_fn_value_names = []string{len: n}

341- tc.resolved_fn_value_set = []bool{len: n}

377+ tc.resolved_fn_value_names = map[int]string{}

342378 tc.expr_type_values = []Type{len: n, init: Type(void_)}

343379 tc.expr_type_set = []bool{len: n}

344380 tc.checking_nodes = []bool{len: n}

345381}

346382

347383fn (mut tc TypeChecker) extend_node_caches(n int) {

348- if n <= tc.resolved_call_names.len && n <= tc.resolved_fn_value_names.len

349- && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len {

384+ if n <= tc.resolved_call_names.len && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len {

350385 return

351386 }

352387 extend_string_cache(mut tc.resolved_call_names, n)

353388 extend_bool_cache(mut tc.resolved_call_set, n)

354- extend_string_cache(mut tc.resolved_fn_value_names, n)

355- extend_bool_cache(mut tc.resolved_fn_value_set, n)

356389 extend_type_cache(mut tc.expr_type_values, n)

357390 extend_bool_cache(mut tc.expr_type_set, n)

358391 extend_bool_cache(mut tc.checking_nodes, n)

@@ -449,8 +482,9 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {

449482 tc.scope_pool_index = 0

450483 tc.reset_node_caches(a.nodes.len)

451484 tc.type_cache = &TypeCache{

452- parse_entries: map[string]Type{}

453- c_entries: map[string]string{}

485+ parse_entries: map[string]Type{}

486+ c_entries: map[string]string{}

487+ ierror_compat_entries: map[string]int{}

454488 }

455489 for node in a.nodes {

456490 if node.kind == .struct_decl && node.value == 'string' {

@@ -492,6 +526,8 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {

492526 if qname !in tc.structs {

493527 tc.structs[qname] = []StructField{}

494528 }

529+ tc.struct_modules[qname] = tc.cur_module

530+ tc.struct_files[qname] = tc.cur_file

495531 if node.generic_params.len > 0 {

496532 tc.struct_generic_params[qname] = node.generic_params.clone()

497533 if qname != node.value {

@@ -512,7 +548,14 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {

512548 v := a.child_node(&node, i)

513549 variants << tc.qualify_name(v.value)

514550 }

515- tc.sum_types[tc.qualify_name(node.value)] = variants

551+ qname := tc.qualify_name(node.value)

552+ tc.sum_types[qname] = variants

553+ if node.generic_params.len > 0 {

554+ tc.sum_generic_params[qname] = node.generic_params.clone()

555+ if qname != node.value {

556+ tc.sum_generic_params[node.value] = node.generic_params.clone()

557+ }

558+ }

516559 } else if node.typ.len > 0 {

517560 qname := tc.qualify_name(node.value)

518561 tc.type_aliases[qname] = tc.qualify_type_text(node.typ)

@@ -587,14 +630,22 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {

587630 .struct_decl {

588631 mut fields := []StructField{}

589632 mut field_c_abi_fns := map[string]string{}

633+ mut shadows_builtin_error_embed := false

590634 for i in 0 .. node.children_count {

591635 f := a.child_node(&node, i)

592636 if f.kind != .field_decl {

593637 continue

594638 }

595639 field_typ := if f.typ.len > 0 { f.typ } else { f.value }

640+ field_is_embed := source_field_decl_is_embed(f, field_typ)

641+ shadows_builtin_error := field_is_embed

642+ && field_typ in ['Error', 'MessageError']

643+ && tc.unqualified_type_name_shadows_builtin_in_scope(field_typ, tc.cur_file, tc.cur_module)

644+ if shadows_builtin_error {

645+ shadows_builtin_error_embed = true

646+ }

596647 mut typ := tc.parse_type(field_typ)

597- if f.value == field_typ {

648+ if f.value == field_typ || shadows_builtin_error {

598649 typ = tc.resolve_known_field_type(field_typ, typ)

599650 }

600651 if c_abi_fn := tc.c_abi_fn_ptr_type_for_type_text(field_typ) {

@@ -607,6 +658,11 @@ pub fn (mut tc TypeChecker) collect(a &flat.FlatAst) {

607658 }

608659 qname := tc.qualify_name(node.value)

609660 tc.structs[qname] = fields

661+ tc.struct_modules[qname] = tc.cur_module

662+ tc.struct_files[qname] = tc.cur_file

663+ if shadows_builtin_error_embed {

664+ tc.struct_error_embeds_shadow_builtin[qname] = true

665+ }

610666 for field_name, c_abi_fn in field_c_abi_fns {

611667 tc.struct_field_c_abi_fns[struct_field_c_abi_key(qname, field_name)] = c_abi_fn

612668 }

@@ -1161,6 +1217,25 @@ fn (mut tc TypeChecker) register_c_variadic_fn(name string) {

11611217 }

11621218}

11631219

1220+fn (mut tc TypeChecker) insert_fn_param_binding(p flat.Node) {

1221+ if p.kind != .param || p.value.len == 0 {

1222+ return

1223+ }

1224+ typ := tc.parse_type(p.typ)

1225+ tc.cur_scope.insert(p.value, typ)

1226+ if p.is_mut {

1227+ tc.cur_fn_mut_param_base_types[p.value] = mut_param_base_type(typ)

1228+ tc.cur_fn_mut_param_scope_depths[p.value] = tc.cur_scope_depth()

1229+ }

1230+}

1231+

1232+fn mut_param_base_type(typ Type) Type {

1233+ if typ is Pointer {

1234+ return typ.base_type

1235+ }

1236+ return typ

1237+}

1238+

11641239// annotate_types performs a scope-aware walk over every function body, tracking

11651240// local variable types as they are declared, and records complex/contextual

11661241// expression types. This mirrors what the v2 transformer relies on: the type

@@ -1190,13 +1265,15 @@ pub fn (mut tc TypeChecker) annotate_types_with_used(used_fns map[string]bool) {

11901265 if !tc.should_annotate_fn(node, used_fns) {

11911266 continue

11921267 }

1268+ mut saved_mut_params := tc.cur_fn_mut_param_base_types.move()

1269+ mut saved_mut_param_depths := tc.cur_fn_mut_param_scope_depths.move()

1270+ tc.cur_fn_mut_param_base_types = map[string]Type{}

1271+ tc.cur_fn_mut_param_scope_depths = map[string]int{}

11931272 tc.cur_scope = tc.file_scope

11941273 tc.push_scope()

11951274 for pi in 0 .. node.children_count {

11961275 p := tc.a.child_node(&node, pi)

1197- if p.kind == .param && p.value.len > 0 {

1198- tc.cur_scope.insert(p.value, tc.parse_type(p.typ))

1199- }

1276+ tc.insert_fn_param_binding(p)

12001277 }

12011278 tc.insert_implicit_veb_ctx(node)

12021279 for i in 0 .. node.children_count {

@@ -1206,6 +1283,8 @@ pub fn (mut tc TypeChecker) annotate_types_with_used(used_fns map[string]bool) {

12061283 }

12071284 }

12081285 tc.pop_scope()

1286+ tc.cur_fn_mut_param_base_types = saved_mut_params.move()

1287+ tc.cur_fn_mut_param_scope_depths = saved_mut_param_depths.move()

12091288 }

12101289 }

12111290}

@@ -1528,12 +1607,26 @@ pub fn (tc &TypeChecker) resolved_call_name(id flat.NodeId) ?string {

15281607// resolved_fn_value_name returns the checker-resolved function name for a function value node.

15291608pub fn (tc &TypeChecker) resolved_fn_value_name(id flat.NodeId) ?string {

15301609 idx := int(id)

1531- if idx >= 0 && idx < tc.resolved_fn_value_set.len && tc.resolved_fn_value_set[idx] {

1532- return tc.resolved_fn_value_names[idx]

1610+ if idx >= 0 {

1611+ if name := tc.resolved_fn_value_names[idx] {

1612+ return name

1613+ }

15331614 }

15341615 return none

15351616}

15361617

1618+// copy_cloned_resolution copies checker-owned call/function-value resolution metadata

1619+// from an original node to a transform-created clone. The call cache remains dense; the

1620+// function-value cache is intentionally sparse because only a tiny subset of nodes use it.

1621+pub fn (mut tc TypeChecker) copy_cloned_resolution(src_id flat.NodeId, dst_id flat.NodeId) {

1622+ if name := tc.resolved_call_name(src_id) {

1623+ tc.remember_resolved_call(dst_id, name)

1624+ }

1625+ if name := tc.resolved_fn_value_name(src_id) {

1626+ tc.remember_resolved_fn_value(dst_id, name)

1627+ }

1628+}

1629+

15371630// resolve_fn_value_name_for_expected resolves and records a function value in an expected FnType context.

15381631fn (mut tc TypeChecker) resolve_fn_value_name_for_expected(id flat.NodeId, expected Type) ?string {

15391632 if name := tc.resolved_fn_value_name(id) {

@@ -1572,13 +1665,7 @@ fn (mut tc TypeChecker) remember_resolved_fn_value(id flat.NodeId, name string)

15721665 if idx < 0 {

15731666 return

15741667 }

1575- if idx >= tc.resolved_fn_value_names.len {

1576- tc.extend_node_caches(tc.a.nodes.len)

1577- }

1578- if idx < tc.resolved_fn_value_names.len {

1579- tc.resolved_fn_value_names[idx] = name

1580- tc.resolved_fn_value_set[idx] = true

1581- }

1668+ tc.resolved_fn_value_names[idx] = name

15821669}

15831670

15841671fn (mut tc TypeChecker) remember_resolved_fn_value_chain(id flat.NodeId, name string) {

@@ -1657,6 +1744,10 @@ pub fn (mut tc TypeChecker) check_semantics() {

16571744 tc.check_const_field_values(node)

16581745 }

16591746 .fn_decl {

1747+ mut saved_mut_params := tc.cur_fn_mut_param_base_types.move()

1748+ mut saved_mut_param_depths := tc.cur_fn_mut_param_scope_depths.move()

1749+ tc.cur_fn_mut_param_base_types = map[string]Type{}

1750+ tc.cur_fn_mut_param_scope_depths = map[string]int{}

16601751 tc.check_decl_type_strings(flat.NodeId(i), node)

16611752 tc.cur_fn_ret_type = tc.parse_type(node.typ)

16621753 tc.cur_fn_node_id = i

@@ -1665,9 +1756,7 @@ pub fn (mut tc TypeChecker) check_semantics() {

16651756 tc.push_scope()

16661757 for pi in 0 .. node.children_count {

16671758 p := tc.a.child_node(&node, pi)

1668- if p.kind == .param && p.value.len > 0 {

1669- tc.cur_scope.insert(p.value, tc.parse_type(p.typ))

1670- }

1759+ tc.insert_fn_param_binding(p)

16711760 }

16721761 tc.insert_implicit_veb_ctx(node)

16731762 tc.check_fn_body(node)

@@ -1682,6 +1771,8 @@ pub fn (mut tc TypeChecker) check_semantics() {

16821771 }

16831772 tc.pop_scope()

16841773 tc.cur_fn_ret_type = Type(void_)

1774+ tc.cur_fn_mut_param_base_types = saved_mut_params.move()

1775+ tc.cur_fn_mut_param_scope_depths = saved_mut_param_depths.move()

16851776 }

16861777 .c_fn_decl {

16871778 if tc.reject_unsupported_generics {

@@ -2992,6 +3083,10 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) {

29923083 tc.check_select_stmt(node)

29933084 return

29943085 }

3086+ if node.kind == .infix {

3087+ tc.check_infix(id, node)

3088+ return

3089+ }

29953090 // A method value stored in a container escapes the single-use guarantee of its per-site

29963091 // static receiver, so reject `[obj.method]` / `arr << obj.method` / `{'k': obj.method}`.

29973092 if node.kind == .array_literal {

@@ -3003,15 +3098,92 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) {

30033098 for j := 1; j < node.children_count; j += 2 {

30043099 tc.reject_stored_method_value(tc.a.child(&node, j))

30053100 }

3006- } else if node.kind == .infix && node.op == .left_shift && node.children_count >= 2 {

3101+ }

3102+

3103+ for i in 0 .. node.children_count {

3104+ tc.check_node(tc.a.child(&node, i))

3105+ }

3106+}

3107+

3108+// check_infix validates type-sensitive infix operations that would otherwise reach CGen

3109+// as raw helper calls with incompatible arguments.

3110+fn (mut tc TypeChecker) check_infix(id flat.NodeId, node flat.Node) {

3111+ for i in 0 .. node.children_count {

3112+ tc.check_node(tc.a.child(&node, i))

3113+ }

3114+ if node.op == .left_shift && node.children_count >= 2 {

30073115 if unwrap_pointer(tc.resolve_type(tc.a.child(&node, 0))) is Array {

30083116 tc.reject_stored_method_value(tc.a.child(&node, 1))

30093117 }

30103118 }

3119+ if node.op != .plus || node.children_count < 2 || !tc.should_diagnose(id) {

3120+ return

3121+ }

3122+ lhs_id := tc.a.child(&node, 0)

3123+ rhs_id := tc.a.child(&node, 1)

3124+ lhs_type := tc.infix_read_type(lhs_id)

3125+ rhs_type := tc.infix_read_type(rhs_id)

3126+ lhs_is_string := type_is_string_like(lhs_type)

3127+ rhs_is_string := type_is_string_like(rhs_type)

3128+ if lhs_is_string == rhs_is_string {

3129+ return

3130+ }

3131+ if lhs_type is Unknown || rhs_type is Unknown {

3132+ return

3133+ }

3134+ tc.record_error(.assignment_mismatch,

3135+ 'operator `+` cannot concatenate `${lhs_type.name()}` and `${rhs_type.name()}`', id)

3136+}

30113137

3012- for i in 0 .. node.children_count {

3013- tc.check_node(tc.a.child(&node, i))

3138+fn (tc &TypeChecker) infix_read_type(id flat.NodeId) Type {

3139+ typ := tc.resolve_type(id)

3140+ if int(id) < 0 {

3141+ return typ

3142+ }

3143+ node := tc.a.nodes[int(id)]

3144+ if node.kind == .ident {

3145+ if base := tc.mut_param_base_for_current_ident(node.value, typ) {

3146+ return base

3147+ }

3148+ }

3149+ return typ

3150+}

3151+

3152+fn (tc &TypeChecker) mut_param_base_for_current_ident(name string, typ Type) ?Type {

3153+ if typ !is Pointer {

3154+ return none

3155+ }

3156+ param_depth := tc.cur_fn_mut_param_scope_depths[name] or { return none }

3157+ binding_depth := tc.current_binding_depth(name) or { return none }

3158+ if binding_depth != param_depth {

3159+ return none

3160+ }

3161+ return tc.cur_fn_mut_param_base_types[name] or { return none }

3162+}

3163+

3164+fn (tc &TypeChecker) current_binding_depth(name string) ?int {

3165+ mut depth := tc.cur_scope_depth()

3166+ mut scope := tc.cur_scope

3167+ for scope != unsafe { nil } {

3168+ for i := scope.names.len - 1; i >= 0; i-- {

3169+ if scope.names[i] == name {

3170+ return depth

3171+ }

3172+ }

3173+ scope = scope.parent

3174+ depth--

3175+ }

3176+ return none

3177+}

3178+

3179+fn type_is_string_like(typ Type) bool {

3180+ if typ is String {

3181+ return true

30143182 }

3183+ if typ is Alias {

3184+ return type_is_string_like(typ.base_type)

3185+ }

3186+ return false

30153187}

30163188

30173189// check_select_stmt validates a `select { ... }` statement. A receive branch

@@ -3088,13 +3260,15 @@ fn (mut tc TypeChecker) check_or_expr(node flat.Node) {

30883260// check_fn_literal validates check fn literal state for types.

30893261fn (mut tc TypeChecker) check_fn_literal(node flat.Node) {

30903262 saved_ret := tc.cur_fn_ret_type

3263+ mut saved_mut_params := tc.cur_fn_mut_param_base_types.move()

3264+ mut saved_mut_param_depths := tc.cur_fn_mut_param_scope_depths.move()

3265+ tc.cur_fn_mut_param_base_types = map[string]Type{}

3266+ tc.cur_fn_mut_param_scope_depths = map[string]int{}

30913267 tc.cur_fn_ret_type = tc.parse_type(node.typ)

30923268 tc.push_scope()

30933269 for i in 0 .. node.children_count {

30943270 child := tc.a.child_node(&node, i)

3095- if child.kind == .param && child.value.len > 0 {

3096- tc.cur_scope.insert(child.value, tc.parse_type(child.typ))

3097- }

3271+ tc.insert_fn_param_binding(child)

30983272 }

30993273 for i in 0 .. node.children_count {

31003274 child_id := tc.a.child(&node, i)

@@ -3106,6 +3280,8 @@ fn (mut tc TypeChecker) check_fn_literal(node flat.Node) {

31063280 }

31073281 tc.pop_scope()

31083282 tc.cur_fn_ret_type = saved_ret

3283+ tc.cur_fn_mut_param_base_types = saved_mut_params.move()

3284+ tc.cur_fn_mut_param_scope_depths = saved_mut_param_depths.move()

31093285}

31103286

31113287// check_lambda_expr validates check lambda expr state for types.

@@ -3229,6 +3405,9 @@ fn (mut tc TypeChecker) check_decl_assign(id flat.NodeId, node flat.Node) {

32293405 if tc.check_multi_return_decl_assign(id, node) {

32303406 return

32313407 }

3408+ if tc.check_assignment_marker(id, node) {

3409+ return

3410+ }

32323411 mut i := 0

32333412 for i + 1 < node.children_count {

32343413 lhs_id := tc.a.child(&node, i)

@@ -3470,16 +3649,20 @@ fn (mut tc TypeChecker) check_assign(id flat.NodeId, node flat.Node) {

34703649 if tc.check_multi_return_assign(id, node) {

34713650 return

34723651 }

3652+ if tc.check_assignment_marker(id, node) {

3653+ return

3654+ }

34733655 mut i := 0

34743656 for i + 1 < node.children_count {

34753657 lhs_id := tc.a.child(&node, i)

34763658 rhs_id := tc.a.child(&node, i + 1)

34773659 lhs_type := tc.resolve_lvalue_type(lhs_id)

3660+ expected_type := tc.assignment_expected_type(lhs_id, lhs_type)

34783661 tc.check_node(rhs_id)

3479- rhs_type := tc.resolve_expr(rhs_id, lhs_type)

3480- if !tc.type_compatible(rhs_type, lhs_type) {

3662+ rhs_type := tc.resolve_expr(rhs_id, expected_type)

3663+ if !tc.type_compatible(rhs_type, expected_type) {

34813664 tc.type_mismatch(.assignment_mismatch,

3482- 'cannot assign `${rhs_type.name()}` to `${lhs_type.name()}`', id)

3665+ 'cannot assign `${rhs_type.name()}` to `${expected_type.name()}`', id)

34833666 }

34843667 if node.kind in [.assign, .selector_assign, .index_assign] {

34853668 if tc.expr_is_method_value(rhs_id) && !tc.lvalue_is_local_var(lhs_id) {

@@ -3496,6 +3679,14 @@ fn (mut tc TypeChecker) check_assign(id flat.NodeId, node flat.Node) {

34963679 }

34973680}

34983681

3682+fn (mut tc TypeChecker) check_assignment_marker(id flat.NodeId, node flat.Node) bool {

3683+ if node.value.starts_with('for init assignment mismatch:') {

3684+ tc.record_error(.assignment_mismatch, node.value, id)

3685+ return true

3686+ }

3687+ return false

3688+}

3689+

34993690// index_assign_lhs_is_map supports index assign lhs is map handling for TypeChecker.

35003691fn (tc &TypeChecker) index_assign_lhs_is_map(node flat.Node) bool {

35013692 if node.children_count == 0 {

@@ -3534,9 +3725,10 @@ fn (mut tc TypeChecker) check_multi_return_assign(id flat.NodeId, node flat.Node

35343725 }

35353726 for i, lhs_id in lhs_ids {

35363727 lhs_type := tc.resolve_lvalue_type(lhs_id)

3537- if !tc.type_compatible(rhs_type.types[i], lhs_type) {

3728+ expected_type := tc.assignment_expected_type(lhs_id, lhs_type)

3729+ if !tc.type_compatible(rhs_type.types[i], expected_type) {

35383730 tc.type_mismatch(.assignment_mismatch,

3539- 'cannot assign `${rhs_type.types[i].name()}` to `${lhs_type.name()}`', id)

3731+ 'cannot assign `${rhs_type.types[i].name()}` to `${expected_type.name()}`', id)

35403732 }

35413733 }

35423734 return true

@@ -3563,6 +3755,19 @@ fn (mut tc TypeChecker) check_postfix(id flat.NodeId, node flat.Node) {

35633755 }

35643756}

35653757

3758+fn (tc &TypeChecker) assignment_expected_type(lhs_id flat.NodeId, lhs_type Type) Type {

3759+ if int(lhs_id) < 0 {

3760+ return lhs_type

3761+ }

3762+ lhs := tc.a.nodes[int(lhs_id)]

3763+ if lhs.kind == .ident {

3764+ if base := tc.mut_param_base_for_current_ident(lhs.value, lhs_type) {

3765+ return base

3766+ }

3767+ }

3768+ return lhs_type

3769+}

3770+

35663771// resolve_lvalue_type resolves resolve lvalue type information for types.

35673772fn (mut tc TypeChecker) resolve_lvalue_type(lhs_id flat.NodeId) Type {

35683773 if int(lhs_id) < 0 {

@@ -3657,13 +3862,104 @@ fn (mut tc TypeChecker) check_return(id flat.NodeId, node flat.Node) {

36573862 }

36583863 child_id := tc.a.child(&node, 0)

36593864 tc.check_node(child_id)

3865+ if expected is ResultType {

3866+ if bad_type := tc.invalid_ierror_return_expr_type_name(child_id, expected) {

3867+ tc.record_error(.return_mismatch,

3868+ 'cannot return `${bad_type}` as `${Type(expected).name()}`', id)

3869+ return

3870+ }

3871+ }

36603872 actual := tc.resolve_expr(child_id, expected)

36613873 if !tc.type_compatible(actual, expected) {

3874+ if expected is ResultType {

3875+ if is_ierror_type(actual) || tc.type_compatible_with_ierror_payload(actual) {

3876+ return

3877+ }

3878+ }

36623879 tc.type_mismatch(.return_mismatch,

36633880 'cannot return `${actual.name()}` as `${expected.name()}`', id)

36643881 }

36653882}

36663883

3884+fn (tc &TypeChecker) invalid_ierror_return_expr_type_name(id flat.NodeId, expected ResultType) ?string {

3885+ if !tc.valid_node_id(id) {

3886+ return none

3887+ }

3888+ raw_type := tc.resolve_type(id)

3889+ if tc.type_compatible(raw_type, expected) {

3890+ return none

3891+ }

3892+ if tc.type_compatible(raw_type, expected.base_type) {

3893+ return none

3894+ }

3895+ node := tc.a.nodes[int(id)]

3896+ match node.kind {

3897+ .expr_stmt, .paren {

3898+ if node.children_count > 0 {

3899+ return tc.invalid_ierror_return_expr_type_name(tc.a.child(&node, 0), expected)

3900+ }

3901+ }

3902+ .prefix {

3903+ if node.op == .amp && node.children_count > 0 {

3904+ return tc.invalid_ierror_return_expr_type_name(tc.a.child(&node, 0), expected)

3905+ }

3906+ }

3907+ .struct_init {

3908+ concrete := tc.resolve_unqualified_builtin_error_struct_name(node.value) or {

3909+ tc.resolve_selective_import_type_symbol(node.value) or {

3910+ tc.qualify_name(node.value)

3911+ }

3912+ }

3913+ if !tc.named_type_compatible_with_ierror(concrete) {

3914+ return concrete

3915+ }

3916+ }

3917+ .match_stmt {

3918+ for i in 1 .. node.children_count {

3919+ branch_id := tc.a.child(&node, i)

3920+ if !tc.valid_node_id(branch_id) || tc.a.nodes[int(branch_id)].kind != .match_branch {

3921+ continue

3922+ }

3923+ tail := tc.branch_tail_expr_id(branch_id)

3924+ if bad_type := tc.invalid_ierror_return_expr_type_name(tail, expected) {

3925+ return bad_type

3926+ }

3927+ }

3928+ }

3929+ .if_expr {

3930+ if node.children_count > 1 {

3931+ then_tail := tc.branch_tail_expr_id(tc.a.child(&node, 1))

3932+ if bad_type := tc.invalid_ierror_return_expr_type_name(then_tail, expected) {

3933+ return bad_type

3934+ }

3935+ }

3936+ if node.children_count > 2 {

3937+ else_id := tc.a.child(&node, 2)

3938+ else_tail := if tc.valid_node_id(else_id)

3939+ && tc.a.nodes[int(else_id)].kind == .if_expr {

3940+ else_id

3941+ } else {

3942+ tc.branch_tail_expr_id(else_id)

3943+ }

3944+ if bad_type := tc.invalid_ierror_return_expr_type_name(else_tail, expected) {

3945+ return bad_type

3946+ }

3947+ }

3948+ }

3949+ else {

3950+ if is_ierror_type(raw_type) || tc.type_compatible_with_ierror_payload(raw_type) {

3951+ return none

3952+ }

3953+ if raw_type is Unknown || raw_type is Void {

3954+ return none

3955+ }

3956+ return raw_type.name()

3957+ }

3958+ }

3959+

3960+ return none

3961+}

3962+

36673963fn multi_return_payload_type(typ Type) ?MultiReturn {

36683964 if typ is MultiReturn {

36693965 return typ

@@ -3685,6 +3981,10 @@ fn multi_return_payload_type(typ Type) ?MultiReturn {

36853981

36863982// check_call validates check call state for types.

36873983fn (mut tc TypeChecker) check_call(id flat.NodeId, node flat.Node) {

3984+ if sum_name := tc.sum_constructor_call_name(node) {

3985+ tc.check_sum_constructor_call(id, node, sum_name)

3986+ return

3987+ }

36883988 if info := tc.resolve_call_info(id, node) {

36893989 if info.name.len > 0 && !is_array_dsl_call_name(info.name) {

36903990 tc.remember_resolved_call(id, info.name)

@@ -3708,99 +4008,250 @@ fn (mut tc TypeChecker) check_call(id flat.NodeId, node flat.Node) {

37084008 }

37094009}

37104010

3711-// should_diagnose reports whether should diagnose applies in types.

3712-fn (tc &TypeChecker) should_diagnose(id flat.NodeId) bool {

3713- if int(id) < 0 || int(id) < tc.a.user_code_start {

3714- return false

3715- }

3716- if int(id) < tc.a.nodes.len && !tc.a.nodes[int(id)].pos.is_valid() && !tc.diagnose_unknown_calls {

3717- return false

3718- }

3719- if tc.diagnostic_files.len == 0 {

3720- return true

3721- }

3722- return tc.cur_file in tc.diagnostic_files

3723-}

3724-

3725-fn (tc &TypeChecker) should_diagnose_unsupported_generic(id flat.NodeId) bool {

3726- if tc.should_diagnose(id) {

3727- return true

3728- }

3729- if int(id) < 0 || int(id) < tc.a.user_code_start {

3730- return false

4011+fn (mut tc TypeChecker) check_sum_constructor_call(id flat.NodeId, node flat.Node, sum_name string) {

4012+ expected := Type(SumType{

4013+ name: sum_name

4014+ })

4015+ tc.remember_expr_type(id, expected)

4016+ actual_count := node.children_count - 1

4017+ if actual_count != 1 {

4018+ if tc.should_diagnose(id) {

4019+ tc.record_error(.call_arg_mismatch,

4020+ 'argument count mismatch for `${tc.call_display_name(node)}`: expected 1, got ${actual_count}',

4021+ id)

4022+ }

4023+ for i in 1 .. node.children_count {

4024+ tc.check_node(tc.call_arg_value(tc.a.child(&node, i)))

4025+ }

4026+ return

37314027 }

3732- if tc.diagnostic_files.len == 0 {

3733- return false

4028+ arg_id := tc.call_arg_value(tc.a.child(&node, 1))

4029+ tc.check_node(arg_id)

4030+ arg_type := tc.resolve_expr(arg_id, expected)

4031+ if !tc.type_compatible(arg_type, expected) {

4032+ tc.type_mismatch(.call_arg_mismatch,

4033+ 'cannot use `${arg_type.name()}` as sum constructor payload; expected variant of `${sum_name}`',

4034+ id)

37344035 }

3735- return tc.diagnostic_files['generic:' + tc.cur_file]

3736-}

3737-

3738-// should_diagnose_unknown_call reports whether should diagnose unknown call applies in types.

3739-fn (tc &TypeChecker) should_diagnose_unknown_call(id flat.NodeId) bool {

3740- return tc.diagnose_unknown_calls && tc.should_diagnose(id)

37414036}

37424037

3743-// resolve_call_info resolves resolve call info information for types.

3744-fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallInfo {

4038+fn (tc &TypeChecker) sum_constructor_call_name(node flat.Node) ?string {

37454039 if node.children_count == 0 {

37464040 return none

37474041 }

3748- fn_node := tc.a.child_node(&node, 0)

3749- if info := tc.resolve_generic_call_info(fn_node) {

3750- return info

3751- }

3752- if fn_node.kind == .selector {

3753- base_id := tc.a.child(fn_node, 0)

3754- base_node := tc.a.nodes[int(base_id)]

3755- if base_node.kind == .ident && base_node.value == 'C' {

3756- return none

3757- }

3758- if base_node.kind == .ident {

3759- if resolved_mod := tc.resolve_import_alias(base_node.value) {

3760- mod_name := '${resolved_mod}.${fn_node.value}'

3761- if mod_name in tc.fn_ret_types {

3762- return tc.call_info(mod_name, false)

3763- }

3764- }

3765- qbase := tc.qualify_name(base_node.value)

3766- static_name := '${qbase}.${fn_node.value}'

3767- if static_name in tc.fn_ret_types && (qbase in tc.structs

3768- || qbase in tc.enum_names || qbase in tc.sum_types

3769- || qbase in tc.interface_names || qbase in tc.type_aliases) {

3770- // `qbase in tc.type_aliases` covers static methods on a type alias,

3771- // e.g. `fn SimdFloat4.new()` for `type SimdFloat4 = vec.Vec4[f32]`.

3772- return tc.call_info(static_name, false)

3773- }

3774- if fn_node.value == 'zero' && qbase in tc.flag_enums {

3775- return CallInfo{

3776- name: ''

3777- params: []Type{}

3778- return_type: Type(Enum{

3779- name: qbase

3780- is_flag: true

3781- })

3782- params_known: true

4042+ callee_id := tc.a.child(&node, 0)

4043+ callee := tc.a.nodes[int(callee_id)]

4044+ match callee.kind {

4045+ .ident {

4046+ if resolved := tc.resolve_selective_import_type_symbol(callee.value) {

4047+ if sum_name := tc.known_sum_constructor_name(resolved) {

4048+ return sum_name

37834049 }

37844050 }

3785- } else if base_node.kind == .selector {

3786- if method_name := tc.module_const_receiver_method_name(base_node, fn_node.value) {

3787- return tc.call_info(method_name, true)

3788- }

3789- inner := tc.a.child_node(base_node, 0)

3790- if inner.kind == .ident {

3791- mod_name := tc.resolve_import_alias(inner.value) or { inner.value }

3792- full_name := '${mod_name}.${base_node.value}.${fn_node.value}'

3793- if full_name in tc.fn_ret_types {

3794- return tc.call_info(full_name, false)

3795- }

4051+ return tc.known_sum_constructor_name(callee.value)

4052+ }

4053+ .selector {

4054+ base := tc.a.child_node(callee, 0)

4055+ if base.kind == .ident {

4056+ mod_name := tc.resolve_import_alias(base.value) or { base.value }

4057+ return tc.known_sum_constructor_name('${mod_name}.${callee.value}')

37964058 }

37974059 }

3798- if fn_typ := tc.selector_fn_type(fn_node) {

3799- return CallInfo{

3800- name: ''

3801- params: fn_typ.params.clone()

3802- return_type: fn_typ.return_type

3803- params_known: true

4060+ .index, .prefix, .array_init {

4061+ type_name := tc.type_expr_name(callee_id)

4062+ if type_name.len > 0 {

4063+ return tc.known_sum_constructor_name(type_name)

4064+ }

4065+ }

4066+ else {}

4067+ }

4068+

4069+ return none

4070+}

4071+

4072+fn (tc &TypeChecker) type_expr_name(id flat.NodeId) string {

4073+ if int(id) < 0 {

4074+ return ''

4075+ }

4076+ node := tc.a.nodes[int(id)]

4077+ match node.kind {

4078+ .ident {

4079+ if resolved := tc.resolve_selective_import_type_symbol(node.value) {

4080+ return resolved

4081+ }

4082+ return node.value

4083+ }

4084+ .selector {

4085+ if node.children_count == 0 {

4086+ return node.value

4087+ }

4088+ base_id := tc.a.child(&node, 0)

4089+ base_node := tc.a.nodes[int(base_id)]

4090+ base := if base_node.kind == .ident {

4091+ tc.resolve_import_alias(base_node.value) or { tc.type_expr_name(base_id) }

4092+ } else {

4093+ tc.type_expr_name(base_id)

4094+ }

4095+ if base.len == 0 {

4096+ return node.value

4097+ }

4098+ return '${base}.${node.value}'

4099+ }

4100+ .index {

4101+ if node.children_count < 2 || node.value == 'range' {

4102+ return ''

4103+ }

4104+ base := tc.type_expr_name(tc.a.child(&node, 0))

4105+ if base.len == 0 {

4106+ return ''

4107+ }

4108+ mut args := []string{}

4109+ for i in 1 .. node.children_count {

4110+ arg := tc.type_expr_name(tc.a.child(&node, i))

4111+ if arg.len == 0 {

4112+ return ''

4113+ }

4114+ args << arg

4115+ }

4116+ return '${base}[${args.join(', ')}]'

4117+ }

4118+ .array_init {

4119+ if node.value.len == 0 {

4120+ return ''

4121+ }

4122+ return '[]${node.value}'

4123+ }

4124+ .prefix {

4125+ if node.children_count == 0 {

4126+ return ''

4127+ }

4128+ child := tc.type_expr_name(tc.a.child(&node, 0))

4129+ if child.len == 0 {

4130+ return ''

4131+ }

4132+ if node.op == .amp {

4133+ return '&${child}'

4134+ }

4135+ return child

4136+ }

4137+ else {

4138+ return ''

4139+ }

4140+ }

4141+}

4142+

4143+fn (tc &TypeChecker) known_sum_constructor_name(name string) ?string {

4144+ if name in tc.sum_types {

4145+ return name

4146+ }

4147+ base_name := strip_generic_args_name(name)

4148+ if base_name in tc.sum_types {

4149+ return name

4150+ }

4151+ qname := tc.qualify_name(name)

4152+ if qname in tc.sum_types {

4153+ return qname

4154+ }

4155+ qbase := strip_generic_args_name(qname)

4156+ if qbase in tc.sum_types {

4157+ return qname

4158+ }

4159+ return none

4160+}

4161+

4162+// should_diagnose reports whether should diagnose applies in types.

4163+fn (tc &TypeChecker) should_diagnose(id flat.NodeId) bool {

4164+ if int(id) < 0 || int(id) < tc.a.user_code_start {

4165+ return false

4166+ }

4167+ if int(id) < tc.a.nodes.len && !tc.a.nodes[int(id)].pos.is_valid() && !tc.diagnose_unknown_calls {

4168+ return false

4169+ }

4170+ if tc.diagnostic_files.len == 0 {

4171+ return true

4172+ }

4173+ return tc.cur_file in tc.diagnostic_files

4174+}

4175+

4176+fn (tc &TypeChecker) should_diagnose_unsupported_generic(id flat.NodeId) bool {

4177+ if tc.should_diagnose(id) {

4178+ return true

4179+ }

4180+ if int(id) < 0 || int(id) < tc.a.user_code_start {

4181+ return false

4182+ }

4183+ if tc.diagnostic_files.len == 0 {

4184+ return false

4185+ }

4186+ return tc.diagnostic_files['generic:' + tc.cur_file]

4187+}

4188+

4189+// should_diagnose_unknown_call reports whether should diagnose unknown call applies in types.

4190+fn (tc &TypeChecker) should_diagnose_unknown_call(id flat.NodeId) bool {

4191+ return tc.diagnose_unknown_calls && tc.should_diagnose(id)

4192+}

4193+

4194+// resolve_call_info resolves resolve call info information for types.

4195+fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallInfo {

4196+ if node.children_count == 0 {

4197+ return none

4198+ }

4199+ fn_node := tc.a.child_node(&node, 0)

4200+ if info := tc.resolve_generic_call_info(fn_node) {

4201+ return info

4202+ }

4203+ if fn_node.kind == .selector {

4204+ base_id := tc.a.child(fn_node, 0)

4205+ base_node := tc.a.nodes[int(base_id)]

4206+ if base_node.kind == .ident && base_node.value == 'C' {

4207+ return none

4208+ }

4209+ if base_node.kind == .ident {

4210+ if resolved_mod := tc.resolve_import_alias(base_node.value) {

4211+ mod_name := '${resolved_mod}.${fn_node.value}'

4212+ if mod_name in tc.fn_ret_types {

4213+ return tc.call_info(mod_name, false)

4214+ }

4215+ }

4216+ qbase := tc.qualify_name(base_node.value)

4217+ static_name := '${qbase}.${fn_node.value}'

4218+ if static_name in tc.fn_ret_types && (qbase in tc.structs

4219+ || qbase in tc.enum_names || qbase in tc.sum_types

4220+ || qbase in tc.interface_names || qbase in tc.type_aliases) {

4221+ // `qbase in tc.type_aliases` covers static methods on a type alias,

4222+ // e.g. `fn SimdFloat4.new()` for `type SimdFloat4 = vec.Vec4[f32]`.

4223+ return tc.call_info(static_name, false)

4224+ }

4225+ if fn_node.value == 'zero' && qbase in tc.flag_enums {

4226+ return CallInfo{

4227+ name: ''

4228+ params: []Type{}

4229+ return_type: Type(Enum{

4230+ name: qbase

4231+ is_flag: true

4232+ })

4233+ params_known: true

4234+ }

4235+ }

4236+ } else if base_node.kind == .selector {

4237+ if method_name := tc.module_const_receiver_method_name(base_node, fn_node.value) {

4238+ return tc.call_info(method_name, true)

4239+ }

4240+ inner := tc.a.child_node(base_node, 0)

4241+ if inner.kind == .ident {

4242+ mod_name := tc.resolve_import_alias(inner.value) or { inner.value }

4243+ full_name := '${mod_name}.${base_node.value}.${fn_node.value}'

4244+ if full_name in tc.fn_ret_types {

4245+ return tc.call_info(full_name, false)

4246+ }

4247+ }

4248+ }

4249+ if fn_typ := tc.selector_fn_type(fn_node) {

4250+ return CallInfo{

4251+ name: ''

4252+ params: fn_typ.params.clone()

4253+ return_type: fn_typ.return_type

4254+ params_known: true

38044255 }

38054256 }

38064257 base_type := tc.resolve_type(base_id)

@@ -3825,7 +4276,16 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI

38254276 return CallInfo{

38264277 name: 'array_clone'

38274278 params: tarr1(base_type)

3828- return_type: base_type

4279+ return_type: clean

4280+ has_receiver: true

4281+ params_known: true

4282+ }

4283+ }

4284+ if clean is Array && fn_node.value == 'reverse' {

4285+ return CallInfo{

4286+ name: 'array.reverse'

4287+ params: tarr1(base_type)

4288+ return_type: clean

38294289 has_receiver: true

38304290 params_known: true

38314291 }

@@ -3842,7 +4302,7 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI

38424302 if clean is Map {

38434303 if fn_node.value == 'keys' {

38444304 return CallInfo{

3845- name: 'map.keys'

4305+ name: ''

38464306 params: tarr1(base_type)

38474307 return_type: Type(Array{

38484308 elem_type: clean.key_type

@@ -3853,7 +4313,7 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI

38534313 }

38544314 if fn_node.value == 'values' {

38554315 return CallInfo{

3856- name: 'map.values'

4316+ name: ''

38574317 params: tarr1(base_type)

38584318 return_type: Type(Array{

38594319 elem_type: clean.value_type

@@ -3862,10 +4322,6 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI

38624322 params_known: true

38634323 }

38644324 }

3865- map_method := 'map.${fn_node.value}'

3866- if map_method in tc.fn_ret_types {

3867- return tc.call_info(map_method, true)

3868- }

38694325 }

38704326 if clean is Array {

38714327 match fn_node.value {

@@ -4073,6 +4529,9 @@ fn (mut tc TypeChecker) resolve_call_info(id flat.NodeId, node flat.Node) ?CallI

40734529 }

40744530 }

40754531 if clean is SumType {

4532+ if info := tc.resolve_generic_sum_method(clean.name, fn_node.value) {

4533+ return info

4534+ }

40764535 mname := '${clean.name}.${fn_node.value}'

40774536 if mname in tc.fn_ret_types {

40784537 return tc.call_info(mname, true)

@@ -4162,7 +4621,17 @@ fn (mut tc TypeChecker) resolve_generic_call_info(fn_node flat.Node) ?CallInfo {

41624621 return none

41634622 }

41644623 base_node := tc.a.nodes[int(base_id)]

4165- call_name := tc.generic_call_base_name(base_node) or { return none }

4624+ call_name := tc.generic_call_base_name(base_node) or {

4625+ if type_name := tc.generic_call_base_type_name(base_node) {

4626+ return CallInfo{

4627+ name: ''

4628+ params: []Type{}

4629+ return_type: tc.parse_type('${type_name}[${type_arg}]')

4630+ params_known: false

4631+ }

4632+ }

4633+ return none

4634+ }

41664635 if is_veb_run_at_call_name(call_name) {

41674636 return CallInfo{

41684637 name: call_name

@@ -4192,6 +4661,32 @@ fn (mut tc TypeChecker) resolve_generic_call_info(fn_node flat.Node) ?CallInfo {

41924661 return tc.call_info(call_name, false)

41934662}

41944663

4664+fn (tc &TypeChecker) generic_call_base_type_name(base_node flat.Node) ?string {

4665+ if base_node.kind == .ident {

4666+ qname := tc.qualify_name(base_node.value)

4667+ if tc.type_symbol_known(qname) {

4668+ return qname

4669+ }

4670+ if tc.type_symbol_known(base_node.value) {

4671+ return base_node.value

4672+ }

4673+ if resolved := tc.resolve_selective_import_type_symbol(base_node.value) {

4674+ return resolved

4675+ }

4676+ }

4677+ if base_node.kind == .selector && base_node.children_count > 0 {

4678+ inner := tc.a.child_node(&base_node, 0)

4679+ if inner.kind == .ident {

4680+ mod_name := tc.resolve_import_alias(inner.value) or { inner.value }

4681+ full_name := '${mod_name}.${base_node.value}'

4682+ if tc.type_symbol_known(full_name) {

4683+ return full_name

4684+ }

4685+ }

4686+ }

4687+ return none

4688+}

4689+

41954690fn is_decode_call_name(name string) bool {

41964691 return name in ['json.decode', 'json2.decode']

41974692}

@@ -4690,13 +5185,14 @@ fn (tc &TypeChecker) parse_fn_signature_type(name string, typ string) Type {

46905185 tc.file_modules[decl_file] or { tc.cur_module }

46915186 }

46925187 scoped.type_cache = &TypeCache{

4693- parse_enabled: if tc.type_cache != unsafe { nil } {

5188+ parse_enabled: if tc.type_cache != unsafe { nil } {

46945189 tc.type_cache.parse_enabled

46955190 } else {

46965191 false

46975192 }

4698- parse_entries: map[string]Type{}

4699- c_entries: map[string]string{}

5193+ parse_entries: map[string]Type{}

5194+ c_entries: map[string]string{}

5195+ ierror_compat_entries: map[string]int{}

47005196 }

47015197 return scoped.parse_type(typ)

47025198}

@@ -5528,6 +6024,12 @@ fn (tc &TypeChecker) call_display_name(node flat.Node) string {

55286024 return '${base.value}.${fn_node.value}'

55296025 }

55306026 }

6027+ if fn_node.kind in [.index, .prefix, .array_init] {

6028+ type_name := tc.type_expr_name(tc.a.child(&node, 0))

6029+ if type_name.len > 0 {

6030+ return type_name

6031+ }

6032+ }

55316033 return fn_node.value

55326034}

55336035

@@ -5705,13 +6207,43 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {

57056207 for j in 0 .. n_conds {

57066208 cond := tc.a.node(tc.a.child(branch, j))

57076209 if pattern := tc.match_type_pattern(cond) {

5708- if !tc.sum_has_variant(subject_type.name, pattern) {

6210+ if _ := tc.sum_variant_type_for_pattern(subject_type.name, pattern) {

6211+ } else {

57096212 tc.record_error(.condition_mismatch,

57106213 '`${pattern}` is not a variant of sum type `${subject_type.name}`', tc.a.child(branch,

57116214 j))

57126215 }

57136216 }

57146217 }

6218+ } else if is_ierror_type(subject_type) {

6219+ for j in 0 .. n_conds {

6220+ cond_id := tc.a.child(branch, j)

6221+ cond := tc.a.node(cond_id)

6222+ if pattern := tc.match_type_pattern(cond) {

6223+ if _ := tc.resolve_ierror_match_pattern(pattern) {

6224+ } else if tc.should_diagnose(cond_id) {

6225+ tc.record_error(.condition_mismatch,

6226+ '`${pattern}` is not compatible with `IError`', cond_id)

6227+ }

6228+ }

6229+ }

6230+ } else if subject_type is Interface {

6231+ for j in 0 .. n_conds {

6232+ cond_id := tc.a.child(branch, j)

6233+ cond := tc.a.node(cond_id)

6234+ if pattern := tc.match_type_pattern(cond) {

6235+ if concrete := tc.resolve_interface_match_pattern(pattern) {

6236+ if !tc.named_type_implements_interface(concrete, subject_type.name)

6237+ && tc.should_diagnose(cond_id) {

6238+ tc.record_error(.condition_mismatch,

6239+ '`${pattern}` is not compatible with interface `${subject_type.name}`',

6240+ cond_id)

6241+ }

6242+ } else if tc.should_diagnose(cond_id) {

6243+ tc.record_error(.condition_mismatch, 'unknown type `${pattern}`', cond_id)

6244+ }

6245+ }

6246+ }

57156247 }

57166248 saved_smartcasts := clone_smartcasts(tc.smartcasts)

57176249 if subject_key.len > 0 && valid_string_data(subject_key) && n_conds == 1

@@ -5719,7 +6251,16 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {

57196251 cond_id := tc.a.child(branch, 0)

57206252 cond := tc.a.node(cond_id)

57216253 if pattern := tc.match_type_pattern(cond) {

5722- tc.smartcasts[subject_key] = tc.parse_type(pattern)

6254+ smartcast_type := if subject_type is SumType {

6255+ tc.sum_variant_type_for_pattern(subject_type.name, pattern) or { pattern }

6256+ } else if is_ierror_type(subject_type) {

6257+ tc.resolve_ierror_match_pattern(pattern) or { pattern }

6258+ } else if subject_type is Interface {

6259+ tc.resolve_interface_match_pattern(pattern) or { pattern }

6260+ } else {

6261+ pattern

6262+ }

6263+ tc.smartcasts[subject_key] = tc.parse_type(smartcast_type)

57236264 }

57246265 }

57256266 tc.push_scope()

@@ -5731,40 +6272,136 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {

57316272 }

57326273}

57336274

5734-// check_is_expr validates check is expr state for types.

5735-fn (mut tc TypeChecker) check_is_expr(id flat.NodeId, node flat.Node) {

5736- if node.children_count == 0 {

5737- return

5738- }

5739- expr_id := tc.a.child(&node, 0)

5740- tc.check_node(expr_id)

5741- expr_type := unwrap_pointer(tc.resolve_type(expr_id))

5742- if expr_type is SumType {

5743- if node.value.len > 0 && !tc.sum_has_variant(expr_type.name, node.value)

5744- && tc.should_diagnose(id) {

5745- tc.record_error(.condition_mismatch,

5746- '`${node.value}` is not a variant of sum type `${expr_type.name}`', id)

6275+fn (tc &TypeChecker) resolve_interface_match_pattern(pattern string) ?string {

6276+ for candidate in tc.interface_match_pattern_candidates(pattern) {

6277+ if tc.type_symbol_known(candidate) {

6278+ return candidate

57476279 }

5748- return

5749- }

5750- if expr_type is Interface || expr_type is Unknown {

5751- return

5752- }

5753- if tc.should_diagnose(id) {

5754- tc.record_error(.condition_mismatch,

5755- '`is` can only be used with sum type or interface values, not `${expr_type.name()}`',

5756- id)

57576280 }

6281+ return none

57586282}

57596283

5760-// branch_tail_type supports branch tail type handling for TypeChecker.

5761-fn (tc &TypeChecker) branch_tail_type(id flat.NodeId) Type {

5762- if !tc.valid_node_id(id) {

5763- return Type(void_)

6284+fn (tc &TypeChecker) resolve_ierror_match_pattern(pattern string) ?string {

6285+ for candidate in tc.interface_match_pattern_candidates(pattern) {

6286+ if tc.named_type_compatible_with_ierror(candidate) {

6287+ return candidate

6288+ }

57646289 }

5765- node := tc.a.nodes[int(id)]

5766- if node.kind == .if_expr {

5767- return tc.if_expr_tail_type(id)

6290+ return none

6291+}

6292+

6293+fn (tc &TypeChecker) interface_match_pattern_candidates(pattern string) []string {

6294+ mut candidates := []string{}

6295+ if !pattern.contains('.') {

6296+ mut has_scoped_candidate := false

6297+ if resolved := tc.resolve_selective_import_type_symbol(pattern) {

6298+ candidates << resolved

6299+ has_scoped_candidate = true

6300+ }

6301+ if tc.source_declares_type_in_scope(pattern, tc.cur_file, tc.cur_module) {

6302+ candidates << pattern

6303+ has_scoped_candidate = true

6304+ }

6305+ if tc.cur_module.len > 0 && tc.cur_module != 'main' && tc.cur_module != 'builtin' {

6306+ local := '${tc.cur_module}.${pattern}'

6307+ if tc.type_symbol_known(local) {

6308+ candidates << local

6309+ has_scoped_candidate = true

6310+ }

6311+ }

6312+ if !has_scoped_candidate {

6313+ candidates << pattern

6314+ }

6315+ } else if resolved := tc.resolve_import_alias_pattern(pattern) {

6316+ candidates << resolved

6317+ candidates << pattern

6318+ } else {

6319+ candidates << pattern

6320+ }

6321+ qpattern := tc.qualify_name(pattern)

6322+ if qpattern != pattern {

6323+ candidates << qpattern

6324+ }

6325+ mut result := []string{}

6326+ mut seen := map[string]bool{}

6327+ for candidate in candidates {

6328+ if candidate.len == 0 || candidate in seen {

6329+ continue

6330+ }

6331+ seen[candidate] = true

6332+ result << candidate

6333+ }

6334+ return result

6335+}

6336+

6337+fn (tc &TypeChecker) resolve_import_alias_pattern(pattern string) ?string {

6338+ dot := pattern.index_u8(`.`)

6339+ if dot <= 0 {

6340+ return none

6341+ }

6342+ alias := pattern[..dot]

6343+ resolved := tc.resolve_import_alias(alias) or { return none }

6344+ return '${resolved}.${pattern[dot + 1..]}'

6345+}

6346+

6347+// check_is_expr validates check is expr state for types.

6348+fn (mut tc TypeChecker) check_is_expr(id flat.NodeId, node flat.Node) {

6349+ if node.children_count == 0 {

6350+ return

6351+ }

6352+ expr_id := tc.a.child(&node, 0)

6353+ tc.check_node(expr_id)

6354+ expr_type := unwrap_pointer(tc.resolve_type(expr_id))

6355+ if expr_type is SumType {

6356+ if node.value.len > 0 && tc.sum_variant_type_for_pattern(expr_type.name, node.value) == none

6357+ && tc.should_diagnose(id) {

6358+ tc.record_error(.condition_mismatch,

6359+ '`${node.value}` is not a variant of sum type `${expr_type.name}`', id)

6360+ }

6361+ return

6362+ }

6363+ if is_ierror_type(expr_type) {

6364+ if node.value.len > 0 {

6365+ if _ := tc.resolve_ierror_match_pattern(node.value) {

6366+ } else if tc.should_diagnose(id) {

6367+ tc.record_error(.condition_mismatch,

6368+ '`${node.value}` is not compatible with `IError`', id)

6369+ }

6370+ }

6371+ return

6372+ }

6373+ if expr_type is Interface {

6374+ if node.value.len > 0 {

6375+ if concrete := tc.resolve_interface_match_pattern(node.value) {

6376+ if !tc.named_type_implements_interface(concrete, expr_type.name)

6377+ && tc.should_diagnose(id) {

6378+ tc.record_error(.condition_mismatch,

6379+ '`${node.value}` is not compatible with interface `${expr_type.name}`', id)

6380+ }

6381+ } else if tc.should_diagnose(id) {

6382+ tc.record_error(.condition_mismatch, 'unknown type `${node.value}`', id)

6383+ }

6384+ }

6385+ return

6386+ }

6387+ if expr_type is Unknown {

6388+ return

6389+ }

6390+ if tc.should_diagnose(id) {

6391+ tc.record_error(.condition_mismatch,

6392+ '`is` can only be used with sum type or interface values, not `${expr_type.name()}`',

6393+ id)

6394+ }

6395+}

6396+

6397+// branch_tail_type supports branch tail type handling for TypeChecker.

6398+fn (tc &TypeChecker) branch_tail_type(id flat.NodeId) Type {

6399+ if !tc.valid_node_id(id) {

6400+ return Type(void_)

6401+ }

6402+ node := tc.a.nodes[int(id)]

6403+ if node.kind == .if_expr {

6404+ return tc.if_expr_tail_type(id)

57686405 }

57696406 if node.kind == .block {

57706407 if node.children_count == 0 {

@@ -6201,7 +6838,7 @@ fn (tc &TypeChecker) selector_type(_id flat.NodeId, node flat.Node) ?Type {

62016838 return typ

62026839 }

62036840 }

6204- if clean_name == 'IError' || clean_name.ends_with('.IError') {

6841+ if is_builtin_ierror_name(clean_name) {

62056842 if node.value == 'message' {

62066843 return Type(String{})

62076844 }

@@ -6266,14 +6903,16 @@ fn (tc &TypeChecker) lowered_sum_selector_type(sum SumType, field string) ?Type

62666903

62676904// sum_shared_field_type supports sum shared field type handling for TypeChecker.

62686905fn (tc &TypeChecker) sum_shared_field_type(sum SumType, field string) ?Type {

6269- variants := tc.sum_types[sum.name] or { return none }

6906+ base := tc.sum_base_name(sum.name)

6907+ variants := tc.sum_types[base] or { return none }

62706908 if variants.len == 0 {

62716909 return none

62726910 }

62736911 mut has_common := false

62746912 mut common_typ := Type(void_)

62756913 for variant in variants {

6276- variant_field := tc.struct_field_type(variant, field) or { return none }

6914+ concrete := tc.concrete_sum_variant_name(sum.name, variant)

6915+ variant_field := tc.struct_field_type(concrete, field) or { return none }

62776916 if !has_common {

62786917 common_typ = variant_field

62796918 has_common = true

@@ -6847,9 +7486,6 @@ fn (tc &TypeChecker) type_compatible(actual Type, expected Type) bool {

68477486 return tc.type_compatible(actual, expected.base_type)

68487487 }

68497488 if expected is ResultType {

6850- if is_ierror_type(actual) {

6851- return true

6852- }

68537489 if actual is ResultType {

68547490 return tc.type_compatible(actual.base_type, expected.base_type)

68557491 }

@@ -7004,18 +7640,22 @@ fn fn_return_canonical_type_name(typ Type) string {

70047640}

70057641

70067642// is_ierror_type reports whether is ierror type applies in types.

7643+fn is_builtin_ierror_name(name string) bool {

7644+ return name == 'IError' || name == 'builtin.IError'

7645+}

7646+

70077647fn is_ierror_type(t Type) bool {

70087648 if t is Alias {

7009- return t.name == 'IError' || t.name.ends_with('.IError') || is_ierror_type(t.base_type)

7649+ return is_builtin_ierror_name(t.name) || is_ierror_type(t.base_type)

70107650 }

70117651 if t is Pointer {

70127652 return is_ierror_type(t.base_type)

70137653 }

70147654 if t is Struct {

7015- return t.name == 'IError' || t.name.ends_with('.IError')

7655+ return is_builtin_ierror_name(t.name)

70167656 }

70177657 if t is Interface {

7018- return t.name == 'IError' || t.name.ends_with('.IError')

7658+ return is_builtin_ierror_name(t.name)

70197659 }

70207660 return false

70217661}

@@ -7289,75 +7929,408 @@ fn (tc &TypeChecker) const_int_expr(id flat.NodeId, seen []string) ?int {

72897929 return none

72907930}

72917931

7292-// type_implements_interface returns type implements interface data for TypeChecker.

7293-fn (tc &TypeChecker) type_implements_interface(actual Type, expected Interface) bool {

7294- clean := unwrap_pointer(actual)

7295- if clean is Unknown {

7296- return true

7297- }

7298- if clean is Interface {

7299- return tc.interface_implements_interface(clean.name, expected.name)

7300- }

7301- concrete_name := method_type_name(clean)

7302- if concrete_name.len == 0 {

7303- return false

7932+// type_implements_interface returns type implements interface data for TypeChecker.

7933+fn (tc &TypeChecker) type_implements_interface(actual Type, expected Interface) bool {

7934+ clean := unwrap_pointer(actual)

7935+ if clean is Unknown {

7936+ return true

7937+ }

7938+ if clean is Interface {

7939+ return tc.interface_implements_interface(clean.name, expected.name)

7940+ }

7941+ concrete_name := method_type_name(clean)

7942+ if concrete_name.len == 0 {

7943+ return false

7944+ }

7945+ return tc.named_type_implements_interface(concrete_name, expected.name)

7946+}

7947+

7948+// interface_implements_interface supports interface implements interface handling for TypeChecker.

7949+fn (tc &TypeChecker) interface_implements_interface(actual_name string, expected_name string) bool {

7950+ if actual_name == expected_name {

7951+ return true

7952+ }

7953+ for method in tc.interface_method_names(expected_name) {

7954+ actual_key := tc.interface_method_signature_key(actual_name, method) or {

7955+ '${actual_name}.${method}'

7956+ }

7957+ expected_key := tc.interface_method_signature_key(expected_name, method) or {

7958+ '${expected_name}.${method}'

7959+ }

7960+ if actual_key !in tc.fn_param_types

7961+ || !tc.method_signature_compatible(actual_key, expected_key) {

7962+ return false

7963+ }

7964+ }

7965+ for field in tc.interface_field_list(expected_name) {

7966+ actual_field := tc.interface_field_type(actual_name, field.name) or { return false }

7967+ if !tc.type_compatible(actual_field, field.typ)

7968+ || !tc.type_compatible(field.typ, actual_field) {

7969+ return false

7970+ }

7971+ }

7972+ return true

7973+}

7974+

7975+// named_type_implements_interface

7976+// supports helper handling in types.

7977+pub fn (tc &TypeChecker) named_type_implements_interface(concrete_name string, iface_name string) bool {

7978+ // Only the abstract (declared) methods must be provided by the concrete type.

7979+ // Methods defined directly on the interface (default implementations) are

7980+ // inherited and need not be reimplemented.

7981+ for method in tc.interface_abstract_method_names(iface_name) {

7982+ concrete_key := '${concrete_name}.${method}'

7983+ if concrete_key !in tc.fn_param_types {

7984+ return false

7985+ }

7986+ expected_key := tc.interface_method_signature_key(iface_name, method) or {

7987+ '${iface_name}.${method}'

7988+ }

7989+ if !tc.method_signature_compatible(concrete_key, expected_key) {

7990+ return false

7991+ }

7992+ }

7993+ for field in tc.interface_field_list(iface_name) {

7994+ actual_field := tc.struct_field_type(concrete_name, field.name) or { return false }

7995+ if !tc.type_compatible(actual_field, field.typ)

7996+ || !tc.type_compatible(field.typ, actual_field) {

7997+ return false

7998+ }

7999+ }

8000+ return true

8001+}

8002+

8003+pub fn (tc &TypeChecker) named_type_compatible_with_ierror(concrete_name string) bool {

8004+ cache_key := tc.ierror_compat_cache_key(concrete_name)

8005+ if tc.type_cache != unsafe { nil } {

8006+ mut cache := unsafe { tc.type_cache }

8007+ if cached := cache.ierror_compat_entries[cache_key] {

8008+ return cached > 0

8009+ }

8010+ }

8011+ mut seen := map[string]bool{}

8012+ result := tc.named_type_compatible_with_ierror_inner(concrete_name, mut seen)

8013+ if tc.type_cache != unsafe { nil } {

8014+ mut cache := unsafe { tc.type_cache }

8015+ cache.ierror_compat_entries[cache_key] = if result { 1 } else { -1 }

8016+ }

8017+ return result

8018+}

8019+

8020+fn (tc &TypeChecker) ierror_compat_cache_key(concrete_name string) string {

8021+ if concrete_name in ['Error', 'MessageError'] {

8022+ return '${tc.cur_file}\n${tc.cur_module}\n${concrete_name}'

8023+ }

8024+ if concrete_name in tc.structs {

8025+ return concrete_name

8026+ }

8027+ qname := tc.qualify_name(concrete_name)

8028+ if qname in tc.structs {

8029+ return qname

8030+ }

8031+ return concrete_name

8032+}

8033+

8034+fn (tc &TypeChecker) type_compatible_with_ierror_payload(actual Type) bool {

8035+ concrete_name := method_type_name(unwrap_pointer(actual))

8036+ if concrete_name.len == 0 {

8037+ return false

8038+ }

8039+ return tc.named_type_compatible_with_ierror(concrete_name)

8040+}

8041+

8042+fn (tc &TypeChecker) named_type_compatible_with_ierror_inner(concrete_name string, mut seen map[string]bool) bool {

8043+ mut lookup := concrete_name

8044+ if lookup !in tc.structs {

8045+ qname := tc.qualify_name(lookup)

8046+ if qname in tc.structs {

8047+ lookup = qname

8048+ }

8049+ }

8050+ if lookup in ['Error', 'MessageError'] && tc.unqualified_type_name_shadows_builtin(lookup) {

8051+ return false

8052+ }

8053+ if lookup in seen {

8054+ return false

8055+ }

8056+ seen[lookup] = true

8057+ if tc.is_builtin_error_struct_name(lookup) {

8058+ return true

8059+ }

8060+ if tc.named_type_has_non_builtin_error_embed(lookup) {

8061+ return false

8062+ }

8063+ if tc.named_type_implements_ierror_methods(lookup) {

8064+ return true

8065+ }

8066+ struct_module := tc.struct_modules[lookup] or { tc.cur_module }

8067+ struct_file := tc.struct_files[lookup] or { tc.cur_file }

8068+ for field in tc.structs[lookup] or { []StructField{} } {

8069+ embedded_type := embedded_field_type(field) or { continue }

8070+ embedded_name := method_type_name(unwrap_pointer(embedded_type))

8071+ if embedded_name.len == 0 {

8072+ continue

8073+ }

8074+ if tc.embedded_field_is_scoped_builtin_error(field, embedded_name, struct_file,

8075+ struct_module)

8076+ {

8077+ return true

8078+ }

8079+ if embedded_name in ['Error', 'MessageError'] && field.name == embedded_name {

8080+ continue

8081+ }

8082+ if tc.is_builtin_error_struct_name(embedded_name) {

8083+ return true

8084+ }

8085+ if tc.named_type_compatible_with_ierror_inner(embedded_name, mut seen) {

8086+ return true

8087+ }

8088+ }

8089+ return false

8090+}

8091+

8092+fn (tc &TypeChecker) named_type_implements_ierror_methods(concrete_name string) bool {

8093+ iface_name := if 'builtin.IError' in tc.interface_names { 'builtin.IError' } else { 'IError' }

8094+ return tc.named_type_implements_interface(concrete_name, iface_name)

8095+}

8096+

8097+fn (tc &TypeChecker) named_type_has_non_builtin_error_embed(concrete_name string) bool {

8098+ mut lookup := concrete_name

8099+ if lookup !in tc.structs {

8100+ qname := tc.qualify_name(lookup)

8101+ if qname in tc.structs {

8102+ lookup = qname

8103+ }

8104+ }

8105+ if tc.struct_error_embeds_shadow_builtin[lookup] {

8106+ return true

8107+ }

8108+ struct_module := tc.struct_modules[lookup] or { tc.cur_module }

8109+ struct_file := tc.struct_files[lookup] or { tc.cur_file }

8110+ if tc.source_struct_has_non_builtin_error_embed(lookup, struct_file, struct_module) {

8111+ return true

8112+ }

8113+ for field in tc.structs[lookup] or { []StructField{} } {

8114+ if field.name in ['Error', 'MessageError']

8115+ && tc.unqualified_type_name_shadows_builtin_in_scope(field.name, struct_file, struct_module) {

8116+ return true

8117+ }

8118+ embedded_type := embedded_field_type(field) or { continue }

8119+ embedded_name := method_type_name(unwrap_pointer(embedded_type))

8120+ if embedded_name.len == 0 {

8121+ continue

8122+ }

8123+ if tc.embedded_field_is_scoped_builtin_error(field, embedded_name, struct_file,

8124+ struct_module)

8125+ {

8126+ continue

8127+ }

8128+ if embedded_name in ['Error', 'MessageError'] && field.name == embedded_name {

8129+ return true

8130+ }

8131+ if embedded_name.all_after_last('.') in ['Error', 'MessageError']

8132+ && !tc.is_builtin_error_struct_name(embedded_name) {

8133+ return true

8134+ }

8135+ }

8136+ return false

8137+}

8138+

8139+fn (tc &TypeChecker) source_struct_has_non_builtin_error_embed(concrete_name string, file string, mod_name string) bool {

8140+ if isnil(tc.a) {

8141+ return false

8142+ }

8143+ target := concrete_name.all_after_last('.')

8144+ target_module := if concrete_name.contains('.') {

8145+ concrete_name.all_before_last('.')

8146+ } else {

8147+ mod_name

8148+ }

8149+ mut cur_file := ''

8150+ mut cur_module := ''

8151+ for node in tc.a.nodes {

8152+ match node.kind {

8153+ .file {

8154+ cur_file = node.value

8155+ cur_module = ''

8156+ }

8157+ .module_decl {

8158+ cur_module = node.value

8159+ }

8160+ .struct_decl {

8161+ if node.value != target {

8162+ continue

8163+ }

8164+ if target_module.len > 0 {

8165+ if !module_names_match(cur_module, target_module) {

8166+ continue

8167+ }

8168+ } else if file.len == 0 || cur_file != file

8169+ || !module_names_match(cur_module, mod_name) {

8170+ continue

8171+ }

8172+ for i in 0 .. node.children_count {

8173+ field := tc.a.child_node(&node, i)

8174+ if field.kind != .field_decl {

8175+ continue

8176+ }

8177+ field_typ := if field.typ.len > 0 { field.typ } else { field.value }

8178+ if !source_field_decl_is_embed(field, field_typ) {

8179+ continue

8180+ }

8181+ if field_typ in ['Error', 'MessageError']

8182+ && tc.unqualified_type_name_shadows_builtin_in_scope(field_typ, cur_file, cur_module) {

8183+ return true

8184+ }

8185+ resolved := tc.resolve_imported_type_text_in_file(field_typ, cur_file)

8186+ if resolved.all_after_last('.') in ['Error', 'MessageError']

8187+ && !tc.is_builtin_error_struct_name(resolved) {

8188+ return true

8189+ }

8190+ }

8191+ }

8192+ else {}

8193+ }

8194+ }

8195+ return false

8196+}

8197+

8198+fn source_field_decl_is_embed(field flat.Node, field_typ string) bool {

8199+ return field.typ.len == 0 || field.value.len == 0 || field.value == field_typ

8200+ || (field_typ.contains('.') && field.value == field_typ.all_after_last('.'))

8201+}

8202+

8203+fn (tc &TypeChecker) embedded_field_is_scoped_builtin_error(field StructField, embedded_name string, struct_file string, struct_module string) bool {

8204+ if embedded_name !in ['Error', 'MessageError'] || field.name != embedded_name {

8205+ return false

8206+ }

8207+ return !tc.unqualified_type_name_shadows_builtin_in_scope(embedded_name, struct_file,

8208+ struct_module)

8209+}

8210+

8211+fn (tc &TypeChecker) resolve_unqualified_builtin_error_struct_name(name string) ?string {

8212+ if name !in ['Error', 'MessageError'] {

8213+ return none

8214+ }

8215+ if tc.unqualified_type_name_shadows_builtin(name) {

8216+ return none

8217+ }

8218+ if mod_name := tc.struct_modules[name] {

8219+ if mod_name == 'builtin' {

8220+ return name

8221+ }

8222+ }

8223+ if tc.has_builtins {

8224+ return name

8225+ }

8226+ return none

8227+}

8228+

8229+fn (tc &TypeChecker) unqualified_type_name_shadows_builtin(name string) bool {

8230+ return tc.unqualified_type_name_shadows_builtin_in_scope(name, tc.cur_file, tc.cur_module)

8231+}

8232+

8233+fn (tc &TypeChecker) unqualified_type_name_shadows_builtin_in_scope(name string, file string, mod_name string) bool {

8234+ local_name := if mod_name.len > 0 && mod_name !in ['', 'main', 'builtin'] {

8235+ '${mod_name}.${name}'

8236+ } else {

8237+ name

8238+ }

8239+ if local_name == name && mod_name != 'builtin'

8240+ && tc.source_declares_type_in_scope(name, file, mod_name) {

8241+ return true

8242+ }

8243+ if local_name != name && tc.type_symbol_known(local_name) {

8244+ return true

8245+ }

8246+ if file_import_key(file, name) in tc.file_selective_imports {

8247+ if resolved := tc.resolve_selective_import_type_symbol_in_file(name, file) {

8248+ return !tc.is_builtin_error_struct_name(resolved)

8249+ }

8250+ return true

8251+ }

8252+ if local_name != name {

8253+ return false

8254+ }

8255+ if name in tc.structs {

8256+ return tc.struct_modules[name] or { '' } != 'builtin'

8257+ }

8258+ if name in tc.type_aliases || name in tc.sum_types || name in tc.interface_names

8259+ || name in tc.enum_names {

8260+ return true

8261+ }

8262+ return false

8263+}

8264+

8265+fn (tc &TypeChecker) source_declares_type_in_scope(name string, file string, mod_name string) bool {

8266+ if file.len == 0 || isnil(tc.a) {

8267+ return false

8268+ }

8269+ mut cur_file := ''

8270+ mut cur_module := ''

8271+ for node in tc.a.nodes {

8272+ match node.kind {

8273+ .file {

8274+ cur_file = node.value

8275+ cur_module = ''

8276+ }

8277+ .module_decl {

8278+ cur_module = node.value

8279+ }

8280+ .struct_decl, .type_decl, .interface_decl, .enum_decl {

8281+ if cur_file == file && module_names_match(cur_module, mod_name)

8282+ && node.value == name {

8283+ return true

8284+ }

8285+ }

8286+ else {}

8287+ }

8288+ }

8289+ return false

8290+}

8291+

8292+fn module_names_match(a string, b string) bool {

8293+ return a == b || (a in ['', 'main'] && b in ['', 'main'])

8294+}

8295+

8296+fn (tc &TypeChecker) resolve_selective_import_type_symbol_in_file(name string, file string) ?string {

8297+ candidates := tc.file_selective_imports[file_import_key(file, name)] or { return none }

8298+ for candidate in candidates {

8299+ if tc.type_symbol_known(candidate) {

8300+ return candidate

8301+ }

73048302 }

7305- return tc.named_type_implements_interface(concrete_name, expected.name)

8303+ return none

73068304}

73078305

7308-// interface_implements_interface supports interface implements interface handling for TypeChecker.

7309-fn (tc &TypeChecker) interface_implements_interface(actual_name string, expected_name string) bool {

7310- if actual_name == expected_name {

7311- return true

8306+fn (tc &TypeChecker) resolve_imported_type_text_in_file(typ string, file string) string {

8307+ if !typ.contains('.') || typ.starts_with('C.') {

8308+ return typ

73128309 }

7313- for method in tc.interface_method_names(expected_name) {

7314- actual_key := tc.interface_method_signature_key(actual_name, method) or {

7315- '${actual_name}.${method}'

7316- }

7317- expected_key := tc.interface_method_signature_key(expected_name, method) or {

7318- '${expected_name}.${method}'

7319- }

7320- if actual_key !in tc.fn_param_types

7321- || !tc.method_signature_compatible(actual_key, expected_key) {

7322- return false

7323- }

8310+ dot := typ.index_u8(`.`)

8311+ if dot <= 0 {

8312+ return typ

73248313 }

7325- for field in tc.interface_field_list(expected_name) {

7326- actual_field := tc.interface_field_type(actual_name, field.name) or { return false }

7327- if !tc.type_compatible(actual_field, field.typ)

7328- || !tc.type_compatible(field.typ, actual_field) {

7329- return false

8314+ alias := typ[..dot]

8315+ if resolved := tc.file_imports[file_import_key(file, alias)] {

8316+ if resolved != alias {

8317+ return resolved + typ[dot..]

73308318 }

73318319 }

7332- return true

8320+ return typ

73338321}

73348322

7335-// named_type_implements_interface

7336-// supports helper handling in types.

7337-pub fn (tc &TypeChecker) named_type_implements_interface(concrete_name string, iface_name string) bool {

7338- // Only the abstract (declared) methods must be provided by the concrete type.

7339- // Methods defined directly on the interface (default implementations) are

7340- // inherited and need not be reimplemented.

7341- for method in tc.interface_abstract_method_names(iface_name) {

7342- concrete_key := '${concrete_name}.${method}'

7343- if concrete_key !in tc.fn_param_types {

7344- return false

7345- }

7346- expected_key := tc.interface_method_signature_key(iface_name, method) or {

7347- '${iface_name}.${method}'

7348- }

7349- if !tc.method_signature_compatible(concrete_key, expected_key) {

7350- return false

7351- }

8323+fn (tc &TypeChecker) is_builtin_error_struct_name(name string) bool {

8324+ if name in ['builtin.Error', 'builtin.MessageError'] {

8325+ return true

73528326 }

7353- for field in tc.interface_field_list(iface_name) {

7354- actual_field := tc.struct_field_type(concrete_name, field.name) or { return false }

7355- if !tc.type_compatible(actual_field, field.typ)

7356- || !tc.type_compatible(field.typ, actual_field) {

8327+ if name in ['Error', 'MessageError'] {

8328+ if tc.unqualified_type_name_shadows_builtin(name) {

73578329 return false

73588330 }

8331+ return tc.struct_modules[name] or { '' } == 'builtin'

73598332 }

7360- return true

8333+ return false

73618334}

73628335

73638336// interface_method_names supports interface method names handling for TypeChecker.

@@ -7563,6 +8536,12 @@ fn (tc &TypeChecker) substitute_generic_type(typ Type, args []string, param_name

75638536 base_type: tc.substitute_generic_type(typ.base_type, args, param_names)

75648537 })

75658538 }

8539+ if typ is SumType {

8540+ if typ.name.contains('[') {

8541+ return tc.parse_type(subst_generic_text(typ.name, args, param_names))

8542+ }

8543+ return typ

8544+ }

75668545 if typ is FnType {

75678546 mut params := []Type{}

75688547 for param in typ.params {

@@ -7679,6 +8658,9 @@ fn embedded_field_type(field StructField) ?Type {

76798658 if field_type_name.len == 0 {

76808659 return none

76818660 }

8661+ if field.name.len == 0 {

8662+ return field.typ

8663+ }

76828664 mut names := [field_type_name]

76838665 base_name, _, is_generic := generic_type_application_parts(field_type_name)

76848666 if is_generic {

@@ -7746,33 +8728,256 @@ fn method_type_name(t Type) string {

77468728 return ''

77478729}

77488730

8731+fn (tc &TypeChecker) sum_base_name(sum_name string) string {

8732+ base, _, ok := generic_type_application_parts(sum_name)

8733+ if ok {

8734+ return base

8735+ }

8736+ if sum_name in tc.sum_types {

8737+ return sum_name

8738+ }

8739+ qname := tc.qualify_name(sum_name)

8740+ if qname in tc.sum_types {

8741+ return qname

8742+ }

8743+ return sum_name

8744+}

8745+

8746+fn (tc &TypeChecker) sum_params_for_base(base string) []string {

8747+ if params := tc.sum_generic_params[base] {

8748+ return params

8749+ }

8750+ short := base.all_after_last('.')

8751+ if params := tc.sum_generic_params[short] {

8752+ return params

8753+ }

8754+ return []string{}

8755+}

8756+

8757+fn (tc &TypeChecker) concrete_sum_variant_name(sum_name string, variant string) string {

8758+ base, args, ok := generic_type_application_parts(sum_name)

8759+ if !ok {

8760+ return variant

8761+ }

8762+ params := tc.sum_params_for_base(base)

8763+ if params.len == 0 || params.len != args.len {

8764+ return variant

8765+ }

8766+ return subst_generic_text(variant, args, params)

8767+}

8768+

8769+fn (tc &TypeChecker) generic_type_name_matches(a string, b string) bool {

8770+ if a == b {

8771+ return true

8772+ }

8773+ a_base, a_args, a_ok := generic_type_application_parts(a)

8774+ b_base, b_args, b_ok := generic_type_application_parts(b)

8775+ if a_ok || b_ok {

8776+ if !a_ok || !b_ok || a_args.len != b_args.len {

8777+ return false

8778+ }

8779+ if !tc.generic_type_base_matches(a_base, b_base) {

8780+ return false

8781+ }

8782+ for i in 0 .. a_args.len {

8783+ if !tc.generic_type_arg_matches(a_args[i], b_args[i]) {

8784+ return false

8785+ }

8786+ }

8787+ return true

8788+ }

8789+ return tc.generic_type_base_matches(a, b)

8790+}

8791+

8792+fn (tc &TypeChecker) generic_type_base_matches(a string, b string) bool {

8793+ a_clean := a.trim_space()

8794+ b_clean := b.trim_space()

8795+ if a_clean == b_clean {

8796+ return true

8797+ }

8798+ a_resolved := tc.resolve_generic_match_base(a_clean)

8799+ b_resolved := tc.resolve_generic_match_base(b_clean)

8800+ if a_resolved == b_resolved {

8801+ return true

8802+ }

8803+ if a_clean.contains('.') || b_clean.contains('.') || a_resolved.contains('.')

8804+ || b_resolved.contains('.') {

8805+ return false

8806+ }

8807+ return short_type_name(a_clean) == short_type_name(b_clean)

8808+}

8809+

8810+fn (tc &TypeChecker) resolve_generic_match_base(base string) string {

8811+ if base.len == 0 {

8812+ return base

8813+ }

8814+ if base.contains('.') {

8815+ return tc.resolve_imported_type_text(base)

8816+ }

8817+ if resolved := tc.resolve_selective_import_type_symbol(base) {

8818+ return resolved

8819+ }

8820+ qbase := tc.qualify_name(base)

8821+ if qbase != base && tc.type_symbol_known(qbase) {

8822+ return qbase

8823+ }

8824+ return base

8825+}

8826+

8827+fn (tc &TypeChecker) generic_type_arg_matches(a string, b string) bool {

8828+ a_clean := a.trim_space()

8829+ b_clean := b.trim_space()

8830+ if a_clean == b_clean {

8831+ return true

8832+ }

8833+ if tc.generic_match_arg_is_open_param(a_clean) || tc.generic_match_arg_is_open_param(b_clean) {

8834+ return true

8835+ }

8836+ if a_clean.starts_with('&') || b_clean.starts_with('&') {

8837+ return a_clean.starts_with('&') && b_clean.starts_with('&')

8838+ && tc.generic_type_arg_matches(a_clean[1..], b_clean[1..])

8839+ }

8840+ if a_clean.starts_with('mut ') || b_clean.starts_with('mut ') {

8841+ return a_clean.starts_with('mut ') && b_clean.starts_with('mut ')

8842+ && tc.generic_type_arg_matches(a_clean[4..], b_clean[4..])

8843+ }

8844+ if a_clean.starts_with('?') || b_clean.starts_with('?') {

8845+ return a_clean.starts_with('?') && b_clean.starts_with('?')

8846+ && tc.generic_type_arg_matches(a_clean[1..], b_clean[1..])

8847+ }

8848+ if a_clean.starts_with('!') || b_clean.starts_with('!') {

8849+ return a_clean.starts_with('!') && b_clean.starts_with('!')

8850+ && tc.generic_type_arg_matches(a_clean[1..], b_clean[1..])

8851+ }

8852+ if a_clean.starts_with('...') || b_clean.starts_with('...') {

8853+ return a_clean.starts_with('...') && b_clean.starts_with('...')

8854+ && tc.generic_type_arg_matches(a_clean[3..], b_clean[3..])

8855+ }

8856+ if a_clean.starts_with('[]') || b_clean.starts_with('[]') {

8857+ return a_clean.starts_with('[]') && b_clean.starts_with('[]')

8858+ && tc.generic_type_arg_matches(a_clean[2..], b_clean[2..])

8859+ }

8860+ if a_clean.starts_with('map[') || b_clean.starts_with('map[') {

8861+ if !a_clean.starts_with('map[') || !b_clean.starts_with('map[') {

8862+ return false

8863+ }

8864+ a_bracket_end := find_matching_bracket(a_clean, 3)

8865+ b_bracket_end := find_matching_bracket(b_clean, 3)

8866+ if a_bracket_end >= a_clean.len || b_bracket_end >= b_clean.len {

8867+ return false

8868+ }

8869+ return tc.generic_type_arg_matches(a_clean[4..a_bracket_end], b_clean[4..b_bracket_end])

8870+ && tc.generic_type_arg_matches(a_clean[a_bracket_end + 1..], b_clean[b_bracket_end + 1..])

8871+ }

8872+ if a_clean.starts_with('[') || b_clean.starts_with('[') {

8873+ if !a_clean.starts_with('[') || !b_clean.starts_with('[') {

8874+ return false

8875+ }

8876+ a_bracket_end := find_matching_bracket(a_clean, 0)

8877+ b_bracket_end := find_matching_bracket(b_clean, 0)

8878+ if a_bracket_end >= a_clean.len || b_bracket_end >= b_clean.len

8879+ || a_clean[..a_bracket_end + 1] != b_clean[..b_bracket_end + 1] {

8880+ return false

8881+ }

8882+ return tc.generic_type_arg_matches(a_clean[a_bracket_end + 1..],

8883+ b_clean[b_bracket_end + 1..])

8884+ }

8885+ a_base, a_args, a_ok := generic_type_application_parts(a_clean)

8886+ b_base, b_args, b_ok := generic_type_application_parts(b_clean)

8887+ if a_ok || b_ok {

8888+ if !a_ok || !b_ok || a_args.len != b_args.len {

8889+ return false

8890+ }

8891+ if !tc.generic_type_base_matches(a_base, b_base) {

8892+ return false

8893+ }

8894+ for i in 0 .. a_args.len {

8895+ if !tc.generic_type_arg_matches(a_args[i], b_args[i]) {

8896+ return false

8897+ }

8898+ }

8899+ return true

8900+ }

8901+ a_resolved := tc.resolve_generic_match_arg(a_clean)

8902+ b_resolved := tc.resolve_generic_match_arg(b_clean)

8903+ if a_resolved == b_resolved {

8904+ return true

8905+ }

8906+ return false

8907+}

8908+

8909+fn (tc &TypeChecker) generic_match_arg_is_open_param(arg string) bool {

8910+ return is_bare_generic_param(arg) && !tc.is_known_type_text(arg)

8911+}

8912+

8913+fn (tc &TypeChecker) resolve_generic_match_arg(arg string) string {

8914+ if arg.len == 0 {

8915+ return arg

8916+ }

8917+ if !arg.contains('.') {

8918+ if resolved := tc.resolve_selective_import_type_symbol(arg) {

8919+ return resolved

8920+ }

8921+ }

8922+ qarg := tc.qualify_type_text(arg)

8923+ if qarg.len > 0 {

8924+ return qarg

8925+ }

8926+ return arg

8927+}

8928+

8929+fn (tc &TypeChecker) bare_variant_name_matches(candidate string, declared string, concrete string) bool {

8930+ if generic_type_application(candidate) {

8931+ return false

8932+ }

8933+ candidate_base := candidate.trim_space()

8934+ declared_base := strip_generic_args_name(declared).trim_space()

8935+ concrete_base := strip_generic_args_name(concrete).trim_space()

8936+ candidate_resolved := tc.resolve_generic_match_base(candidate_base)

8937+ declared_resolved := tc.resolve_generic_match_base(declared_base)

8938+ concrete_resolved := tc.resolve_generic_match_base(concrete_base)

8939+ if candidate_resolved == declared_resolved || candidate_resolved == concrete_resolved {

8940+ return true

8941+ }

8942+ if candidate_base.contains('.') || candidate_resolved.contains('.') {

8943+ return false

8944+ }

8945+ return (!declared_base.contains('.') && candidate_base == short_type_name(declared_base))

8946+ || (!concrete_base.contains('.') && candidate_base == short_type_name(concrete_base))

8947+}

8948+

77498949// type_matches_sum returns type matches sum data for TypeChecker.

77508950fn (tc &TypeChecker) type_matches_sum(actual Type, expected Type) bool {

77518951 if expected is SumType {

77528952 actual_name := actual.name()

7753- return tc.sum_has_variant(expected.name, actual_name)

8953+ return tc.sum_variant_type_for_pattern(expected.name, actual_name) != none

77548954 }

77558955 return false

77568956}

77578957

77588958// sum_has_variant converts sum has variant data for types.

77598959fn (tc &TypeChecker) sum_has_variant(sum_name string, variant_name string) bool {

7760- variants := tc.sum_types[sum_name] or { return false }

7761- variant_short := short_type_name(variant_name)

7762- for variant in variants {

7763- if variant == variant_name || short_type_name(variant) == variant_short {

7764- return true

7765- }

7766- }

8960+ return tc.sum_variant_type_for_pattern(sum_name, variant_name) != none

8961+}

8962+

8963+pub fn (tc &TypeChecker) sum_variant_type_for_pattern(sum_name string, variant_name string) ?string {

8964+ base := tc.sum_base_name(sum_name)

8965+ variants := tc.sum_types[base] or { return none }

8966+ mut candidates := [variant_name]

77678967 qvariant := tc.qualify_name(variant_name)

77688968 if qvariant != variant_name {

7769- for variant in variants {

7770- if variant == qvariant || short_type_name(variant) == variant_short {

7771- return true

8969+ candidates << qvariant

8970+ }

8971+ for variant in variants {

8972+ concrete := tc.concrete_sum_variant_name(sum_name, variant)

8973+ for candidate in candidates {

8974+ if tc.generic_type_name_matches(candidate, concrete)

8975+ || tc.bare_variant_name_matches(candidate, variant, concrete) {

8976+ return concrete

77728977 }

77738978 }

77748979 }

7775- return false

8980+ return none

77768981}

77778982

77788983// match_type_pattern supports match type pattern handling for TypeChecker.

@@ -7980,6 +9185,18 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {

79809185 reason: 'unknown'

79819186 })

79829187 }

9188+ if !typ.contains('.') {

9189+ if resolved := tc.resolve_selective_import_type_symbol(typ) {

9190+ if resolved_type := tc.type_from_known_symbol(resolved) {

9191+ return resolved_type

9192+ }

9193+ }

9194+ }

9195+ if builtin_error_name := tc.resolve_unqualified_builtin_error_struct_name(typ) {

9196+ return Type(Struct{

9197+ name: builtin_error_name

9198+ })

9199+ }

79839200 if typ.starts_with('C.') {

79849201 return Type(Struct{

79859202 name: typ

@@ -8002,14 +9219,14 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {

80029219 base_type: tc.parse_type(tc.type_aliases[typ])

80039220 })

80049221 }

8005- if typ in tc.interface_names {

9222+ if qtyp in tc.interface_names {

80069223 return Type(Interface{

8007- name: typ

9224+ name: qtyp

80089225 })

80099226 }

8010- if qtyp in tc.interface_names {

9227+ if typ in tc.interface_names {

80119228 return Type(Interface{

8012- name: qtyp

9229+ name: typ

80139230 })

80149231 }

80159232 if typ in tc.structs {

@@ -8122,7 +9339,7 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {

81229339 }

81239340 if qbase in tc.sum_types {

81249341 return Type(SumType{

8125- name: qbase

9342+ name: qbase + generic_suffix

81269343 })

81279344 }

81289345 if !base.contains('.') {

@@ -8145,7 +9362,7 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {

81459362 }

81469363 if resolved in tc.sum_types {

81479364 return Type(SumType{

8148- name: resolved

9365+ name: resolved + generic_suffix

81499366 })

81509367 }

81519368 }

@@ -8168,7 +9385,7 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {

81689385 }

81699386 if base in tc.sum_types {

81709387 return Type(SumType{

8171- name: base

9388+ name: base + generic_suffix

81729389 })

81739390 }

81749391 if is_concrete_generic && !is_builtin_type_name(base) {

@@ -8221,6 +9438,35 @@ fn (tc &TypeChecker) parse_type_uncached(typ string) Type {

82219438 })

82229439}

82239440

9441+fn (tc &TypeChecker) array_literal_elem_type(node flat.Node) Type {

9442+ if node.children_count == 0 {

9443+ return Type(int_)

9444+ }

9445+ mut elem_type := tc.resolve_type(tc.a.child(&node, 0))

9446+ mut all_numeric := elem_type.is_integer() || elem_type.is_float()

9447+ mut has_f32 := elem_type.name() == 'f32'

9448+ mut has_f64 := elem_type.name() == 'f64'

9449+ for i in 1 .. node.children_count {

9450+ child_type := tc.resolve_type(tc.a.child(&node, i))

9451+ if !(child_type.is_integer() || child_type.is_float()) {

9452+ all_numeric = false

9453+ }

9454+ if child_type.name() == 'f32' {

9455+ has_f32 = true

9456+ }

9457+ if child_type.name() == 'f64' {

9458+ has_f64 = true

9459+ }

9460+ }

9461+ if all_numeric && has_f64 {

9462+ return Type(f64_)

9463+ }

9464+ if all_numeric && has_f32 {

9465+ return tc.parse_type('f32')

9466+ }

9467+ return elem_type

9468+}

9469+

82249470fn (tc &TypeChecker) is_known_type_text(typ string) bool {

82259471 qtyp := tc.qualify_name(typ)

82269472 if !typ.contains('.') {

@@ -8680,7 +9926,7 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {

86809926 clean_type := unwrap_pointer(base_type)

86819927 if clean_type is Array {

86829928 if fn_node.value == 'clone' || fn_node.value == 'reverse' {

8683- return base_type

9929+ return clean_type

86849930 }

86859931 if fn_node.value == 'filter' || fn_node.value == 'sorted' {

86869932 return base_type

@@ -8736,7 +9982,7 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {

87369982 return unknown_type('`.wait()` requires an array of threads')

87379983 }

87389984 if fn_node.value == 'clone' {

8739- return base_type

9985+ return clean_type

87409986 }

87419987 elem_type := array_elem_type(clean_type)

87429988 elem_name := elem_type.name()

@@ -9081,7 +10327,7 @@ pub fn (tc &TypeChecker) resolve_type(id flat.NodeId) Type {

908110327 }

908210328 .array_literal {

908310329 if node.children_count > 0 {

9084- elem_type := tc.resolve_type(tc.a.child(&node, 0))

10330+ elem_type := tc.array_literal_elem_type(node)

908510331 return Type(ArrayFixed{

908610332 elem_type: elem_type

908710333 len: node.children_count

@@ -9501,6 +10747,54 @@ pub fn (tc &TypeChecker) resolve_generic_struct_method(type_name string, method

950110747 }

950210748}

950310749

10750+pub fn (tc &TypeChecker) resolve_generic_sum_method(type_name string, method string) ?CallInfo {

10751+ mut base := type_name

10752+ mut concrete_args := []string{}

10753+ parsed_base, parsed_args, is_generic := generic_type_application_parts(type_name)

10754+ if is_generic {

10755+ base = parsed_base

10756+ for arg in parsed_args {

10757+ concrete_args << arg.trim_space()

10758+ }

10759+ }

10760+ params := tc.sum_params_for_base(base)

10761+ if params.len == 0 {

10762+ return none

10763+ }

10764+ if !is_generic {

10765+ concrete_args = params.clone()

10766+ }

10767+ if params.len != concrete_args.len {

10768+ return none

10769+ }

10770+ generic_key := '${base}[${params.join(', ')}].${method}'

10771+ ret := tc.fn_ret_types[generic_key] or { return none }

10772+ mut sub_ret := tc.substitute_generic_type(ret, concrete_args, params)

10773+ if ret_text := tc.fn_ret_type_texts[generic_key] {

10774+ sub_ret = tc.parse_fn_signature_type(generic_key, subst_generic_text(ret_text,

10775+ concrete_args, params))

10776+ }

10777+ mut sub_params := []Type{}

10778+ if param_texts := tc.fn_param_type_texts[generic_key] {

10779+ for pt in param_texts {

10780+ sub_params << tc.parse_fn_signature_type(generic_key, subst_generic_text(pt,

10781+ concrete_args, params))

10782+ }

10783+ } else if ptypes := tc.fn_param_types[generic_key] {

10784+ for pt in ptypes {

10785+ sub_params << tc.substitute_generic_type(pt, concrete_args, params)

10786+ }

10787+ }

10788+ return CallInfo{

10789+ name: generic_key

10790+ params: sub_params

10791+ return_type: sub_ret

10792+ has_receiver: true

10793+ is_variadic: tc.fn_variadic[generic_key] or { false }

10794+ params_known: true

10795+ }

10796+}

10797+

950410798// subst_generic_text textually substitutes the generic parameter names `params` with the

950510799// concrete argument texts `args` inside a type text, preserving the generic application

950610800// form so a method signature mentioning the receiver type (`Box[T]`) becomes the concrete