v / vlib / v2 / types / checker.v
9709 lines · 9294 sloc · 264.77 KB · ddb021b9866c3b4523b746fa2f4c16a594f8bd89
Raw
1// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module types
5
6import sync
7import time
8import strconv
9import v2.ast
10import v2.errors
11import v2.pref
12import v2.token
13
14const embed_file_helper_type_name = '__V2EmbedFileData'
15const max_checker_expr_depth = 40
16
17pub struct Environment {
18pub mut:
19 // errors with no default value
20 scopes shared map[string]&Scope = map[string]&Scope{}
21 // Function scopes - stores the scope for each function by qualified name (module__fn_name)
22 // This allows later passes (transformer, codegen) to look up local variable types
23 fn_scopes shared map[string]&Scope = map[string]&Scope{}
24 // types map[int]Type
25 // methods - shared for parallel type checking
26 methods shared map[string][]&Fn = map[string][]&Fn{}
27 generic_types map[string][]map[string]Type
28 cur_generic_types []map[string]Type
29 // Expression types keyed by token.Pos.id. Positive IDs come from the parser;
30 // negative IDs come from transformer's synthesized nodes. This must stay sparse:
31 // parser position IDs are not dense enough to use as direct array indexes in
32 // large self-host builds.
33 expr_types map[int]Type
34 selector_names map[int]string
35 // Drop-codegen handoff: per-fn list of bindings whose `drop(mut self)`
36 // method must be called at the fn's natural exit, in declaration order.
37 // Populated by `ownership_snapshot_drops_at_fn_exit` after each fn body
38 // is checked (with moves removed), read by the cleanc backend to emit
39 // `Type__drop(&var)` calls before the fn's closing brace. Only populated
40 // when the checker runs with `-d ownership`; empty under plain V.
41 drop_at_fn_exit map[string][]DropEntry
42 // Drop-codegen handoff for early returns: per-fn list of per-return-stmt
43 // drop snapshots, in source order. The cleanc backend walks the fn body
44 // in the same order and maintains a parallel counter to match each
45 // ReturnStmt to its snapshot. Each inner []DropEntry is the set of
46 // bindings that must be dropped just BEFORE the corresponding return.
47 // Bindings that are themselves being returned (and thus moved out) are
48 // excluded by the checker — the return moves them. Only populated when
49 // the checker runs with `-d ownership`; empty under plain V.
50 drop_at_returns map[string][][]DropEntry
51 // Shared C-language scope. Phase 1 workers register C struct decls into
52 // here under c_scope_mu; phase 2 (sequential) registers C fn signatures.
53 // All checkers point their per-checker `c_scope` field at this instance
54 // so that the `C` Module pasted into each module scope resolves uniformly.
55 c_scope &Scope = unsafe { nil }
56 c_scope_mu &sync.Mutex = unsafe { nil }
57}
58
59pub fn Environment.new() &Environment {
60 return Environment.new_with_capacity(0, 0)
61}
62
63// Environment.new_with_capacity returns a new checker environment with the hot
64// expression metadata maps pre-sized for large flat-AST builds.
65pub fn Environment.new_with_capacity(expr_types_cap int, selector_names_cap int) &Environment {
66 mut env := &Environment{
67 expr_types: map[int]Type{}
68 selector_names: map[int]string{}
69 c_scope: new_scope(unsafe { nil })
70 c_scope_mu: sync.new_mutex()
71 }
72 if expr_types_cap > 0 {
73 env.expr_types.reserve(u32(expr_types_cap))
74 }
75 if selector_names_cap > 0 {
76 env.selector_names.reserve(u32(selector_names_cap))
77 }
78 return env
79}
80
81// set_expr_type stores the computed type for an expression by its unique ID.
82pub fn (mut e Environment) set_expr_type(id int, typ Type) {
83 e.expr_types[id] = typ
84}
85
86// get_expr_type retrieves the computed type for an expression by its unique ID.
87pub fn (e &Environment) get_expr_type(id int) ?Type {
88 typ := e.expr_types[id] or { return none }
89 if typ is Void || type_has_null_data(typ) {
90 return none
91 }
92 return typ
93}
94
95// has_expr_type reports whether an expression has a stored type. Unlike
96// get_expr_type, it treats an explicit `void` type as present.
97pub fn (e &Environment) has_expr_type(id int) bool {
98 typ := e.expr_types[id] or { return false }
99 if typ is Void {
100 return u8(typ) != 1
101 }
102 return !type_has_null_data(typ)
103}
104
105pub fn (e &Environment) expr_type_count() int {
106 return e.expr_types.len
107}
108
109pub fn (e &Environment) all_expr_types() []Type {
110 mut out := []Type{cap: e.expr_types.len}
111 for _, typ in e.expr_types {
112 out << typ
113 }
114 return out
115}
116
117// release_expr_type_cache_after_ssa releases expression-position metadata once
118// SSA has consumed it. Native MIR/codegen keeps type IDs in SSA/MIR values and
119// does not need these maps on the ARM64 path.
120pub fn (mut e Environment) release_expr_type_cache_after_ssa() {
121 unsafe {
122 e.expr_types.free()
123 e.selector_names.free()
124 }
125 e.expr_types = map[int]Type{}
126 e.selector_names = map[int]string{}
127}
128
129// type_has_null_data checks if a Type sumtype has a missing payload.
130// Some Type variants are stored inline and legitimately have a zero data word;
131// larger variants need a non-null payload pointer.
132fn type_has_null_data(t Type) bool {
133 return !type_has_valid_payload(t)
134}
135
136fn expr_call_payload_ref(expr ast.Expr) ?&ast.CallExpr {
137 slot0 := unsafe { (&u64(&expr))[0] }
138 slot1 := unsafe { (&u64(&expr))[1] }
139 tag := sumtype_tag_slot(slot0, slot1)
140 if !is_ast_expr_call_expr_tag(tag) {
141 return none
142 }
143 data := sumtype_payload_slot(slot0, slot1)
144 if data == 0 {
145 return none
146 }
147 return unsafe { &ast.CallExpr(voidptr(data)) }
148}
149
150fn expr_ident_payload(expr ast.Expr) ?ast.Ident {
151 slot0 := unsafe { (&u64(&expr))[0] }
152 slot1 := unsafe { (&u64(&expr))[1] }
153 tag := sumtype_tag_slot(slot0, slot1)
154 if tag == u64(13) {
155 data := sumtype_payload_slot(slot0, slot1)
156 if data == 0 {
157 return none
158 }
159 return unsafe { *&ast.Ident(voidptr(data)) }
160 }
161
162 match expr {
163 ast.Ident {
164 return expr
165 }
166 else {}
167 }
168
169 return none
170}
171
172fn ast_expr_call_expr_tag() u64 {
173 expr := ast.Expr(ast.CallExpr{})
174 slot0 := unsafe { (&u64(&expr))[0] }
175 slot1 := unsafe { (&u64(&expr))[1] }
176 return sumtype_tag_slot(slot0, slot1)
177}
178
179fn is_ast_expr_call_expr_tag(tag u64) bool {
180 return tag == ast_expr_call_expr_tag() || tag == u64(4) || tag == u64(118)
181}
182
183fn ast_expr_ident_tag() u64 {
184 expr := ast.Expr(ast.Ident{})
185 slot0 := unsafe { (&u64(&expr))[0] }
186 slot1 := unsafe { (&u64(&expr))[1] }
187 return sumtype_tag_slot(slot0, slot1)
188}
189
190fn is_ast_expr_ident_tag(tag u64) bool {
191 return tag == ast_expr_ident_tag() || tag == u64(13) || tag == u64(117)
192}
193
194fn stmt_for_in_payload(stmt ast.Stmt) ?ast.ForInStmt {
195 match stmt {
196 ast.ForInStmt {
197 return stmt
198 }
199 else {}
200 }
201
202 slot0 := unsafe { (&u64(&stmt))[0] }
203 slot1 := unsafe { (&u64(&stmt))[1] }
204 tag := sumtype_tag_slot(slot0, slot1)
205 if !is_ast_stmt_for_in_stmt_tag(tag) {
206 return none
207 }
208 data := sumtype_payload_slot(slot0, slot1)
209 if data == 0 {
210 return none
211 }
212 return unsafe { *&ast.ForInStmt(voidptr(data)) }
213}
214
215fn ast_stmt_for_in_stmt_tag() u64 {
216 stmt := ast.Stmt(ast.ForInStmt{})
217 slot0 := unsafe { (&u64(&stmt))[0] }
218 slot1 := unsafe { (&u64(&stmt))[1] }
219 return sumtype_tag_slot(slot0, slot1)
220}
221
222fn is_ast_stmt_for_in_stmt_tag(tag u64) bool {
223 return tag == ast_stmt_for_in_stmt_tag() || tag == u64(13) || tag == u64(116)
224}
225
226fn sumtype_tag_slot(slot0 u64, slot1 u64) u64 {
227 if sumtype_slot_is_payload(slot0) {
228 return slot1
229 }
230 return slot0
231}
232
233fn sumtype_payload_slot(slot0 u64, slot1 u64) u64 {
234 if sumtype_slot_is_payload(slot0) {
235 return slot0
236 }
237 if sumtype_slot_is_payload(slot1) {
238 return slot1
239 }
240 return 0
241}
242
243fn sumtype_slot_is_payload(slot u64) bool {
244 return slot >= 4096 && slot < 281474976710656
245}
246
247fn checker_string_has_valid_data(s string) bool {
248 if s.len == 0 {
249 return true
250 }
251 if s.len < 0 || s.len > 1024 {
252 return false
253 }
254 ptr := unsafe { u64(s.str) }
255 return ptr >= 4096 && ptr < 281474976710656
256}
257
258fn embed_file_helper_type() Type {
259 string_fn := Type(fn_with_return_type(empty_fn_type(), Type(string_)))
260 bytes_fn := Type(fn_with_return_type(empty_fn_type(), Type(Array{
261 elem_type: Type(u8_)
262 })))
263 data_fn := Type(fn_with_return_type(empty_fn_type(), Type(Pointer{
264 base_type: Type(u8_)
265 })))
266 void_fn := Type(fn_with_return_type(empty_fn_type(), Type(void_)))
267 return Type(Struct{
268 name: embed_file_helper_type_name
269 fields: [
270 Field{
271 name: '_data'
272 typ: Type(string_)
273 },
274 Field{
275 name: 'len'
276 typ: Type(int_)
277 },
278 Field{
279 name: 'path'
280 typ: Type(string_)
281 },
282 Field{
283 name: 'apath'
284 typ: Type(string_)
285 },
286 Field{
287 name: 'to_string'
288 typ: string_fn
289 },
290 Field{
291 name: 'str'
292 typ: string_fn
293 },
294 Field{
295 name: 'to_bytes'
296 typ: bytes_fn
297 },
298 Field{
299 name: 'data'
300 typ: data_fn
301 },
302 Field{
303 name: 'free'
304 typ: void_fn
305 },
306 ]
307 })
308}
309
310fn is_embed_file_call_expr(expr ast.Expr) bool {
311 return match expr {
312 ast.CallExpr {
313 expr.lhs is ast.Ident && expr.lhs.name == 'embed_file'
314 }
315 ast.CallOrCastExpr {
316 expr.lhs is ast.Ident && expr.lhs.name == 'embed_file'
317 }
318 else {
319 false
320 }
321 }
322}
323
324fn is_comptime_res_call_expr(expr ast.Expr) bool {
325 return match expr {
326 ast.CallExpr {
327 expr.lhs is ast.Ident && expr.lhs.name == 'res'
328 }
329 ast.CallOrCastExpr {
330 expr.lhs is ast.Ident && expr.lhs.name == 'res'
331 }
332 else {
333 false
334 }
335 }
336}
337
338fn is_comptime_env_call_expr(expr ast.Expr) bool {
339 return match expr {
340 ast.CallExpr {
341 expr.lhs is ast.Ident && expr.lhs.name == 'env'
342 }
343 ast.CallOrCastExpr {
344 expr.lhs is ast.Ident && expr.lhs.name == 'env'
345 }
346 else {
347 false
348 }
349 }
350}
351
352fn (mut c Checker) register_method_type(type_name string, method_name string, fn_type FnType) {
353 mut methods_for_type := []&Fn{}
354 lock c.env.methods {
355 if type_name in c.env.methods {
356 methods_for_type = unsafe { c.env.methods[type_name] }
357 for method in methods_for_type {
358 if method.name == method_name {
359 return
360 }
361 }
362 }
363 mut obj := &Fn{
364 name: method_name
365 typ: Type(fn_type)
366 }
367 methods_for_type << obj
368 c.env.methods[type_name] = methods_for_type
369 }
370}
371
372fn (mut c Checker) register_flag_enum_methods(enum_type Enum) {
373 enum_typ := Type(enum_type)
374 enum_param := Parameter{
375 name: 'value'
376 typ: enum_typ
377 }
378 for method_name in ['has', 'all'] {
379 c.register_method_type(enum_type.name, method_name, FnType{
380 params: [enum_param]
381 return_type: Type(bool_)
382 })
383 }
384 for method_name in ['set', 'clear'] {
385 c.register_method_type(enum_type.name, method_name, FnType{
386 params: [enum_param]
387 return_type: Type(void_)
388 })
389 }
390 c.register_method_type(enum_type.name, 'zero', FnType{
391 return_type: enum_typ
392 })
393}
394
395fn (mut c Checker) register_enum_methods(enum_type Enum) {
396 c.register_method_type(enum_type.name, 'str', FnType{
397 return_type: Type(string_)
398 })
399 if enum_type.is_flag {
400 c.register_flag_enum_methods(enum_type)
401 }
402}
403
404// lookup_method looks up a method by receiver type name and method name
405// Returns the method's FnType if found
406pub fn (e &Environment) lookup_method(type_name string, method_name string) ?FnType {
407 mut methods := []&Fn{}
408 rlock e.methods {
409 if type_name in e.methods {
410 methods = unsafe { e.methods[type_name] }
411 }
412 }
413 for method in methods {
414 if method.get_name() == method_name {
415 typ := method.get_typ()
416 if typ is FnType {
417 return typ
418 }
419 }
420 }
421 return none
422}
423
424// lookup_fn looks up a function by module and name in the environment's scopes
425// Returns the function's FnType if found
426pub fn (e &Environment) lookup_fn(module_name string, fn_name string) ?FnType {
427 mut scope := &Scope(unsafe { nil })
428 mut found_scope := false
429 lock e.scopes {
430 if module_name in e.scopes {
431 scope = unsafe { e.scopes[module_name] }
432 found_scope = true
433 }
434 }
435 if !found_scope {
436 return none
437 }
438 if obj := scope.lookup_parent(fn_name, 0) {
439 if obj is Fn {
440 typ := obj.get_typ()
441 if typ is FnType {
442 return typ
443 }
444 }
445 }
446 return none
447}
448
449// lookup_local_var looks up a local variable by name in the given scope.
450// Walks up the scope chain to find the variable and returns its type.
451pub fn (e &Environment) lookup_local_var(scope &Scope, name string) ?Type {
452 mut s := unsafe { scope }
453 if obj := s.lookup_parent(name, 0) {
454 return obj.typ()
455 }
456 return none
457}
458
459// set_fn_scope stores the scope for a function by its qualified name
460pub fn (mut e Environment) set_fn_scope(module_name string, fn_name string, scope &Scope) {
461 key := if module_name == '' { fn_name } else { '${module_name}__${fn_name}' }
462 lock e.fn_scopes {
463 e.fn_scopes[key] = scope
464 }
465}
466
467// get_fn_scope retrieves the scope for a function by its qualified name
468pub fn (e &Environment) get_fn_scope(module_name string, fn_name string) ?&Scope {
469 key := if module_name == '' { fn_name } else { '${module_name}__${fn_name}' }
470 mut scope := &Scope(unsafe { nil })
471 mut found_scope := false
472 lock e.fn_scopes {
473 if key in e.fn_scopes {
474 scope = unsafe { e.fn_scopes[key] }
475 found_scope = true
476 }
477 }
478 if !found_scope {
479 return none
480 }
481 return scope
482}
483
484// get_scope retrieves a module scope by exact module name.
485pub fn (e &Environment) get_scope(module_name string) ?&Scope {
486 mut scope := &Scope(unsafe { nil })
487 mut found_scope := false
488 lock e.scopes {
489 if module_name in e.scopes {
490 scope = unsafe { e.scopes[module_name] }
491 found_scope = true
492 }
493 }
494 if !found_scope {
495 return none
496 }
497 return scope
498}
499
500// get_fn_scope_by_key retrieves a function scope by its fully-qualified key.
501pub fn (e &Environment) get_fn_scope_by_key(key string) ?&Scope {
502 mut scope := &Scope(unsafe { nil })
503 mut found_scope := false
504 lock e.fn_scopes {
505 if key in e.fn_scopes {
506 scope = unsafe { e.fn_scopes[key] }
507 found_scope = true
508 }
509 }
510 if !found_scope {
511 return none
512 }
513 return scope
514}
515
516// snapshot_scopes returns a non-shared copy of the scopes map.
517pub fn (e &Environment) snapshot_scopes() map[string]&Scope {
518 mut result := map[string]&Scope{}
519 lock e.scopes {
520 // Use .keys() + index lookup instead of `for k, v in` to avoid
521 // ARM64 chained-access bug with shared map iteration.
522 scope_keys := e.scopes.keys()
523 for k in scope_keys {
524 v := e.scopes[k] or { continue }
525 result[k] = v
526 }
527 }
528 return result
529}
530
531// snapshot_methods returns a non-shared copy of the methods map.
532pub fn (e &Environment) snapshot_methods() map[string][]&Fn {
533 mut result := map[string][]&Fn{}
534 lock e.methods {
535 method_keys := e.methods.keys()
536 for k in method_keys {
537 v := e.methods[k] or { continue }
538 result[k] = v
539 }
540 }
541 return result
542}
543
544// snapshot_fn_scopes returns a non-shared copy of the fn_scopes map.
545pub fn (e &Environment) snapshot_fn_scopes() map[string]&Scope {
546 mut result := map[string]&Scope{}
547 lock e.fn_scopes {
548 fn_scope_keys := e.fn_scopes.keys()
549 for k in fn_scope_keys {
550 v := e.fn_scopes[k] or { continue }
551 result[k] = v
552 }
553 }
554 return result
555}
556
557pub enum DeferredKind {
558 fn_decl
559 fn_decl_generic
560 struct_decl
561 const_decl
562}
563
564pub struct Deferred {
565pub:
566 kind DeferredKind
567 func fn () = unsafe { nil }
568 scope &Scope
569}
570
571struct PendingConstField {
572 scope &Scope
573 field ast.FieldInit
574}
575
576struct PendingInterfaceDecl {
577 scope &Scope
578 decl ast.InterfaceDecl
579}
580
581struct PendingStructDecl {
582 scope &Scope
583 module_name string
584 decl ast.StructDecl
585}
586
587struct FieldAccessInfo {
588 field Field
589 owner_struct string
590 module_mut_fields []Field
591 module_mut_owner_structs []string
592}
593
594struct PendingTypeDecl {
595 scope &Scope
596 decl ast.TypeDecl
597}
598
599struct PendingFnBody {
600 scope &Scope
601 decl ast.FnDecl
602 typ FnType
603 scope_fn_name string
604 module_name string
605 flat &ast.FlatAst = unsafe { nil }
606 flat_decl_id ast.FlatNodeId = -1
607}
608
609struct Checker {
610 pref &pref.Preferences
611 // info Info
612 // TODO: mod
613 mod &Module = new_module('main', '')
614mut:
615 env &Environment = &Environment{}
616 file_set &token.FileSet
617 scope &Scope = new_scope(unsafe { nil })
618 c_scope &Scope = new_scope(unsafe { nil })
619 expected_type ?Type
620 // Current file's module name (for saving function scopes)
621 cur_file_module string
622 // Function root scope - used to flatten local variable types for transformer lookup
623 fn_root_scope &Scope = unsafe { nil }
624 fallback_vars map[string]Type
625 receiver_generic_types map[string]map[string]Type
626 generic_type_params map[string][]string
627 // when true, function declarations only register signatures and queue body checking
628 collect_fn_signatures_only bool
629 pending_fn_body_flat &ast.FlatAst = unsafe { nil }
630 pending_fn_body_flat_id ast.FlatNodeId = -1
631 pending_const_fields []PendingConstField
632 pending_interface_decls []PendingInterfaceDecl
633 pending_struct_decls []PendingStructDecl
634 pending_type_decls []PendingTypeDecl
635 pending_fn_bodies []PendingFnBody
636
637 generic_params []string
638 // TODO: remove once fields/methods with same name
639 // are no longer allowed & removed.
640 expecting_method bool
641 // Temporary recursion guard for debugging expression cycles.
642 expr_depth int
643 expr_stack []string
644 // Whether we are inside an unsafe{} block
645 inside_unsafe bool
646 // Whether the currently checked expression is directly inside a return
647 // statement. Used for result error propagation in `or {}` fallbacks.
648 inside_return_stmt bool
649 // Ownership tracking: variables that hold owned values
650 // (from `.to_owned()` for strings, or any non-Copy value for other types).
651 owned_vars map[string]token.Pos // var name -> position where it became owned
652 // Display name of each owned variable's type, used in move diagnostics.
653 owned_var_types map[string]string // var name -> type display name
654 // Variables that have been moved (assigned to another variable)
655 moved_vars map[string]MovedVar // var name -> info about the move
656 // Functions that return owned values (detected from return statements)
657 ownership_fns map[string]bool // fn name -> returns ownership
658 // Function parameters that received owned values at call sites
659 ownership_fn_params map[string]bool // "fn_name__param_N" -> true
660 // Functions that return a specific parameter (index stored, -1 = not set)
661 ownership_fn_returns_param map[string]int // fn_name -> parameter index
662 // Current function name for ownership tracking
663 ownership_cur_fn string
664 // Borrow tracking: variables currently borrowed via &
665 borrowed_vars map[string][]BorrowInfo // var name -> list of active borrows
666 // Drop schedule: per-function list of owned bindings implementing the
667 // `Drop` interface. Each entry is the destructor call codegen must emit
668 // before the binding's owning scope exits. Populated as `Drop`-typed
669 // values are bound; pruned (via the `moved_vars` view) when they are
670 // moved or returned. Exposed so the transformer / backends can lower it.
671 drop_schedule map[string][]DropEntry
672 // Per-fn list of per-return drop snapshots collected during checking,
673 // in source order. Reset at fn entry, published to
674 // `env.drop_at_returns[publish_key]` at fn exit. Transient — not used
675 // outside of fn body checking.
676 pending_return_drops [][]DropEntry
677 // Source positions for `implements Drop` struct declarations, indexed
678 // by struct name. Used to point the "missing drop method" diagnostic at
679 // the struct decl rather than at an unrelated use site.
680 ownership_drop_decl_positions map[string]token.Pos
681 // Consuming-self method registry: `${short_type}__${method_name}` -> true
682 // when the method has a by-value receiver (no `&`, no `mut`). Populated
683 // by `ownership_prescan_value_receivers` once all method signatures are
684 // visible, consulted at every call site to move the receiver of an owned
685 // var. See checker_ownership.v.
686 ownership_value_receiver_methods map[string]bool
687 // Owned-global registry: `global_name` -> display type name. Populated by
688 // `ownership_prescan_owned_globals` from `__global` decls whose declared
689 // type implements `Owned` (or is an Rc/Arc wrapper). Used to reject moves
690 // out of program-wide mutable state, where the compiler can't see across
691 // function boundaries to know who else might still hold the binding.
692 ownership_owned_globals map[string]string
693 // Pass-through fn registry: `fn_name` -> list of parameter indices that
694 // the fn returns directly (`return param_i`). Populated by
695 // `escape_prescan_passthrough_fns` over opt-in (`[^a]`) fn decls, consulted
696 // at every escape site to catch inter-procedural leaks like
697 // `return passthrough(&local)`. See checker_escape.v.
698 escape_passthrough_fns map[string][]int
699}
700
701pub fn Checker.new(prefs &pref.Preferences, file_set &token.FileSet, env &Environment) &Checker {
702 mut c_scope := env.c_scope
703 if isnil(c_scope) {
704 c_scope = new_scope(unsafe { nil })
705 }
706 return &Checker{
707 pref: unsafe { prefs }
708 file_set: unsafe { file_set }
709 env: unsafe { env }
710 c_scope: c_scope
711 expected_type: none
712 }
713}
714
715// qualify_type_name returns the fully qualified type name with module prefix.
716// e.g., "File" in module "ast" becomes "ast__File".
717// Builtin types and main module types are not prefixed.
718fn (c &Checker) qualify_type_name(name string) string {
719 // Don't qualify builtin or main module types
720 if c.cur_file_module == 'builtin' || c.cur_file_module == '' || c.cur_file_module == 'main' {
721 return name
722 }
723 return '${c.cur_file_module}__${name}'
724}
725
726fn (c &Checker) type_ref_name(expr ast.Expr) string {
727 match expr {
728 ast.Ident {
729 return c.qualify_type_name(expr.name)
730 }
731 ast.LifetimeExpr {
732 return '^' + expr.name
733 }
734 ast.SelectorExpr {
735 return expr.name().replace('.', '__')
736 }
737 ast.Type {
738 if expr is ast.PointerType {
739 return c.type_ref_name(expr.base_type)
740 }
741 return expr.name().replace('.', '__')
742 }
743 else {
744 return expr.name().replace('.', '__')
745 }
746 }
747}
748
749fn (mut c Checker) decl_field_type(expr ast.Expr) Type {
750 match expr {
751 ast.Ident {
752 if typ := builtin_type(expr.name) {
753 return typ
754 }
755 if obj := universe.lookup_parent(expr.name, 0) {
756 if typ := object_as_type(obj) {
757 return typ
758 }
759 }
760 qualified_name := c.qualify_type_name(expr.name)
761 if typ := c.lookup_type_by_name(qualified_name) {
762 return typ
763 }
764 if typ := c.lookup_type_by_name(expr.name) {
765 return typ
766 }
767 }
768 ast.SelectorExpr {
769 if typ := c.lookup_type_by_name(expr.name().replace('.', '__')) {
770 return typ
771 }
772 }
773 else {}
774 }
775
776 return c.type_expr(expr)
777}
778
779fn to_optional_type(typ Type) ?Type {
780 return typ
781}
782
783fn unwrap_map_type(typ Type) ?Map {
784 mut cur := typ
785 for {
786 if type_data_ptr_is_nil(cur) {
787 break
788 }
789 if cur is Pointer {
790 cur = (cur as Pointer).base_type
791 continue
792 }
793 if cur is Alias {
794 cur = (cur as Alias).base_type
795 continue
796 }
797 if cur is OptionType {
798 cur = (cur as OptionType).base_type
799 continue
800 }
801 if cur is ResultType {
802 cur = (cur as ResultType).base_type
803 continue
804 }
805 break
806 }
807 if cur is Map {
808 return cur as Map
809 }
810 return none
811}
812
813fn object_from_type(typ Type) Object {
814 return TypeObject{
815 typ: typ
816 }
817}
818
819fn object_as_type(obj Object) ?Type {
820 if obj is Type {
821 return obj
822 }
823 if obj is TypeObject {
824 return obj.typ
825 }
826 return none
827}
828
829fn value_object_from_type(name string, typ Type) Object {
830 mut obj := Global{
831 name: name
832 typ: Type(void_)
833 }
834 obj.typ = typ
835 return obj
836}
837
838fn global_decl_field_is_c_extern(decl ast.GlobalDecl, field ast.FieldDecl) bool {
839 return field.name.starts_with('C.') || decl.attributes.has('c_extern')
840 || field.attributes.has('c_extern')
841}
842
843fn module_storage_object(module_name string, decl ast.GlobalDecl, field ast.FieldDecl, typ Type) Global {
844 storage_module := if global_decl_field_is_c_extern(decl, field) { '' } else { module_name }
845 mut obj := Global{
846 name: field.name
847 mod: storage_module
848 is_public: field.is_public
849 is_mut: field.is_mut
850 typ: Type(void_)
851 }
852 obj.typ = typ
853 return obj
854}
855
856fn (mut c Checker) module_storage_predecl_type(field ast.FieldDecl) Type {
857 if field.typ !is ast.EmptyExpr {
858 if typ := c.module_storage_predecl_type_expr(field.typ) {
859 return typ
860 }
861 return c.type_expr(field.typ)
862 }
863 match field.value {
864 ast.BasicLiteral, ast.StringLiteral {
865 return c.expr(field.value)
866 }
867 else {}
868 }
869
870 return Type(int_)
871}
872
873fn (mut c Checker) module_storage_predecl_type_expr(expr ast.Expr) ?Type {
874 match expr {
875 ast.Ident {
876 if typ := builtin_type(expr.name) {
877 return typ
878 }
879 if obj := universe.lookup_parent(expr.name, 0) {
880 if typ := object_as_type(obj) {
881 return typ
882 }
883 }
884 if typ := c.lookup_type_in_scope_chain(expr.name) {
885 return typ
886 }
887 if typ := c.lookup_type_in_imported_modules(expr.name) {
888 return typ
889 }
890 return Type(NamedType(c.qualify_type_name(expr.name)))
891 }
892 ast.SelectorExpr {
893 parts := selector_expr_parts(expr)
894 if parts.len >= 2 {
895 module_alias := parts[parts.len - 2]
896 tname := parts[parts.len - 1]
897 if typ := c.lookup_type_in_module(module_alias, tname) {
898 return typ
899 }
900 }
901 return Type(NamedType(expr.name().replace('.', '__')))
902 }
903 ast.Type {
904 if expr is ast.ArrayType {
905 if elem_type := c.module_storage_predecl_type_expr(expr.elem_type) {
906 return Type(Array{
907 elem_type: elem_type
908 })
909 }
910 }
911 if expr is ast.ArrayFixedType {
912 if elem_type := c.module_storage_predecl_type_expr(expr.elem_type) {
913 mut len := 0
914 if expr.len is ast.BasicLiteral {
915 if expr.len.kind == .number {
916 len = int(strconv.parse_int(expr.len.value, 0, 64) or { 0 })
917 }
918 } else if expr.len is ast.Ident {
919 if obj := c.scope.lookup_parent(expr.len.name, 0) {
920 if obj is Const {
921 len = obj.int_val
922 }
923 }
924 }
925 return Type(ArrayFixed{
926 len: len
927 elem_type: elem_type
928 })
929 }
930 }
931 if expr is ast.MapType {
932 if key_type := c.module_storage_predecl_type_expr(expr.key_type) {
933 if value_type := c.module_storage_predecl_type_expr(expr.value_type) {
934 return Type(Map{
935 key_type: key_type
936 value_type: value_type
937 })
938 }
939 }
940 }
941 if expr is ast.OptionType {
942 if base_type := c.module_storage_predecl_type_expr(expr.base_type) {
943 return Type(OptionType{
944 base_type: base_type
945 })
946 }
947 }
948 if expr is ast.PointerType {
949 if base_type := c.module_storage_predecl_type_expr(expr.base_type) {
950 return Type(Pointer{
951 base_type: base_type
952 lifetime: expr.lifetime
953 })
954 }
955 }
956 if expr is ast.ResultType {
957 if base_type := c.module_storage_predecl_type_expr(expr.base_type) {
958 return Type(ResultType{
959 base_type: base_type
960 })
961 }
962 }
963 }
964 else {}
965 }
966
967 return none
968}
969
970fn (mut c Checker) preregister_module_storage_decl(decl ast.GlobalDecl) {
971 for field in decl.fields {
972 field_type := c.module_storage_predecl_type(field)
973 obj := module_storage_object(c.cur_file_module, decl, field, field_type)
974 c.scope.insert(field.name, obj)
975 }
976}
977
978fn (obj Global) is_module_storage() bool {
979 return obj.mod != ''
980}
981
982fn (obj Global) requires_explicit_module_storage_mut() bool {
983 _ = obj
984 // This V2 palier keeps legacy `__global name` mutable on purpose, so
985 // existing V2/runtime sources do not need syntax that the V1 parser/vfmt
986 // still rejects. `is_mut` remains source metadata for the explicit spelling.
987 return false
988}
989
990fn (mut c Checker) module_storage_for_lhs(expr ast.Expr) ?Global {
991 unwrapped := c.unwrap_expr(expr)
992 match unwrapped {
993 ast.Ident {
994 if obj := c.scope.lookup_parent(unwrapped.name, 0) {
995 if obj is Global && obj.is_module_storage() {
996 return obj
997 }
998 }
999 }
1000 ast.SelectorExpr {
1001 if unwrapped.lhs is ast.Ident {
1002 lhs_ident := unwrapped.lhs as ast.Ident
1003 if lhs_obj := c.scope.lookup_parent(lhs_ident.name, 0) {
1004 if lhs_obj is Module {
1005 if rhs_obj := lhs_obj.scope.lookup_parent(unwrapped.rhs.name, 0) {
1006 if rhs_obj is Global && rhs_obj.is_module_storage() {
1007 declaring_mod := if rhs_obj.mod != '' {
1008 rhs_obj.mod
1009 } else {
1010 lhs_obj.name
1011 }
1012 if declaring_mod != c.cur_file_module && !rhs_obj.is_public {
1013 c.error_with_pos('module global `${lhs_ident.name}.${unwrapped.rhs.name}` is private',
1014 unwrapped.pos)
1015 return none
1016 }
1017 return rhs_obj
1018 }
1019 }
1020 }
1021 if lhs_obj is Global && lhs_obj.is_module_storage() {
1022 return lhs_obj
1023 }
1024 }
1025 }
1026 if storage := c.module_storage_for_lhs(unwrapped.lhs) {
1027 return storage
1028 }
1029 }
1030 ast.IndexExpr {
1031 if storage := c.module_storage_for_lhs(unwrapped.lhs) {
1032 return storage
1033 }
1034 }
1035 else {}
1036 }
1037
1038 return none
1039}
1040
1041fn (mut c Checker) check_module_storage_assignment(lhs ast.Expr, op token.Token, pos token.Pos) {
1042 if op == .decl_assign {
1043 return
1044 }
1045 if storage := c.module_storage_for_lhs(lhs) {
1046 if storage.requires_explicit_module_storage_mut() && !storage.is_mut {
1047 mut display_name := storage.name
1048 if storage.mod != '' && !storage.name.starts_with('${storage.mod}.') {
1049 display_name = '${storage.mod}.${storage.name}'
1050 }
1051 c.error_with_pos('cannot assign to immutable module global `${display_name}`; declare it with `__global mut`',
1052 pos)
1053 }
1054 }
1055}
1056
1057fn field_owner_module(field Field, owner_struct string) string {
1058 if field.owner_module != '' {
1059 return field.owner_module
1060 }
1061 if owner_struct.contains('__') {
1062 return owner_struct.all_before_last('__')
1063 }
1064 return ''
1065}
1066
1067fn field_access_display(info FieldAccessInfo) string {
1068 owner_module := field_owner_module(info.field, info.owner_struct)
1069 struct_name := if info.owner_struct.contains('__') {
1070 info.owner_struct.all_after_last('__')
1071 } else {
1072 info.owner_struct
1073 }
1074 if owner_module != '' && struct_name != '' {
1075 return '${owner_module}.${struct_name}.${info.field.name}'
1076 }
1077 if struct_name != '' {
1078 return '${struct_name}.${info.field.name}'
1079 }
1080 return info.field.name
1081}
1082
1083fn (mut c Checker) find_field_info(t Type, raw_name string) ?FieldAccessInfo {
1084 name := if raw_name.len > 0 && raw_name[0] == `@` { raw_name[1..] } else { raw_name }
1085 match t {
1086 Alias {
1087 al := t as Alias
1088 mut base_type := al.base_type
1089 if base_type.name() == '' {
1090 live_base_type := c.resolve_stale_alias(al.name)
1091 if live_base_type.name() != '' {
1092 base_type = live_base_type
1093 }
1094 }
1095 if base_type.name() != '' && base_type.name() != al.name {
1096 return c.find_field_info(base_type, name)
1097 }
1098 }
1099 Pointer {
1100 pt := t as Pointer
1101 return c.find_field_info(pt.base_type, name)
1102 }
1103 OptionType {
1104 ot := t as OptionType
1105 return c.find_field_info(ot.base_type, name)
1106 }
1107 ResultType {
1108 rt := t as ResultType
1109 return c.find_field_info(rt.base_type, name)
1110 }
1111 NamedType {
1112 nt := t as NamedType
1113 concrete_types := c.resolve_active_generic_named_types(nt)
1114 if concrete_types.len == 1 {
1115 return c.find_field_info(concrete_types[0], name)
1116 }
1117 if concrete_types.len > 1 {
1118 mut prev_info := FieldAccessInfo{}
1119 mut found_info := false
1120 mut has_missing_field := false
1121 mut has_mismatched_field_type := false
1122 mut module_mut_fields := []Field{}
1123 mut module_mut_owner_structs := []string{}
1124 for concrete_type in concrete_types {
1125 info := c.find_field_info(concrete_type, name) or {
1126 has_missing_field = true
1127 continue
1128 }
1129 if found_info && info.field.typ.name() != prev_info.field.typ.name() {
1130 has_mismatched_field_type = true
1131 }
1132 if info.module_mut_fields.len > 0 {
1133 for i, module_mut_field in info.module_mut_fields {
1134 module_mut_fields << module_mut_field
1135 if i < info.module_mut_owner_structs.len {
1136 module_mut_owner_structs << info.module_mut_owner_structs[i]
1137 } else {
1138 module_mut_owner_structs << info.owner_struct
1139 }
1140 }
1141 } else if info.field.is_module_mut {
1142 module_mut_fields << info.field
1143 module_mut_owner_structs << info.owner_struct
1144 }
1145 prev_info = info
1146 found_info = true
1147 }
1148 if found_info {
1149 if (has_missing_field || has_mismatched_field_type)
1150 && module_mut_fields.len == 0 {
1151 return none
1152 }
1153 return FieldAccessInfo{
1154 field: prev_info.field
1155 owner_struct: prev_info.owner_struct
1156 module_mut_fields: module_mut_fields
1157 module_mut_owner_structs: module_mut_owner_structs
1158 }
1159 }
1160 }
1161 }
1162 Struct {
1163 st := t as Struct
1164 if st.fields.len == 0 && st.embedded.len == 0 && st.name != '' {
1165 if obj := c.lookup_type_by_name(st.name) {
1166 if obj is Struct {
1167 if obj.fields.len > 0 || obj.embedded.len > 0 || obj.name != st.name {
1168 if info := c.find_field_info(obj, name) {
1169 return info
1170 }
1171 }
1172 }
1173 }
1174 }
1175 for field in st.fields {
1176 if field.name == name {
1177 return FieldAccessInfo{
1178 field: field
1179 owner_struct: st.name
1180 }
1181 }
1182 }
1183 for embedded_type in st.embedded {
1184 mut live_type := Type(embedded_type)
1185 if obj := c.lookup_type_by_name(embedded_type.name) {
1186 live_type = obj
1187 }
1188 if info := c.find_field_info(live_type, name) {
1189 return info
1190 }
1191 }
1192 }
1193 SumType {
1194 smt := c.live_sumtype(t as SumType)
1195 mut prev_info := FieldAccessInfo{}
1196 mut found_info := false
1197 mut module_mut_fields := []Field{}
1198 mut module_mut_owner_structs := []string{}
1199 for variant in smt.variants {
1200 mut variant_type := resolve_alias(variant)
1201 if live_variant := c.lookup_type_by_name(variant_type.name()) {
1202 variant_type = resolve_alias(live_variant)
1203 }
1204 info := c.find_field_info(variant_type, name) or { return none }
1205 if found_info && info.field.typ.name() != prev_info.field.typ.name() {
1206 return none
1207 }
1208 if info.module_mut_fields.len > 0 {
1209 for i, module_mut_field in info.module_mut_fields {
1210 module_mut_fields << module_mut_field
1211 if i < info.module_mut_owner_structs.len {
1212 module_mut_owner_structs << info.module_mut_owner_structs[i]
1213 } else {
1214 module_mut_owner_structs << info.owner_struct
1215 }
1216 }
1217 } else if info.field.is_module_mut {
1218 module_mut_fields << info.field
1219 module_mut_owner_structs << info.owner_struct
1220 }
1221 prev_info = info
1222 found_info = true
1223 }
1224 if found_info {
1225 return FieldAccessInfo{
1226 field: prev_info.field
1227 owner_struct: prev_info.owner_struct
1228 module_mut_fields: module_mut_fields
1229 module_mut_owner_structs: module_mut_owner_structs
1230 }
1231 }
1232 }
1233 else {}
1234 }
1235
1236 return none
1237}
1238
1239fn module_mut_field_access_is_external(info FieldAccessInfo, cur_file_module string) bool {
1240 if _ := module_mut_field_access_external_info(info, cur_file_module) {
1241 return true
1242 }
1243 return false
1244}
1245
1246fn module_mut_field_access_has_module_mut(info FieldAccessInfo) bool {
1247 return info.field.is_module_mut || info.module_mut_fields.len > 0
1248}
1249
1250fn module_mut_field_access_external_info(info FieldAccessInfo, cur_file_module string) ?FieldAccessInfo {
1251 if info.module_mut_fields.len > 0 {
1252 for i, module_mut_field in info.module_mut_fields {
1253 owner_struct := if i < info.module_mut_owner_structs.len {
1254 info.module_mut_owner_structs[i]
1255 } else {
1256 info.owner_struct
1257 }
1258 variant_info := FieldAccessInfo{
1259 field: module_mut_field
1260 owner_struct: owner_struct
1261 }
1262 owner_module := field_owner_module(variant_info.field, variant_info.owner_struct)
1263 if owner_module != '' && owner_module != cur_file_module {
1264 return variant_info
1265 }
1266 }
1267 return none
1268 }
1269 owner_module := field_owner_module(info.field, info.owner_struct)
1270 if info.field.is_module_mut && owner_module != '' && owner_module != cur_file_module {
1271 return info
1272 }
1273 return none
1274}
1275
1276fn (mut c Checker) check_module_mut_method_value_extraction(lhs ast.Expr, method_type Type, pos token.Pos) {
1277 if c.expecting_method {
1278 return
1279 }
1280 if method_type is FnType && method_type.is_mut_receiver {
1281 c.check_module_mut_field_mutation(lhs, pos)
1282 }
1283}
1284
1285fn (mut c Checker) module_mut_field_access_in_expr(expr ast.Expr) ?FieldAccessInfo {
1286 match expr {
1287 ast.AsCastExpr {
1288 return c.module_mut_field_access_in_expr(expr.expr)
1289 }
1290 ast.CastExpr {
1291 return c.module_mut_field_access_in_expr(expr.expr)
1292 }
1293 ast.PrefixExpr {
1294 if expr.op == .mul {
1295 return c.module_mut_field_access_in_expr(expr.expr)
1296 }
1297 }
1298 else {}
1299 }
1300
1301 unwrapped := c.unwrap_expr(expr)
1302 match unwrapped {
1303 ast.AsCastExpr {
1304 return c.module_mut_field_access_in_expr(unwrapped.expr)
1305 }
1306 ast.CastExpr {
1307 return c.module_mut_field_access_in_expr(unwrapped.expr)
1308 }
1309 ast.IndexExpr {
1310 return c.module_mut_field_access_in_expr(unwrapped.lhs)
1311 }
1312 ast.SelectorExpr {
1313 mut lhs_module_mut_info := FieldAccessInfo{}
1314 mut has_lhs_module_mut_info := false
1315 if info := c.module_mut_field_access_in_expr(unwrapped.lhs) {
1316 if module_mut_field_access_is_external(info, c.cur_file_module) {
1317 return info
1318 }
1319 lhs_module_mut_info = info
1320 has_lhs_module_mut_info = true
1321 }
1322 rhs_name := c.selector_rhs_name(unwrapped)
1323 if lhs_type := c.expr_type_without_field_smartcast(unwrapped.lhs) {
1324 if info := c.find_field_info(lhs_type, rhs_name) {
1325 if module_mut_field_access_has_module_mut(info) {
1326 return info
1327 }
1328 }
1329 }
1330 smartcast_lhs_type := c.expr(unwrapped.lhs)
1331 if info := c.find_field_info(smartcast_lhs_type, rhs_name) {
1332 if module_mut_field_access_has_module_mut(info) {
1333 return info
1334 }
1335 }
1336 if has_lhs_module_mut_info {
1337 return lhs_module_mut_info
1338 }
1339 }
1340 else {}
1341 }
1342
1343 return none
1344}
1345
1346fn (mut c Checker) check_module_mut_field_mutation(expr ast.Expr, pos token.Pos) {
1347 if info := c.module_mut_field_access_in_expr(expr) {
1348 if external_info := module_mut_field_access_external_info(info, c.cur_file_module) {
1349 owner_module := field_owner_module(external_info.field, external_info.owner_struct)
1350 c.error_with_pos('cannot mutate module-mutable field `${field_access_display(external_info)}` outside module `${owner_module}`',
1351 pos)
1352 }
1353 }
1354}
1355
1356fn (mut c Checker) check_module_mut_field_mut_arg(expr ast.Expr, pos token.Pos) {
1357 if info := c.module_mut_field_access_in_expr(expr) {
1358 if external_info := module_mut_field_access_external_info(info, c.cur_file_module) {
1359 owner_module := field_owner_module(external_info.field, external_info.owner_struct)
1360 c.error_with_pos('cannot pass module-mutable field `${field_access_display(external_info)}` as mut outside module `${owner_module}`',
1361 pos)
1362 }
1363 }
1364}
1365
1366fn (mut c Checker) check_module_mut_field_mut_ref(expr ast.Expr, pos token.Pos) {
1367 if info := c.module_mut_field_access_in_expr(expr) {
1368 if external_info := module_mut_field_access_external_info(info, c.cur_file_module) {
1369 owner_module := field_owner_module(external_info.field, external_info.owner_struct)
1370 c.error_with_pos('cannot take mutable reference to module-mutable field `${field_access_display(external_info)}` outside module `${owner_module}`',
1371 pos)
1372 }
1373 }
1374}
1375
1376fn (mut c Checker) check_module_mut_call_arg(arg ast.Expr) {
1377 if arg is ast.ModifierExpr && arg.kind == .key_mut {
1378 c.check_module_mut_field_mut_arg(arg.expr, arg.pos)
1379 }
1380}
1381
1382fn is_empty_expr(e ast.Expr) bool {
1383 return e is ast.EmptyExpr
1384}
1385
1386fn with_generic_params(fn_type FnType, params []string) FnType {
1387 return FnType{
1388 generic_params: params
1389 params: fn_type.params
1390 return_type: fn_type.return_type
1391 is_variadic: fn_type.is_variadic
1392 is_mut_receiver: fn_type.is_mut_receiver
1393 attributes: fn_type.attributes
1394 generic_types: fn_type.generic_types
1395 }
1396}
1397
1398fn empty_channel() Channel {
1399 return Channel{
1400 elem_type: none
1401 }
1402}
1403
1404fn empty_fn_type() FnType {
1405 return FnType{
1406 return_type: none
1407 }
1408}
1409
1410fn empty_thread() Thread {
1411 return Thread{
1412 elem_type: none
1413 }
1414}
1415
1416fn comptime_method_info_type() Type {
1417 return Type(Struct{
1418 name: '__method_info'
1419 fields: [
1420 Field{
1421 name: 'name'
1422 typ: Type(string_)
1423 },
1424 Field{
1425 name: 'attrs'
1426 typ: Type(Array{
1427 elem_type: Type(string_)
1428 })
1429 },
1430 Field{
1431 name: 'args'
1432 typ: Type(Array{
1433 elem_type: comptime_function_param_info_type()
1434 })
1435 },
1436 Field{
1437 name: 'location'
1438 typ: Type(string_)
1439 },
1440 Field{
1441 name: 'return_type'
1442 typ: comptime_type_info_type()
1443 },
1444 ]
1445 })
1446}
1447
1448fn comptime_function_param_info_type() Type {
1449 return Type(Struct{
1450 name: '__function_param_info'
1451 fields: [
1452 Field{
1453 name: 'name'
1454 typ: Type(string_)
1455 },
1456 Field{
1457 name: 'typ'
1458 typ: comptime_type_info_type()
1459 },
1460 ]
1461 })
1462}
1463
1464fn comptime_type_info_ref_type() Type {
1465 return Type(Struct{
1466 name: '__type_info'
1467 })
1468}
1469
1470fn comptime_type_info_type() Type {
1471 return Type(Struct{
1472 name: '__type_info'
1473 fields: [
1474 Field{
1475 name: 'name'
1476 typ: Type(string_)
1477 },
1478 Field{
1479 name: 'idx'
1480 typ: Type(int_)
1481 },
1482 Field{
1483 name: 'typ'
1484 typ: comptime_type_info_ref_type()
1485 },
1486 Field{
1487 name: 'unaliased_typ'
1488 typ: comptime_type_info_ref_type()
1489 },
1490 Field{
1491 name: 'indirections'
1492 typ: Type(u8_)
1493 },
1494 ]
1495 })
1496}
1497
1498fn comptime_field_info_type() Type {
1499 return Type(Struct{
1500 name: '__field_info'
1501 fields: [
1502 Field{
1503 name: 'name'
1504 typ: Type(string_)
1505 },
1506 Field{
1507 name: 'typ'
1508 typ: comptime_type_info_type()
1509 },
1510 Field{
1511 name: 'unaliased_typ'
1512 typ: comptime_type_info_type()
1513 },
1514 Field{
1515 name: 'attrs'
1516 typ: Type(Array{
1517 elem_type: Type(string_)
1518 })
1519 },
1520 Field{
1521 name: 'is_pub'
1522 typ: Type(bool_)
1523 },
1524 Field{
1525 name: 'is_mut'
1526 typ: Type(bool_)
1527 },
1528 Field{
1529 name: 'is_embed'
1530 typ: Type(bool_)
1531 },
1532 Field{
1533 name: 'is_shared'
1534 typ: Type(bool_)
1535 },
1536 Field{
1537 name: 'is_atomic'
1538 typ: Type(bool_)
1539 },
1540 Field{
1541 name: 'is_option'
1542 typ: Type(bool_)
1543 },
1544 Field{
1545 name: 'is_array'
1546 typ: Type(bool_)
1547 },
1548 Field{
1549 name: 'is_map'
1550 typ: Type(bool_)
1551 },
1552 Field{
1553 name: 'is_chan'
1554 typ: Type(bool_)
1555 },
1556 Field{
1557 name: 'is_enum'
1558 typ: Type(bool_)
1559 },
1560 Field{
1561 name: 'is_struct'
1562 typ: Type(bool_)
1563 },
1564 Field{
1565 name: 'is_alias'
1566 typ: Type(bool_)
1567 },
1568 Field{
1569 name: 'indirections'
1570 typ: Type(u8_)
1571 },
1572 ]
1573 })
1574}
1575
1576fn comptime_enum_value_info_type() Type {
1577 return Type(Struct{
1578 name: '__enum_value_info'
1579 fields: [
1580 Field{
1581 name: 'name'
1582 typ: Type(string_)
1583 },
1584 Field{
1585 name: 'value'
1586 typ: Type(int_)
1587 },
1588 Field{
1589 name: 'attrs'
1590 typ: Type(Array{
1591 elem_type: Type(string_)
1592 })
1593 },
1594 ]
1595 })
1596}
1597
1598fn comptime_type_metadata_selector_type(name string) ?Type {
1599 return match name {
1600 'name' {
1601 Type(string_)
1602 }
1603 'idx' {
1604 Type(int_)
1605 }
1606 'typ', 'unaliased_typ' {
1607 comptime_type_info_type()
1608 }
1609 'indirections' {
1610 Type(u8_)
1611 }
1612 'fields' {
1613 Type(Array{
1614 elem_type: comptime_field_info_type()
1615 })
1616 }
1617 'methods' {
1618 Type(Array{
1619 elem_type: comptime_method_info_type()
1620 })
1621 }
1622 'variants' {
1623 Type(Array{
1624 elem_type: comptime_type_info_type()
1625 })
1626 }
1627 'values' {
1628 Type(Array{
1629 elem_type: comptime_enum_value_info_type()
1630 })
1631 }
1632 else {
1633 none
1634 }
1635 }
1636}
1637
1638fn (c &Checker) is_comptime_type_selector_lhs_ident(name string) bool {
1639 if name in c.generic_params {
1640 return true
1641 }
1642 for generic_types in c.env.cur_generic_types {
1643 if name in generic_types {
1644 return true
1645 }
1646 }
1647 if _ := c.lookup_type_by_name(name) {
1648 return true
1649 }
1650 if _ := c.lookup_type_by_name(c.qualify_type_name(name)) {
1651 return true
1652 }
1653 return false
1654}
1655
1656fn fn_with_return_type(fn_type FnType, return_type Type) FnType {
1657 return FnType{
1658 generic_params: fn_type.generic_params
1659 params: fn_type.params
1660 return_type: to_optional_type(return_type)
1661 is_variadic: fn_type.is_variadic
1662 is_mut_receiver: fn_type.is_mut_receiver
1663 attributes: fn_type.attributes
1664 generic_types: fn_type.generic_types
1665 }
1666}
1667
1668pub fn (mut c Checker) get_module_scope(module_name string, parent &Scope) &Scope {
1669 mut scope := &Scope(unsafe { nil })
1670 lock c.env.scopes {
1671 if module_name in c.env.scopes {
1672 scope = unsafe { c.env.scopes[module_name] }
1673 } else {
1674 scope = new_scope(parent)
1675 c.env.scopes[module_name] = scope
1676 }
1677 }
1678 return scope
1679}
1680
1681// check_flat is the Phase 2 consumer entry point: accepts a FlatAst directly
1682// rather than []ast.File. Top-level passes walk the FlatAst and decode only
1683// the legacy nodes they still need internally.
1684pub fn (mut c Checker) check_flat(flat &ast.FlatAst) {
1685 c.register_selector_names_from_flat(flat)
1686 c.preregister_all_scopes_from_flat(flat)
1687 c.preregister_all_types_from_flat(flat)
1688 c.register_imported_symbols_from_flat(flat)
1689 c.collect_fn_signatures_only = true
1690 c.preregister_all_fn_signatures_from_flat(flat)
1691 c.collect_fn_signatures_only = false
1692 c.register_imported_symbols_from_flat(flat)
1693 for ff in flat.files {
1694 c.check_file_from_flat(flat, ff)
1695 }
1696 c.process_pending_const_fields()
1697 $if ownership ? {
1698 c.ownership_prescan_fn_bodies()
1699 c.ownership_validate_drop_impls()
1700 c.lifetime_validate_files_from_flat(flat)
1701 c.escape_validate_files_from_flat(flat)
1702 }
1703 c.process_pending_fn_bodies()
1704 c.check_struct_field_defaults_from_flat(flat)
1705 c.check_enum_field_values_from_flat(flat)
1706}
1707
1708// register_selector_names_from_flat reads selector_names directly from
1709// FlatFile entries without rehydrating to legacy ast.File. First example of
1710// a Phase 2 consumer reading the flat representation in place.
1711fn (mut c Checker) register_selector_names_from_flat(flat &ast.FlatAst) {
1712 for ff in flat.files {
1713 for id, name in ff.selector_names {
1714 c.env.selector_names[id] = name
1715 }
1716 }
1717}
1718
1719// register_imported_symbols_from_flat mirrors register_imported_symbols but
1720// pulls each file's mod + imports through the FlatAst readers.
1721fn (mut c Checker) register_imported_symbols_from_flat(flat &ast.FlatAst) {
1722 builtin_scope := c.get_module_scope('builtin', universe)
1723 for ff in flat.files {
1724 mod := flat.file_mod(ff)
1725 mut file_scope := c.get_module_scope(mod, builtin_scope)
1726 for imp in c.active_file_imports_from_flat(flat, ff) {
1727 if imp.symbols.len == 0 {
1728 continue
1729 }
1730 import_mod := if imp.is_aliased {
1731 imp.name.all_after_last('.')
1732 } else {
1733 imp.alias
1734 }
1735 mut import_scope := c.get_module_scope(import_mod, builtin_scope)
1736 for symbol in imp.symbols {
1737 if symbol is ast.Ident {
1738 if sym_obj := import_scope.lookup_parent(symbol.name, 0) {
1739 if sym_obj is Global && sym_obj.is_module_storage() {
1740 continue
1741 }
1742 file_scope.insert(symbol.name, sym_obj)
1743 }
1744 }
1745 }
1746 }
1747 }
1748}
1749
1750// active_file_imports_from_flat returns the import statements declared in a
1751// FlatFile, including those guarded by comptime `$if` blocks whose condition
1752// currently evaluates true.
1753fn (mut c Checker) active_file_imports_from_flat(flat &ast.FlatAst, ff ast.FlatFile) []ast.ImportStmt {
1754 // s258: walk the file's top-level statement cursors instead of decoding the
1755 // entire file via top-level statement decoding. This runs per file (preregister_scopes +
1756 // register_imported_symbols), so the full legacy-AST decode was pure churn;
1757 // the cursor walk only decodes the tiny comptime `$if` conditions. Mirrors the
1758 // builder's s253 cursor-native import collection. Removes a legacy-AST decode
1759 // site (toward dropping the old AST) and cuts flat-path memory.
1760 file_node := ast.Cursor{
1761 flat: unsafe { flat }
1762 id: ff.file_id
1763 }
1764 mut imports := file_node.list_at(1).import_stmts()
1765 c.collect_active_imports_from_stmts_cursor(file_node.list_at(2), mut imports)
1766 return imports
1767}
1768
1769// collect_active_imports_from_stmts_cursor is the cursor-native mirror of
1770// collect_active_imports_from_stmts: it appends top-level `import` statements and
1771// the imports of active comptime `$if` branches, walking the FlatAst directly.
1772fn (c &Checker) collect_active_imports_from_stmts_cursor(stmts ast.CursorList, mut imports []ast.ImportStmt) {
1773 for i in 0 .. stmts.len() {
1774 c.collect_active_imports_from_stmt_cursor(stmts.at(i), mut imports)
1775 }
1776}
1777
1778fn (c &Checker) collect_active_imports_from_stmt_cursor(s ast.Cursor, mut imports []ast.ImportStmt) {
1779 match s.kind() {
1780 .stmt_import {
1781 imports << s.import_stmt()
1782 }
1783 .stmt_expr {
1784 inner := s.edge(0)
1785 if inner.kind() == .expr_comptime {
1786 cif := inner.edge(0)
1787 if cif.kind() == .expr_if {
1788 c.collect_active_imports_from_if_cursor(cif, mut imports)
1789 }
1790 }
1791 }
1792 else {}
1793 }
1794}
1795
1796// collect_active_imports_from_if_cursor mirrors collect_active_imports_from_if_expr.
1797// expr_if layout: edge0 = cond, edge1 = else_expr, edge2.. = then-branch stmts.
1798fn (c &Checker) collect_active_imports_from_if_cursor(if_c ast.Cursor, mut imports []ast.ImportStmt) {
1799 if c.eval_comptime_cond_cursor(if_c.edge(0)) {
1800 for i in 2 .. if_c.edge_count() {
1801 c.collect_active_imports_from_stmt_cursor(if_c.edge(i), mut imports)
1802 }
1803 return
1804 }
1805 else_c := if_c.edge(1)
1806 if else_c.kind() == .expr_if {
1807 if else_c.edge(0).kind() == .expr_empty {
1808 for i in 2 .. else_c.edge_count() {
1809 c.collect_active_imports_from_stmt_cursor(else_c.edge(i), mut imports)
1810 }
1811 } else {
1812 c.collect_active_imports_from_if_cursor(else_c, mut imports)
1813 }
1814 }
1815}
1816
1817fn (c &Checker) eval_comptime_cond_cursor(cond ast.Cursor) bool {
1818 if !cond.is_valid() {
1819 return false
1820 }
1821 match cond.kind() {
1822 .expr_ident {
1823 return c.eval_comptime_flag(cond.name())
1824 }
1825 .expr_prefix {
1826 op := unsafe { token.Token(int(cond.aux())) }
1827 if op == .not {
1828 return !c.eval_comptime_cond_cursor(cond.edge(0))
1829 }
1830 }
1831 .expr_infix {
1832 op := unsafe { token.Token(int(cond.aux())) }
1833 if op == .and {
1834 return c.eval_comptime_cond_cursor(cond.edge(0))
1835 && c.eval_comptime_cond_cursor(cond.edge(1))
1836 }
1837 if op == .logical_or {
1838 return c.eval_comptime_cond_cursor(cond.edge(0))
1839 || c.eval_comptime_cond_cursor(cond.edge(1))
1840 }
1841 }
1842 .expr_postfix {
1843 op := unsafe { token.Token(int(cond.aux())) }
1844 inner := cond.edge(0)
1845 if op == .question && inner.kind() == .expr_ident {
1846 return pref.comptime_optional_flag_value(c.pref, inner.name())
1847 }
1848 }
1849 .expr_paren {
1850 return c.eval_comptime_cond_cursor(cond.edge(0))
1851 }
1852 else {}
1853 }
1854
1855 return false
1856}
1857
1858// preregister_all_scopes_from_flat mirrors preregister_all_scopes but pulls
1859// each file's mod + imports through the FlatAst readers, no []ast.File hop.
1860fn (mut c Checker) preregister_all_scopes_from_flat(flat &ast.FlatAst) {
1861 for ff in flat.files {
1862 c.preregister_scopes_from_flat(flat, ff)
1863 }
1864}
1865
1866fn (mut c Checker) preregister_scopes_from_flat(flat &ast.FlatAst, ff ast.FlatFile) {
1867 builtin_scope := c.get_module_scope('builtin', universe)
1868 mod := flat.file_mod(ff)
1869 mod_scope := c.get_module_scope(mod, builtin_scope)
1870 c.scope = mod_scope
1871 // add self (own module) for constants. can use own module prefix inside module
1872 c.scope.insert(mod, Module{
1873 name: mod
1874 scope: c.get_module_scope(mod, builtin_scope)
1875 })
1876 // add imports (including comptime-conditional)
1877 for imp in c.active_file_imports_from_flat(flat, ff) {
1878 import_mod := if imp.is_aliased { imp.name.all_after_last('.') } else { imp.alias }
1879 c.scope.insert(imp.alias, Module{
1880 name: import_mod
1881 scope: c.get_module_scope(import_mod, builtin_scope)
1882 })
1883 }
1884 // add C
1885 c.scope.insert('C', Module{ name: 'C', scope: c.c_scope })
1886}
1887
1888// preregister_all_types_from_flat mirrors preregister_all_types but reads
1889// each file's mod + top-level stmts directly from the FlatAst.
1890fn (mut c Checker) preregister_all_types_from_flat(flat &ast.FlatAst) {
1891 for ff in flat.files {
1892 c.preregister_types_from_flat(flat, ff)
1893 }
1894 c.register_imported_symbols_from_flat(flat)
1895 c.process_pending_struct_decls()
1896 c.process_pending_type_decls()
1897 c.process_pending_interface_decls()
1898}
1899
1900fn (mut c Checker) preregister_types_from_flat(flat &ast.FlatAst, ff ast.FlatFile) {
1901 mod := flat.file_mod(ff)
1902 c.cur_file_module = mod
1903 mut mod_scope := &Scope(unsafe { nil })
1904 lock c.env.scopes {
1905 if mod in c.env.scopes {
1906 mod_scope = unsafe { c.env.scopes[mod] }
1907 } else {
1908 eprintln('warning: scope not found for mod: ${mod}, skipping')
1909 return
1910 }
1911 }
1912 c.scope = mod_scope
1913 decls := ast.Cursor{
1914 flat: unsafe { flat }
1915 id: ff.file_id
1916 }.list_at(2)
1917 for di in 0 .. decls.len() {
1918 c.preregister_decl_stmt_from_flat(decls.at(di), true, false)
1919 }
1920}
1921
1922// preregister_all_fn_signatures_from_flat mirrors preregister_all_fn_signatures
1923// but walks each file's top-level stmt cursors directly from the FlatAst.
1924fn (mut c Checker) preregister_all_fn_signatures_from_flat(flat &ast.FlatAst) {
1925 for ff in flat.files {
1926 c.preregister_fn_signatures_from_flat(flat, ff)
1927 }
1928}
1929
1930fn (mut c Checker) preregister_fn_signatures_from_flat(flat &ast.FlatAst, ff ast.FlatFile) {
1931 mod := flat.file_mod(ff)
1932 c.cur_file_module = mod
1933 mut mod_scope := &Scope(unsafe { nil })
1934 lock c.env.scopes {
1935 if mod in c.env.scopes {
1936 mod_scope = unsafe { c.env.scopes[mod] }
1937 } else {
1938 eprintln('warning: scope not found for mod: ${mod}, skipping')
1939 return
1940 }
1941 }
1942 c.scope = mod_scope
1943 prev_collect := c.collect_fn_signatures_only
1944 c.collect_fn_signatures_only = true
1945 decls := ast.Cursor{
1946 flat: unsafe { flat }
1947 id: ff.file_id
1948 }.list_at(2)
1949 for i in 0 .. decls.len() {
1950 c.preregister_decl_stmt_from_flat(decls.at(i), false, true)
1951 }
1952 c.collect_fn_signatures_only = prev_collect
1953}
1954
1955fn (mut c Checker) module_storage_predecl_type_from_flat_field(field_c ast.Cursor) Type {
1956 typ_c := field_c.edge(0)
1957 if typ_c.is_valid() && typ_c.kind() != .expr_empty {
1958 typ_expr := typ_c.type_expr()
1959 if typ := c.module_storage_predecl_type_expr(typ_expr) {
1960 return typ
1961 }
1962 return c.type_expr(typ_expr)
1963 }
1964 value_c := field_c.edge(1)
1965 if typ := c.literal_expr_type_from_cursor(value_c) {
1966 return typ
1967 }
1968 if value_c.kind() in [.expr_basic_literal, .expr_string] {
1969 return c.expr(value_c.attribute_expr())
1970 }
1971
1972 return Type(int_)
1973}
1974
1975fn (c &Checker) literal_expr_type_from_cursor(expr ast.Cursor) ?Type {
1976 if !expr.is_valid() {
1977 return none
1978 }
1979 match expr.kind() {
1980 .expr_basic_literal {
1981 kind := unsafe { token.Token(int(expr.aux())) }
1982 match kind {
1983 .char {
1984 return Type(rune_)
1985 }
1986 .key_false, .key_true {
1987 return bool_
1988 }
1989 .number {
1990 if expr.name().contains('.') {
1991 return float_literal_
1992 }
1993 return int_literal_
1994 }
1995 else {
1996 return none
1997 }
1998 }
1999 }
2000 .expr_string {
2001 kind := unsafe { ast.StringLiteralKind(int(expr.aux())) }
2002 if kind == .c {
2003 return charptr_
2004 }
2005 return Type(string_)
2006 }
2007 .expr_paren, .expr_modifier {
2008 return c.literal_expr_type_from_cursor(expr.edge(0))
2009 }
2010 else {}
2011 }
2012
2013 return none
2014}
2015
2016fn (mut c Checker) register_literal_expr_type_from_cursor(expr ast.Cursor) ?Type {
2017 typ := c.literal_expr_type_from_cursor(expr) or { return none }
2018 pos := expr.pos()
2019 if pos.id > 0 {
2020 c.env.set_expr_type(pos.id, typ)
2021 }
2022 return typ
2023}
2024
2025fn match_branch_from_flat_cursor(c ast.Cursor) ast.MatchBranch {
2026 conds := c.list_at(0)
2027 mut cond := []ast.Expr{cap: conds.len()}
2028 for i in 0 .. conds.len() {
2029 cond << conds.at(i).expr()
2030 }
2031 stmt_list := c.list_at(1)
2032 mut stmts := []ast.Stmt{cap: stmt_list.len()}
2033 for i in 0 .. stmt_list.len() {
2034 stmts << stmt_list.at(i).stmt()
2035 }
2036 return ast.MatchBranch{
2037 cond: cond
2038 stmts: stmts
2039 pos: c.pos()
2040 }
2041}
2042
2043fn match_expr_from_flat_cursor(c ast.Cursor) ast.MatchExpr {
2044 mut branches := []ast.MatchBranch{cap: c.edge_count() - 1}
2045 for i in 1 .. c.edge_count() {
2046 branches << match_branch_from_flat_cursor(c.edge(i))
2047 }
2048 return ast.MatchExpr{
2049 expr: c.edge(0).expr()
2050 branches: branches
2051 pos: c.pos()
2052 }
2053}
2054
2055fn (mut c Checker) preregister_module_storage_decl_from_flat(stmt_c ast.Cursor) {
2056 decl := stmt_c.global_decl(false)
2057 fields := stmt_c.list_at(1)
2058 for i in 0 .. fields.len() {
2059 field := fields.at(i)
2060 field_decl := field.field_decl(false)
2061 field_type := c.module_storage_predecl_type_from_flat_field(field)
2062 obj := module_storage_object(c.cur_file_module, decl, field_decl, field_type)
2063 c.scope.insert(field_decl.name, obj)
2064 }
2065}
2066
2067fn (mut c Checker) check_global_decl_from_flat(stmt_c ast.Cursor) {
2068 decl := stmt_c.global_decl(false)
2069 fields := stmt_c.list_at(1)
2070 for i in 0 .. fields.len() {
2071 field := fields.at(i)
2072 field_decl := field.field_decl(false)
2073 field_typ := field.edge(0).type_expr()
2074 field_type := if field_typ !is ast.EmptyExpr {
2075 c.type_expr(field_typ)
2076 } else {
2077 value_c := field.edge(1)
2078 if typ := c.register_literal_expr_type_from_cursor(value_c) {
2079 typ
2080 } else {
2081 c.expr(value_c.expr())
2082 }
2083 }
2084 obj := module_storage_object(c.cur_file_module, decl, field_decl, field_type)
2085 c.scope.insert_or_update(field_decl.name, obj)
2086 }
2087}
2088
2089fn (mut c Checker) check_expr_stmt_from_flat(stmt_c ast.Cursor) {
2090 expr_c := stmt_c.edge(0)
2091 if expr_c.kind() == .expr_match {
2092 c.match_expr(match_expr_from_flat_cursor(expr_c), false)
2093 return
2094 }
2095 if _ := c.register_literal_expr_type_from_cursor(expr_c) {
2096 return
2097 }
2098 c.expr(expr_c.expr())
2099}
2100
2101fn (mut c Checker) check_assert_stmt_from_flat(stmt_c ast.Cursor) {
2102 expr_c := stmt_c.edge(0)
2103 if _ := c.register_literal_expr_type_from_cursor(expr_c) {
2104 // Literal-only asserts do not need legacy expression materialization.
2105 } else {
2106 c.expr(expr_c.expr())
2107 }
2108 extra := stmt_c.edge(1)
2109 if extra.is_valid() && extra.kind() != .expr_empty {
2110 if _ := c.register_literal_expr_type_from_cursor(extra) {
2111 return
2112 }
2113 c.expr(extra.expr())
2114 }
2115}
2116
2117fn (mut c Checker) preregister_decl_stmt_from_flat(stmt_c ast.Cursor, want_types bool, want_fns bool) {
2118 match stmt_c.kind() {
2119 .stmt_const_decl {
2120 if want_types {
2121 c.decl(ast.Stmt(stmt_c.const_decl()))
2122 }
2123 }
2124 .stmt_enum_decl {
2125 if want_types {
2126 c.decl(ast.Stmt(stmt_c.enum_decl(false)))
2127 }
2128 }
2129 .stmt_fn_decl {
2130 if want_fns {
2131 decl := stmt_c.fn_decl_signature()
2132 prev_flat := c.pending_fn_body_flat
2133 prev_flat_id := c.pending_fn_body_flat_id
2134 c.pending_fn_body_flat = stmt_c.flat
2135 c.pending_fn_body_flat_id = stmt_c.id
2136 c.preregister_fn_signature_stmt(ast.Stmt(decl))
2137 c.pending_fn_body_flat = prev_flat
2138 c.pending_fn_body_flat_id = prev_flat_id
2139 }
2140 }
2141 .stmt_global_decl {
2142 if want_types {
2143 c.preregister_module_storage_decl_from_flat(stmt_c)
2144 }
2145 }
2146 .stmt_interface_decl {
2147 if want_types {
2148 c.decl(ast.Stmt(stmt_c.interface_decl()))
2149 }
2150 }
2151 .stmt_struct_decl {
2152 if want_types {
2153 c.decl(ast.Stmt(stmt_c.struct_decl()))
2154 }
2155 }
2156 .stmt_type_decl {
2157 if want_types {
2158 c.decl(ast.Stmt(stmt_c.type_decl()))
2159 }
2160 }
2161 .stmt_expr {
2162 c.preregister_active_comptime_decl_stmt_from_flat(stmt_c, want_types, want_fns)
2163 }
2164 else {}
2165 }
2166}
2167
2168fn (mut c Checker) preregister_active_comptime_decl_stmt_from_flat(stmt_c ast.Cursor, want_types bool, want_fns bool) {
2169 if stmt_c.kind() != .stmt_expr {
2170 return
2171 }
2172 inner := stmt_c.edge(0)
2173 if inner.kind() != .expr_comptime {
2174 return
2175 }
2176 if_c := inner.edge(0)
2177 if if_c.kind() != .expr_if {
2178 return
2179 }
2180 c.preregister_active_if_decl_stmts_from_flat(if_c, want_types, want_fns)
2181}
2182
2183fn (mut c Checker) preregister_active_if_decl_stmts_from_flat(if_c ast.Cursor, want_types bool, want_fns bool) {
2184 if c.eval_comptime_cond_cursor(if_c.edge(0)) {
2185 for i in 2 .. if_c.edge_count() {
2186 c.preregister_decl_stmt_from_flat(if_c.edge(i), want_types, want_fns)
2187 }
2188 return
2189 }
2190 else_c := if_c.edge(1)
2191 if else_c.kind() == .expr_if {
2192 if else_c.edge(0).kind() == .expr_empty {
2193 for i in 2 .. else_c.edge_count() {
2194 c.preregister_decl_stmt_from_flat(else_c.edge(i), want_types, want_fns)
2195 }
2196 } else {
2197 c.preregister_active_if_decl_stmts_from_flat(else_c, want_types, want_fns)
2198 }
2199 }
2200}
2201
2202// check_file_from_flat mirrors check_file but pulls mod + stmts straight
2203// from the FlatAst, so the heavy per-file pass no longer needs a rehydrated
2204// ast.File.
2205pub fn (mut c Checker) check_file_from_flat(flat &ast.FlatAst, ff ast.FlatFile) {
2206 mod := flat.file_mod(ff)
2207 mut sw := time.StopWatch{}
2208 if c.pref.verbose {
2209 sw = time.new_stopwatch()
2210 }
2211 c.cur_file_module = mod
2212 mut mod_scope := &Scope(unsafe { nil })
2213 lock c.env.scopes {
2214 if mod in c.env.scopes {
2215 mod_scope = unsafe { c.env.scopes[mod] }
2216 } else {
2217 panic('not found for mod: ${mod}')
2218 }
2219 }
2220 c.scope = mod_scope
2221 decls := ast.Cursor{
2222 flat: unsafe { flat }
2223 id: ff.file_id
2224 }.list_at(2)
2225 for di in 0 .. decls.len() {
2226 dc := decls.at(di)
2227 match dc.kind() {
2228 .stmt_const_decl, .stmt_directive, .stmt_empty, .stmt_enum_decl, .stmt_fn_decl,
2229 .stmt_import, .stmt_interface_decl, .stmt_module, .stmt_struct_decl, .stmt_type_decl,
2230 .stmt_attributes {
2231 // Declarations/imports/modules were handled by preregistration, and
2232 // c.stmt has no extra work for them.
2233 }
2234 .stmt_global_decl {
2235 c.check_global_decl_from_flat(dc)
2236 }
2237 .stmt_expr {
2238 c.check_expr_stmt_from_flat(dc)
2239 }
2240 .stmt_assert {
2241 c.check_assert_stmt_from_flat(dc)
2242 }
2243 else {
2244 c.stmt(dc.stmt())
2245 }
2246 }
2247 }
2248 if c.pref.verbose {
2249 check_time := sw.elapsed()
2250 println('type check ${flat.file_name(ff)}: ${check_time.milliseconds()}ms (${check_time.microseconds()}µs)')
2251 }
2252}
2253
2254// check_struct_field_defaults_from_flat visits struct field default value
2255// expressions across every FlatFile without rehydrating ast.File.
2256fn (mut c Checker) check_struct_field_defaults_from_flat(flat &ast.FlatAst) {
2257 for ff in flat.files {
2258 mod := flat.file_mod(ff)
2259 mut mod_scope := &Scope(unsafe { nil })
2260 lock c.env.scopes {
2261 if mod in c.env.scopes {
2262 mod_scope = unsafe { c.env.scopes[mod] }
2263 } else {
2264 continue
2265 }
2266 }
2267 c.scope = mod_scope
2268 c.cur_file_module = mod
2269 // Walk struct fields directly from the flat decl; only the field type/value
2270 // expressions that actually need checking are materialized.
2271 decls := ast.Cursor{
2272 flat: unsafe { flat }
2273 id: ff.file_id
2274 }.list_at(2)
2275 for di in 0 .. decls.len() {
2276 dc := decls.at(di)
2277 if dc.kind() != .stmt_struct_decl {
2278 continue
2279 }
2280 fields := dc.list_at(4)
2281 for fi in 0 .. fields.len() {
2282 field := fields.at(fi)
2283 value_c := field.edge(1)
2284 if !value_c.is_valid() || value_c.kind() == .expr_empty {
2285 continue
2286 }
2287 field_typ := c.type_expr(field.edge(0).type_expr())
2288 prev_expected := c.expected_type
2289 c.expected_type = to_optional_type(field_typ)
2290 if _ := c.register_literal_expr_type_from_cursor(value_c) {
2291 c.expected_type = prev_expected
2292 continue
2293 }
2294 field_value := value_c.expr()
2295 c.expr(field_value)
2296 $if ownership ? {
2297 c.ownership_consume_expr(field_value, field_value.pos(), 'struct field')
2298 }
2299 c.expected_type = prev_expected
2300 }
2301 }
2302 }
2303}
2304
2305// check_enum_field_values_from_flat visits enum field value expressions
2306// across every FlatFile without rehydrating ast.File.
2307fn (mut c Checker) check_enum_field_values_from_flat(flat &ast.FlatAst) {
2308 for ff in flat.files {
2309 mod := flat.file_mod(ff)
2310 mut mod_scope := &Scope(unsafe { nil })
2311 lock c.env.scopes {
2312 if mod in c.env.scopes {
2313 mod_scope = unsafe { c.env.scopes[mod] }
2314 } else {
2315 continue
2316 }
2317 }
2318 c.scope = mod_scope
2319 c.cur_file_module = mod
2320 // Walk enum fields directly from the flat decl; only non-empty value
2321 // expressions are materialized.
2322 decls := ast.Cursor{
2323 flat: unsafe { flat }
2324 id: ff.file_id
2325 }.list_at(2)
2326 for di in 0 .. decls.len() {
2327 dc := decls.at(di)
2328 if dc.kind() != .stmt_enum_decl {
2329 continue
2330 }
2331 fields := dc.list_at(2)
2332 for fi in 0 .. fields.len() {
2333 field := fields.at(fi)
2334 value_c := field.edge(1)
2335 if value_c.is_valid() && value_c.kind() != .expr_empty {
2336 if _ := c.register_literal_expr_type_from_cursor(value_c) {
2337 continue
2338 }
2339 c.expr(value_c.expr())
2340 }
2341 }
2342 }
2343 }
2344}
2345
2346pub fn (mut c Checker) check_files(files []ast.File) {
2347 // c.file_set = unsafe { file_set }
2348 for file in files {
2349 for id, name in file.selector_names {
2350 c.env.selector_names[id] = name
2351 }
2352 }
2353 c.preregister_all_scopes(files)
2354 c.preregister_all_types(files)
2355 c.register_imported_symbols(files)
2356 c.collect_fn_signatures_only = true
2357 c.preregister_all_fn_signatures(files)
2358 c.collect_fn_signatures_only = false
2359 // Re-run symbol imports after function signatures are registered so
2360 // `import mod { fn_name }` can bind imported functions as well as types.
2361 c.register_imported_symbols(files)
2362 for file in files {
2363 c.check_file(file)
2364 }
2365 c.process_pending_const_fields()
2366 $if ownership ? {
2367 c.ownership_prescan_fn_bodies()
2368 c.ownership_validate_drop_impls()
2369 c.lifetime_validate_files(files)
2370 c.escape_validate_files(files)
2371 }
2372 c.process_pending_fn_bodies()
2373 c.check_final_default_exprs(files)
2374}
2375
2376fn (mut c Checker) register_imported_symbols(files []ast.File) {
2377 builtin_scope := c.get_module_scope('builtin', universe)
2378 for file in files {
2379 mut file_scope := c.get_module_scope(file.mod, builtin_scope)
2380 for imp in c.active_file_imports(file) {
2381 if imp.symbols.len == 0 {
2382 continue
2383 }
2384 import_mod := if imp.is_aliased {
2385 imp.name.all_after_last('.')
2386 } else {
2387 imp.alias
2388 }
2389 mut import_scope := c.get_module_scope(import_mod, builtin_scope)
2390 for symbol in imp.symbols {
2391 if symbol is ast.Ident {
2392 if sym_obj := import_scope.lookup_parent(symbol.name, 0) {
2393 if sym_obj is Global && sym_obj.is_module_storage() {
2394 continue
2395 }
2396 file_scope.insert(symbol.name, sym_obj)
2397 }
2398 }
2399 }
2400 }
2401 }
2402}
2403
2404fn (mut c Checker) active_file_imports(file ast.File) []ast.ImportStmt {
2405 mut imports := file.imports.clone()
2406 c.collect_active_imports_from_stmts(file.stmts, mut imports)
2407 return imports
2408}
2409
2410fn (mut c Checker) collect_active_imports_from_stmts(stmts []ast.Stmt, mut imports []ast.ImportStmt) {
2411 for stmt in stmts {
2412 match stmt {
2413 ast.ImportStmt {
2414 imports << stmt
2415 }
2416 ast.ExprStmt {
2417 if stmt.expr is ast.ComptimeExpr && stmt.expr.expr is ast.IfExpr {
2418 c.collect_active_imports_from_if_expr(stmt.expr.expr, mut imports)
2419 }
2420 }
2421 else {}
2422 }
2423 }
2424}
2425
2426fn (mut c Checker) collect_active_imports_from_if_expr(node ast.IfExpr, mut imports []ast.ImportStmt) {
2427 if c.eval_comptime_cond(node.cond) {
2428 c.collect_active_imports_from_stmts(node.stmts, mut imports)
2429 return
2430 }
2431 match node.else_expr {
2432 ast.IfExpr {
2433 if node.else_expr.cond is ast.EmptyExpr {
2434 c.collect_active_imports_from_stmts(node.else_expr.stmts, mut imports)
2435 } else {
2436 c.collect_active_imports_from_if_expr(node.else_expr, mut imports)
2437 }
2438 }
2439 else {}
2440 }
2441}
2442
2443pub fn (mut c Checker) check_file(file ast.File) {
2444 if !c.pref.verbose {
2445 unsafe {
2446 goto start_no_time
2447 }
2448 }
2449 mut sw := time.new_stopwatch()
2450 start_no_time:
2451 // Track current file's module for function scope saving
2452 c.cur_file_module = file.mod
2453 // file_scope := new_scope(c.mod.scope)
2454 // mut mod_scope := new_scope(c.mod.scope)
2455 // c.env.scopes[file.mod] = mod_scope
2456 mut mod_scope := &Scope(unsafe { nil })
2457 lock c.env.scopes {
2458 if file.mod in c.env.scopes {
2459 mod_scope = unsafe { c.env.scopes[file.mod] }
2460 } else {
2461 panic('not found for mod: ${file.mod}')
2462 }
2463 }
2464 c.scope = mod_scope
2465 // mut mod_scope := c.env.scopes[file.mod] or {
2466 // panic('scope should exist')
2467 // }
2468 // c.scope = mod_scope
2469 for stmt in file.stmts {
2470 match stmt {
2471 // Types and constants are pre-registered in preregister_all_types
2472 ast.ConstDecl, ast.EnumDecl, ast.InterfaceDecl, ast.StructDecl, ast.TypeDecl {
2473 continue
2474 }
2475 // Functions are pre-registered in preregister_all_fn_signatures
2476 ast.FnDecl {
2477 continue
2478 }
2479 else {
2480 c.decl(stmt)
2481 }
2482 }
2483 }
2484 for stmt in file.stmts {
2485 c.stmt(stmt)
2486 }
2487 if c.pref.verbose {
2488 check_time := sw.elapsed()
2489 println('type check ${file.name}: ${check_time.milliseconds()}ms (${check_time.microseconds()}µs)')
2490 }
2491}
2492
2493pub fn (mut c Checker) preregister_scopes(file ast.File) {
2494 builtin_scope := c.get_module_scope('builtin', universe)
2495
2496 mod_scope := c.get_module_scope(file.mod, builtin_scope)
2497 c.scope = mod_scope
2498 // add self (own module) for constants. can use own module prefix inside module
2499 c.scope.insert(file.mod, Module{
2500 name: file.mod
2501 scope: c.get_module_scope(file.mod, builtin_scope)
2502 })
2503 // add imports
2504 for imp in c.active_file_imports(file) {
2505 mod := if imp.is_aliased { imp.name.all_after_last('.') } else { imp.alias }
2506 c.scope.insert(imp.alias, Module{ name: mod, scope: c.get_module_scope(mod, builtin_scope) })
2507 }
2508 // add C
2509 c.scope.insert('C', Module{ name: 'C', scope: c.c_scope })
2510}
2511
2512fn (mut c Checker) preregister_all_scopes(files []ast.File) {
2513 // builtin_scope := c.get_module_scope('builtin', universe)
2514 // preregister scopes & imports
2515 for file in files {
2516 c.preregister_scopes(file)
2517 // mod_scope := c.get_module_scope(file.mod, builtin_scope)
2518 // c.scope = mod_scope
2519 // // add self (own module) for constants
2520 // c.scope.insert(file.mod, Module{scope: c.get_module_scope(file.mod, builtin_scope)})
2521 // // add imports
2522 // for imp in file.imports {
2523 // mod := if imp.is_aliased { imp.name.all_after_last('.') } else { imp.alias }
2524 // c.scope.insert(imp.alias, Module{scope: c.get_module_scope(mod, builtin_scope)})
2525 // }
2526 // // add C
2527 // c.scope.insert('C', Module{scope: c.c_scope})
2528 }
2529}
2530
2531pub fn (mut c Checker) preregister_types(file ast.File) {
2532 c.cur_file_module = file.mod
2533 mut mod_scope := &Scope(unsafe { nil })
2534 lock c.env.scopes {
2535 if file.mod in c.env.scopes {
2536 mod_scope = unsafe { c.env.scopes[file.mod] }
2537 } else {
2538 eprintln('warning: scope not found for mod: ${file.mod}, skipping')
2539 return
2540 }
2541 }
2542 c.scope = mod_scope
2543 for stmt in file.stmts {
2544 c.preregister_type_stmt(stmt)
2545 }
2546}
2547
2548fn (mut c Checker) preregister_all_types(files []ast.File) {
2549 for file in files {
2550 c.preregister_types(file)
2551 }
2552 c.register_imported_symbols(files)
2553 c.process_pending_struct_decls()
2554 c.process_pending_type_decls()
2555 c.process_pending_interface_decls()
2556}
2557
2558// preregister_all_fn_signatures registers all function/method signatures
2559// before processing any function bodies. This ensures methods are available
2560// when checking code that calls them, regardless of file order.
2561fn (mut c Checker) preregister_all_fn_signatures(files []ast.File) {
2562 for file in files {
2563 c.preregister_fn_signatures(file)
2564 }
2565}
2566
2567pub fn (mut c Checker) preregister_fn_signatures(file ast.File) {
2568 c.cur_file_module = file.mod
2569 mut mod_scope := &Scope(unsafe { nil })
2570 lock c.env.scopes {
2571 if file.mod in c.env.scopes {
2572 mod_scope = unsafe { c.env.scopes[file.mod] }
2573 } else {
2574 eprintln('warning: scope not found for mod: ${file.mod}, skipping')
2575 return
2576 }
2577 }
2578 c.scope = mod_scope
2579 prev_collect := c.collect_fn_signatures_only
2580 c.collect_fn_signatures_only = true
2581 for stmt in file.stmts {
2582 c.preregister_fn_signature_stmt(stmt)
2583 }
2584 c.collect_fn_signatures_only = prev_collect
2585}
2586
2587fn (mut c Checker) preregister_type_stmt(stmt ast.Stmt) {
2588 match stmt {
2589 ast.ConstDecl, ast.EnumDecl, ast.InterfaceDecl, ast.StructDecl, ast.TypeDecl {
2590 c.decl(stmt)
2591 }
2592 ast.GlobalDecl {
2593 c.preregister_module_storage_decl(stmt)
2594 }
2595 ast.ExprStmt {
2596 c.preregister_active_comptime_decl_stmt(stmt, true, false)
2597 }
2598 else {}
2599 }
2600}
2601
2602fn (mut c Checker) preregister_fn_signature_stmt(stmt ast.Stmt) {
2603 match stmt {
2604 ast.FnDecl {
2605 c.decl(stmt)
2606 }
2607 ast.ExprStmt {
2608 c.preregister_active_comptime_decl_stmt(stmt, false, true)
2609 }
2610 else {}
2611 }
2612}
2613
2614fn (mut c Checker) preregister_active_comptime_decl_stmt(stmt ast.ExprStmt, want_types bool, want_fns bool) {
2615 if stmt.expr !is ast.ComptimeExpr {
2616 return
2617 }
2618 cexpr := stmt.expr as ast.ComptimeExpr
2619 if cexpr.expr !is ast.IfExpr {
2620 return
2621 }
2622 c.preregister_active_if_decl_stmts(cexpr.expr as ast.IfExpr, want_types, want_fns)
2623}
2624
2625fn (mut c Checker) preregister_active_if_decl_stmts(node ast.IfExpr, want_types bool, want_fns bool) {
2626 if c.eval_comptime_cond(node.cond) {
2627 c.preregister_decl_stmts(node.stmts, want_types, want_fns)
2628 return
2629 }
2630 match node.else_expr {
2631 ast.IfExpr {
2632 if node.else_expr.cond is ast.EmptyExpr {
2633 c.preregister_decl_stmts(node.else_expr.stmts, want_types, want_fns)
2634 } else {
2635 c.preregister_active_if_decl_stmts(node.else_expr, want_types, want_fns)
2636 }
2637 }
2638 else {}
2639 }
2640}
2641
2642fn (mut c Checker) preregister_decl_stmts(stmts []ast.Stmt, want_types bool, want_fns bool) {
2643 for stmt in stmts {
2644 match stmt {
2645 ast.ConstDecl, ast.EnumDecl, ast.InterfaceDecl, ast.StructDecl, ast.TypeDecl {
2646 if want_types {
2647 c.decl(stmt)
2648 }
2649 }
2650 ast.FnDecl {
2651 if want_fns {
2652 c.decl(stmt)
2653 }
2654 }
2655 ast.GlobalDecl {
2656 if want_types {
2657 c.preregister_module_storage_decl(stmt)
2658 }
2659 }
2660 ast.ExprStmt {
2661 c.preregister_active_comptime_decl_stmt(stmt, want_types, want_fns)
2662 }
2663 else {}
2664 }
2665 }
2666}
2667
2668fn (mut c Checker) decl(decl ast.Stmt) {
2669 match decl {
2670 ast.ConstDecl {
2671 for field in decl.fields {
2672 // c.log('const decl: ${field.name}')
2673 mut int_val := 0
2674 if field.value is ast.BasicLiteral {
2675 if field.value.kind == .number {
2676 int_val = int(strconv.parse_int(field.value.value, 0, 64) or { 0 })
2677 }
2678 }
2679 obj := Const{
2680 mod: c.mod
2681 name: field.name
2682 int_val: int_val
2683 // typ: c.expr(field.value)
2684 }
2685 c.scope.insert(field.name, obj)
2686 c.pending_const_fields << PendingConstField{
2687 scope: c.scope
2688 field: field
2689 }
2690 }
2691 }
2692 ast.EnumDecl {
2693 // Enum field value expressions are checked after all module
2694 // types are pre-registered, so imported enum references like
2695 // `http.Status.found` can resolve.
2696 mut fields := []Field{}
2697 for field in decl.fields {
2698 fields << Field{
2699 name: field.name
2700 }
2701 }
2702 // as_type := decl.as_type !is ast.EmptyExpr { c.expr(decl.as_type) } else { Type(int_) }
2703 mut is_flag := decl.attributes.has('flag')
2704 obj := Enum{
2705 is_flag: is_flag
2706 name: c.qualify_type_name(decl.name)
2707 fields: fields
2708 }
2709 enum_type := Type(obj)
2710 c.scope.insert(decl.name, object_from_type(enum_type))
2711 c.scope.insert_type(decl.name, enum_type)
2712 c.register_enum_methods(obj)
2713 }
2714 ast.FnDecl {
2715 c.fn_decl(decl)
2716 }
2717 ast.GlobalDecl {
2718 for field in decl.fields {
2719 mut field_type := Type(int_)
2720 if field.typ !is ast.EmptyExpr {
2721 field_type = c.type_expr(field.typ)
2722 } else if field.value !is ast.EmptyExpr {
2723 field_type = c.expr(field.value)
2724 }
2725 obj := module_storage_object(c.cur_file_module, decl, field, field_type)
2726 c.scope.insert_or_update(field.name, obj)
2727 }
2728 }
2729 ast.InterfaceDecl {
2730 // TODO:
2731 obj := Interface{
2732 name: c.qualify_type_name(decl.name)
2733 }
2734 interface_type := Type(obj)
2735 c.scope.insert(decl.name, object_from_type(interface_type))
2736 c.scope.insert_type(decl.name, interface_type)
2737 c.pending_interface_decls << PendingInterfaceDecl{
2738 scope: c.scope
2739 decl: decl
2740 }
2741 }
2742 ast.StructDecl {
2743 // c.log(' # StructDecl: ${decl.name}')
2744 // TODO: clean this up
2745 c.pending_struct_decls << PendingStructDecl{
2746 scope: c.scope
2747 module_name: c.cur_file_module
2748 decl: decl
2749 }
2750 // c.log('struct decl: ${decl.name}')
2751 // Don't qualify C types
2752 qualified_name := if decl.language == .c {
2753 decl.name
2754 } else {
2755 c.qualify_type_name(decl.name)
2756 }
2757 generic_params := generic_param_names_from_exprs(decl.generic_params)
2758 if generic_params.len > 0 {
2759 c.generic_type_params[decl.name] = generic_params.clone()
2760 c.generic_type_params[qualified_name] = generic_params.clone()
2761 }
2762 obj := Struct{
2763 name: qualified_name
2764 generic_params: generic_params
2765 is_soa: decl.attributes.has('soa')
2766 }
2767 mut typ := Type(obj)
2768 // TODO: proper
2769 if decl.language == .c {
2770 // c_scope is shared across parallel preregister workers; lock.
2771 c.env.c_scope_mu.lock()
2772 c.c_scope.insert(decl.name, object_from_type(typ))
2773 c.c_scope.insert_type(decl.name, typ)
2774 c.env.c_scope_mu.unlock()
2775 } else {
2776 c.scope.insert(decl.name, object_from_type(typ))
2777 c.scope.insert_type(decl.name, typ)
2778 }
2779 }
2780 ast.TypeDecl {
2781 generic_params := generic_param_names_from_exprs(decl.generic_params)
2782 if generic_params.len > 0 {
2783 qualified_name := c.qualify_type_name(decl.name)
2784 c.generic_type_params[decl.name] = generic_params.clone()
2785 c.generic_type_params[qualified_name] = generic_params.clone()
2786 }
2787 // alias
2788 if decl.variants.len == 0 {
2789 alias_type := Alias{
2790 name: c.qualify_type_name(decl.name)
2791 }
2792 mut typ := Type(alias_type)
2793 c.scope.insert(decl.name, object_from_type(typ))
2794 c.scope.insert_type(decl.name, typ)
2795 c.pending_type_decls << PendingTypeDecl{
2796 scope: c.scope
2797 decl: decl
2798 }
2799 }
2800 // sum type
2801 else {
2802 sum_type := SumType{
2803 name: c.qualify_type_name(decl.name)
2804 generic_params: generic_params
2805 // variants: decl.variants
2806 }
2807 mut typ := Type(sum_type)
2808 c.scope.insert(decl.name, object_from_type(typ))
2809 c.scope.insert_type(decl.name, typ)
2810 c.pending_type_decls << PendingTypeDecl{
2811 scope: c.scope
2812 decl: decl
2813 }
2814 }
2815 }
2816 else {}
2817 }
2818}
2819
2820fn (mut c Checker) check_types(exp_type Type, got_type Type) bool {
2821 // Prefer name-based equality first so recursive composite types
2822 // (e.g. `map[string]Any` where `Any` contains `map[string]Any`)
2823 // do not recurse indefinitely through structural `==`.
2824 if same_type_name(exp_type, got_type) {
2825 return true
2826 }
2827 exp_name := exp_type.name()
2828 got_name := got_type.name()
2829 if exp_name != '' && exp_name == got_name {
2830 return true
2831 }
2832 if exp_name == 'int' && (got_name == 'Duration' || got_name == 'time__Duration') {
2833 return true
2834 }
2835 if got_name == 'int' && (exp_name == 'Duration' || exp_name == 'time__Duration') {
2836 return true
2837 }
2838 // unwrap aliases for compatibility checks
2839 if exp_type is Alias {
2840 exp_al := exp_type as Alias
2841 mut exp_base := exp_al.base_type
2842 if exp_base.name() == '' && exp_al.name != '' {
2843 // Stale alias - re-resolve base type from scope
2844 exp_base = c.resolve_stale_alias(exp_al.name)
2845 }
2846 if exp_base.name() != '' {
2847 return c.check_types(exp_base, got_type)
2848 }
2849 }
2850 if got_type is Alias {
2851 got_al := got_type as Alias
2852 mut got_base := got_al.base_type
2853 if got_base.name() == '' && got_al.name != '' {
2854 // Stale alias - re-resolve base type from scope
2855 got_base = c.resolve_stale_alias(got_al.name)
2856 }
2857 if got_base.name() != '' {
2858 return c.check_types(exp_type, got_base)
2859 }
2860 }
2861 if exp_type is Interface && c.type_satisfies_interface(got_type, exp_type) {
2862 return true
2863 }
2864 if exp_type is Array && got_type is Array {
2865 return c.check_types(exp_type.elem_type, got_type.elem_type)
2866 }
2867 if exp_type is ArrayFixed && got_type is ArrayFixed {
2868 return exp_type.len == got_type.len && c.check_types(exp_type.elem_type, got_type.elem_type)
2869 }
2870 // Self-hosted binaries can occasionally lose primitive literal flags while
2871 // still preserving stable type names like `int_literal`/`float_literal`.
2872 if exp_name in ['i8', 'i16', 'int', 'i64', 'u8', 'u16', 'u32', 'u64', 'isize', 'usize', 'byte', 'rune', 'f32', 'f64']
2873 && got_name in ['int_literal', 'float_literal'] {
2874 return true
2875 }
2876 // Treat all `string` spellings as equivalent:
2877 // builtin string type, legacy `struct string`, and aliases named `string`.
2878 if c.is_string_like(exp_type) && c.is_string_like(got_type) {
2879 return true
2880 }
2881 if is_duration_type_name(exp_name) && (got_type.is_number() || is_numeric_type_name(got_name)) {
2882 return true
2883 }
2884 if is_duration_type_name(got_name) && (exp_type.is_number() || is_numeric_type_name(exp_name)) {
2885 return true
2886 }
2887 // allow nil in expression contexts that expect pointer-like values
2888 if got_type is Nil {
2889 match exp_type {
2890 FnType, Interface, Pointer {
2891 return true
2892 }
2893 else {}
2894 }
2895 }
2896 // number literals
2897 if exp_type.is_number() && got_type.is_number_literal() {
2898 return true
2899 }
2900 if exp_type.is_number_literal() && got_type.is_number() {
2901 return true
2902 }
2903 // primitives: allow numeric type promotions (e.g. int→f64, int→u16)
2904 if exp_type is Primitive && got_type is Primitive {
2905 exp_prim := exp_type as Primitive
2906 got_prim := got_type as Primitive
2907 if exp_prim.is_number() && got_prim.is_number() {
2908 return true
2909 }
2910 }
2911 if got_type is Char || got_type is Rune {
2912 if exp_type is Primitive {
2913 exp_prim := exp_type as Primitive
2914 if exp_prim.is_integer() {
2915 return true
2916 }
2917 }
2918 }
2919 // sum type: accept any variant type
2920 if exp_type is SumType {
2921 if c.sum_type_accepts_variant(exp_type as SumType, got_type) {
2922 return true
2923 }
2924 }
2925 // voidptr: any pointer type is assignable to voidptr (Pointer{void_})
2926 if exp_type is Pointer {
2927 exp_pt := exp_type as Pointer
2928 if exp_pt.base_type is Void {
2929 if got_type is Pointer {
2930 return true
2931 }
2932 }
2933 }
2934
2935 return false
2936}
2937
2938fn (mut c Checker) type_satisfies_interface(got_type Type, iface Interface) bool {
2939 mut actual_type := resolve_alias(got_type)
2940 if actual_type is Pointer {
2941 actual_type = Type(Pointer{
2942 base_type: resolve_alias(actual_type.base_type)
2943 })
2944 } else if actual_type is Struct && actual_type.name != '' {
2945 if live_type := c.lookup_type_by_name(actual_type.name) {
2946 if live_type is Struct && (live_type.fields.len > actual_type.fields.len
2947 || live_type.embedded.len > actual_type.embedded.len
2948 || live_type.implements.len > actual_type.implements.len) {
2949 actual_type = live_type
2950 }
2951 }
2952 }
2953 if actual_type is Struct && c.struct_implements_name(actual_type, iface.name) {
2954 return true
2955 }
2956 if actual_type is Interface && actual_type.name == iface.name {
2957 return true
2958 }
2959
2960 iface_fields := if iface.fields.len == 0 && iface.name != '' {
2961 c.resolve_interface_fields(iface)
2962 } else {
2963 iface.fields
2964 }
2965 for field in iface_fields {
2966 if field.name == 'type_name' {
2967 continue
2968 }
2969 field_type := resolve_alias(field.typ)
2970 if field.is_interface_method && field_type is FnType {
2971 method_type := if actual_type is Interface {
2972 actual_field := c.find_interface_field(actual_type, field.name, true) or {
2973 return false
2974 }
2975 actual_field.typ
2976 } else {
2977 c.lookup_method_direct(actual_type, field.name) or { return false }
2978 }
2979 if method_type !is FnType {
2980 return false
2981 }
2982 if !c.fn_types_compatible(field_type, method_type as FnType) {
2983 return false
2984 }
2985 continue
2986 }
2987 member_type := if info := c.find_field_info(actual_type, field.name) {
2988 info.field.typ
2989 } else if actual_type is Interface {
2990 actual_field := c.find_interface_field(actual_type, field.name, false) or {
2991 return false
2992 }
2993 actual_field.typ
2994 } else {
2995 return false
2996 }
2997 if !c.check_types(field_type, member_type) {
2998 return false
2999 }
3000 }
3001 return true
3002}
3003
3004fn (mut c Checker) fn_types_compatible(expected FnType, got FnType) bool {
3005 if expected.params.len != got.params.len || expected.is_variadic != got.is_variadic {
3006 return false
3007 }
3008 for i, expected_param in expected.params {
3009 got_param := got.params[i]
3010 if !c.check_types(expected_param.typ, got_param.typ) {
3011 return false
3012 }
3013 }
3014 expected_return := expected.return_type or { Type(void_) }
3015 got_return := got.return_type or { Type(void_) }
3016 return c.check_types(expected_return, got_return)
3017}
3018
3019fn (c &Checker) live_sumtype(smt SumType) SumType {
3020 if smt.name == '' || !checker_string_has_valid_data(smt.name) {
3021 return smt
3022 }
3023 if live_type := c.lookup_type_by_name(smt.name) {
3024 if live_type is SumType {
3025 live_smt := live_type as SumType
3026 if live_smt.variants.len > smt.variants.len {
3027 return live_smt
3028 }
3029 }
3030 }
3031 return smt
3032}
3033
3034fn (c &Checker) sum_type_accepts_variant(smt SumType, got_type Type) bool {
3035 live_smt := c.live_sumtype(smt)
3036 got_name := got_type.name()
3037 got_name_ok := checker_string_has_valid_data(got_name)
3038 for variant in live_smt.variants {
3039 mut variant_type := resolve_alias(variant)
3040 variant_name := variant_type.name()
3041 if checker_string_has_valid_data(variant_name) {
3042 if live_variant := c.lookup_type_by_name(variant_name) {
3043 variant_type = resolve_alias(live_variant)
3044 }
3045 }
3046 resolved_variant_name := variant_type.name()
3047 if got_name_ok && checker_string_has_valid_data(resolved_variant_name)
3048 && resolved_variant_name == got_name {
3049 return true
3050 }
3051 if variant_type is SumType {
3052 if c.sum_type_accepts_variant(variant_type as SumType, got_type) {
3053 return true
3054 }
3055 }
3056 }
3057 return false
3058}
3059
3060fn (c &Checker) is_string_struct(typ Type) bool {
3061 if typ is Struct {
3062 return typ.name == 'string' || typ.name.ends_with('__string')
3063 }
3064 return false
3065}
3066
3067fn (c &Checker) is_string_like(typ Type) bool {
3068 return typ.name() == 'string' || c.is_string_struct(typ)
3069}
3070
3071fn (c &Checker) is_ierror_like(typ Type) bool {
3072 if typ is Interface {
3073 if type_data_ptr_is_nil(typ) {
3074 return false
3075 }
3076 iface := typ as Interface
3077 return iface.name == 'IError' || iface.name == 'builtin__IError'
3078 }
3079 return false
3080}
3081
3082fn is_duration_type_name(name string) bool {
3083 return name == 'Duration' || name.ends_with('__Duration')
3084}
3085
3086fn is_numeric_type_name(name string) bool {
3087 return name in ['i8', 'i16', 'int', 'i64', 'u8', 'u16', 'u32', 'u64', 'isize', 'usize', 'byte',
3088 'rune', 'f32', 'f64', 'int_literal', 'float_literal']
3089}
3090
3091fn (c &Checker) can_or_block_propagate_error(raw_cond_type Type, cond ast.Expr, err_type Type) bool {
3092 if !c.is_ierror_like(err_type) {
3093 return false
3094 }
3095 mut expected_is_result := false
3096 if expected_type := c.expected_type {
3097 expected_is_result = expected_type is ResultType
3098 }
3099 if raw_cond_type is ResultType {
3100 return true
3101 }
3102 if expected_is_result && c.inside_return_stmt {
3103 return true
3104 }
3105 return expected_is_result && cond is ast.IndexExpr
3106 && (cond as ast.IndexExpr).expr is ast.RangeExpr && c.is_string_like(raw_cond_type)
3107}
3108
3109fn (mut c Checker) insert_error_scope_vars() {
3110 mut err_type := Type(string_)
3111 if obj := c.scope.lookup_parent('IError', 0) {
3112 err_type = obj.typ()
3113 }
3114 c.scope.insert('err', object_from_type(err_type))
3115 c.scope.insert('errcode', object_from_type(Type(int_)))
3116}
3117
3118fn (c &Checker) is_string_iterable_type(typ Type) bool {
3119 mut cur := typ
3120 for {
3121 if type_data_ptr_is_nil(cur) {
3122 return false
3123 }
3124 if cur is Alias {
3125 cur = (cur as Alias).base_type
3126 continue
3127 }
3128 if cur is Pointer {
3129 cur = (cur as Pointer).base_type
3130 continue
3131 }
3132 break
3133 }
3134 return cur is String || c.is_string_struct(cur)
3135}
3136
3137fn (c &Checker) for_in_value_type(iter_type Type) Type {
3138 if c.is_string_iterable_type(iter_type) {
3139 return Type(u8_)
3140 }
3141 return iter_type.value_type()
3142}
3143
3144fn (mut c Checker) expr(expr ast.Expr) Type {
3145 c.expr_depth++
3146 if c.expr_depth > max_checker_expr_depth {
3147 c.expr_depth--
3148 return Type(void_)
3149 }
3150 mut pushed_stack := false
3151 if expr !is ast.ArrayInitExpr {
3152 c.expr_stack << 'expr'
3153 pushed_stack = true
3154 }
3155 if c.expr_depth > 2000 {
3156 start := if c.expr_stack.len > 40 { c.expr_stack.len - 40 } else { 0 }
3157 eprintln('checker expr recursion depth=${c.expr_depth}')
3158 for i := start; i < c.expr_stack.len; i++ {
3159 eprintln(' ${c.expr_stack[i]}')
3160 }
3161 panic('checker expr recursion')
3162 }
3163 typ := c.expr_impl(expr)
3164 // Store the computed type in the environment for expressions with positions.
3165 // SSA needs contextual array literal types, for example struct fields of
3166 // type []ast.Expr initialized with [ast.CallExpr{...}].
3167 pos := expr.pos()
3168 if pos.is_valid() {
3169 c.env.set_expr_type(pos.id, typ)
3170 }
3171 c.expr_depth--
3172 if pushed_stack && c.expr_stack.len > 0 {
3173 c.expr_stack.delete_last()
3174 }
3175 return typ
3176}
3177
3178fn (mut c Checker) generic_args_expr(expr ast.GenericArgs) Type {
3179 // Keep this logic out of expr_impl to avoid huge stack frames in the
3180 // generated native checker path.
3181 mut name := ''
3182 match expr.lhs {
3183 ast.Ident {
3184 name = expr.lhs.name
3185 }
3186 ast.SelectorExpr {
3187 name = expr.lhs.rhs.name
3188 }
3189 else {}
3190 }
3191
3192 lhs_type := c.expr(expr.lhs)
3193 if lhs_type is FnType {
3194 fn_type := lhs_type as FnType
3195 // If function already has generic params (from declaration), preserve them
3196 // but return a fresh copy so call_expr doesn't mutate the shared module scope object.
3197 if fn_type.generic_params.len > 0 {
3198 return Type(FnType{
3199 generic_params: fn_type.generic_params
3200 params: fn_type.params
3201 return_type: fn_type.return_type
3202 is_variadic: fn_type.is_variadic
3203 is_mut_receiver: fn_type.is_mut_receiver
3204 attributes: fn_type.attributes
3205 })
3206 }
3207 mut args := []string{}
3208 for arg in expr.args {
3209 args << c.expr(arg).name()
3210 }
3211 return Type(with_generic_params(fn_type, args))
3212 }
3213 // If the LHS is indexable (array, map, string), re-interpret as IndexExpr.
3214 // The parser sometimes produces nested GenericArgs for nested indexing
3215 // like `a[b[c[d]]]` when it should produce nested IndexExprs.
3216 if expr.args.len == 1 && c.is_indexable_type(lhs_type) && !c.is_generic_struct_type(lhs_type) {
3217 return c.expr(ast.Expr(ast.IndexExpr{
3218 lhs: expr.lhs
3219 expr: expr.args[0]
3220 pos: expr.pos
3221 }))
3222 }
3223 if generic_struct := c.generic_struct_template(lhs_type) {
3224 return c.instantiate_generic_struct(generic_struct, expr.args)
3225 }
3226
3227 mut args := []string{}
3228 for arg in expr.args {
3229 args << c.expr(arg).name()
3230 }
3231
3232 return Struct{
3233 name: name
3234 generic_params: args
3235 }
3236}
3237
3238fn (mut c Checker) index_expr(expr ast.IndexExpr) Type {
3239 lhs_type := c.expr(expr.lhs)
3240 c.expr(expr.expr)
3241 // TODO: make sure lhs_type is indexable
3242 // if !lhs_type.is_indexable() { c.error('cannot index ${lhs_type.name()}') }
3243 return c.index_expr_result_type(lhs_type, expr)
3244}
3245
3246fn (mut c Checker) index_expr_result_type(lhs_type Type, expr ast.IndexExpr) Type {
3247 // For slicing (RangeExpr), return the same type as the container
3248 // For single index, return the element type
3249 is_range := expr.expr is ast.RangeExpr
3250 mut container_type := lhs_type
3251 // Const/global strings can be represented as pointer-to-string in some paths.
3252 // For direct indexing/slicing expressions, treat them as plain strings.
3253 // But inside unsafe blocks, &string is used as pointer-to-string-array,
3254 // so indexing should yield string (via value_type on Pointer).
3255 mut lhs_check := resolve_alias(lhs_type)
3256 if lhs_check is Pointer && !c.inside_unsafe {
3257 mut ptr_base := resolve_alias((lhs_check as Pointer).base_type)
3258 if ptr_base is String || (ptr_base is Struct && (ptr_base.name == 'string'
3259 || ptr_base.name.ends_with('__string'))) {
3260 is_explicit_deref := expr.lhs is ast.PrefixExpr
3261 && (expr.lhs as ast.PrefixExpr).op == .mul
3262 if !is_explicit_deref {
3263 container_type = Type(string_)
3264 }
3265 }
3266 }
3267 if !is_range && c.inside_unsafe {
3268 if ptr_value_type := indexed_pointer_value_type(container_type) {
3269 return ptr_value_type
3270 }
3271 }
3272 value_type := if is_range {
3273 resolved_container := resolve_alias(container_type)
3274 if resolved_container is ArrayFixed {
3275 arr_fixed := resolved_container as ArrayFixed
3276 Type(Array{
3277 elem_type: arr_fixed.elem_type
3278 })
3279 } else {
3280 container_type
3281 }
3282 } else {
3283 if c.is_string_iterable_type(container_type) {
3284 Type(u8_)
3285 } else {
3286 container_type.value_type()
3287 }
3288 }
3289 // c.log('IndexExpr: ${value_type.name()} / ${lhs_type.name()}')
3290 return value_type
3291}
3292
3293fn indexed_pointer_value_type(typ Type) ?Type {
3294 resolved := resolve_alias(typ)
3295 if resolved is Pointer {
3296 ptr := resolved as Pointer
3297 base := resolve_alias(ptr.base_type)
3298 if base is String {
3299 return Type(string_)
3300 }
3301 if base is Struct && (base.name == 'string' || base.name.ends_with('__string')) {
3302 return Type(string_)
3303 }
3304 if base is Array {
3305 return base.elem_type
3306 }
3307 if base is ArrayFixed {
3308 return base.elem_type
3309 }
3310 if base is Map {
3311 return base.value_type
3312 }
3313 return ptr.base_type
3314 }
3315 return none
3316}
3317
3318fn (mut c Checker) scope_type_for_ident_expr(expr ast.Expr) ?Type {
3319 match expr {
3320 ast.Ident {
3321 if obj := c.scope.lookup_parent(expr.name, 0) {
3322 return obj.typ()
3323 }
3324 }
3325 else {}
3326 }
3327
3328 return none
3329}
3330
3331fn (mut c Checker) enum_type_for_shorthand_field(field_name string) ?Type {
3332 mut cur_scope := c.scope
3333 for cur_scope != unsafe { nil } {
3334 for _, obj in cur_scope.objects {
3335 typ := obj.typ()
3336 if typ is Enum {
3337 for field in typ.fields {
3338 if field.name == field_name {
3339 return typ
3340 }
3341 }
3342 }
3343 }
3344 cur_scope = cur_scope.parent
3345 }
3346 lock c.env.scopes {
3347 for _, scope in c.env.scopes {
3348 for _, obj in scope.objects {
3349 typ := obj.typ()
3350 if typ is Enum {
3351 for field in typ.fields {
3352 if field.name == field_name {
3353 return typ
3354 }
3355 }
3356 }
3357 }
3358 }
3359 }
3360 return none
3361}
3362
3363fn (mut c Checker) enum_type_for_in_rhs_shorthand(rhs ast.Expr) ?Type {
3364 if rhs is ast.ArrayInitExpr && rhs.exprs.len > 0 {
3365 first := rhs.exprs[0]
3366 if first is ast.SelectorExpr && first.lhs is ast.EmptyExpr {
3367 return c.enum_type_for_shorthand_field(first.rhs.name)
3368 }
3369 }
3370 if rhs is ast.SelectorExpr && rhs.lhs is ast.EmptyExpr {
3371 return c.enum_type_for_shorthand_field(rhs.rhs.name)
3372 }
3373 return none
3374}
3375
3376fn (c &Checker) is_empty_selector_expr(expr ast.Expr) bool {
3377 match expr {
3378 ast.SelectorExpr {
3379 return expr.lhs is ast.EmptyExpr
3380 }
3381 else {
3382 return false
3383 }
3384 }
3385}
3386
3387fn (mut c Checker) set_infix_expected_type(expr ast.InfixExpr, lhs_type Type) {
3388 if lhs_type is Enum {
3389 c.expected_type = to_optional_type(Type(lhs_type))
3390 return
3391 }
3392 lhs_base := lhs_type.base_type()
3393 if lhs_base is Enum {
3394 c.expected_type = to_optional_type(Type(lhs_base))
3395 return
3396 }
3397 if expr.op == .left_shift {
3398 if lhs_type is Array {
3399 c.expected_type = to_optional_type(lhs_type.elem_type)
3400 return
3401 }
3402 if lhs_base is Array {
3403 c.expected_type = to_optional_type((lhs_base as Array).elem_type)
3404 return
3405 }
3406 }
3407 if expr.op in [.key_in, .not_in] && lhs_type !is Void {
3408 // Support typed arrays in `x in [...]` / `x !in [...]` when the lhs
3409 // enum arrives wrapped (e.g. aliases) or through non-enum wrappers.
3410 mut in_lhs_type := if lhs_type is Pointer {
3411 (lhs_type as Pointer).base_type
3412 } else {
3413 lhs_type
3414 }
3415 if in_lhs_type.name() == '' {
3416 if rhs_enum_type := c.enum_type_for_in_rhs_shorthand(expr.rhs) {
3417 in_lhs_type = rhs_enum_type
3418 }
3419 }
3420 if in_lhs_type.name() == '' {
3421 return
3422 }
3423 c.expected_type = to_optional_type(in_lhs_type)
3424 return
3425 }
3426 if expr.op in [.key_in, .not_in] {
3427 // Fallback: use scope type for enum shorthand in `in` expressions
3428 // when lhs expression type inference produced void.
3429 if lhs_ident_type := c.scope_type_for_ident_expr(expr.lhs) {
3430 c.expected_type = to_optional_type(lhs_ident_type)
3431 }
3432 return
3433 }
3434 if c.is_empty_selector_expr(expr.rhs) {
3435 // Generic enum-shorthand fallback for comparisons like `mode == .x`.
3436 if lhs_type !is Void {
3437 c.expected_type = to_optional_type(lhs_type)
3438 } else if lhs_ident_type := c.scope_type_for_ident_expr(expr.lhs) {
3439 c.expected_type = to_optional_type(lhs_ident_type)
3440 }
3441 }
3442}
3443
3444fn (mut c Checker) infix_rhs_type(expr ast.InfixExpr) Type {
3445 if expr.op == .and {
3446 // In `a is T && a.field ...`, RHS is evaluated only when the smart-cast is true.
3447 // Type-check RHS in a nested scope with casts from LHS applied.
3448 c.open_scope()
3449 sc_names, sc_types := c.extract_smartcasts(expr.lhs)
3450 c.apply_smartcasts(sc_names, sc_types)
3451 rhs_type := c.expr(expr.rhs)
3452 c.close_scope()
3453 return rhs_type
3454 }
3455 return c.expr(expr.rhs)
3456}
3457
3458fn (mut c Checker) logical_and_expr_part(expr ast.Expr) {
3459 unwrapped := c.unwrap_expr(expr)
3460 if unwrapped is ast.InfixExpr && unwrapped.op == .and {
3461 c.logical_and_expr_part(unwrapped.lhs)
3462 c.logical_and_expr_part(unwrapped.rhs)
3463 return
3464 }
3465 c.expr(expr)
3466 sc_names, sc_types := c.extract_smartcasts(unwrapped)
3467 c.apply_smartcasts(sc_names, sc_types)
3468}
3469
3470fn (c &Checker) unalias_type(t Type) Type {
3471 next := t.base_type()
3472 if next is Alias {
3473 return c.unalias_type(next)
3474 }
3475 return next
3476}
3477
3478fn (c &Checker) promote_infix_numeric_type(lhs_type Type, rhs_type Type) Type {
3479 // Promote untyped numeric literals to the concrete numeric type on the other side.
3480 // This keeps expressions like `1 + i64_var` and `24 * time.hour` typed correctly.
3481 rhs_concrete := c.unalias_type(rhs_type)
3482 if lhs_type.is_number_literal() && rhs_concrete.is_number() && !rhs_concrete.is_number_literal() {
3483 return rhs_type
3484 }
3485 lhs_concrete := c.unalias_type(lhs_type)
3486 if rhs_type.is_number_literal() && lhs_concrete.is_number() && !lhs_concrete.is_number_literal() {
3487 return lhs_type
3488 }
3489 return lhs_type
3490}
3491
3492fn (mut c Checker) infix_expr(expr ast.InfixExpr) Type {
3493 if expr.op == .and {
3494 c.open_scope()
3495 c.logical_and_expr_part(expr)
3496 c.close_scope()
3497 return bool_
3498 }
3499 lhs_type := c.expr(expr.lhs)
3500 // Preserve enum context for shorthand RHS values like `.void_t`.
3501 expected_type := c.expected_type
3502 c.set_infix_expected_type(expr, lhs_type)
3503 rhs_type := c.infix_rhs_type(expr)
3504 $if ownership ? {
3505 lhs_base := lhs_type.base_type()
3506 if expr.op == .left_shift && (lhs_type is Array || lhs_base is Array) {
3507 c.ownership_consume_expr(expr.rhs, expr.rhs.pos(), 'array append')
3508 }
3509 }
3510 lhs_base_for_mut := lhs_type.base_type()
3511 if expr.op == .left_shift && (lhs_type is Array || lhs_base_for_mut is Array) {
3512 c.check_module_mut_field_mutation(expr.lhs, expr.pos)
3513 }
3514 c.expected_type = expected_type
3515 if expr.op.is_comparison() {
3516 return bool_
3517 }
3518 // Check for operator overloading on struct types
3519 resolved_lhs := resolve_alias(lhs_type)
3520 if resolved_lhs is Struct {
3521 resolved_lhs_st := resolved_lhs as Struct
3522 op_name := expr.op.str()
3523 tname := resolved_lhs_st.name
3524 mut methods := []&Fn{}
3525 rlock c.env.methods {
3526 if tname in c.env.methods {
3527 methods = unsafe { c.env.methods[tname] }
3528 }
3529 }
3530 for method in methods {
3531 if method.name == op_name {
3532 method_type := c.specialize_method_type_for_receiver(method.typ, lhs_type, op_name)
3533 if method_type is FnType {
3534 method_ft := method_type as FnType
3535 if ret := method_ft.return_type {
3536 return ret
3537 }
3538 }
3539 return method_type
3540 }
3541 }
3542 }
3543 return c.promote_infix_numeric_type(lhs_type, rhs_type)
3544}
3545
3546fn (mut c Checker) init_expr(expr ast.InitExpr) Type {
3547 // TODO: try handle this from expr
3548 // mut typ_expr := expr.typ
3549 // if expr.typ is ast.GenericArgs {
3550 // typ_expr = expr.typ.lhs
3551 // }
3552 typ := c.expr(expr.typ)
3553 // Unwrap aliases to get the base struct type for field lookups.
3554 mut resolved := resolve_alias(typ)
3555 // Visit field value expressions to store their types in the environment
3556 resolved_imm := resolved
3557 if resolved_imm is Struct {
3558 resolved_imm_st := resolved_imm as Struct
3559 for field in expr.fields {
3560 if field.value !is ast.EmptyExpr {
3561 expected_type_prev := c.expected_type
3562 for sf in resolved_imm_st.fields {
3563 if sf.name == field.name {
3564 if sf.is_module_mut {
3565 owner_module := field_owner_module(sf, resolved_imm_st.name)
3566 if owner_module != '' && owner_module != c.cur_file_module {
3567 info := FieldAccessInfo{
3568 field: sf
3569 owner_struct: resolved_imm_st.name
3570 }
3571 c.error_with_pos('cannot initialize module-mutable field `${field_access_display(info)}` outside module `${owner_module}`',
3572 expr.pos)
3573 }
3574 }
3575 c.expected_type = to_optional_type(sf.typ)
3576 break
3577 }
3578 }
3579 c.expr(field.value)
3580 $if ownership ? {
3581 c.ownership_consume_expr(field.value, field.value.pos(), 'struct field')
3582 }
3583 c.expected_type = expected_type_prev
3584 }
3585 }
3586 } else {
3587 for field in expr.fields {
3588 if field.value !is ast.EmptyExpr {
3589 c.expr(field.value)
3590 $if ownership ? {
3591 c.ownership_consume_expr(field.value, field.value.pos(), 'struct field')
3592 }
3593 }
3594 }
3595 }
3596 return typ
3597}
3598
3599fn (mut c Checker) keyword_operator_expr(expr ast.KeywordOperator) Type {
3600 // TODO:
3601 typ := c.expr(expr.exprs[0])
3602 match expr.op {
3603 .key_go, .key_spawn {
3604 return empty_thread()
3605 }
3606 .key_typeof {
3607 // typeof(expr) returns a comptime TypeInfo with .name and .idx
3608 return Struct{
3609 name: 'TypeInfo'
3610 fields: [
3611 Field{
3612 name: 'name'
3613 typ: string_
3614 },
3615 Field{
3616 name: 'idx'
3617 typ: int_
3618 },
3619 ]
3620 }
3621 }
3622 .key_sizeof, .key_isreftype {
3623 return int_
3624 }
3625 else {
3626 return typ
3627 }
3628 }
3629}
3630
3631fn (mut c Checker) map_init_expr(expr ast.MapInitExpr) Type {
3632 mut typ := Type(void_)
3633 mut map_key_type := Type(void_)
3634 mut map_value_type := Type(void_)
3635 mut has_map_type := false
3636 mut inferred_from_first_entry := false
3637 // `map[type]type{...}`
3638 if expr.typ !is ast.EmptyExpr {
3639 typ = c.expr(expr.typ)
3640 if map_typ := unwrap_map_type(typ) {
3641 map_key_type = map_typ.key_type
3642 map_value_type = map_typ.value_type
3643 has_map_type = true
3644 }
3645 } else if exp_type := c.expected_type {
3646 if map_typ := unwrap_map_type(exp_type) {
3647 map_key_type = map_typ.key_type
3648 map_value_type = map_typ.value_type
3649 has_map_type = true
3650 }
3651 }
3652 // `{}`
3653 if expr.keys.len == 0 {
3654 if expr.typ !is ast.EmptyExpr {
3655 return typ
3656 }
3657 if exp_type := c.expected_type {
3658 return exp_type
3659 }
3660 c.error_with_pos('empty map {} used in unsupported context', expr.pos)
3661 return Type(void_)
3662 }
3663 // `{key: value}` without an explicit/expected map type: infer from first pair.
3664 if !has_map_type {
3665 map_key_type = c.expr(expr.keys[0]).typed_default()
3666 map_value_type = c.expr(expr.vals[0]).typed_default()
3667 $if ownership ? {
3668 c.ownership_consume_expr(expr.keys[0], expr.keys[0].pos(), 'map key')
3669 c.ownership_consume_expr(expr.vals[0], expr.vals[0].pos(), 'map value')
3670 }
3671 has_map_type = true
3672 inferred_from_first_entry = true
3673 }
3674 expected_type_prev := c.expected_type
3675 for i, key_expr in expr.keys {
3676 val_expr := expr.vals[i]
3677 if inferred_from_first_entry && i == 0 {
3678 continue
3679 }
3680 c.expected_type = to_optional_type(map_key_type)
3681 key_type := c.expr(key_expr).typed_default()
3682 c.expected_type = to_optional_type(map_value_type)
3683 val_type := c.expr(val_expr).typed_default()
3684 $if ownership ? {
3685 c.ownership_consume_expr(key_expr, key_expr.pos(), 'map key')
3686 c.ownership_consume_expr(val_expr, val_expr.pos(), 'map value')
3687 }
3688 if !c.check_types(map_key_type, key_type) {
3689 c.error_with_pos('invalid map key: expecting ${map_key_type.name()}, got ${key_type.name()}',
3690 key_expr.pos())
3691 }
3692 if !c.check_types(map_value_type, val_type) {
3693 c.error_with_pos('invalid map value: expecting ${map_value_type.name()}, got ${val_type.name()}',
3694 val_expr.pos())
3695 }
3696 }
3697 c.expected_type = expected_type_prev
3698 if expr.typ !is ast.EmptyExpr {
3699 return typ
3700 }
3701 return Map{
3702 key_type: map_key_type
3703 value_type: map_value_type
3704 }
3705}
3706
3707fn (mut c Checker) or_expr(expr ast.OrExpr) Type {
3708 mut cond := c.resolve_expr(expr.expr)
3709 raw_cond_type := c.expr(cond)
3710 mut cond_type := raw_cond_type.unwrap()
3711 if cond_type is FnType && expr.expr is ast.CallOrCastExpr {
3712 coce := expr.expr as ast.CallOrCastExpr
3713 cond = ast.Expr(ast.CallExpr{
3714 lhs: coce.lhs
3715 args: [coce.expr]
3716 pos: coce.pos
3717 })
3718 cond_type = c.expr(cond).unwrap()
3719 }
3720 if cond_type is FnType {
3721 fn_type := cond_type as FnType
3722 if ret_type := fn_type.return_type {
3723 cond_type = ret_type.unwrap()
3724 }
3725 }
3726 // c.log('OrExpr: ${cond_type.name()}')
3727 if expr.stmts.len > 0 {
3728 c.open_scope()
3729 c.insert_error_scope_vars()
3730 if cond_type is Void {
3731 c.stmt_list(expr.stmts)
3732 c.close_scope()
3733 return cond_type
3734 }
3735 last_expr_idx := trailing_expr_stmt_index(expr.stmts)
3736 if last_expr_idx > 0 {
3737 c.stmt_list(expr.stmts[..last_expr_idx])
3738 } else if last_expr_idx == -1 {
3739 c.stmt_list(expr.stmts)
3740 }
3741 if last_expr_idx >= 0 {
3742 last_stmt := expr.stmts[last_expr_idx] as ast.ExprStmt
3743 expected_type_prev := c.expected_type
3744 c.expected_type = to_optional_type(cond_type)
3745 expr_stmt_type := c.expr(last_stmt.expr).unwrap()
3746 c.expected_type = expected_type_prev
3747 c.close_scope()
3748 // c.log('OrExpr: last_stmt_type: ${cond_type.name()}')
3749 // TODO: non returning call (currently just checking void)
3750 // should probably lookup function/method and check for noreturn attribute?
3751 // if cond is ast.CallExpr {}
3752 // do we need to do promotion here
3753
3754 // last stmt expr does does not return a type
3755 // None is always valid in or-blocks (propagates the error)
3756 if cond is ast.SqlExpr && expr_stmt_type !is Void && expr_stmt_type !is None
3757 && (cond_type is Void || !c.check_types(cond_type, expr_stmt_type)) {
3758 return expr_stmt_type
3759 }
3760 if c.can_or_block_propagate_error(raw_cond_type, cond, expr_stmt_type) {
3761 return cond_type
3762 }
3763 if expr_stmt_type is Nil && cond is ast.IndexExpr {
3764 return cond_type
3765 }
3766 if expr_stmt_type !is Void && expr_stmt_type !is None
3767 && !c.check_types(cond_type, expr_stmt_type) {
3768 c.error_with_pos('or expr expecting ${cond_type.name()}, got ${expr_stmt_type.name()}',
3769 expr.pos)
3770 }
3771 return cond_type
3772 }
3773 c.stmt_list(expr.stmts)
3774 c.close_scope()
3775 }
3776 return cond_type
3777}
3778
3779fn (mut c Checker) prefix_expr(expr ast.PrefixExpr) Type {
3780 expr_type := c.expr(expr.expr)
3781 if expr.op == .amp {
3782 c.check_module_mut_field_mut_ref(expr.expr, expr.pos)
3783 return Pointer{
3784 base_type: expr_type
3785 }
3786 } else if expr.op == .mul {
3787 if expr_type is Pointer {
3788 expr_pt := expr_type as Pointer
3789 // c.log('DEREF')
3790 return expr_pt.base_type
3791 } else if expr_type is Interface {
3792 // Interface types are internally pointers, so dereference is valid
3793 // This handles match narrowing where the concrete type is still behind a pointer
3794 return expr_type
3795 } else if expr_type is Struct {
3796 // Allow deref on structs narrowed from interfaces in match expressions
3797 // TODO: properly track interface narrowing instead of this workaround
3798 return expr_type
3799 } else if expr_type is Void && expr.expr is ast.ParenExpr {
3800 paren_expr := expr.expr as ast.ParenExpr
3801 if paren_expr.expr is ast.PrefixExpr {
3802 inner_prefix := paren_expr.expr as ast.PrefixExpr
3803 if inner_prefix.op == .amp {
3804 return c.expr(inner_prefix.expr)
3805 }
3806 }
3807 } else {
3808 c.error_with_pos('deref on non pointer type `${expr_type.name()}`', expr.pos)
3809 }
3810 } else if expr.op == .arrow {
3811 // Channel receive: <-ch returns the channel's element type
3812 if expr_type is Channel {
3813 if elem_type := expr_type.elem_type {
3814 return elem_type
3815 }
3816 }
3817 return Type(void_)
3818 }
3819 return c.expr(expr.expr)
3820}
3821
3822fn fixed_array_literal_element_type(expr ast.Expr, typ Type) Type {
3823 if expr is ast.ArrayInitExpr && expr.exprs.len > 0 && is_empty_expr(expr.len) {
3824 if typ is Array {
3825 return ArrayFixed{
3826 len: expr.exprs.len
3827 elem_type: typ.elem_type
3828 }
3829 }
3830 }
3831 return typ
3832}
3833
3834fn (mut c Checker) expr_impl(expr ast.Expr) Type {
3835 c.log('expr')
3836 if call := expr_call_payload_ref(expr) {
3837 return c.call_expr(call)
3838 }
3839 match expr {
3840 ast.ArrayInitExpr {
3841 // c.log('ArrayInit:')
3842 // Avoid recursively checking len/cap here. In ARM64 self-hosted
3843 // compilers these auxiliary expressions can form cycles and exhaust
3844 // the native stack before the general expression-depth guard can recover.
3845 // NOTE: expr.init is not processed here because it may contain
3846 // enum shorthands (.value) that require array element type context.
3847 // `[...base, e1, e2]` — spread / update syntax
3848 if expr.update_expr !is ast.EmptyExpr {
3849 update_type := c.expr(expr.update_expr)
3850 mut update_base := resolve_alias(update_type)
3851 if update_base is Pointer {
3852 update_base = resolve_alias((update_base as Pointer).base_type)
3853 }
3854 if update_base !is Array {
3855 c.error_with_pos('invalid array update: non-array type `${update_type.name()}`',
3856 expr.pos)
3857 return Type(void_)
3858 }
3859 array_t := update_base as Array
3860 elem_type := array_t.elem_type
3861 for elem_expr in expr.exprs {
3862 expected_type_prev := c.expected_type
3863 c.expected_type = to_optional_type(elem_type)
3864 got_type := c.expr(elem_expr)
3865 c.expected_type = expected_type_prev
3866 if !c.check_types(elem_type, got_type) {
3867 c.error_with_pos('expecting element of type: ${elem_type.name()}, got ${got_type.name()}',
3868 expr.pos)
3869 }
3870 }
3871 return Type(Array{
3872 elem_type: elem_type
3873 })
3874 }
3875 // `[1,2,3,4]`
3876 if expr.exprs.len > 0 {
3877 expected_type_prev := c.expected_type
3878 mut first_expected_type := expected_type_prev
3879 if exp_type := expected_type_prev {
3880 match exp_type {
3881 Array {
3882 first_expected_type = to_optional_type(exp_type.elem_type)
3883 }
3884 ArrayFixed {
3885 first_expected_type = to_optional_type(exp_type.elem_type)
3886 }
3887 else {}
3888 }
3889 }
3890 is_fixed := !is_empty_expr(expr.len)
3891 // TODO: check all exprs
3892 first_expr := expr.exprs[0]
3893 c.expected_type = first_expected_type
3894 mut first_elem_type := c.expr(first_expr)
3895 c.expected_type = expected_type_prev
3896 if is_fixed {
3897 first_elem_type = fixed_array_literal_element_type(first_expr, first_elem_type)
3898 }
3899 $if ownership ? {
3900 c.ownership_consume_expr(first_expr, first_expr.pos(), 'array element')
3901 }
3902 // NOTE: why did I have this shortcut here?
3903 // if expr.exprs.len == 1 {
3904 // if first_elem_type.is_number_literal() {
3905 // return Array{
3906 // elem_type: int_
3907 // }
3908 // }
3909 // }
3910 // TODO: promote [0] - proper
3911 mut expected_type := first_elem_type
3912 mut return_type := Type(Array{
3913 elem_type: first_elem_type
3914 })
3915 if exp_type := c.expected_type {
3916 match exp_type {
3917 Array {
3918 expected_type = exp_type.elem_type
3919 return_type = Type(exp_type)
3920 }
3921 ArrayFixed {
3922 expected_type = exp_type.elem_type
3923 return_type = Type(exp_type)
3924 }
3925 SumType {
3926 if c.check_types(exp_type, first_elem_type)
3927 || c.check_types(first_elem_type, exp_type) {
3928 expected_type = exp_type
3929 return_type = Type(Array{
3930 elem_type: exp_type
3931 })
3932 }
3933 }
3934 else {}
3935 }
3936 } else {
3937 // `[Type.value_a, .value_b]`
3938 // set expected type for checking `.value_b`
3939 if first_elem_type is Enum {
3940 c.expected_type = to_optional_type(first_elem_type)
3941 }
3942 }
3943
3944 for i, elem_expr in expr.exprs {
3945 if i == 0 {
3946 continue
3947 }
3948 expected_type_before_elem := c.expected_type
3949 c.expected_type = to_optional_type(expected_type)
3950 mut elem_type := c.expr(elem_expr)
3951 c.expected_type = expected_type_before_elem
3952 if is_fixed {
3953 elem_type = fixed_array_literal_element_type(elem_expr, elem_type)
3954 }
3955 $if ownership ? {
3956 c.ownership_consume_expr(elem_expr, elem_expr.pos(), 'array element')
3957 }
3958 // TODO: best way to handle this?
3959 if expected_type.is_number_literal() && elem_type.is_number_literal() {
3960 expected_type = if expected_type.is_float_literal()
3961 || elem_type.is_float_literal() {
3962 Type(float_literal_)
3963 } else {
3964 Type(int_literal_)
3965 }
3966 first_elem_type = expected_type
3967 if return_type is Array {
3968 return_type = Type(Array{
3969 elem_type: expected_type
3970 })
3971 }
3972 elem_type = expected_type
3973 } else if elem_type.is_number_literal() && first_elem_type.is_number() {
3974 elem_type = first_elem_type
3975 }
3976 // sum type variant lists can include nested variants (e.g. ast.Node
3977 // accepts ast.InfixExpr through ast.Expr). V2 does not fully model
3978 // nested sum variants yet, so avoid rejecting them here.
3979 if expected_type !is SumType && !c.check_types(expected_type, elem_type) {
3980 // TODO: add general method for promotion/coercion
3981 c.error_with_pos('expecting element of type: ${expected_type.name()}, got ${elem_type.name()}',
3982 expr.pos)
3983 }
3984 }
3985 c.expected_type = expected_type_prev
3986 if is_fixed {
3987 arr_len := expr.exprs.len
3988 return ArrayFixed{
3989 len: arr_len
3990 elem_type: expected_type
3991 }
3992 }
3993 if exp_type := expected_type_prev {
3994 if exp_type is Array {
3995 return exp_type
3996 }
3997 if exp_type is ArrayFixed {
3998 return exp_type
3999 }
4000 }
4001 return return_type
4002 }
4003 // `[]int{}`
4004 if is_empty_expr(expr.typ) {
4005 if exp_type := c.expected_type {
4006 return exp_type
4007 }
4008 }
4009 return c.expr(expr.typ)
4010 }
4011 ast.AsCastExpr {
4012 c.expr(expr.expr)
4013 return c.type_expr(expr.typ)
4014 }
4015 ast.BasicLiteral {
4016 // c.log('ast.BasicLiteral: ${expr.kind.str()}: ${expr.value}')
4017 match expr.kind {
4018 .char {
4019 return Type(rune_)
4020 }
4021 .key_false, .key_true {
4022 return bool_
4023 }
4024 // TODO:
4025 .number {
4026 // TODO: had to be a better way to do this
4027 // should this be handled earlier? scanner?
4028 if expr.value.contains('.') {
4029 return float_literal_
4030 }
4031 return int_literal_
4032 }
4033 else {
4034 panic('invalid ast.BasicLiteral kind: ${expr.kind}')
4035 }
4036 }
4037 }
4038 ast.AssocExpr {
4039 typ := c.expr(expr.typ)
4040
4041 expected_type_prev := c.expected_type
4042 c.expected_type = to_optional_type(typ)
4043 base_type := c.expr(expr.expr)
4044 c.expected_type = expected_type_prev
4045
4046 // Base expression should match the assoc target type (allow pointers and sum types).
4047 mut base_check := resolve_alias(base_type)
4048 if base_check is Pointer {
4049 if !type_data_ptr_is_nil(base_check) {
4050 base_check = resolve_alias((base_check as Pointer).base_type)
4051 }
4052 }
4053 // Smart-casting does not apply to mutable variables. Use an immutable copy.
4054 final_base := base_check
4055 if final_base is SumType {
4056 sum_t := final_base as SumType
4057 if typ !in sum_t.variants {
4058 c.error_with_pos('expected base of type: ${typ.name()}, got ${base_type.name()}',
4059 expr.pos)
4060 }
4061 } else if !c.check_types(typ, final_base) {
4062 c.error_with_pos('expected base of type: ${typ.name()}, got ${base_type.name()}',
4063 expr.pos)
4064 }
4065
4066 // Unwrap aliases to get the base struct type for field lookups.
4067 mut resolved := resolve_alias(typ)
4068 resolved_imm := resolved
4069 if resolved_imm is Struct {
4070 resolved_imm_st2 := resolved_imm as Struct
4071 for field in expr.fields {
4072 expected_type_prev2 := c.expected_type
4073 mut field_type := Type(void_)
4074 mut found := false
4075 for sf in resolved_imm_st2.fields {
4076 if sf.name == field.name {
4077 if sf.is_module_mut {
4078 owner_module := field_owner_module(sf, resolved_imm_st2.name)
4079 if owner_module != '' && owner_module != c.cur_file_module {
4080 info := FieldAccessInfo{
4081 field: sf
4082 owner_struct: resolved_imm_st2.name
4083 }
4084 c.error_with_pos('cannot initialize module-mutable field `${field_access_display(info)}` outside module `${owner_module}`',
4085 expr.pos)
4086 }
4087 }
4088 field_type = sf.typ
4089 found = true
4090 break
4091 }
4092 }
4093 if !found {
4094 c.error_with_pos('unknown field `${field.name}` for type `${resolved_imm.name()}`',
4095 expr.pos)
4096 }
4097 c.expected_type = to_optional_type(field_type)
4098 got_type := c.expr(field.value)
4099 c.expected_type = expected_type_prev2
4100 if !c.check_types(field_type, got_type) {
4101 c.error_with_pos('expected field `${field.name}` of type: ${field_type.name()}, got ${got_type.name()}',
4102 expr.pos)
4103 }
4104 }
4105 } else {
4106 // Still visit values to populate env types for later passes.
4107 for field in expr.fields {
4108 c.expr(field.value)
4109 }
4110 }
4111 return typ
4112 }
4113 ast.CallOrCastExpr {
4114 // c.log('CallOrCastExpr: ${lhs_type.name()}')
4115 // lhs_type := c.expr(expr.lhs)
4116 // // call
4117 // if lhs_type is FnType {
4118 // return c.call_expr(ast.CallExpr{lhs: expr.lhs, args: [expr.expr]})
4119 // }
4120 // // cast
4121 // // expr_type := c.expr(expr.expr)
4122 // // TODO: check if expr_type can be cast to lhs_type
4123 // return lhs_type
4124 return c.expr(c.resolve_call_or_cast_expr(expr))
4125 }
4126 ast.CallExpr {
4127 // TODO/FIXME:
4128 // we need a way to handle C.stat|sigaction() / C.stat|sigaction{}
4129 // multiple items with same name inside scope lookup.
4130 if expr.lhs is ast.SelectorExpr {
4131 if expr.lhs.rhs.name == 'stat' {
4132 return int_
4133 }
4134 }
4135 return c.call_expr(&expr)
4136 }
4137 ast.CastExpr {
4138 typ := c.expr(expr.typ)
4139 c.expr(expr.expr)
4140 c.log('CastExpr: ${typ.name()}')
4141 return typ
4142 }
4143 ast.ComptimeExpr {
4144 if is_comptime_res_call_expr(expr.expr) {
4145 return Type(bool_)
4146 }
4147 if is_comptime_env_call_expr(expr.expr) {
4148 return Type(string_)
4149 }
4150 cexpr := c.resolve_expr(expr.expr)
4151 // TODO: move to checker, where `ast.*Or*` nodes will be resolved.
4152 if cexpr !is ast.CallExpr && cexpr !is ast.CallOrCastExpr && cexpr !is ast.IfExpr {
4153 c.error_with_pos('unsupported comptime: ${cexpr.name()}', expr.pos)
4154 }
4155 // Handle compile-time $if/$else - evaluate condition and check only the matching branch
4156 if cexpr is ast.IfExpr {
4157 return c.comptime_if_else(cexpr)
4158 }
4159 if is_embed_file_call_expr(cexpr) {
4160 return embed_file_helper_type()
4161 }
4162 if cexpr is ast.CallExpr {
4163 if cexpr.lhs is ast.SelectorExpr {
4164 call_lhs := cexpr.lhs as ast.SelectorExpr
4165 if call_lhs.lhs is ast.Ident {
4166 mod_ident := call_lhs.lhs as ast.Ident
4167 if mod_ident.name == 'veb' && call_lhs.rhs.name == 'html' {
4168 for arg in cexpr.args {
4169 c.expr(arg)
4170 }
4171 if typ := c.lookup_type_in_module('veb', 'Result') {
4172 return typ
4173 }
4174 return Type(Struct{
4175 name: 'veb__Result'
4176 })
4177 }
4178 }
4179 }
4180 // Handle $d(name, default_value) — returns default value's type
4181 if cexpr.lhs is ast.Ident && cexpr.lhs.name == 'd' && cexpr.args.len == 2 {
4182 return c.expr(cexpr.args[1])
4183 }
4184 return c.expr(cexpr)
4185 }
4186 c.log('ComptimeExpr: ' + cexpr.name())
4187 }
4188 ast.EmptyExpr {
4189 // TODO:
4190 return Type(void_)
4191 }
4192 ast.FnLiteral {
4193 $if ownership ? {
4194 // Captured owned vars are MOVED into the closure (Rust-style
4195 // `move ||`). `mut x` / `&x` captures are borrows. See
4196 // `ownership_check_closure_captures`.
4197 c.ownership_check_closure_captures(expr)
4198 }
4199 return c.fn_type(expr.typ, FnTypeAttribute.empty)
4200 }
4201 ast.GenericArgs {
4202 return c.generic_args_expr(expr)
4203 }
4204 ast.GenericArgOrIndexExpr {
4205 return c.expr(c.resolve_generic_arg_or_index_expr(expr))
4206 }
4207 ast.Ident {
4208 // c.log('ident: ${expr.name}')
4209 if typ := builtin_type(expr.name) {
4210 return typ
4211 }
4212 obj := c.ident(expr)
4213 typ := obj.typ()
4214 // TODO:
4215 if expr.name == 'string' {
4216 if typ is Struct {
4217 return Type(string_)
4218 }
4219 }
4220 return typ
4221 }
4222 ast.LifetimeExpr {
4223 return Type(NamedType('^' + expr.name))
4224 }
4225 ast.IfExpr {
4226 // if guard
4227 if expr.cond is ast.IfGuardExpr {
4228 guard := expr.cond as ast.IfGuardExpr
4229 mut guard_rhs_type := Type(void_)
4230 if guard.stmt.rhs.len > 0 {
4231 guard_rhs_type = c.expr(guard.stmt.rhs[0])
4232 }
4233 c.open_scope()
4234 c.assign_stmt(guard.stmt, true)
4235 c.stmt_list(expr.stmts)
4236 mut typ := Type(void_)
4237 last_expr_idx := trailing_expr_stmt_index(expr.stmts)
4238 if last_expr_idx >= 0 {
4239 last_stmt := expr.stmts[last_expr_idx] as ast.ExprStmt
4240 typ = c.expr(last_stmt.expr)
4241 }
4242 c.close_scope()
4243 mut else_type := Type(void_)
4244 if expr.else_expr !is ast.EmptyExpr {
4245 c.open_scope()
4246 if guard_rhs_type is ResultType || guard_rhs_type is OptionType {
4247 c.insert_error_scope_vars()
4248 }
4249 else_type = c.expr(expr.else_expr)
4250 c.close_scope()
4251 }
4252 if else_type is Void && typ !is Void {
4253 return typ
4254 }
4255 if typ is Void {
4256 return else_type
4257 }
4258 return else_type
4259 }
4260
4261 // normal if
4262 return c.if_expr(expr)
4263 }
4264 ast.IfGuardExpr {
4265 c.assign_stmt(expr.stmt, true)
4266 // TODO:
4267 return bool_
4268 }
4269 ast.IndexExpr {
4270 return c.index_expr(expr)
4271 }
4272 ast.InfixExpr {
4273 return c.infix_expr(expr)
4274 }
4275 ast.InitExpr {
4276 return c.init_expr(expr)
4277 }
4278 ast.KeywordOperator {
4279 return c.keyword_operator_expr(expr)
4280 }
4281 ast.MapInitExpr {
4282 return c.map_init_expr(expr)
4283 }
4284 ast.MatchExpr {
4285 return c.match_expr(expr, true)
4286 }
4287 ast.ModifierExpr {
4288 // if expr.expr !is ast.Ident && expr.expr !is ast.Type {
4289 // panic('not ident: ${expr.expr.type_name()}')
4290 // }
4291 return c.expr(expr.expr)
4292 }
4293 ast.OrExpr {
4294 return c.or_expr(expr)
4295 }
4296 ast.ParenExpr {
4297 return c.expr(expr.expr)
4298 }
4299 ast.PostfixExpr {
4300 if expr.op in [.inc, .dec] {
4301 c.check_module_storage_assignment(expr.expr, expr.op, expr.pos)
4302 c.check_module_mut_field_mutation(expr.expr, expr.pos)
4303 }
4304 typ := c.expr(expr.expr)
4305 // The `!` and `?` propagation operators unwrap result/option types.
4306 if expr.op in [.not, .question] {
4307 if typ is FnType {
4308 if ret_type := typ.return_type {
4309 return ret_type.unwrap()
4310 }
4311 }
4312 return typ.unwrap()
4313 }
4314 return typ
4315 }
4316 ast.PrefixExpr {
4317 return c.prefix_expr(expr)
4318 }
4319 ast.RangeExpr {
4320 start_type := c.expr(expr.start)
4321 c.expr(expr.end)
4322 return Type(Array{
4323 elem_type: start_type
4324 })
4325 }
4326 ast.SelectExpr {
4327 if expr.stmt !is ast.EmptyStmt {
4328 c.stmt(expr.stmt)
4329 }
4330 c.stmt_list(expr.stmts)
4331 if expr.next !is ast.EmptyExpr {
4332 c.expr(expr.next)
4333 }
4334 }
4335 ast.SqlExpr {
4336 c.expr(expr.expr)
4337 if expr.is_create {
4338 return ResultType{
4339 base_type: Type(void_)
4340 }
4341 }
4342 if expr.is_count {
4343 return Type(int_)
4344 }
4345 if expr.table_name != '' {
4346 table_type := c.type_expr(ast.Ident{
4347 name: expr.table_name
4348 pos: expr.pos
4349 })
4350 return Type(Array{
4351 elem_type: table_type
4352 })
4353 }
4354 if exp_type := c.expected_type {
4355 return exp_type
4356 }
4357 return ResultType{
4358 base_type: Type(void_)
4359 }
4360 }
4361 ast.SelectorExpr {
4362 // enum value: `.green`
4363 if expr.lhs is ast.EmptyExpr {
4364 if exp_type := c.expected_type {
4365 if exp_type is Enum {
4366 if exp_type.name != '' {
4367 return exp_type
4368 }
4369 }
4370 exp_base := exp_type.base_type()
4371 if exp_base is Enum {
4372 if exp_base.name != '' {
4373 return exp_base
4374 }
4375 }
4376 }
4377 if enum_type := c.enum_type_for_shorthand_field(expr.rhs.name) {
4378 return enum_type
4379 }
4380 if exp_type := c.expected_type {
4381 if exp_type.name() != '' {
4382 return exp_type
4383 }
4384 }
4385 c.error_with_pos('c.expected_type is not set', expr.pos)
4386 return Type(void_)
4387
4388 // return int_
4389 }
4390 // normal selector
4391 return c.selector_expr(expr)
4392 }
4393 ast.StringInterLiteral {
4394 for inter in expr.inters {
4395 c.expr(inter.expr)
4396 }
4397 return Type(string_)
4398 }
4399 ast.StringLiteral {
4400 // C strings (c'...' or c"...") return charptr
4401 if expr.kind == .c {
4402 return charptr_
4403 }
4404 return Type(string_)
4405 }
4406 ast.Tuple {
4407 mut types := []Type{}
4408 for x in expr.exprs {
4409 types << c.expr(x)
4410 }
4411 return Tuple{
4412 types: types
4413 }
4414 }
4415 ast.Type {
4416 return c.type_node_expr(expr)
4417 }
4418 ast.UnsafeExpr {
4419 // TODO: proper
4420 prev_inside_unsafe := c.inside_unsafe
4421 c.inside_unsafe = true
4422 c.stmt_list(expr.stmts)
4423 last_expr_idx := trailing_expr_stmt_index(expr.stmts)
4424 if last_expr_idx >= 0 {
4425 last_stmt := expr.stmts[last_expr_idx] as ast.ExprStmt
4426 ret := c.expr(last_stmt.expr)
4427 c.inside_unsafe = prev_inside_unsafe
4428 return ret
4429 }
4430 c.inside_unsafe = prev_inside_unsafe
4431 // TODO: impl: avoid returning types everywhere / using void
4432 // perhaps use a struct and set the type and other info in it when needed
4433 return Type(void_)
4434 }
4435 ast.LockExpr {
4436 // Lock expression - check statements and return last expression type
4437 c.stmt_list(expr.stmts)
4438 last_expr_idx := trailing_expr_stmt_index(expr.stmts)
4439 if last_expr_idx >= 0 {
4440 last_stmt := expr.stmts[last_expr_idx] as ast.ExprStmt
4441 return c.expr(last_stmt.expr)
4442 }
4443 return Type(void_)
4444 }
4445 else {}
4446 }
4447
4448 // TODO: remove (add all variants)
4449 c.log('expr: unhandled ${expr.name()}')
4450 return int_
4451}
4452
4453fn (c &Checker) lookup_type_in_scope_chain(name string) ?Type {
4454 if name == '' {
4455 return none
4456 }
4457 mut cur_scope := c.scope
4458 for cur_scope != unsafe { nil } {
4459 if typ := cur_scope.lookup_type(name) {
4460 return typ
4461 }
4462 cur_scope = cur_scope.parent
4463 }
4464 return none
4465}
4466
4467fn (mut c Checker) type_expr(expr ast.Expr) Type {
4468 resolved := c.resolve_expr(expr)
4469 match resolved {
4470 ast.Ident {
4471 if typ := builtin_type(resolved.name) {
4472 return typ
4473 }
4474 if obj := universe.lookup_parent(resolved.name, 0) {
4475 if typ := object_as_type(obj) {
4476 return typ
4477 }
4478 }
4479 if typ := c.lookup_type_in_scope_chain(resolved.name) {
4480 return typ
4481 }
4482 if typ := c.lookup_type_in_imported_modules(resolved.name) {
4483 return typ
4484 }
4485 }
4486 ast.SelectorExpr {
4487 parts := selector_expr_parts(resolved)
4488 if parts.len >= 2 {
4489 module_alias := parts[parts.len - 2]
4490 tname := parts[parts.len - 1]
4491 if typ := c.lookup_type_in_module(module_alias, tname) {
4492 return typ
4493 }
4494 }
4495 }
4496 ast.Type {
4497 return c.type_node_expr(resolved)
4498 }
4499 ast.CallOrCastExpr {
4500 return c.type_expr(c.resolve_call_or_cast_expr(resolved))
4501 }
4502 else {}
4503 }
4504
4505 return c.expr(resolved)
4506}
4507
4508fn (mut c Checker) lookup_type_in_module(module_alias string, type_name string) ?Type {
4509 if module_obj := c.scope.lookup_parent(module_alias, 0) {
4510 if module_obj is Module {
4511 if typ := module_obj.scope.lookup_type_parent(type_name, 0) {
4512 return typ
4513 }
4514 }
4515 }
4516 mut found := Type(void_)
4517 mut ok := false
4518 lock c.env.scopes {
4519 if module_alias in c.env.scopes {
4520 mod_scope := unsafe { c.env.scopes[module_alias] }
4521 if typ := mod_scope.lookup_type_parent(type_name, 0) {
4522 found = typ
4523 ok = true
4524 }
4525 }
4526 }
4527 if ok {
4528 return found
4529 }
4530 return none
4531}
4532
4533fn (mut c Checker) lookup_type_in_imported_modules(type_name string) ?Type {
4534 for _, obj in c.scope.objects {
4535 match obj {
4536 Module {
4537 if typ := obj.scope.lookup_type_parent(type_name, 0) {
4538 return typ
4539 }
4540 if obj.name == '' {
4541 continue
4542 }
4543 if typ := c.lookup_type_in_module(obj.name, type_name) {
4544 return typ
4545 }
4546 }
4547 else {}
4548 }
4549 }
4550 return none
4551}
4552
4553fn (mut c Checker) lookup_object_in_imported_modules(name string) ?Object {
4554 mut cur_scope := c.scope
4555 for cur_scope != unsafe { nil } {
4556 for _, obj in cur_scope.objects {
4557 match obj {
4558 Module {
4559 if rhs_obj := obj.scope.lookup_parent(name, 0) {
4560 if rhs_obj is Global && rhs_obj.is_module_storage() {
4561 continue
4562 }
4563 return rhs_obj
4564 }
4565 if obj.name == '' {
4566 continue
4567 }
4568 if module_obj := c.scope.lookup_parent(obj.name, 0) {
4569 if module_obj is Module {
4570 if rhs_obj := module_obj.scope.lookup_parent(name, 0) {
4571 if rhs_obj is Global && rhs_obj.is_module_storage() {
4572 continue
4573 }
4574 return rhs_obj
4575 }
4576 }
4577 }
4578 }
4579 else {}
4580 }
4581 }
4582 cur_scope = cur_scope.parent
4583 }
4584 return none
4585}
4586
4587fn selector_expr_parts(expr ast.SelectorExpr) []string {
4588 mut parts := []string{}
4589 collect_selector_expr_parts(ast.Expr(expr), mut parts)
4590 return parts
4591}
4592
4593fn collect_selector_expr_parts(expr ast.Expr, mut parts []string) bool {
4594 match expr {
4595 ast.Ident {
4596 parts << expr.name
4597 return true
4598 }
4599 ast.SelectorExpr {
4600 if !collect_selector_expr_parts(expr.lhs, mut parts) {
4601 return false
4602 }
4603 parts << expr.rhs.name
4604 return true
4605 }
4606 else {
4607 return false
4608 }
4609 }
4610}
4611
4612fn (mut c Checker) type_node_expr(type_expr ast.Type) Type {
4613 if type_expr is ast.ArrayType {
4614 arr_type := type_expr as ast.ArrayType
4615 return Array{
4616 elem_type: c.type_expr(arr_type.elem_type)
4617 }
4618 }
4619 if type_expr is ast.ArrayFixedType {
4620 arr_fixed_type := type_expr as ast.ArrayFixedType
4621 mut len := 0
4622 if arr_fixed_type.len is ast.BasicLiteral {
4623 if arr_fixed_type.len.kind == .number {
4624 len = int(strconv.parse_int(arr_fixed_type.len.value, 0, 64) or { 0 })
4625 }
4626 } else if arr_fixed_type.len is ast.Ident {
4627 if obj := c.scope.lookup_parent(arr_fixed_type.len.name, 0) {
4628 if obj is Const {
4629 len = obj.int_val
4630 }
4631 }
4632 }
4633 return ArrayFixed{
4634 len: len
4635 elem_type: c.type_expr(arr_fixed_type.elem_type)
4636 }
4637 }
4638 if type_expr is ast.ChannelType {
4639 ch_type := type_expr as ast.ChannelType
4640 if ch_type.elem_type !is ast.EmptyExpr {
4641 return Channel{
4642 elem_type: to_optional_type(c.type_expr(ch_type.elem_type))
4643 }
4644 }
4645 return empty_channel()
4646 }
4647 if type_expr is ast.FnType {
4648 fn_type := type_expr as ast.FnType
4649 return c.fn_type(fn_type, FnTypeAttribute.empty)
4650 }
4651 if type_expr is ast.GenericType {
4652 gen_type := type_expr as ast.GenericType
4653 return c.expr(ast.Expr(ast.GenericArgs{
4654 lhs: gen_type.name
4655 args: gen_type.params
4656 }))
4657 }
4658 if type_expr is ast.MapType {
4659 map_type := type_expr as ast.MapType
4660 return Map{
4661 key_type: c.type_expr(map_type.key_type)
4662 value_type: c.type_expr(map_type.value_type)
4663 }
4664 }
4665 if type_expr is ast.NilType {
4666 return Type(nil_)
4667 }
4668 if type_expr is ast.NoneType {
4669 return Type(none_)
4670 }
4671 if type_expr is ast.OptionType {
4672 opt_type := type_expr as ast.OptionType
4673 return OptionType{
4674 base_type: c.type_expr(opt_type.base_type)
4675 }
4676 }
4677 if type_expr is ast.PointerType {
4678 ptr_type := type_expr as ast.PointerType
4679 return Pointer{
4680 base_type: c.type_expr(ptr_type.base_type)
4681 lifetime: ptr_type.lifetime
4682 }
4683 }
4684 if type_expr is ast.ResultType {
4685 res_type := type_expr as ast.ResultType
4686 return ResultType{
4687 base_type: c.type_expr(res_type.base_type)
4688 }
4689 }
4690 if type_expr is ast.ThreadType {
4691 thread_type := type_expr as ast.ThreadType
4692 if thread_type.elem_type !is ast.EmptyExpr {
4693 return Thread{
4694 elem_type: to_optional_type(c.type_expr(thread_type.elem_type))
4695 }
4696 }
4697 return empty_thread()
4698 }
4699 if type_expr is ast.TupleType {
4700 tuple_type := type_expr as ast.TupleType
4701 mut types := []Type{}
4702 for tx in tuple_type.types {
4703 types << c.type_expr(tx)
4704 }
4705 return Tuple{
4706 types: types
4707 }
4708 }
4709 if type_expr is ast.AnonStructType {
4710 anon := type_expr as ast.AnonStructType
4711 mut fields := []Field{}
4712 for field in anon.fields {
4713 field_typ := c.type_expr(field.typ)
4714 fields << Field{
4715 name: field.name
4716 typ: field_typ
4717 default_expr: field.value
4718 attributes: field.attributes
4719 is_public: field.is_public
4720 is_mut: field.is_mut
4721 is_module_mut: field.is_module_mut
4722 is_interface_method: field.is_interface_method
4723 owner_module: c.cur_file_module
4724 }
4725 }
4726 return Struct{
4727 fields: fields
4728 }
4729 }
4730 c.log('expr.Type: unhandled ${type_expr.type_name()}')
4731 return int_
4732}
4733
4734fn (mut c Checker) stmt(stmt ast.Stmt) {
4735 match stmt {
4736 ast.AssertStmt {
4737 c.expr(stmt.expr)
4738 if stmt.extra !is ast.EmptyExpr {
4739 c.expr(stmt.extra)
4740 }
4741 }
4742 ast.AssignStmt {
4743 c.assign_stmt(stmt, false)
4744 }
4745 ast.BlockStmt {
4746 c.stmt_list(stmt.stmts)
4747 }
4748 // ast.Decl {
4749 // // Handled earlier
4750 // // match stmt {
4751 // // ast.FnDecl{}
4752 // // ast.TypeDecl {}
4753 // // else {}
4754 // // }
4755 // }
4756 ast.GlobalDecl {
4757 for field in stmt.fields {
4758 // c.log('GlobalDecl: ${field.name} - ${obj.typ.type_name()}')
4759 field_type := if field.typ !is ast.EmptyExpr {
4760 c.type_expr(field.typ)
4761 } else {
4762 c.expr(field.value)
4763 }
4764 obj := module_storage_object(c.cur_file_module, stmt, field, field_type)
4765 c.scope.insert_or_update(field.name, obj)
4766 }
4767 }
4768 ast.DeferStmt {
4769 c.stmt_list(stmt.stmts)
4770 }
4771 ast.ExprStmt {
4772 if stmt.expr is ast.MatchExpr {
4773 c.match_expr(stmt.expr, false)
4774 } else {
4775 c.expr(stmt.expr)
4776 }
4777 }
4778 ast.ForStmt {
4779 c.open_scope()
4780 // TODO: vars for other for loops
4781 if for_in := stmt_for_in_payload(stmt.init) {
4782 expected_type_prev := c.expected_type
4783 c.expected_type = none
4784 expr_type := c.expr(for_in.expr)
4785 c.expected_type = expected_type_prev
4786 if key_ident := expr_ident_payload(for_in.key) {
4787 // TODO: remove
4788 if expr_type is Void {
4789 if c.pref.verbose {
4790 c.scope.print(false)
4791 }
4792 c.close_scope()
4793 c.error_with_pos('for-in expression does not have a value',
4794 for_in.expr.pos())
4795 }
4796 key_type := expr_type.key_type()
4797 c.scope.insert(key_ident.name, object_from_type(key_type))
4798 c.fallback_vars[key_ident.name] = key_type
4799 if c.fn_root_scope != unsafe { nil }
4800 && !same_scope_ptr(c.fn_root_scope, c.scope) {
4801 root_key_obj := object_from_type(key_type)
4802 c.fn_root_scope.insert_or_update(key_ident.name, root_key_obj)
4803 }
4804 // Store key type in expr_types
4805 if key_ident.pos.is_valid() {
4806 c.env.set_expr_type(key_ident.pos.id, key_type)
4807 }
4808 }
4809 mut value_type := c.for_in_value_type(expr_type)
4810 if for_in.expr is ast.SelectorExpr && c.selector_rhs_name(for_in.expr) == 'params' {
4811 params_lhs_type := c.expr(for_in.expr.lhs)
4812 if is_ast_fn_type_type(params_lhs_type) {
4813 value_type = c.ast_parameter_type()
4814 }
4815 }
4816 // For iterator structs (e.g. RunesIterator), value_type() returns
4817 // the struct itself. The transformer lowers these to direct iteration,
4818 // so don't flatten the stale iterator type to fn_root_scope.
4819 is_iterator_struct := value_type is Struct
4820 if for_in.value is ast.ModifierExpr {
4821 // Store the non-ref type for fn_root_scope because the transformer
4822 // lowers mutable for-in loops to indexed access with value copies.
4823 non_ref_value_type := value_type
4824 if for_in.value.kind == .key_mut {
4825 c.check_module_mut_field_mutation(for_in.expr, for_in.value.pos)
4826 value_type = Type(value_type.ref())
4827 }
4828 if for_in.value.expr is ast.Ident {
4829 c.scope.insert(for_in.value.expr.name, object_from_type(value_type))
4830 c.fallback_vars[for_in.value.expr.name] = value_type
4831 if !is_iterator_struct && c.fn_root_scope != unsafe { nil }
4832 && !same_scope_ptr(c.fn_root_scope, c.scope) {
4833 root_value_obj := object_from_type(non_ref_value_type)
4834 c.fn_root_scope.insert_or_update(for_in.value.expr.name, root_value_obj)
4835 }
4836 // Store value type in expr_types (Ident inside ModifierExpr)
4837 if for_in.value.expr.pos.is_valid() {
4838 c.env.set_expr_type(for_in.value.expr.pos.id, value_type)
4839 }
4840 }
4841 // Store ModifierExpr type too
4842 if for_in.value.pos.is_valid() {
4843 c.env.set_expr_type(for_in.value.pos.id, value_type)
4844 }
4845 } else if value_ident := expr_ident_payload(for_in.value) {
4846 c.scope.insert(value_ident.name, object_from_type(value_type))
4847 c.fallback_vars[value_ident.name] = value_type
4848 if !is_iterator_struct && c.fn_root_scope != unsafe { nil }
4849 && !same_scope_ptr(c.fn_root_scope, c.scope) {
4850 root_value_obj := object_from_type(value_type)
4851 c.fn_root_scope.insert_or_update(value_ident.name, root_value_obj)
4852 }
4853 // Store value type in expr_types
4854 if value_ident.pos.is_valid() {
4855 c.env.set_expr_type(value_ident.pos.id, value_type)
4856 }
4857 }
4858 } else {
4859 if stmt.cond !is ast.EmptyExpr {
4860 sc_names, sc_types := c.extract_smartcasts(stmt.cond)
4861 c.apply_smartcasts(sc_names, sc_types)
4862 }
4863 // c.stmt(stmt.init)
4864 }
4865 // sc_names, sc_types := c.extract_smartcasts(stmt.cond)
4866 // c.apply_smartcasts(sc_names, sc_types)
4867 c.stmt(stmt.init)
4868 c.expr(stmt.cond)
4869 c.stmt(stmt.post)
4870 c.stmt_list(stmt.stmts)
4871 c.close_scope()
4872 }
4873 ast.ImportStmt {
4874 // c.log('import: ${stmt.name} as ${stmt.alias}')
4875 }
4876 // ast.FnDecl - handled by preregister_all_fn_signatures / process_pending_fn_bodies
4877 ast.ReturnStmt {
4878 c.log('ReturnStmt:')
4879 prev_inside_return_stmt := c.inside_return_stmt
4880 c.inside_return_stmt = true
4881 for expr in stmt.exprs {
4882 c.expr(expr)
4883 }
4884 c.inside_return_stmt = prev_inside_return_stmt
4885 $if ownership ? {
4886 c.ownership_check_return(stmt)
4887 }
4888 }
4889 ast.ComptimeStmt {
4890 c.stmt(stmt.stmt)
4891 }
4892 ast.LabelStmt {
4893 c.stmt(stmt.stmt)
4894 }
4895 else {}
4896 }
4897}
4898
4899fn (mut c Checker) stmt_list(stmts []ast.Stmt) {
4900 for stmt in stmts {
4901 c.stmt(stmt)
4902 if stmt is ast.ExprStmt && stmt.expr is ast.IfExpr {
4903 if_expr := stmt.expr as ast.IfExpr
4904 if ownership_stmts_terminate(if_expr.stmts) {
4905 sc_names, sc_types := c.extract_smartcasts_from_false_condition(if_expr.cond)
4906 c.apply_smartcasts(sc_names, sc_types)
4907 }
4908 }
4909 }
4910}
4911
4912fn trailing_expr_stmt_index(stmts []ast.Stmt) int {
4913 mut i := stmts.len
4914 for i > 0 {
4915 i--
4916 if stmts[i] is ast.EmptyStmt {
4917 continue
4918 }
4919 if stmts[i] is ast.ExprStmt {
4920 expr_stmt := stmts[i] as ast.ExprStmt
4921 if expr_stmt.expr is ast.IfExpr
4922 && (expr_stmt.expr as ast.IfExpr).else_expr is ast.EmptyExpr {
4923 break
4924 }
4925 return i
4926 }
4927 break
4928 }
4929 return -1
4930}
4931
4932fn pending_const_type_is_unresolved(typ Type) bool {
4933 match typ {
4934 Alias, OptionType, ResultType, Pointer {
4935 return type_data_ptr_is_nil(typ)
4936 }
4937 else {
4938 return false
4939 }
4940 }
4941}
4942
4943fn (mut c Checker) sync_imported_const_type(source Const) {
4944 if source.mod == unsafe { nil } {
4945 return
4946 }
4947 lock c.env.scopes {
4948 for _, scope_ptr in c.env.scopes {
4949 mut scope := unsafe { scope_ptr }
4950 obj := scope.objects[source.name] or { continue }
4951 if obj is Const {
4952 if obj.mod == source.mod {
4953 mut updated := obj
4954 updated.typ = source.typ
4955 scope.objects[source.name] = Object(updated)
4956 }
4957 }
4958 }
4959 }
4960}
4961
4962fn (mut c Checker) update_const_object_type(scope &Scope, name string, const_type Type) {
4963 mut target_scope := unsafe { scope }
4964 obj := target_scope.objects[name] or { return }
4965 if obj is Const {
4966 mut updated := obj
4967 updated.typ = const_type.typed_default()
4968 target_scope.objects[name] = Object(updated)
4969 c.sync_imported_const_type(updated)
4970 }
4971}
4972
4973fn (mut c Checker) process_pending_const_fields() {
4974 mut pending := c.pending_const_fields.clone()
4975 c.pending_const_fields.clear()
4976 mut made_progress := true
4977 for pending.len > 0 && made_progress {
4978 made_progress = false
4979 mut remaining := []PendingConstField{}
4980 for item in pending {
4981 c.scope = item.scope
4982 const_type := c.expr(item.field.value).typed_default()
4983 if pending_const_type_is_unresolved(const_type) {
4984 remaining << item
4985 continue
4986 }
4987 c.update_const_object_type(item.scope, item.field.name, const_type)
4988 made_progress = true
4989 }
4990 pending = remaining.clone()
4991 }
4992 for item in pending {
4993 c.scope = item.scope
4994 const_type := c.expr(item.field.value).typed_default()
4995 if pending_const_type_is_unresolved(const_type) {
4996 continue
4997 }
4998 c.update_const_object_type(item.scope, item.field.name, const_type)
4999 }
5000}
5001
5002fn append_missing_fields(mut dst []Field, src []Field) {
5003 for field in src {
5004 mut exists := false
5005 for existing in dst {
5006 if existing.name == field.name {
5007 exists = true
5008 break
5009 }
5010 }
5011 if !exists {
5012 dst << field
5013 }
5014 }
5015}
5016
5017fn (mut c Checker) interface_decl_fields(decl ast.InterfaceDecl) []Field {
5018 mut fields := []Field{}
5019 mut has_type_name := false
5020 for field in decl.fields {
5021 if field.name == 'type_name' {
5022 has_type_name = true
5023 }
5024 mut field_type := c.type_expr(field.typ)
5025 if field.is_interface_method && field.is_mut {
5026 if mut field_type is FnType {
5027 field_type.is_mut_receiver = true
5028 }
5029 }
5030 fields << Field{
5031 name: field.name
5032 typ: field_type
5033 default_expr: field.value
5034 attributes: field.attributes
5035 is_mut: field.is_mut
5036 is_interface_method: field.is_interface_method
5037 }
5038 }
5039 if !has_type_name {
5040 fields << Field{
5041 name: 'type_name'
5042 typ: Type(fn_with_return_type(empty_fn_type(), String(0)))
5043 is_interface_method: true
5044 }
5045 }
5046 return fields
5047}
5048
5049fn (mut c Checker) process_pending_interface_decls() {
5050 for pending in c.pending_interface_decls {
5051 c.scope = pending.scope
5052 fields := c.interface_decl_fields(pending.decl)
5053 mut scope := pending.scope
5054 if id := scope.lookup(pending.decl.name) {
5055 if id_type := object_as_type(id) {
5056 if id_type is Interface {
5057 interface_type := Type(Interface{
5058 name: id_type.name
5059 fields: fields
5060 })
5061 scope.objects[pending.decl.name] = object_from_type(interface_type)
5062 scope.insert_type(pending.decl.name, interface_type)
5063 }
5064 }
5065 }
5066 }
5067 for _ in 0 .. c.pending_interface_decls.len {
5068 for pending in c.pending_interface_decls {
5069 c.scope = pending.scope
5070 mut merged_fields := c.interface_decl_fields(pending.decl)
5071 for embedded_expr in pending.decl.embedded {
5072 embedded_type := resolve_alias(c.type_expr(embedded_expr))
5073 if embedded_type is Interface {
5074 append_missing_fields(mut merged_fields, embedded_type.fields)
5075 }
5076 }
5077 mut scope := pending.scope
5078 if id := scope.lookup(pending.decl.name) {
5079 if id_type := object_as_type(id) {
5080 if id_type is Interface {
5081 interface_type := Type(Interface{
5082 name: id_type.name
5083 fields: merged_fields
5084 })
5085 scope.objects[pending.decl.name] = object_from_type(interface_type)
5086 scope.insert_type(pending.decl.name, interface_type)
5087 }
5088 }
5089 }
5090 }
5091 }
5092 c.pending_interface_decls.clear()
5093}
5094
5095fn (mut c Checker) process_pending_struct_decls() {
5096 prev_module := c.cur_file_module
5097 for pending in c.pending_struct_decls {
5098 c.scope = pending.scope
5099 c.cur_file_module = pending.module_name
5100 // Insert generic type parameters into scope so field types can reference them
5101 mut generic_params := []string{}
5102 for gp in pending.decl.generic_params {
5103 gp_name := if gp is ast.Ident {
5104 gp.name
5105 } else if gp is ast.LifetimeExpr {
5106 '^' + gp.name
5107 } else {
5108 ''
5109 }
5110 if gp_name != '' {
5111 generic_params << gp_name
5112 c.scope.insert(gp_name, Type(NamedType(gp_name)))
5113 }
5114 }
5115 mut implements_names := []string{}
5116 for impl_expr in pending.decl.implements {
5117 impl_type := c.type_expr(impl_expr)
5118 impl_name := impl_type.name()
5119 if impl_name != '' && impl_name != 'void' {
5120 implements_names << impl_name
5121 continue
5122 }
5123 fallback_name := c.type_ref_name(impl_expr)
5124 if fallback_name != '' {
5125 implements_names << fallback_name
5126 }
5127 }
5128 // Stash the struct declaration position keyed by name so that the
5129 // ownership validator can attach the "missing drop method" diagnostic
5130 // to the decl site instead of an arbitrary use site.
5131 $if ownership ? {
5132 for impl_name in implements_names {
5133 if impl_name == 'Drop' || impl_name.all_after_last('__') == 'Drop' {
5134 c.ownership_drop_decl_positions[pending.decl.name] = pending.decl.pos
5135 break
5136 }
5137 }
5138 }
5139 mut fields := []Field{}
5140 for field_idx in 0 .. pending.decl.fields.len {
5141 field_typ := c.decl_field_type(pending.decl.fields[field_idx].typ)
5142 fields << Field{
5143 name: pending.decl.fields[field_idx].name
5144 typ: field_typ
5145 default_expr: pending.decl.fields[field_idx].value
5146 attributes: pending.decl.fields[field_idx].attributes
5147 is_public: pending.decl.fields[field_idx].is_public
5148 is_mut: pending.decl.fields[field_idx].is_mut
5149 is_module_mut: pending.decl.fields[field_idx].is_module_mut
5150 is_interface_method: pending.decl.fields[field_idx].is_interface_method
5151 owner_module: pending.module_name
5152 }
5153 }
5154 mut embedded := []Struct{}
5155 for embedded_expr in pending.decl.embedded {
5156 embedded_type := resolve_alias(c.type_expr(embedded_expr))
5157 if embedded_type is Struct {
5158 embedded << embedded_type
5159 } else if pending.decl.is_union {
5160 // Union members may be aliases whose base structs are still empty
5161 // placeholders. Keep a named placeholder here so
5162 // find_field_or_method can re-resolve the live type from scope.
5163 embedded_name := c.type_ref_name(embedded_expr)
5164 if embedded_name != '' {
5165 embedded << Struct{
5166 name: embedded_name
5167 }
5168 }
5169 } else {
5170 c.error_with_pos('can only structs, `${embedded_type.name()}` is not a struct.',
5171 pending.decl.pos)
5172 }
5173 }
5174 // Detect @[soa] attribute
5175 is_soa := pending.decl.attributes.has('soa')
5176 if is_soa {
5177 if pending.decl.is_union {
5178 c.error_with_pos('`@[soa]` attribute cannot be used with unions', pending.decl.pos)
5179 }
5180 if pending.decl.embedded.len > 0 {
5181 c.error_with_pos('`@[soa]` structs cannot have embedded structs', pending.decl.pos)
5182 }
5183 for field in fields {
5184 match field.typ {
5185 Primitive, Char, Rune, ISize, USize {}
5186 else {
5187 c.error_with_pos('`@[soa]` structs can only contain primitive numeric types, not `${field.typ.name()}`',
5188 pending.decl.pos)
5189 }
5190 }
5191 }
5192 }
5193 mut update_scope := if pending.decl.language == .c { c.c_scope } else { pending.scope }
5194 if sd_obj := update_scope.lookup(pending.decl.name) {
5195 if sd_type := object_as_type(sd_obj) {
5196 if sd_type is Struct {
5197 // Write updated struct back to scope (lookup returns a copy)
5198 struct_type := Type(Struct{
5199 name: sd_type.name
5200 generic_params: sd_type.generic_params
5201 implements: implements_names
5202 fields: fields
5203 embedded: embedded
5204 is_soa: is_soa
5205 })
5206 update_scope.objects[pending.decl.name] = object_from_type(struct_type)
5207 update_scope.insert_type(pending.decl.name, struct_type)
5208 }
5209 }
5210 }
5211 }
5212 c.cur_file_module = prev_module
5213 c.pending_struct_decls.clear()
5214}
5215
5216fn (mut c Checker) process_pending_type_decls() {
5217 for pending in c.pending_type_decls {
5218 c.scope = pending.scope
5219 mut scope := pending.scope
5220 prev_generic_params := c.generic_params
5221 c.generic_params = generic_param_names_from_exprs(pending.decl.generic_params)
5222 if pending.decl.variants.len == 0 {
5223 if obj := scope.lookup(pending.decl.name) {
5224 if obj_type := object_as_type(obj) {
5225 if obj_type is Alias {
5226 base_type := c.type_expr(pending.decl.base_type)
5227 alias_type := Type(Alias{
5228 name: obj_type.name
5229 base_type: base_type
5230 })
5231 scope.objects[pending.decl.name] = object_from_type(alias_type)
5232 scope.insert_type(pending.decl.name, alias_type)
5233 }
5234 }
5235 }
5236 } else {
5237 if obj := scope.lookup(pending.decl.name) {
5238 if obj_type := object_as_type(obj) {
5239 if obj_type is SumType {
5240 mut variants := obj_type.variants.clone()
5241 for variant in pending.decl.variants {
5242 // Keep the inferred variant type in a local first so generated C
5243 // does not take the address of a temporary expression result.
5244 variant_type := c.type_expr(variant)
5245 variants << variant_type
5246 }
5247 sum_type := Type(SumType{
5248 name: obj_type.name
5249 generic_params: obj_type.generic_params
5250 variants: variants
5251 })
5252 scope.objects[pending.decl.name] = object_from_type(sum_type)
5253 scope.insert_type(pending.decl.name, sum_type)
5254 }
5255 }
5256 }
5257 }
5258 c.generic_params = prev_generic_params
5259 }
5260 c.pending_type_decls.clear()
5261}
5262
5263fn (mut c Checker) process_pending_fn_bodies() {
5264 // Use index-based loop to handle nested FnDecls that get added during processing.
5265 for c.pending_fn_bodies.len > 0 {
5266 mut progressed := false
5267 mut i := 0
5268 for i < c.pending_fn_bodies.len {
5269 if c.check_pending_fn_body(c.pending_fn_bodies[i]) {
5270 c.pending_fn_bodies.delete(i)
5271 progressed = true
5272 continue
5273 }
5274 i++
5275 }
5276 if !progressed {
5277 break
5278 }
5279 }
5280 c.pending_fn_bodies.clear()
5281}
5282
5283fn (mut c Checker) check_pending_fn_body(pending PendingFnBody) bool {
5284 signature_decl := pending.decl
5285 decl_generic_params := collect_fn_generic_params(signature_decl)
5286 has_decl_generic_params := signature_decl.typ.generic_params.len > 0
5287 has_generic_params := decl_generic_params.len > 0
5288 if has_decl_generic_params {
5289 mut generic_types := []map[string]Type{}
5290 mut has_generic_types := false
5291 for name, inferred in c.env.generic_types {
5292 // Extract the base function name from the key by stripping
5293 // module prefix (before '.') and generic args (after '[').
5294 mut base_name := name
5295 bracket_pos := name.index_u8(`[`)
5296 if bracket_pos > 0 {
5297 base_name = name[..bracket_pos]
5298 }
5299 dot_pos := base_name.last_index_u8(`.`)
5300 short_name := if dot_pos > 0 && dot_pos < base_name.len - 1 {
5301 base_name[dot_pos + 1..]
5302 } else {
5303 base_name
5304 }
5305 if base_name == signature_decl.name || short_name == signature_decl.name {
5306 generic_types = inferred.clone()
5307 has_generic_types = true
5308 break
5309 }
5310 }
5311 if !has_generic_types {
5312 // Skip checking generic functions that are never instantiated.
5313 return false
5314 }
5315 for generic_type_map in generic_types {
5316 c.env.cur_generic_types << generic_type_map
5317 }
5318 }
5319 if !has_decl_generic_params || c.env.cur_generic_types.len > 0 {
5320 mut decl := signature_decl
5321 if pending.flat != unsafe { nil } && pending.flat_decl_id >= 0 {
5322 flat_decl := ast.Cursor{
5323 flat: pending.flat
5324 id: pending.flat_decl_id
5325 }.fn_decl()
5326 if flat_decl.name != '' {
5327 decl = flat_decl
5328 }
5329 }
5330 prev_scope := c.scope
5331 prev_module := c.cur_file_module
5332 prev_fn_root_scope := c.fn_root_scope
5333 mut prev_fallback_vars := c.fallback_vars.move()
5334 prev_generic_params := c.generic_params
5335 c.scope = pending.scope
5336 c.cur_file_module = pending.module_name
5337 c.fn_root_scope = pending.scope
5338 c.fallback_vars = map[string]Type{}
5339 for param in pending.typ.params {
5340 if param.name != '' {
5341 c.fallback_vars[param.name] = param.typ
5342 }
5343 }
5344 if decl.is_method {
5345 mut receiver_type := c.type_expr(decl.receiver.typ)
5346 if decl.receiver.is_mut && receiver_type !is Pointer {
5347 receiver_type = Type(Pointer{
5348 base_type: receiver_type
5349 })
5350 }
5351 c.fallback_vars[decl.receiver.name] = receiver_type
5352 }
5353 if has_generic_params {
5354 c.generic_params = decl_generic_params
5355 for gp_name in decl_generic_params {
5356 c.scope.insert(gp_name, Type(NamedType(gp_name)))
5357 }
5358 }
5359 expected_type := c.expected_type
5360 c.expected_type = pending.typ.return_type
5361 // Ownership: save/restore per-function state
5362 mut prev_ownership_fn := ''
5363 mut prev_owned := map[string]token.Pos{}
5364 mut prev_owned_types := map[string]string{}
5365 mut prev_moved := map[string]MovedVar{}
5366 mut prev_borrowed := map[string][]BorrowInfo{}
5367 $if ownership ? {
5368 prev_ownership_fn = c.ownership_cur_fn
5369 prev_owned = c.owned_vars.clone()
5370 prev_owned_types = c.owned_var_types.clone()
5371 prev_moved = c.moved_vars.clone()
5372 prev_borrowed = c.borrowed_vars.clone()
5373 c.ownership_enter_fn(decl.name, decl)
5374 }
5375 c.stmt_list(decl.stmts)
5376 $if ownership ? {
5377 publish_keys := ownership_publish_keys_for(pending.module_name, decl)
5378 c.ownership_snapshot_drops_at_fn_exit(decl.name, publish_keys)
5379 c.ownership_publish_pending_return_drops(publish_keys)
5380 c.ownership_leave_fn(prev_ownership_fn, prev_owned, prev_owned_types, prev_moved,
5381 prev_borrowed)
5382 }
5383 c.expected_type = expected_type
5384 c.generic_params = prev_generic_params
5385 c.fallback_vars = prev_fallback_vars.move()
5386 c.fn_root_scope = prev_fn_root_scope
5387 c.env.set_fn_scope(pending.module_name, pending.scope_fn_name, pending.scope)
5388 c.scope = prev_scope
5389 c.cur_file_module = prev_module
5390 }
5391 c.env.cur_generic_types = []map[string]Type{}
5392 return true
5393}
5394
5395fn generic_param_names_from_exprs(exprs []ast.Expr) []string {
5396 mut params := []string{}
5397 for gp in exprs {
5398 if gp is ast.Ident && gp.name !in params {
5399 params << gp.name
5400 } else if gp is ast.LifetimeExpr && '^' + gp.name !in params {
5401 params << '^' + gp.name
5402 }
5403 }
5404 return params
5405}
5406
5407fn collect_fn_generic_params(decl ast.FnDecl) []string {
5408 mut params := generic_param_names_from_exprs(decl.typ.generic_params)
5409 if !decl.is_method {
5410 return params
5411 }
5412 collect_receiver_generic_params(mut params, decl.receiver.typ)
5413 return params
5414}
5415
5416fn collect_receiver_generic_params(mut params []string, expr ast.Expr) {
5417 match expr {
5418 ast.GenericArgs {
5419 for arg in expr.args {
5420 if arg is ast.Ident && arg.name !in params {
5421 params << arg.name
5422 } else if arg is ast.LifetimeExpr && '^' + arg.name !in params {
5423 params << '^' + arg.name
5424 }
5425 }
5426 collect_receiver_generic_params(mut params, expr.lhs)
5427 }
5428 ast.PrefixExpr {
5429 collect_receiver_generic_params(mut params, expr.expr)
5430 }
5431 ast.Type {
5432 if expr is ast.PointerType {
5433 collect_receiver_generic_params(mut params, expr.base_type)
5434 }
5435 }
5436 else {}
5437 }
5438}
5439
5440// check_struct_field_defaults visits struct field default expressions after all
5441// function signatures and bodies are registered, so function calls in defaults resolve.
5442fn (mut c Checker) check_struct_field_defaults(files []ast.File) {
5443 for file in files {
5444 mut mod_scope := &Scope(unsafe { nil })
5445 lock c.env.scopes {
5446 if file.mod in c.env.scopes {
5447 mod_scope = unsafe { c.env.scopes[file.mod] }
5448 } else {
5449 continue
5450 }
5451 }
5452 c.scope = mod_scope
5453 c.cur_file_module = file.mod
5454 for stmt in file.stmts {
5455 if stmt is ast.StructDecl {
5456 for field in stmt.fields {
5457 if field.value !is ast.EmptyExpr {
5458 field_typ := c.type_expr(field.typ)
5459 prev_expected := c.expected_type
5460 c.expected_type = to_optional_type(field_typ)
5461 c.expr(field.value)
5462 $if ownership ? {
5463 c.ownership_consume_expr(field.value, field.value.pos(), 'struct field')
5464 }
5465 c.expected_type = prev_expected
5466 }
5467 }
5468 }
5469 }
5470 }
5471}
5472
5473// check_enum_field_values visits enum field value expressions.
5474fn (mut c Checker) check_enum_field_values(files []ast.File) {
5475 for file in files {
5476 mut mod_scope := &Scope(unsafe { nil })
5477 lock c.env.scopes {
5478 if file.mod in c.env.scopes {
5479 mod_scope = unsafe { c.env.scopes[file.mod] }
5480 } else {
5481 continue
5482 }
5483 }
5484 c.scope = mod_scope
5485 c.cur_file_module = file.mod
5486 for stmt in file.stmts {
5487 if stmt is ast.EnumDecl {
5488 for field in stmt.fields {
5489 if field.value !is ast.EmptyExpr {
5490 c.expr(field.value)
5491 }
5492 }
5493 }
5494 }
5495 }
5496}
5497
5498// check_final_default_exprs visits struct field defaults and enum values after
5499// function signatures and deferred function bodies are registered.
5500pub fn (mut c Checker) check_final_default_exprs(files []ast.File) {
5501 c.check_struct_field_defaults(files)
5502 c.check_enum_field_values(files)
5503}
5504
5505// take_deferred is kept for compatibility with parallel type-checking plumbing.
5506pub fn (mut c Checker) take_deferred() []Deferred {
5507 return []Deferred{}
5508}
5509
5510// add_deferred is kept for compatibility with parallel type-checking plumbing.
5511pub fn (mut c Checker) add_deferred(items []Deferred) {
5512 _ = items
5513}
5514
5515// process_struct_deferred is kept for compatibility with parallel type-checking plumbing.
5516pub fn (mut c Checker) process_struct_deferred() {
5517 c.process_pending_struct_decls()
5518 c.process_pending_type_decls()
5519 c.process_pending_interface_decls()
5520}
5521
5522// process_all_deferred is kept for compatibility with parallel type-checking plumbing.
5523pub fn (mut c Checker) process_all_deferred() {
5524 c.process_struct_deferred()
5525 c.process_pending_const_fields()
5526 $if ownership ? {
5527 c.ownership_prescan_fn_bodies()
5528 c.ownership_validate_drop_impls()
5529 }
5530 c.process_pending_fn_bodies()
5531}
5532
5533fn (mut c Checker) assign_stmt(stmt ast.AssignStmt, unwrap_optional bool) {
5534 for i, lx in stmt.lhs {
5535 // TODO: proper / tuple (handle multi return)
5536 mut rx := stmt.rhs[0]
5537 if i < stmt.rhs.len {
5538 rx = stmt.rhs[i]
5539 }
5540 // lhs_type := c.scope.lookup_parent
5541 // TODO: ident field for blank ident?
5542 mut is_blank_ident := false
5543 if lx is ast.Ident {
5544 is_blank_ident = lx.name == '_'
5545 }
5546 mut lhs_type := Type(void_)
5547 if stmt.op != .decl_assign && !is_blank_ident {
5548 lhs_type = c.expr(lx)
5549 }
5550 expected_type := c.expected_type
5551 mut enum_context_type := Type(void_)
5552 mut enum_context := Enum{}
5553 if lhs_type is Enum {
5554 enum_context = lhs_type as Enum
5555 enum_context_type = Type(lhs_type)
5556 c.expected_type = to_optional_type(Type(lhs_type))
5557 } else {
5558 lhs_base := lhs_type.base_type()
5559 if lhs_base is Enum {
5560 enum_context = lhs_base as Enum
5561 enum_context_type = lhs_type
5562 c.expected_type = to_optional_type(Type(lhs_base))
5563 }
5564 }
5565 rhs_type := if enum_context_type !is Void && rx is ast.SelectorExpr {
5566 sel := rx as ast.SelectorExpr
5567 if sel.lhs is ast.EmptyExpr {
5568 mut has_field := false
5569 for field in enum_context.fields {
5570 if field.name == sel.rhs.name {
5571 has_field = true
5572 break
5573 }
5574 }
5575 if has_field {
5576 enum_context_type
5577 } else {
5578 c.error_with_pos('enum `${enum_context.name}` has no value `${sel.rhs.name}`',
5579 sel.pos)
5580 Type(void_)
5581 }
5582 } else {
5583 c.expr(rx)
5584 }
5585 } else {
5586 c.expr(rx)
5587 }
5588 c.check_module_storage_assignment(lx, stmt.op, stmt.pos)
5589 if stmt.op != .decl_assign {
5590 c.check_module_mut_field_mutation(lx, stmt.pos)
5591 }
5592 // if t := expected_type {
5593 // c.log('AssignStmt: setting expected_type to: ${t.name()}')
5594 // } else {
5595 // c.log('AssignStmt: setting expected_type to: none')
5596 // }
5597 c.expected_type = expected_type
5598 // c.expected_type = none
5599 mut expr_type := rhs_type
5600 if unwrap_optional {
5601 expr_type = expr_type.unwrap()
5602 }
5603 if expr_type is Tuple {
5604 expr_type = expr_type.types[i]
5605 }
5606 if expr_type is Void {
5607 if fallback_type := c.sql_or_fallback_type(rx) {
5608 expr_type = fallback_type
5609 }
5610 }
5611 // promote untyped literals
5612 expr_type = expr_type.typed_default()
5613 // TODO: assignment check
5614 // if stmt.op != .decl_assign {
5615 // c.assignment(expr_type, lhs_type) or {
5616 // c.log('error!!')
5617 // c.error_with_pos(err.msg(), stmt.pos)
5618 // }
5619 // }
5620 // TODO: proper
5621 // TODO: modifiers, lx_unwrapped := c.unwrap...
5622 // or some method to use modifiers
5623 lx_unwrapped := c.unwrap_expr(lx)
5624 if lx_unwrapped is ast.Ident {
5625 value_obj := value_object_from_type(lx_unwrapped.name, expr_type)
5626 c.fallback_vars[lx_unwrapped.name] = expr_type
5627 if stmt.op == .decl_assign {
5628 // For := declarations, always overwrite any existing entry
5629 // (handles variable shadowing and re-declaration after scope exit)
5630 c.scope.insert_or_update(lx_unwrapped.name, value_obj)
5631 } else {
5632 // For = assignments, don't insert if variable exists in parent scope
5633 // (e.g., __global in module scope) to avoid shadowing with weaker type
5634 if _ := c.scope.lookup_parent(lx_unwrapped.name, 0) {
5635 // Already in scope chain — skip insert
5636 } else {
5637 c.scope.insert(lx_unwrapped.name, value_obj)
5638 }
5639 }
5640 // Also insert into function root scope for transformer type lookups.
5641 // This flattens nested scope variables into the function scope.
5642 if c.fn_root_scope != unsafe { nil } && !same_scope_ptr(c.fn_root_scope, c.scope) {
5643 if stmt.op == .decl_assign {
5644 c.fn_root_scope.insert_or_update(lx_unwrapped.name, value_obj)
5645 } else if _ := c.fn_root_scope.lookup_parent(lx_unwrapped.name, 0) {
5646 // Variable exists in scope chain — don't shadow in fn_root_scope
5647 } else {
5648 c.fn_root_scope.insert(lx_unwrapped.name, value_obj)
5649 }
5650 }
5651 c.update_receiver_generic_types(lx_unwrapped.name, rx, stmt.op != .decl_assign)
5652 // Store the type in expr_types for the lhs ident position
5653 // so the transformer can look it up directly
5654 lx_pos := lx_unwrapped.pos
5655 if lx_pos.is_valid() {
5656 c.env.set_expr_type(lx_pos.id, expr_type)
5657 }
5658 }
5659 // Also store the type for ModifierExpr-wrapped idents (e.g., `mut x := ...`)
5660 if lx is ast.ModifierExpr {
5661 lx_mod_pos := lx.pos
5662 if lx_mod_pos.is_valid() {
5663 c.env.set_expr_type(lx_mod_pos.id, expr_type)
5664 }
5665 }
5666 // Ownership: track owned variables and moves
5667 $if ownership ? {
5668 lhs_name := if lx_unwrapped is ast.Ident {
5669 lx_unwrapped.name
5670 } else {
5671 ''
5672 }
5673 if lhs_name.len > 0 && lhs_name != '_' {
5674 if stmt.op == .decl_assign {
5675 // 1. Match the `.to_owned()` / ownership-returning fn path
5676 // — the existing opt-in for `string` values.
5677 marked := c.ownership_mark_from_call(lhs_name, rx, stmt.pos)
5678 // 2. Standard move-on-assign for any already-owned RHS ident.
5679 if !marked {
5680 c.ownership_check_assign(lhs_name, rx, stmt.pos)
5681 }
5682 // 3. Copy/Owned trait: if the value's static type is
5683 // explicitly marked `implements Owned` and we didn't
5684 // already mark the LHS owned above, the LHS now owns
5685 // a fresh value (struct literal, call returning the
5686 // owned type, etc.). Borrows (`r := &foo`) are skipped
5687 // — they yield a pointer (Copy) regardless.
5688 // Drop types are also marked owned even when not Owned,
5689 // so the scope-exit drop call gets scheduled.
5690 if !marked && lhs_name !in c.owned_vars
5691 && (c.is_owned_type(expr_type) || c.is_drop_type(expr_type))
5692 && !ownership_is_borrow_or_ref_rhs(rx) {
5693 c.ownership_mark_owned(lhs_name, expr_type, stmt.pos)
5694 }
5695 } else if stmt.op == .assign {
5696 // Reassignment: check if LHS is currently borrowed
5697 c.ownership_check_reassign(lhs_name, stmt.pos)
5698 // Also check if new value is owned (via .to_owned() or fn return)
5699 marked := c.ownership_mark_from_call(lhs_name, rx, stmt.pos)
5700 if !marked && (c.is_owned_type(expr_type) || c.is_drop_type(expr_type))
5701 && !ownership_is_borrow_or_ref_rhs(rx) {
5702 c.ownership_mark_owned(lhs_name, expr_type, stmt.pos)
5703 }
5704 } else if stmt.op in [.left_shift, .left_shift_assign] {
5705 lhs_base := lhs_type.base_type()
5706 if lhs_type is Array || lhs_base is Array {
5707 c.ownership_consume_expr(rx, rx.pos(), 'array append')
5708 }
5709 }
5710 }
5711 }
5712 }
5713}
5714
5715fn (mut c Checker) sql_or_fallback_type(expr ast.Expr) ?Type {
5716 if expr !is ast.OrExpr {
5717 return none
5718 }
5719 or_expr := expr as ast.OrExpr
5720 if or_expr.expr !is ast.SqlExpr {
5721 return none
5722 }
5723 last_expr_idx := trailing_expr_stmt_index(or_expr.stmts)
5724 if last_expr_idx < 0 {
5725 return none
5726 }
5727 last_stmt := or_expr.stmts[last_expr_idx] as ast.ExprStmt
5728 fallback_type := c.expr(last_stmt.expr).unwrap()
5729 if fallback_type is Void || fallback_type is None {
5730 return none
5731 }
5732 return fallback_type
5733}
5734
5735// TODO:
5736// fn (mut c Checker) assignment(lx ast.Expr, typ Type) {
5737fn (mut c Checker) assignment(from_type Type, to_type Type) ! {
5738 // same type
5739 if same_type_name(from_type, to_type) {
5740 return
5741 }
5742 // numbers literals
5743 if from_type.is_number_literal() && to_type.is_number() {
5744 return
5745 }
5746 // aliases
5747 if from_type is Alias {
5748 return c.assignment(from_type.base_type, to_type)
5749 }
5750 if to_type is Alias {
5751 return c.assignment(from_type, to_type.base_type)
5752 }
5753 if to_type is Interface && c.type_satisfies_interface(from_type, to_type) {
5754 return
5755 }
5756 // TODO: provide context about if inside unsafe
5757 if to_type is FnType {
5758 // allow nil to fn pointer
5759 if from_type is Nil {
5760 return
5761 }
5762 }
5763 // pointers
5764 if to_type is Pointer {
5765 if from_type is Nil {
5766 return
5767 }
5768 if from_type.is_number() {
5769 return
5770 }
5771 // same
5772 if from_type is Pointer {
5773 // allow assigning &void to any pointer
5774 if from_type.base_type is Void {
5775 return
5776 }
5777 //
5778 if from_type.base_type.is_number() {
5779 return
5780 }
5781 // return c.assignment(from_type.base_type, to_type.base_type)!
5782 if same_type_name(from_type.base_type, to_type.base_type) {
5783 return
5784 }
5785 }
5786 }
5787 if to_type.is_number() {
5788 // for now all all numvers to be compatible
5789 if from_type.is_number() {
5790 return
5791 }
5792 // TODO: since char is currently its own type, should this be changed?
5793 if from_type is Char {
5794 return
5795 }
5796 }
5797 // // dump(from_type)
5798 // // dump(to_type)
5799 return error('cannot assign `${from_type.name()}` to `${to_type.name()}`')
5800}
5801
5802// fn (mut c Checker) implicit_type(from_type Type, to_type Type) !Type {
5803// if from_type.is_number_literal() && to_type.is_numver() {
5804// return to_type
5805// }
5806// }
5807
5808fn (mut c Checker) block(_stmts []ast.Stmt) {
5809}
5810
5811fn (mut c Checker) apply_smartcast(sc_name_ ast.Expr, sc_type Type) {
5812 sc_name := c.unwrap_ident(sc_name_)
5813 if sc_name is ast.Ident {
5814 // println('added smartcast for ${sc_name.name} to ${sc_type.name()}')
5815 c.scope.insert_or_update(sc_name.name, object_from_type(sc_type))
5816 } else if sc_name is ast.SelectorExpr {
5817 // field := c.selector_expr(sc_name)
5818 // if sc_name.lhs is ast.Ident {
5819 // field := c.find_field_or_method()
5820 // c.scope.insert(sc_name.lhs.name, SmartCastSelector{origin: field, field: sc_name.rhs.name, cast_type: sc_type})
5821 // }
5822 // c.log('@@ selector smartcast: ${sc_name.name()} - ${field.type_name()}')
5823 // println('added smartcast for ${sc_name.name()} to ${sc_type.name()}')
5824 smartcast_name := c.smartcast_selector_name(sc_name)
5825 if smartcast_name != '' {
5826 c.scope.field_smartcasts[smartcast_name] = sc_type
5827 }
5828 } else {
5829 smartcast_name := c.smartcast_expr_name(sc_name)
5830 if smartcast_name != '' {
5831 c.scope.field_smartcasts[smartcast_name] = sc_type
5832 }
5833 }
5834}
5835
5836fn (c &Checker) smartcast_expr_name(expr ast.Expr) string {
5837 if expr is ast.ParenExpr {
5838 return c.smartcast_expr_name(expr.expr)
5839 }
5840 if expr is ast.AsCastExpr {
5841 return c.smartcast_expr_name(expr.expr)
5842 }
5843 if expr is ast.SelectorExpr {
5844 lhs_name := c.smartcast_expr_name(expr.lhs)
5845 if lhs_name != '' {
5846 return '${lhs_name}.${expr.rhs.name}'
5847 }
5848 if expr.pos.is_valid() {
5849 if full_name := c.env.selector_names[expr.pos.id] {
5850 return full_name
5851 }
5852 }
5853 parts := selector_expr_parts(expr)
5854 if parts.len > 0 {
5855 return parts.join('.')
5856 }
5857 fallback_name := expr.name()
5858 if fallback_name != 'Expr' {
5859 return fallback_name
5860 }
5861 }
5862 match expr {
5863 ast.AsCastExpr {
5864 return c.smartcast_expr_name(expr.expr)
5865 }
5866 ast.CallExpr, ast.CallOrCastExpr, ast.IndexExpr {
5867 fallback_name := ast.Expr(expr).name()
5868 if fallback_name != 'Expr' {
5869 return fallback_name
5870 }
5871 }
5872 else {}
5873 }
5874
5875 mut parts := []string{}
5876 mut cur := expr
5877 for {
5878 match cur {
5879 ast.Ident {
5880 parts << cur.name
5881 break
5882 }
5883 ast.SelectorExpr {
5884 if cur.pos.is_valid() {
5885 if full_name := c.env.selector_names[cur.pos.id] {
5886 parts << full_name
5887 break
5888 }
5889 }
5890 return ''
5891 }
5892 ast.ParenExpr {
5893 cur = cur.expr
5894 }
5895 else {
5896 return ''
5897 }
5898 }
5899 }
5900 mut out := ''
5901 for i := parts.len - 1; i >= 0; i-- {
5902 if out.len > 0 {
5903 out += '.'
5904 }
5905 out += parts[i]
5906 }
5907 return out
5908}
5909
5910fn (c &Checker) smartcast_selector_name(expr ast.SelectorExpr) string {
5911 if expr.pos.is_valid() {
5912 if full_name := c.env.selector_names[expr.pos.id] {
5913 return full_name
5914 }
5915 }
5916 return c.smartcast_expr_name(expr)
5917}
5918
5919fn (mut c Checker) smartcasted_selector_type(expr ast.SelectorExpr) ?Type {
5920 parts := selector_expr_parts(expr)
5921 if parts.len < 2 {
5922 return none
5923 }
5924 expecting_method := c.expecting_method
5925 for prefix_len := parts.len - 1; prefix_len > 0; prefix_len-- {
5926 prefix_name := parts[..prefix_len].join('.')
5927 mut typ := c.scope.lookup_field_smartcast(prefix_name) or { continue }
5928 mut ok := true
5929 for i := prefix_len; i < parts.len; i++ {
5930 c.expecting_method = expecting_method && i == parts.len - 1
5931 if next_type := c.find_field_or_method(typ, parts[i]) {
5932 typ = next_type
5933 } else {
5934 ok = false
5935 break
5936 }
5937 }
5938 c.expecting_method = expecting_method
5939 if ok {
5940 return typ
5941 }
5942 }
5943 c.expecting_method = expecting_method
5944 return none
5945}
5946
5947fn (mut c Checker) expr_type_without_field_smartcast(expr ast.Expr) ?Type {
5948 return c.expr_type_without_field_smartcast_inner(expr, true)
5949}
5950
5951fn (mut c Checker) expr_type_without_field_smartcast_inner(expr ast.Expr, ignore_current_smartcast bool) ?Type {
5952 unwrapped := c.unwrap_expr(expr)
5953 if !ignore_current_smartcast {
5954 smartcast_name := c.smartcast_expr_name(unwrapped)
5955 if smartcast_name != '' {
5956 if cast_type := c.scope.lookup_field_smartcast(smartcast_name) {
5957 return cast_type
5958 }
5959 }
5960 }
5961 match unwrapped {
5962 ast.Ident {
5963 if ignore_current_smartcast {
5964 if original_type := c.fallback_vars[unwrapped.name] {
5965 return original_type
5966 }
5967 }
5968 return c.ident(unwrapped).typ()
5969 }
5970 ast.SelectorExpr {
5971 lhs_type := c.expr_type_without_field_smartcast_inner(unwrapped.lhs, false) or {
5972 return none
5973 }
5974 expecting_method := c.expecting_method
5975 c.expecting_method = false
5976 field_type := c.find_field_or_method(lhs_type, unwrapped.rhs.name) or {
5977 c.expecting_method = expecting_method
5978 return none
5979 }
5980 c.expecting_method = expecting_method
5981 return field_type
5982 }
5983 ast.IndexExpr {
5984 lhs_type := c.expr_type_without_field_smartcast_inner(unwrapped.lhs, false) or {
5985 return none
5986 }
5987 return c.index_expr_result_type(lhs_type, unwrapped)
5988 }
5989 else {
5990 return none
5991 }
5992 }
5993}
5994
5995fn (mut c Checker) apply_smartcasts(sc_names []ast.Expr, sc_types []Type) {
5996 for i, sc_name in sc_names {
5997 c.apply_smartcast(sc_name, sc_types[i])
5998 }
5999}
6000
6001fn (mut c Checker) extract_smartcasts_from_false_condition(expr ast.Expr) ([]ast.Expr, []Type) {
6002 mut names := []ast.Expr{}
6003 mut types := []Type{}
6004 expr_u := c.unwrap_expr(expr)
6005 if expr_u is ast.InfixExpr {
6006 if expr_u.op == .not_is {
6007 names << expr_u.lhs
6008 types << c.expr(c.unwrap_expr(expr_u.rhs))
6009 } else if expr_u.op == .logical_or {
6010 lhs_names, lhs_types := c.extract_smartcasts_from_false_condition(expr_u.lhs)
6011 rhs_names, rhs_types := c.extract_smartcasts_from_false_condition(expr_u.rhs)
6012 names << lhs_names
6013 types << lhs_types
6014 names << rhs_names
6015 types << rhs_types
6016 }
6017 } else if expr_u is ast.PrefixExpr && expr_u.op == .not {
6018 inner := c.unwrap_expr(expr_u.expr)
6019 if inner is ast.InfixExpr && inner.op == .key_is {
6020 names << inner.lhs
6021 types << c.expr(c.unwrap_expr(inner.rhs))
6022 }
6023 }
6024 return names, types
6025}
6026
6027fn (mut c Checker) extract_smartcasts(expr ast.Expr) ([]ast.Expr, []Type) {
6028 mut names := []ast.Expr{}
6029 mut types := []Type{}
6030 expr_u := c.unwrap_expr(expr)
6031 if expr_u is ast.InfixExpr {
6032 if expr_u.op == .key_is {
6033 // eprintln('adding smartcast')
6034 names << expr_u.lhs
6035 types << c.expr(c.unwrap_expr(expr_u.rhs))
6036 // typ := c.expr(expr_u.rhs)
6037 // types << typ
6038 } else if expr_u.op == .and {
6039 lhs_names, lhs_types := c.extract_smartcasts(c.unwrap_expr(expr_u.lhs))
6040 rhs_names, rhs_types := c.extract_smartcasts(c.unwrap_expr(expr_u.rhs))
6041 names << lhs_names
6042 types << lhs_types
6043 names << rhs_names
6044 types << rhs_types
6045 }
6046 }
6047 // else if cond is ast.ParenExpr {
6048 // names2, types2 := c.extract_smartcasts(cond.expr)
6049 // names << names2
6050 // types << types2
6051 // }
6052 return names, types
6053}
6054
6055fn (mut c Checker) fn_decl(decl ast.FnDecl) {
6056 // c.log('ast.FnDecl: ${decl.name}: ${c.file.name}')
6057 // c.log('return type:')
6058 // c.expr(decl.typ.return_type)
6059 mut prev_scope := c.scope
6060 c.open_scope()
6061 prev_generic_params := c.generic_params
6062 decl_generic_params := collect_fn_generic_params(decl)
6063 for gp_name in decl_generic_params {
6064 c.scope.insert(gp_name, Type(NamedType(gp_name)))
6065 }
6066 if decl_generic_params.len > 0 {
6067 c.generic_params = decl_generic_params
6068 }
6069 mut fn_typ := c.fn_type_with_insert_params(decl.typ,
6070 FnTypeAttribute.from_ast_attributes(decl.attributes), true)
6071 c.generic_params = prev_generic_params
6072 mut typ := Type(fn_typ)
6073 // Heap-allocate Fn so pointer stored in env.methods remains valid
6074 mut obj := &Fn{
6075 name: decl.name
6076 typ: Type(void_)
6077 }
6078 obj.typ = typ
6079 // TODO:
6080 if decl.is_method {
6081 mut receiver_type := c.type_expr(decl.receiver.typ)
6082 if decl.receiver.is_mut {
6083 fn_typ.is_mut_receiver = true
6084 typ = Type(fn_typ)
6085 obj.typ = typ
6086 if mut receiver_type is Pointer {
6087 c.error_with_pos('use `mut Type` not `mut &Type`. TODO: proper error message',
6088 decl.receiver.pos)
6089 }
6090 receiver_type = Type(Pointer{
6091 base_type: receiver_type
6092 })
6093 }
6094
6095 c.scope.insert(decl.receiver.name, object_from_type(receiver_type))
6096 c.fallback_vars[decl.receiver.name] = receiver_type
6097 // TODO: interface methods
6098 receiver_base_type := receiver_type.base_type()
6099 method_owner_type := if receiver_type is Pointer {
6100 receiver_base_type
6101 } else {
6102 receiver_type
6103 }
6104 // Register method in shared methods map (safe inside lock)
6105 method_type_name := method_owner_type.name()
6106 lock c.env.methods {
6107 mut methods_for_type := []&Fn{}
6108 found_in_map := method_type_name in c.env.methods
6109 if found_in_map {
6110 methods_for_type = unsafe { c.env.methods[method_type_name] }
6111 }
6112 methods_for_type << obj
6113 c.env.methods[method_type_name] = methods_for_type
6114 }
6115 c.log('registering method: ${decl.name} for ${receiver_type.name()} - ${method_owner_type.name()} - ${receiver_base_type.name()}')
6116 // Note: receiver was already inserted at line 1515 with correct type (including Pointer for mut)
6117 } else {
6118 if decl.language == .c {
6119 c.c_scope.insert(decl.name, *obj)
6120 } else {
6121 prev_scope.insert(decl.name, *obj)
6122 }
6123 }
6124 c.maybe_insert_implicit_veb_context(fn_typ)
6125 fn_scope := c.scope
6126 scope_fn_name := if decl.is_method {
6127 // Get receiver type name for method scope key.
6128 // Keep alias receiver names (e.g. `Builder`) instead of unwrapping to base
6129 // array/map types, otherwise transformer cannot retrieve method scopes.
6130 // Strip module prefix since set_fn_scope already prepends the module.
6131 mut receiver_type := c.type_expr(decl.receiver.typ)
6132 mut recv_scope_type := receiver_type
6133 if receiver_type is Pointer {
6134 recv_scope_type = (receiver_type as Pointer).base_type
6135 }
6136 mut recv_name := if recv_scope_type is Alias {
6137 recv_scope_type.name()
6138 } else {
6139 bt := recv_scope_type.base_type()
6140 bt.name()
6141 }
6142 if c.cur_file_module != '' {
6143 prefix := '${c.cur_file_module}__'
6144 if recv_name.starts_with(prefix) {
6145 recv_name = recv_name[prefix.len..]
6146 }
6147 }
6148 '${recv_name}__${decl.name}'
6149 } else {
6150 decl.name
6151 }
6152 module_name := c.cur_file_module
6153 pending := PendingFnBody{
6154 scope: fn_scope
6155 decl: decl
6156 typ: fn_typ
6157 scope_fn_name: scope_fn_name
6158 module_name: module_name
6159 flat: c.pending_fn_body_flat
6160 flat_decl_id: c.pending_fn_body_flat_id
6161 }
6162 if c.collect_fn_signatures_only {
6163 c.pending_fn_bodies << pending
6164 } else {
6165 c.check_pending_fn_body(pending)
6166 }
6167 c.close_scope()
6168}
6169
6170fn (mut c Checker) maybe_insert_implicit_veb_context(fn_typ FnType) {
6171 if _ := c.scope.lookup('ctx') {
6172 return
6173 }
6174 ret_type := fn_typ.return_type or { return }
6175 if ret_type.name() !in ['veb__Result', 'veb.Result', 'Result'] {
6176 return
6177 }
6178 ctx_type := c.lookup_type_in_scope_chain('Context') or { return }
6179 c.scope.insert('ctx', object_from_type(Type(Pointer{
6180 base_type: ctx_type
6181 })))
6182}
6183
6184// eval_comptime_cond evaluates a compile-time condition expression
6185fn (c &Checker) eval_comptime_cond(cond ast.Expr) bool {
6186 match cond {
6187 ast.Ident {
6188 return c.eval_comptime_flag(cond.name)
6189 }
6190 ast.PrefixExpr {
6191 if cond.op == .not {
6192 return !c.eval_comptime_cond(cond.expr)
6193 }
6194 }
6195 ast.InfixExpr {
6196 if cond.op == .and {
6197 return c.eval_comptime_cond(cond.lhs) && c.eval_comptime_cond(cond.rhs)
6198 }
6199 if cond.op == .logical_or {
6200 return c.eval_comptime_cond(cond.lhs) || c.eval_comptime_cond(cond.rhs)
6201 }
6202 }
6203 ast.PostfixExpr {
6204 if cond.op == .question {
6205 if cond.expr is ast.Ident {
6206 return pref.comptime_optional_flag_value(c.pref, cond.expr.name)
6207 }
6208 }
6209 }
6210 ast.ParenExpr {
6211 return c.eval_comptime_cond(cond.expr)
6212 }
6213 else {}
6214 }
6215
6216 return false
6217}
6218
6219// eval_comptime_flag evaluates a single comptime flag/identifier
6220fn (c &Checker) eval_comptime_flag(name string) bool {
6221 return pref.comptime_flag_value(c.pref, name)
6222}
6223
6224fn (mut c Checker) comptime_stmt_list_type(stmts []ast.Stmt) Type {
6225 last_expr_idx := trailing_expr_stmt_index(stmts)
6226 if last_expr_idx >= 0 {
6227 if last_expr_idx > 0 {
6228 c.stmt_list(stmts[..last_expr_idx])
6229 }
6230 last_stmt := stmts[last_expr_idx] as ast.ExprStmt
6231 return c.expr(last_stmt.expr)
6232 }
6233 c.stmt_list(stmts)
6234 return Type(void_)
6235}
6236
6237// comptime_if_else checks a compile-time $if/$else expression,
6238// only processing the branch that matches the current platform
6239fn (mut c Checker) comptime_if_else(node ast.IfExpr) Type {
6240 if c.eval_comptime_cond(node.cond) {
6241 // Condition is true - check the then branch
6242 return c.comptime_stmt_list_type(node.stmts)
6243 } else {
6244 // Condition is false - check the else branch
6245 match node.else_expr {
6246 ast.IfExpr {
6247 // Check if this is a plain $else block (empty condition) or chained $else $if
6248 if node.else_expr.cond is ast.EmptyExpr {
6249 // Plain $else { ... } - statements are in the IfExpr.stmts
6250 return c.comptime_stmt_list_type(node.else_expr.stmts)
6251 } else {
6252 // Chained $else $if - check recursively
6253 return c.comptime_if_else(node.else_expr)
6254 }
6255 }
6256 ast.EmptyExpr {
6257 // No else branch
6258 }
6259 else {
6260 // Other expression types - just evaluate
6261 return c.expr(node.else_expr)
6262 }
6263 }
6264 }
6265 return Type(void_)
6266}
6267
6268fn (mut c Checker) if_expr(expr ast.IfExpr) Type {
6269 c.open_scope()
6270 // BIG MESS peanut head! Fix this :)
6271 // TODO: this probably is not the best way to do this
6272 // need to think about this some more. also should this only
6273 // work for the top level? how complicated can this get? eiip
6274 // NOTE: in the current compiler there are smartcasts and then there
6275 // are explicit auto casts. I have inplemeted this just using smartcasts
6276 // for now, however I will come back to this, and work out what is most appropriate
6277 mut cond_type := Type(void_)
6278 mut is_inline_smartcast := false
6279 cond := c.unwrap_expr(expr.cond)
6280 if cond is ast.InfixExpr && cond.op == .and {
6281 cond_type = Type(bool_)
6282 c.logical_and_expr_part(cond)
6283 is_inline_smartcast = true
6284 } else if cond is ast.InfixExpr {
6285 cond_type = Type(bool_)
6286 mut left_node := c.unwrap_expr(cond.lhs)
6287 for {
6288 if left_node !is ast.InfixExpr {
6289 break
6290 }
6291 left_infix := left_node as ast.InfixExpr
6292 left_node_rhs := c.unwrap_expr(left_infix.rhs)
6293 if left_infix.op == .and && left_node_rhs is ast.InfixExpr {
6294 if left_node_rhs.op == .key_is {
6295 is_inline_smartcast = true
6296 // c.expr(left_node.rhs)
6297 sc_names, sc_types := c.extract_smartcasts(left_infix.rhs)
6298 c.apply_smartcasts(sc_names, sc_types)
6299 // c.expr(left_node.lhs)
6300 }
6301 }
6302 if left_infix.op == .key_is {
6303 is_inline_smartcast = true
6304 // c.expr(left_node.lhs)
6305 sc_names, sc_types := c.extract_smartcasts(left_node)
6306 c.apply_smartcasts(sc_names, sc_types)
6307 // c.expr(left_node.rhs)
6308 break
6309 } else if left_infix.op == .and {
6310 left_node = c.unwrap_expr(left_infix.lhs)
6311 } else {
6312 break
6313 }
6314 }
6315 right_node := c.unwrap_expr(cond.rhs)
6316 if right_node is ast.InfixExpr && right_node.op == .key_is {
6317 is_inline_smartcast = true
6318 // c.expr(right_node.lhs)
6319 sc_names, sc_types := c.extract_smartcasts(right_node)
6320 c.apply_smartcasts(sc_names, sc_types)
6321 // c.expr(right_node.rhs)
6322 }
6323 }
6324 if !is_inline_smartcast {
6325 // println('## not is_inline_smartcast')
6326 cond_type = c.expr(expr.cond)
6327 sc_names, sc_types := c.extract_smartcasts(expr.cond)
6328 c.apply_smartcasts(sc_names, sc_types)
6329 }
6330 _ = cond_type
6331 mut ownership_before_owned := map[string]token.Pos{}
6332 mut ownership_before_moved := map[string]MovedVar{}
6333 mut ownership_before_borrowed := map[string][]BorrowInfo{}
6334 $if ownership ? {
6335 ownership_before_owned = c.owned_vars.clone()
6336 ownership_before_moved = c.moved_vars.clone()
6337 ownership_before_borrowed = c.borrowed_vars.clone()
6338 }
6339 c.stmt_list(expr.stmts)
6340 mut typ := Type(void_)
6341 last_expr_idx := trailing_expr_stmt_index(expr.stmts)
6342 if last_expr_idx >= 0 {
6343 last_stmt := expr.stmts[last_expr_idx] as ast.ExprStmt
6344 typ = c.expr(last_stmt.expr)
6345 }
6346 mut ownership_then_owned := map[string]token.Pos{}
6347 mut ownership_then_moved := map[string]MovedVar{}
6348 mut ownership_then_borrowed := map[string][]BorrowInfo{}
6349 mut ownership_then_returns := false
6350 $if ownership ? {
6351 ownership_then_owned = c.owned_vars.clone()
6352 ownership_then_moved = c.moved_vars.clone()
6353 ownership_then_borrowed = c.borrowed_vars.clone()
6354 ownership_then_returns = ownership_stmts_terminate(expr.stmts)
6355 c.ownership_restore_state(ownership_before_owned, ownership_before_moved,
6356 ownership_before_borrowed)
6357 }
6358 c.close_scope()
6359 // return typ
6360 expected_type_before_else := c.expected_type
6361 if typ !is Void {
6362 c.expected_type = to_optional_type(typ)
6363 }
6364 else_type := if expr.else_expr !is ast.EmptyExpr { c.expr(expr.else_expr) } else { typ }
6365 c.expected_type = expected_type_before_else
6366 $if ownership ? {
6367 if expr.else_expr !is ast.EmptyExpr {
6368 c.ownership_merge_if_state(ownership_before_owned, ownership_before_moved,
6369 ownership_before_borrowed, ownership_then_owned, ownership_then_moved,
6370 ownership_then_borrowed, ownership_then_returns, true, c.owned_vars.clone(),
6371 c.moved_vars.clone(), c.borrowed_vars.clone(),
6372 ownership_expr_terminates(expr.else_expr))
6373 } else {
6374 c.ownership_merge_if_state(ownership_before_owned, ownership_before_moved,
6375 ownership_before_borrowed, ownership_then_owned, ownership_then_moved,
6376 ownership_then_borrowed, ownership_then_returns, false, map[string]token.Pos{},
6377 map[string]MovedVar{}, map[string][]BorrowInfo{}, false)
6378 }
6379 }
6380 if else_type is Void && typ !is Void {
6381 return typ
6382 }
6383 if typ is Void {
6384 return else_type
6385 }
6386 return else_type
6387 // TODO: check all branches types match
6388}
6389
6390fn (mut c Checker) match_expr(expr ast.MatchExpr, used_as_expr bool) Type {
6391 expr_type := c.expr(expr.expr)
6392 expected_type := c.expected_type
6393 if expr_type is Enum {
6394 c.expected_type = to_optional_type(Type(expr_type))
6395 } else {
6396 expr_base := expr_type.base_type()
6397 if expr_base is Enum {
6398 c.expected_type = to_optional_type(Type(expr_base))
6399 }
6400 }
6401 // Ownership: snapshot before any arm runs so each arm gets independent
6402 // per-arm consumption tracking — a move inside arm 0 must not bleed into
6403 // arm 1's view. Mirrors the if/else snapshot pattern in if_expr above.
6404 mut ownership_before_owned := map[string]token.Pos{}
6405 mut ownership_before_moved := map[string]MovedVar{}
6406 mut ownership_before_borrowed := map[string][]BorrowInfo{}
6407 mut ownership_arms := []OwnershipArmState{}
6408 $if ownership ? {
6409 ownership_before_owned = c.owned_vars.clone()
6410 ownership_before_moved = c.moved_vars.clone()
6411 ownership_before_borrowed = c.borrowed_vars.clone()
6412 }
6413 mut last_stmt_type := Type(void_)
6414 for _, branch in expr.branches {
6415 $if ownership ? {
6416 c.ownership_restore_state(ownership_before_owned, ownership_before_moved,
6417 ownership_before_borrowed)
6418 }
6419 c.open_scope()
6420 for cond in branch.cond {
6421 expr_unwrapped := c.unwrap_ident(expr.expr)
6422 cond_type := c.expr(cond)
6423 if c.match_branch_cond_smartcasts(cond, cond_type) {
6424 c.apply_smartcast(expr_unwrapped, cond_type)
6425 }
6426 }
6427 c.stmt_list(branch.stmts)
6428 // mut is_noreturn := false
6429 if used_as_expr {
6430 last_expr_idx := trailing_expr_stmt_index(branch.stmts)
6431 if last_expr_idx >= 0 {
6432 last_stmt := branch.stmts[last_expr_idx] as ast.ExprStmt
6433 t := c.expr(last_stmt.expr)
6434 // TODO: non else branch can only return void if its a noreturn
6435 // if last_stmt is ast.ExprStmt {
6436 // if last_stmt.expr is ast.CallExpr {
6437 // lhs := c.expr(last_stmt.expr.lhs)
6438 // if lhs is FnType {
6439 // is_noreturn = lhs.attributes.has(.noreturn)
6440 // }
6441 // }
6442 // }
6443 // TODO: fix last branch / void expr
6444 // actually make sure its an else branch
6445 if _ := expected_type {
6446 // TODO: re-enable type checking for match branches when v2 handles sum types better
6447 // Currently disabled because v2 checker doesn't understand sum type compatibility
6448 last_stmt_type = t
6449 }
6450 }
6451 }
6452 $if ownership ? {
6453 ownership_arms << OwnershipArmState{
6454 owned: c.owned_vars.clone()
6455 moved: c.moved_vars.clone()
6456 borrowed: c.borrowed_vars.clone()
6457 terminates: ownership_stmts_terminate(branch.stmts)
6458 }
6459 }
6460 c.close_scope()
6461 }
6462 $if ownership ? {
6463 c.ownership_merge_match_state(ownership_before_owned, ownership_before_moved,
6464 ownership_before_borrowed, ownership_arms)
6465 }
6466 c.expected_type = expected_type
6467 return last_stmt_type
6468}
6469
6470fn match_branch_generic_cond_type_is_smartcastable(typ Type) bool {
6471 return match typ {
6472 Alias, Struct, SumType {
6473 true
6474 }
6475 else {
6476 false
6477 }
6478 }
6479}
6480
6481fn (mut c Checker) match_branch_generic_ident_is_type_name(name string) bool {
6482 if builtin_type(name) != none {
6483 return true
6484 }
6485 if obj := universe.lookup_parent(name, 0) {
6486 if _ := object_as_type(obj) {
6487 return true
6488 }
6489 }
6490 if _ := c.lookup_type_in_scope_chain(name) {
6491 return true
6492 }
6493 if _ := c.lookup_type_in_imported_modules(name) {
6494 return true
6495 }
6496 return false
6497}
6498
6499fn (c &Checker) match_branch_generic_ident_is_active_param(name string) bool {
6500 if name in c.generic_params {
6501 return true
6502 }
6503 for generic_types in c.env.cur_generic_types {
6504 if name in generic_types {
6505 return true
6506 }
6507 }
6508 return false
6509}
6510
6511fn (mut c Checker) match_branch_generic_selector_is_type(expr ast.SelectorExpr) bool {
6512 parts := selector_expr_parts(expr)
6513 if parts.len < 2 {
6514 return false
6515 }
6516 module_alias := parts[parts.len - 2]
6517 selector_type_name := parts[parts.len - 1]
6518 if _ := c.lookup_type_in_module(module_alias, selector_type_name) {
6519 return true
6520 }
6521 return false
6522}
6523
6524fn (mut c Checker) match_branch_generic_arg_is_type_pattern(expr ast.Expr) bool {
6525 match expr {
6526 ast.Ident {
6527 return c.match_branch_generic_ident_is_type_name(expr.name)
6528 || c.match_branch_generic_ident_is_active_param(expr.name)
6529 }
6530 ast.SelectorExpr {
6531 return c.match_branch_generic_selector_is_type(expr)
6532 }
6533 ast.Type {
6534 return true
6535 }
6536 ast.GenericArgs {
6537 return c.match_branch_generic_args_is_type_pattern(expr)
6538 }
6539 ast.LifetimeExpr {
6540 return true
6541 }
6542 else {
6543 return false
6544 }
6545 }
6546}
6547
6548fn (mut c Checker) match_branch_generic_args_is_type_pattern(expr ast.GenericArgs) bool {
6549 if !c.match_branch_generic_arg_is_type_pattern(expr.lhs) {
6550 return false
6551 }
6552 for arg in expr.args {
6553 if !c.match_branch_generic_arg_is_type_pattern(arg) {
6554 return false
6555 }
6556 }
6557 return true
6558}
6559
6560fn (mut c Checker) match_branch_cond_smartcasts(cond ast.Expr, cond_type Type) bool {
6561 resolved := c.resolve_expr(cond)
6562 return match resolved {
6563 ast.Ident {
6564 true
6565 }
6566 ast.SelectorExpr {
6567 // `.value` enum value (without type)
6568 resolved.lhs !is ast.EmptyExpr
6569 }
6570 ast.Type {
6571 true
6572 }
6573 ast.GenericArgs {
6574 match_branch_generic_cond_type_is_smartcastable(cond_type)
6575 && c.match_branch_generic_args_is_type_pattern(resolved)
6576 }
6577 else {
6578 false
6579 }
6580 }
6581}
6582
6583// TODO: unwrap ModifierExpr etc
6584// fn (mut c Checker) argument() ast.Expr {}
6585
6586fn (mut c Checker) resolve_generic_arg_or_index_expr(expr ast.GenericArgOrIndexExpr) ast.Expr {
6587 mut lhs_type := c.expr(expr.lhs)
6588 // expr_type := c.expr(expr.expr)
6589 if c.is_callable_type(lhs_type) || c.is_generic_struct_type(lhs_type) {
6590 return ast.GenericArgs{
6591 lhs: expr.lhs
6592 args: [expr.expr]
6593 }
6594 } else {
6595 // c.unwrap_lhs_expr(ast.IndexExpr{lhs: expr.lhs, expr: expr.expr})
6596 return ast.IndexExpr{
6597 lhs: expr.lhs
6598 expr: expr.expr
6599 }
6600 }
6601}
6602
6603fn (c &Checker) generic_struct_template(t Type) ?Struct {
6604 mut cur := resolve_alias(t)
6605 if cur is Struct {
6606 st := cur as Struct
6607 if st.generic_params.len > 0 {
6608 return st
6609 }
6610 }
6611 return none
6612}
6613
6614fn (c &Checker) is_generic_struct_type(t Type) bool {
6615 return c.generic_struct_template(t) != none
6616}
6617
6618fn receiver_generic_type_key(scope &Scope, name string) string {
6619 return '${voidptr(scope)}:${name}'
6620}
6621
6622fn (mut c Checker) generic_type_map_from_init_expr(expr ast.Expr) ?map[string]Type {
6623 resolved := c.resolve_expr(expr)
6624 match resolved {
6625 ast.InitExpr {
6626 return c.generic_type_map_from_type_ref(resolved.typ)
6627 }
6628 ast.AssocExpr {
6629 return c.generic_type_map_from_type_ref(resolved.typ)
6630 }
6631 ast.ArrayInitExpr {
6632 if resolved.typ !is ast.EmptyExpr {
6633 return c.generic_type_map_from_type_ref(resolved.typ)
6634 }
6635 }
6636 ast.CastExpr {
6637 return c.generic_type_map_from_type_ref(resolved.typ)
6638 }
6639 ast.CallOrCastExpr {
6640 if generic_map := c.generic_type_map_from_type_ref(resolved.lhs) {
6641 return generic_map
6642 }
6643 call_or_cast := c.resolve_call_or_cast_expr(resolved)
6644 if call_or_cast is ast.CastExpr {
6645 return c.generic_type_map_from_type_ref(call_or_cast.typ)
6646 }
6647 }
6648 else {}
6649 }
6650
6651 return none
6652}
6653
6654fn (mut c Checker) generic_param_names_from_type_ref(lhs ast.Expr, base_type Type) []string {
6655 if base := c.generic_struct_template(base_type) {
6656 return base.generic_params.clone()
6657 }
6658 mut keys := []string{}
6659 base_type_name := base_type.name()
6660 if base_type_name != '' {
6661 keys << base_type_name
6662 }
6663 lhs_name := lhs.name()
6664 if lhs_name != '' {
6665 keys << lhs_name
6666 qualified_lhs_name := c.qualify_type_name(lhs_name)
6667 if qualified_lhs_name != lhs_name {
6668 keys << qualified_lhs_name
6669 }
6670 }
6671 for key in keys {
6672 if params := c.generic_type_params[key] {
6673 return params.clone()
6674 }
6675 }
6676 return []string{}
6677}
6678
6679fn (mut c Checker) generic_type_map_from_generic_type_parts(lhs ast.Expr, args []ast.Expr) ?map[string]Type {
6680 base_type := c.type_expr(lhs)
6681 mut generic_type_map := map[string]Type{}
6682 mut arg_types := []Type{cap: args.len}
6683 for arg in args {
6684 arg_types << c.expr(arg)
6685 }
6686 generic_params := c.generic_param_names_from_type_ref(lhs, base_type)
6687 for i, generic_param in generic_params {
6688 if i >= arg_types.len {
6689 break
6690 }
6691 generic_type_map[generic_param] = arg_types[i]
6692 }
6693 if generic_type_map.len > 0 {
6694 return generic_type_map
6695 }
6696 return none
6697}
6698
6699fn (mut c Checker) generic_type_map_from_type_ref(expr ast.Expr) ?map[string]Type {
6700 match expr {
6701 ast.GenericArgs {
6702 return c.generic_type_map_from_generic_type_parts(expr.lhs, expr.args)
6703 }
6704 ast.GenericArgOrIndexExpr {
6705 return c.generic_type_map_from_generic_type_parts(expr.lhs, [expr.expr])
6706 }
6707 ast.Type {
6708 if expr is ast.GenericType {
6709 return c.generic_type_map_from_generic_type_parts(expr.name, expr.params)
6710 }
6711 }
6712 else {}
6713 }
6714
6715 resolved := c.resolve_expr(expr)
6716 match resolved {
6717 ast.GenericArgs {
6718 return c.generic_type_map_from_generic_type_parts(resolved.lhs, resolved.args)
6719 }
6720 ast.Type {
6721 if resolved is ast.GenericType {
6722 return c.generic_type_map_from_generic_type_parts(resolved.name, resolved.params)
6723 }
6724 }
6725 else {}
6726 }
6727
6728 return none
6729}
6730
6731fn (mut c Checker) update_receiver_generic_types(name string, rhs ast.Expr, preserve_existing bool) {
6732 receiver_scope, _ := c.scope.lookup_parent_with_scope(name, 0) or { return }
6733 key := receiver_generic_type_key(receiver_scope, name)
6734 if generic_type_map := c.generic_type_map_from_init_expr(rhs) {
6735 c.receiver_generic_types[key] = generic_type_map.clone()
6736 return
6737 }
6738 if preserve_existing {
6739 return
6740 }
6741 c.receiver_generic_types.delete(key)
6742}
6743
6744fn (mut c Checker) merge_receiver_generic_types(receiver ast.Expr, mut generic_type_map map[string]Type) bool {
6745 mut added := false
6746 if receiver is ast.Ident {
6747 receiver_scope, _ := c.scope.lookup_parent_with_scope(receiver.name, 0) or { return false }
6748 key := receiver_generic_type_key(receiver_scope, receiver.name)
6749 if receiver_map := c.receiver_generic_types[key] {
6750 for generic_param, concrete_type in receiver_map {
6751 if generic_param !in generic_type_map {
6752 generic_type_map[generic_param] = concrete_type
6753 added = true
6754 }
6755 }
6756 }
6757 }
6758 return added
6759}
6760
6761fn (mut c Checker) type_from_generic_arg_name(name string) ?Type {
6762 if typ := builtin_type(name) {
6763 return typ
6764 }
6765 if name.starts_with('[]') {
6766 elem_type := c.type_from_generic_arg_name(name[2..]) or { return none }
6767 return Type(Array{
6768 elem_type: elem_type
6769 })
6770 }
6771 for generic_types in c.env.cur_generic_types {
6772 if concrete := generic_types[name] {
6773 if concrete.name() != name {
6774 return concrete
6775 }
6776 }
6777 }
6778 if typ := c.lookup_type_in_scope_chain(name) {
6779 return typ
6780 }
6781 if typ := c.lookup_type_in_imported_modules(name) {
6782 return typ
6783 }
6784 return none
6785}
6786
6787fn (mut c Checker) generic_type_map_from_receiver_struct_fields(receiver_type Struct) map[string]Type {
6788 mut generic_type_map := map[string]Type{}
6789 if receiver_type.name == '' || receiver_type.fields.len == 0 {
6790 return generic_type_map
6791 }
6792 template_type := c.lookup_type_by_name(receiver_type.name) or { return generic_type_map }
6793 if template_type !is Struct {
6794 return generic_type_map
6795 }
6796 template := template_type as Struct
6797 if template.generic_params.len == 0 || template.fields.len == 0 {
6798 return generic_type_map
6799 }
6800 for template_field in template.fields {
6801 for actual_field in receiver_type.fields {
6802 if actual_field.name != template_field.name {
6803 continue
6804 }
6805 c.infer_generic_type(template_field.typ, actual_field.typ, mut generic_type_map) or {}
6806 break
6807 }
6808 }
6809 return generic_type_map
6810}
6811
6812fn (mut c Checker) generic_type_map_from_receiver_struct_args(receiver_type Struct) map[string]Type {
6813 mut generic_type_map := map[string]Type{}
6814 if receiver_type.name == '' || receiver_type.generic_params.len == 0 {
6815 return generic_type_map
6816 }
6817 template_params := c.generic_type_params[receiver_type.name] or { return generic_type_map }
6818 for i, generic_param in template_params {
6819 if i >= receiver_type.generic_params.len {
6820 break
6821 }
6822 arg_name := receiver_type.generic_params[i]
6823 arg_type := c.type_from_generic_arg_name(arg_name) or { continue }
6824 if arg_type.name() == generic_param {
6825 continue
6826 }
6827 generic_type_map[generic_param] = arg_type
6828 }
6829 return generic_type_map
6830}
6831
6832fn (mut c Checker) generic_type_map_from_receiver_type(receiver_type Type) map[string]Type {
6833 match receiver_type {
6834 Pointer {
6835 return c.generic_type_map_from_receiver_type(receiver_type.base_type)
6836 }
6837 Alias {
6838 return c.generic_type_map_from_receiver_type(receiver_type.base_type)
6839 }
6840 Struct {
6841 generic_type_map_from_fields :=
6842 c.generic_type_map_from_receiver_struct_fields(receiver_type)
6843 if generic_type_map_from_fields.len > 0 {
6844 return generic_type_map_from_fields
6845 }
6846 generic_type_map_from_args :=
6847 c.generic_type_map_from_receiver_struct_args(receiver_type)
6848 if generic_type_map_from_args.len > 0 {
6849 return generic_type_map_from_args
6850 }
6851 mut generic_type_map := map[string]Type{}
6852 for generic_param in receiver_type.generic_params {
6853 arg_type := c.type_from_generic_arg_name(generic_param) or { continue }
6854 if arg_type.name() == generic_param {
6855 continue
6856 }
6857 generic_type_map[generic_param] = arg_type
6858 }
6859 if generic_type_map.len > 0 {
6860 return generic_type_map
6861 }
6862 return generic_type_map
6863 }
6864 else {
6865 return map[string]Type{}
6866 }
6867 }
6868}
6869
6870fn (mut c Checker) generic_type_map_from_cached_receiver_expr(receiver ast.Expr) ?map[string]Type {
6871 receiver_pos := receiver.pos()
6872 if !receiver_pos.is_valid() {
6873 return none
6874 }
6875 receiver_type := c.env.get_expr_type(receiver_pos.id) or { return none }
6876 receiver_map := c.generic_type_map_from_receiver_type(receiver_type)
6877 if receiver_map.len == 0 {
6878 return none
6879 }
6880 return receiver_map
6881}
6882
6883fn (mut c Checker) merge_receiver_generic_types_from_expr(receiver ast.Expr, fn_type FnType, mut generic_type_map map[string]Type) bool {
6884 receiver_map := if init_receiver_map := c.generic_type_map_from_init_expr(receiver) {
6885 init_receiver_map.clone()
6886 } else if cached_receiver_map := c.generic_type_map_from_cached_receiver_expr(receiver) {
6887 cached_receiver_map.clone()
6888 } else {
6889 receiver_type := c.expr_type_without_field_smartcast(receiver) or { return false }
6890 c.generic_type_map_from_receiver_type(receiver_type)
6891 }
6892 mut added := false
6893 for generic_param, arg_type in receiver_map {
6894 if generic_param in fn_type.generic_params && generic_param !in generic_type_map {
6895 generic_type_map[generic_param] = arg_type
6896 added = true
6897 }
6898 }
6899 return added
6900}
6901
6902fn (mut c Checker) instantiate_generic_struct(base Struct, args []ast.Expr) Type {
6903 mut actual_base := base
6904 // If base has no fields (stale copy), look up current version from scope
6905 if base.fields.len == 0 && base.name != '' {
6906 short_name := base.name.all_after('__')
6907 lookup_name := if short_name != base.name { short_name } else { base.name }
6908 if obj := c.scope.lookup_parent(lookup_name, 0) {
6909 if obj_type := object_as_type(obj) {
6910 if obj_type is St