v / vlib / v2 / types / checker.v
9709 lines · 9294 sloc · 264.77 KB
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.e