v / vlib / v2 / builder / parse.v
1216 lines · 1146 sloc · 38.62 KB · 34038bec7b93f4e3ddbbdcbe88ea21f4693eee43
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 builder
5
6import os
7import v2.ast
8import v2.parser
9import v2.pref as vpref
10import v2.token
11
12fn should_expand_single_file_input(input string) bool {
13 if os.file_name(input).ends_with('_test.v') {
14 return true
15 }
16 module_name := file_module_name(input) or { return false }
17 return module_name != 'main'
18}
19
20fn is_module_line_space(ch u8) bool {
21 return ch == ` ` || ch == `\t` || ch == `\v` || ch == `\f`
22}
23
24fn is_module_source_space(ch u8) bool {
25 return is_module_line_space(ch) || ch == `\n` || ch == `\r`
26}
27
28fn file_module_name(path string) ?string {
29 content := os.read_file(path) or { return none }
30 mut start := 0
31 for start < content.len {
32 if is_module_source_space(content[start]) {
33 start++
34 continue
35 }
36 if start + 1 < content.len && content[start] == `/` && content[start + 1] == `/` {
37 start += 2
38 for start < content.len && content[start] != `\n` && content[start] != `\r` {
39 start++
40 }
41 continue
42 }
43 if start + 1 < content.len && content[start] == `/` && content[start + 1] == `*` {
44 start += 2
45 for start + 1 < content.len {
46 if content[start] == `*` && content[start + 1] == `/` {
47 start += 2
48 break
49 }
50 start++
51 }
52 if start >= content.len {
53 break
54 }
55 continue
56 }
57 break
58 }
59 if content.len - start >= 7 && content[start] == `m` && content[start + 1] == `o`
60 && content[start + 2] == `d` && content[start + 3] == `u` && content[start + 4] == `l`
61 && content[start + 5] == `e` && is_module_line_space(content[start + 6]) {
62 mut name_start := start + 7
63 for name_start < content.len && is_module_line_space(content[name_start]) {
64 name_start++
65 }
66 mut name_end := name_start
67 for name_end < content.len && !is_module_source_space(content[name_end]) {
68 name_end++
69 }
70 if name_start < name_end {
71 return content[name_start..name_end]
72 }
73 return none
74 }
75 return none
76}
77
78fn directory_primary_module(files []string) string {
79 for file in files {
80 if module_name := file_module_name(file) {
81 return module_name
82 }
83 }
84 return 'main'
85}
86
87fn collect_same_module_subdir_files(root string, dir string, module_name string, user_defines []string, target_os string, mut files []string, mut seen map[string]bool) {
88 for entry in list_dir_entries(dir) {
89 if entry == '' || entry.starts_with('.') {
90 continue
91 }
92 path := os.join_path(dir, entry)
93 if !os.is_dir(path) {
94 continue
95 }
96 if path != root && os.exists(os.join_path(path, 'v.mod')) {
97 continue
98 }
99 for file in get_v_files_from_dir(path, user_defines, target_os) {
100 if file in seen {
101 continue
102 }
103 if file_module_name(file) or { 'main' } != module_name {
104 continue
105 }
106 files << file
107 seen[file] = true
108 }
109 collect_same_module_subdir_files(root, path, module_name, user_defines, target_os, mut
110 files, mut seen)
111 }
112}
113
114fn get_user_v_files_from_dir(dir string, user_defines []string, target_os string) []string {
115 mut files := get_v_files_from_dir(dir, user_defines, target_os)
116 module_name := directory_primary_module(files)
117 mut seen := map[string]bool{}
118 for file in files {
119 seen[file] = true
120 }
121 collect_same_module_subdir_files(dir, dir, module_name, user_defines, target_os, mut files, mut
122 seen)
123 return files
124}
125
126fn ast_comptime_flag_matches(name string, user_defines []string, target_os string) bool {
127 lower_name := name.to_lower()
128 match lower_name {
129 'macos', 'darwin', 'mac', 'linux', 'windows', 'bsd', 'freebsd', 'openbsd', 'netbsd',
130 'dragonfly', 'android', 'termux', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix',
131 'none' {
132 return flag_os_matches(lower_name, target_os)
133 }
134 'cross' {
135 return flag_os_matches(lower_name, target_os) || lower_name in user_defines
136 }
137 'freestanding', 'bare' {
138 return lower_name in user_defines
139 }
140 else {}
141 }
142
143 return lower_name in user_defines
144}
145
146fn ast_comptime_cond_matches(cond ast.Expr, user_defines []string, target_os string) bool {
147 return ast_comptime_cond_matches_with_explicit(cond, user_defines, user_defines, target_os)
148}
149
150fn ast_comptime_cond_matches_with_explicit(cond ast.Expr, user_defines []string, explicit_user_defines []string, target_os string) bool {
151 return ast_comptime_cond_matches_with_options(cond, user_defines, explicit_user_defines,
152 target_os, true)
153}
154
155fn ast_comptime_cond_matches_with_options(cond ast.Expr, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool) bool {
156 match cond {
157 ast.Ident {
158 return ast_comptime_flag_matches(cond.name, user_defines, target_os)
159 }
160 ast.ComptimeExpr {
161 return ast_comptime_cond_matches_with_options(cond.expr, user_defines,
162 explicit_user_defines, target_os, allow_pkgconfig)
163 }
164 ast.CallExpr {
165 if pkg_name := ast_pkgconfig_call_name(cond) {
166 if !allow_pkgconfig {
167 return false
168 }
169 return vpref.comptime_pkgconfig_value(pkg_name)
170 }
171 }
172 ast.CallOrCastExpr {
173 if pkg_name := ast_pkgconfig_call_name(cond) {
174 if !allow_pkgconfig {
175 return false
176 }
177 return vpref.comptime_pkgconfig_value(pkg_name)
178 }
179 }
180 ast.PrefixExpr {
181 if cond.op == .not {
182 return !ast_comptime_cond_matches_with_options(cond.expr, user_defines,
183 explicit_user_defines, target_os, allow_pkgconfig)
184 }
185 }
186 ast.InfixExpr {
187 if cond.op == .and {
188 return
189 ast_comptime_cond_matches_with_options(cond.lhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
190 && ast_comptime_cond_matches_with_options(cond.rhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
191 }
192 if cond.op == .logical_or {
193 return
194 ast_comptime_cond_matches_with_options(cond.lhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
195 || ast_comptime_cond_matches_with_options(cond.rhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
196 }
197 }
198 ast.PostfixExpr {
199 if cond.op == .question && cond.expr is ast.Ident {
200 return vpref.comptime_optional_define_value(cond.expr.name, user_defines,
201 explicit_user_defines)
202 }
203 }
204 ast.ParenExpr {
205 return ast_comptime_cond_matches_with_options(cond.expr, user_defines,
206 explicit_user_defines, target_os, allow_pkgconfig)
207 }
208 else {}
209 }
210
211 return false
212}
213
214fn ast_pkgconfig_call_name(expr ast.Expr) ?string {
215 match expr {
216 ast.CallExpr {
217 if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' && expr.args.len == 1 {
218 return ast_string_literal_value(expr.args[0])
219 }
220 }
221 ast.CallOrCastExpr {
222 if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' {
223 return ast_string_literal_value(expr.expr)
224 }
225 }
226 else {}
227 }
228
229 return none
230}
231
232fn ast_string_literal_value(expr ast.Expr) ?string {
233 if expr is ast.StringLiteral {
234 return unquote_ast_string_literal_value(expr.value)
235 }
236 return none
237}
238
239fn unquote_ast_string_literal_value(value string) string {
240 if value.len >= 2 && ((value[0] == `"` && value[value.len - 1] == `"`)
241 || (value[0] == `'` && value[value.len - 1] == `'`)) {
242 return value[1..value.len - 1]
243 }
244 return value
245}
246
247fn collect_active_imports_from_if_expr(node ast.IfExpr, user_defines []string, target_os string, mut imports []ast.ImportStmt) {
248 collect_active_imports_from_if_expr_with_explicit(node, user_defines, user_defines, target_os, mut
249 imports)
250}
251
252fn collect_active_imports_from_if_expr_with_explicit(node ast.IfExpr, user_defines []string, explicit_user_defines []string, target_os string, mut imports []ast.ImportStmt) {
253 collect_active_imports_from_if_expr_with_options(node, user_defines, explicit_user_defines,
254 target_os, true, mut imports)
255}
256
257fn collect_active_imports_from_if_expr_with_options(node ast.IfExpr, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool, mut imports []ast.ImportStmt) {
258 if ast_comptime_cond_matches_with_options(node.cond, user_defines, explicit_user_defines,
259 target_os, allow_pkgconfig)
260 {
261 collect_active_imports_from_stmts_with_options(node.stmts, user_defines,
262 explicit_user_defines, target_os, allow_pkgconfig, mut imports)
263 return
264 }
265 match node.else_expr {
266 ast.IfExpr {
267 if node.else_expr.cond is ast.EmptyExpr {
268 collect_active_imports_from_stmts_with_options(node.else_expr.stmts, user_defines,
269 explicit_user_defines, target_os, allow_pkgconfig, mut imports)
270 } else {
271 collect_active_imports_from_if_expr_with_options(node.else_expr, user_defines,
272 explicit_user_defines, target_os, allow_pkgconfig, mut imports)
273 }
274 }
275 else {}
276 }
277}
278
279fn collect_active_imports_from_stmts(stmts []ast.Stmt, user_defines []string, target_os string, mut imports []ast.ImportStmt) {
280 collect_active_imports_from_stmts_with_explicit(stmts, user_defines, user_defines, target_os, mut
281 imports)
282}
283
284fn collect_active_imports_from_stmts_with_explicit(stmts []ast.Stmt, user_defines []string, explicit_user_defines []string, target_os string, mut imports []ast.ImportStmt) {
285 collect_active_imports_from_stmts_with_options(stmts, user_defines, explicit_user_defines,
286 target_os, true, mut imports)
287}
288
289fn collect_active_imports_from_stmts_with_options(stmts []ast.Stmt, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool, mut imports []ast.ImportStmt) {
290 for stmt in stmts {
291 match stmt {
292 ast.ImportStmt {
293 imports << stmt
294 }
295 ast.ExprStmt {
296 if stmt.expr is ast.ComptimeExpr && stmt.expr.expr is ast.IfExpr {
297 collect_active_imports_from_if_expr_with_options(stmt.expr.expr, user_defines,
298 explicit_user_defines, target_os, allow_pkgconfig, mut imports)
299 }
300 }
301 else {}
302 }
303 }
304}
305
306fn imports_contain(imports []ast.ImportStmt, name string) bool {
307 for imp in imports {
308 if imp.name == name {
309 return true
310 }
311 }
312 return false
313}
314
315fn add_implicit_sync_import_if_needed(mut imports []ast.ImportStmt, current_module string, uses_channel bool) {
316 if !uses_channel || current_module == 'sync' || imports_contain(imports, 'sync') {
317 return
318 }
319 imports << ast.ImportStmt{
320 name: 'sync'
321 }
322}
323
324fn field_decl_uses_channel(field ast.FieldDecl) bool {
325 return type_expr_uses_channel(field.typ) || expr_type_slots_use_channel(field.value)
326}
327
328fn fields_use_channel(fields []ast.FieldDecl) bool {
329 for field in fields {
330 if field_decl_uses_channel(field) {
331 return true
332 }
333 }
334 return false
335}
336
337struct ChannelScanOptions {
338 user_defines []string
339 explicit_user_defines []string
340 target_os string
341 allow_pkgconfig bool
342 filter_comptime bool
343}
344
345fn field_inits_use_channel(fields []ast.FieldInit) bool {
346 return field_inits_use_channel_with_options(fields, ChannelScanOptions{
347 allow_pkgconfig: true
348 })
349}
350
351fn field_inits_use_channel_with_options(fields []ast.FieldInit, options ChannelScanOptions) bool {
352 for field in fields {
353 if expr_type_slots_use_channel_with_options(field.value, options) {
354 return true
355 }
356 }
357 return false
358}
359
360fn fn_type_uses_channel(typ ast.FnType) bool {
361 for gp in typ.generic_params {
362 if type_expr_uses_channel(gp) {
363 return true
364 }
365 }
366 for param in typ.params {
367 if type_expr_uses_channel(param.typ) {
368 return true
369 }
370 }
371 return type_expr_uses_channel(typ.return_type)
372}
373
374fn stmts_use_channel(stmts []ast.Stmt) bool {
375 return stmts_use_channel_with_options(stmts, ChannelScanOptions{
376 allow_pkgconfig: true
377 })
378}
379
380fn stmts_use_channel_with_options(stmts []ast.Stmt, options ChannelScanOptions) bool {
381 for stmt in stmts {
382 if stmt_uses_channel_with_options(stmt, options) {
383 return true
384 }
385 }
386 return false
387}
388
389fn stmt_uses_channel(stmt ast.Stmt) bool {
390 return stmt_uses_channel_with_options(stmt, ChannelScanOptions{
391 allow_pkgconfig: true
392 })
393}
394
395fn fn_decl_body_is_active_for_channel_scan(decl ast.FnDecl, options ChannelScanOptions) bool {
396 for attr in decl.attributes {
397 if attr.comptime_cond is ast.EmptyExpr {
398 continue
399 }
400 if !ast_comptime_cond_matches_with_options(attr.comptime_cond, options.user_defines,
401 options.explicit_user_defines, options.target_os, options.allow_pkgconfig) {
402 return false
403 }
404 }
405 return true
406}
407
408fn stmt_uses_channel_with_options(stmt ast.Stmt, options ChannelScanOptions) bool {
409 match stmt {
410 ast.AssertStmt {
411 return expr_type_slots_use_channel_with_options(stmt.expr, options)
412 || expr_type_slots_use_channel_with_options(stmt.extra, options)
413 }
414 ast.AssignStmt {
415 return exprs_type_slots_use_channel_with_options(stmt.lhs, options)
416 || exprs_type_slots_use_channel_with_options(stmt.rhs, options)
417 }
418 ast.BlockStmt {
419 return stmts_use_channel_with_options(stmt.stmts, options)
420 }
421 ast.ComptimeStmt {
422 return stmt_uses_channel_with_options(stmt.stmt, options)
423 }
424 ast.ConstDecl {
425 return field_inits_use_channel_with_options(stmt.fields, options)
426 }
427 ast.DeferStmt {
428 return stmts_use_channel_with_options(stmt.stmts, options)
429 }
430 ast.EnumDecl {
431 for field in stmt.fields {
432 if expr_type_slots_use_channel_with_options(field.value, options) {
433 return true
434 }
435 }
436 }
437 ast.ExprStmt {
438 if options.filter_comptime && stmt.expr is ast.ComptimeExpr
439 && stmt.expr.expr is ast.IfExpr {
440 return comptime_if_expr_uses_channel_with_options(stmt.expr.expr, options)
441 }
442 return expr_type_slots_use_channel_with_options(stmt.expr, options)
443 }
444 ast.FnDecl {
445 signature_uses_channel := type_expr_uses_channel(stmt.receiver.typ)
446 || fn_type_uses_channel(stmt.typ)
447 if !fn_decl_body_is_active_for_channel_scan(stmt, options) {
448 return signature_uses_channel
449 }
450 return signature_uses_channel || stmts_use_channel_with_options(stmt.stmts, options)
451 }
452 ast.ForInStmt {
453 return expr_type_slots_use_channel_with_options(stmt.expr, options)
454 }
455 ast.ForStmt {
456 return stmt_uses_channel_with_options(stmt.init, options)
457 || expr_type_slots_use_channel_with_options(stmt.cond, options)
458 || stmt_uses_channel_with_options(stmt.post, options)
459 || stmts_use_channel_with_options(stmt.stmts, options)
460 }
461 ast.GlobalDecl {
462 return fields_use_channel(stmt.fields)
463 }
464 ast.InterfaceDecl {
465 return fields_use_channel(stmt.fields) || type_exprs_use_channel(stmt.embedded)
466 }
467 ast.LabelStmt {
468 return stmt_uses_channel_with_options(stmt.stmt, options)
469 }
470 ast.ReturnStmt {
471 return exprs_type_slots_use_channel_with_options(stmt.exprs, options)
472 }
473 ast.StructDecl {
474 return type_exprs_use_channel(stmt.implements) || type_exprs_use_channel(stmt.embedded)
475 || type_exprs_use_channel(stmt.generic_params) || fields_use_channel(stmt.fields)
476 }
477 ast.TypeDecl {
478 return type_expr_uses_channel(stmt.base_type) || type_exprs_use_channel(stmt.variants)
479 || type_exprs_use_channel(stmt.generic_params)
480 }
481 else {}
482 }
483
484 return false
485}
486
487fn type_exprs_use_channel(exprs []ast.Expr) bool {
488 for expr in exprs {
489 if type_expr_uses_channel(expr) {
490 return true
491 }
492 }
493 return false
494}
495
496fn exprs_type_slots_use_channel(exprs []ast.Expr) bool {
497 return exprs_type_slots_use_channel_with_options(exprs, ChannelScanOptions{
498 allow_pkgconfig: true
499 })
500}
501
502fn exprs_type_slots_use_channel_with_options(exprs []ast.Expr, options ChannelScanOptions) bool {
503 for expr in exprs {
504 if expr_type_slots_use_channel_with_options(expr, options) {
505 return true
506 }
507 }
508 return false
509}
510
511fn string_inters_use_channel(inters []ast.StringInter) bool {
512 return string_inters_use_channel_with_options(inters, ChannelScanOptions{
513 allow_pkgconfig: true
514 })
515}
516
517fn string_inters_use_channel_with_options(inters []ast.StringInter, options ChannelScanOptions) bool {
518 for inter in inters {
519 if expr_type_slots_use_channel_with_options(inter.expr, options)
520 || expr_type_slots_use_channel_with_options(inter.format_expr, options) {
521 return true
522 }
523 }
524 return false
525}
526
527fn comptime_if_expr_uses_channel_with_options(node ast.IfExpr, options ChannelScanOptions) bool {
528 if ast_comptime_cond_matches_with_options(node.cond, options.user_defines,
529 options.explicit_user_defines, options.target_os, options.allow_pkgconfig)
530 {
531 return stmts_use_channel_with_options(node.stmts, options)
532 }
533 match node.else_expr {
534 ast.IfExpr {
535 if node.else_expr.cond is ast.EmptyExpr {
536 return stmts_use_channel_with_options(node.else_expr.stmts, options)
537 }
538 return comptime_if_expr_uses_channel_with_options(node.else_expr, options)
539 }
540 else {
541 return expr_type_slots_use_channel_with_options(node.else_expr, options)
542 }
543 }
544}
545
546fn expr_type_slots_use_channel(expr ast.Expr) bool {
547 return expr_type_slots_use_channel_with_options(expr, ChannelScanOptions{
548 allow_pkgconfig: true
549 })
550}
551
552fn expr_type_slots_use_channel_with_options(expr ast.Expr, options ChannelScanOptions) bool {
553 match expr {
554 ast.ArrayInitExpr {
555 return type_expr_uses_channel(expr.typ)
556 || expr_type_slots_use_channel_with_options(expr.init, options)
557 || exprs_type_slots_use_channel_with_options(expr.exprs, options)
558 || expr_type_slots_use_channel_with_options(expr.cap, options)
559 || expr_type_slots_use_channel_with_options(expr.len, options)
560 || expr_type_slots_use_channel_with_options(expr.update_expr, options)
561 }
562 ast.AsCastExpr {
563 return type_expr_uses_channel(expr.typ)
564 || expr_type_slots_use_channel_with_options(expr.expr, options)
565 }
566 ast.AssocExpr {
567 return type_expr_uses_channel(expr.typ)
568 || expr_type_slots_use_channel_with_options(expr.expr, options)
569 || field_inits_use_channel_with_options(expr.fields, options)
570 }
571 ast.CallExpr {
572 return expr_type_slots_use_channel_with_options(expr.lhs, options)
573 || exprs_type_slots_use_channel_with_options(expr.args, options)
574 }
575 ast.CallOrCastExpr {
576 return type_expr_uses_channel(expr.lhs)
577 || expr_type_slots_use_channel_with_options(expr.expr, options)
578 }
579 ast.CastExpr {
580 return type_expr_uses_channel(expr.typ)
581 || expr_type_slots_use_channel_with_options(expr.expr, options)
582 }
583 ast.ComptimeExpr {
584 if options.filter_comptime && expr.expr is ast.IfExpr {
585 return comptime_if_expr_uses_channel_with_options(expr.expr, options)
586 }
587 return expr_type_slots_use_channel_with_options(expr.expr, options)
588 }
589 ast.FieldInit {
590 return expr_type_slots_use_channel_with_options(expr.value, options)
591 }
592 ast.FnLiteral {
593 return fn_type_uses_channel(expr.typ)
594 || stmts_use_channel_with_options(expr.stmts, options)
595 }
596 ast.GenericArgOrIndexExpr {
597 return type_expr_uses_channel(expr.lhs) || type_expr_uses_channel(expr.expr)
598 }
599 ast.GenericArgs {
600 return type_expr_uses_channel(expr.lhs) || type_exprs_use_channel(expr.args)
601 }
602 ast.IfExpr {
603 return expr_type_slots_use_channel_with_options(expr.cond, options)
604 || stmts_use_channel_with_options(expr.stmts, options)
605 || expr_type_slots_use_channel_with_options(expr.else_expr, options)
606 }
607 ast.IfGuardExpr {
608 return stmt_uses_channel_with_options(ast.Stmt(expr.stmt), options)
609 }
610 ast.IndexExpr {
611 return expr_type_slots_use_channel_with_options(expr.lhs, options)
612 || expr_type_slots_use_channel_with_options(expr.expr, options)
613 }
614 ast.InfixExpr {
615 return expr_type_slots_use_channel_with_options(expr.lhs, options)
616 || expr_type_slots_use_channel_with_options(expr.rhs, options)
617 }
618 ast.InitExpr {
619 return type_expr_uses_channel(expr.typ)
620 || field_inits_use_channel_with_options(expr.fields, options)
621 }
622 ast.KeywordOperator {
623 return exprs_type_slots_use_channel_with_options(expr.exprs, options)
624 }
625 ast.LambdaExpr {
626 return expr_type_slots_use_channel_with_options(expr.expr, options)
627 }
628 ast.ModifierExpr {
629 return expr_type_slots_use_channel_with_options(expr.expr, options)
630 }
631 ast.LockExpr {
632 return exprs_type_slots_use_channel_with_options(expr.lock_exprs, options)
633 || exprs_type_slots_use_channel_with_options(expr.rlock_exprs, options)
634 || stmts_use_channel_with_options(expr.stmts, options)
635 }
636 ast.MapInitExpr {
637 return type_expr_uses_channel(expr.typ)
638 || exprs_type_slots_use_channel_with_options(expr.keys, options)
639 || exprs_type_slots_use_channel_with_options(expr.vals, options)
640 }
641 ast.MatchExpr {
642 if expr_type_slots_use_channel_with_options(expr.expr, options) {
643 return true
644 }
645 for branch in expr.branches {
646 if exprs_type_slots_use_channel_with_options(branch.cond, options)
647 || stmts_use_channel_with_options(branch.stmts, options) {
648 return true
649 }
650 }
651 }
652 ast.OrExpr {
653 return expr_type_slots_use_channel_with_options(expr.expr, options)
654 || stmts_use_channel_with_options(expr.stmts, options)
655 }
656 ast.ParenExpr {
657 return expr_type_slots_use_channel_with_options(expr.expr, options)
658 }
659 ast.PostfixExpr {
660 return expr_type_slots_use_channel_with_options(expr.expr, options)
661 }
662 ast.PrefixExpr {
663 return expr_type_slots_use_channel_with_options(expr.expr, options)
664 }
665 ast.RangeExpr {
666 return expr_type_slots_use_channel_with_options(expr.start, options)
667 || expr_type_slots_use_channel_with_options(expr.end, options)
668 }
669 ast.SelectExpr {
670 return stmt_uses_channel_with_options(expr.stmt, options)
671 || stmts_use_channel_with_options(expr.stmts, options)
672 || expr_type_slots_use_channel_with_options(expr.next, options)
673 }
674 ast.SelectorExpr {
675 return expr_type_slots_use_channel_with_options(expr.lhs, options)
676 }
677 ast.SqlExpr {
678 return expr_type_slots_use_channel_with_options(expr.expr, options)
679 }
680 ast.StringInterLiteral {
681 return string_inters_use_channel_with_options(expr.inters, options)
682 }
683 ast.Tuple {
684 return exprs_type_slots_use_channel_with_options(expr.exprs, options)
685 }
686 ast.UnsafeExpr {
687 return stmts_use_channel_with_options(expr.stmts, options)
688 }
689 ast.Type {
690 return type_expr_uses_channel(expr)
691 }
692 else {}
693 }
694
695 return false
696}
697
698fn type_expr_uses_channel(expr ast.Expr) bool {
699 match expr {
700 ast.Type {
701 match expr {
702 ast.ChannelType {
703 return true
704 }
705 ast.ArrayType {
706 return type_expr_uses_channel(expr.elem_type)
707 }
708 ast.ArrayFixedType {
709 return type_expr_uses_channel(expr.elem_type)
710 }
711 ast.FnType {
712 return fn_type_uses_channel(expr)
713 }
714 ast.GenericType {
715 return type_expr_uses_channel(expr.name) || type_exprs_use_channel(expr.params)
716 }
717 ast.MapType {
718 return type_expr_uses_channel(expr.key_type)
719 || type_expr_uses_channel(expr.value_type)
720 }
721 ast.OptionType {
722 return type_expr_uses_channel(expr.base_type)
723 }
724 ast.PointerType {
725 return type_expr_uses_channel(expr.base_type)
726 }
727 ast.ResultType {
728 return type_expr_uses_channel(expr.base_type)
729 }
730 ast.ThreadType {
731 return type_expr_uses_channel(expr.elem_type)
732 }
733 ast.TupleType {
734 return type_exprs_use_channel(expr.types)
735 }
736 ast.AnonStructType {
737 return type_exprs_use_channel(expr.generic_params)
738 || type_exprs_use_channel(expr.embedded) || fields_use_channel(expr.fields)
739 }
740 else {}
741 }
742 }
743 else {}
744 }
745
746 return false
747}
748
749fn active_file_imports(file ast.File, user_defines []string, target_os string) []ast.ImportStmt {
750 return active_file_imports_with_explicit(file, user_defines, user_defines, target_os)
751}
752
753fn active_file_imports_with_explicit(file ast.File, user_defines []string, explicit_user_defines []string, target_os string) []ast.ImportStmt {
754 return active_file_imports_with_options(file, user_defines, explicit_user_defines, target_os,
755 true)
756}
757
758fn active_file_imports_with_options(file ast.File, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool) []ast.ImportStmt {
759 mut imports := file.imports.clone()
760 collect_active_imports_from_stmts_with_options(file.stmts, user_defines, explicit_user_defines,
761 target_os, allow_pkgconfig, mut imports)
762 add_implicit_sync_import_if_needed(mut imports, file.mod, stmts_use_channel_with_options(file.stmts, ChannelScanOptions{
763 user_defines: user_defines
764 explicit_user_defines: explicit_user_defines
765 target_os: target_os
766 allow_pkgconfig: allow_pkgconfig
767 filter_comptime: true
768 }))
769 return imports
770}
771
772// active_file_imports_from_flat mirrors active_file_imports but reads the
773// import list and top-level stmts straight from the FlatAst, so import
774// discovery can run without rehydrating an ast.File.
775fn active_file_imports_from_flat(flat &ast.FlatAst, ff ast.FlatFile, user_defines []string, explicit_user_defines []string, target_os string) []ast.ImportStmt {
776 return active_file_imports_from_flat_with_options(flat, ff, user_defines,
777 explicit_user_defines, target_os, true)
778}
779
780fn active_file_imports_from_flat_with_options(flat &ast.FlatAst, ff ast.FlatFile, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool) []ast.ImportStmt {
781 // s253: walk the FlatAst via cursors instead of rehydrating the whole file to
782 // legacy ast.Stmt with a top-level decode. The decode-then-walk path (a) costs a
783 // full legacy-AST materialisation per file just to answer "does this file use a
784 // channel / which comptime imports are active?", and (b) crashes on the arm64
785 // self-host — the legacy `*_use_channel` walkers pass `[]Expr`/sum types by
786 // value through deep recursion over decoded nodes, hitting the arm64
787 // chained-access bug. Cursor reads are plain int-array lookups, so the walk is
788 // cheap and arm64-safe; only the tiny comptime-condition sub-exprs are decoded
789 // (reusing the exact legacy evaluator) so semantics match the AST path.
790 mut imports := ast.Cursor{
791 flat: unsafe { flat }
792 id: ff.file_id
793 }.list_at(1).import_stmts()
794 options := ChannelScanOptions{
795 user_defines: user_defines
796 explicit_user_defines: explicit_user_defines
797 target_os: target_os
798 allow_pkgconfig: allow_pkgconfig
799 filter_comptime: true
800 }
801 file_node := ast.Cursor{
802 flat: unsafe { flat }
803 id: ff.file_id
804 }
805 stmts := file_node.list_at(2) // file node edge 2 = top-level statement list
806 flat_collect_active_imports(stmts, options, mut imports)
807 module_name := if ff.mod_idx >= 0 && ff.mod_idx < flat.strings.len {
808 flat.strings[ff.mod_idx]
809 } else {
810 'main'
811 }
812 add_implicit_sync_import_if_needed(mut imports, module_name, flat_stmts_use_channel(stmts,
813 options))
814 return imports
815}
816
817// flat_collect_active_imports is the cursor-native mirror of
818// `collect_active_imports_from_stmts_with_options`: it appends top-level
819// `import` statements and the imports of active comptime `$if` branches.
820fn flat_collect_active_imports(stmts ast.CursorList, options ChannelScanOptions, mut imports []ast.ImportStmt) {
821 for i in 0 .. stmts.len() {
822 flat_collect_active_imports_stmt(stmts.at(i), options, mut imports)
823 }
824}
825
826fn flat_collect_active_imports_stmt(s ast.Cursor, options ChannelScanOptions, mut imports []ast.ImportStmt) {
827 match s.kind() {
828 .stmt_import {
829 imports << s.import_stmt()
830 }
831 .stmt_expr {
832 inner := s.edge(0)
833 if inner.kind() == .expr_comptime {
834 cif := inner.edge(0)
835 if cif.kind() == .expr_if {
836 flat_collect_active_imports_from_if(cif, options, mut imports)
837 }
838 }
839 }
840 else {}
841 }
842}
843
844// flat_collect_active_imports_from_if mirrors
845// `collect_active_imports_from_if_expr_with_options`. expr_if layout:
846// edge0 = cond, edge1 = else_expr, edge2.. = then-branch statements.
847fn flat_collect_active_imports_from_if(if_c ast.Cursor, options ChannelScanOptions, mut imports []ast.ImportStmt) {
848 if flat_comptime_cond_matches(if_c.edge(0), options) {
849 for i in 2 .. if_c.edge_count() {
850 flat_collect_active_imports_stmt(if_c.edge(i), options, mut imports)
851 }
852 return
853 }
854 else_c := if_c.edge(1)
855 if else_c.kind() == .expr_if {
856 if else_c.edge(0).kind() == .expr_empty {
857 for i in 2 .. else_c.edge_count() {
858 flat_collect_active_imports_stmt(else_c.edge(i), options, mut imports)
859 }
860 } else {
861 flat_collect_active_imports_from_if(else_c, options, mut imports)
862 }
863 }
864}
865
866// flat_stmts_use_channel is the cursor-native mirror of
867// `stmts_use_channel_with_options`.
868fn flat_stmts_use_channel(stmts ast.CursorList, options ChannelScanOptions) bool {
869 for i in 0 .. stmts.len() {
870 if flat_node_uses_channel(stmts.at(i), options) {
871 return true
872 }
873 }
874 return false
875}
876
877// flat_node_uses_channel returns true if the active subtree rooted at `c`
878// references a channel type. Channel types are always encoded as `.typ_channel`
879// nodes, so a generic structural recursion over every edge finds them without
880// per-kind edge knowledge (aux_list nodes are transparent — their edges are the
881// list items). Two arms must prune to match the legacy scan's comptime
882// filtering: a FnDecl body is scanned only when its comptime attributes keep it
883// active, and a comptime `$if` (ComptimeExpr wrapping an IfExpr) scans only the
884// branch selected by the condition.
885fn flat_node_uses_channel(c ast.Cursor, options ChannelScanOptions) bool {
886 if !c.is_valid() {
887 return false
888 }
889 k := c.kind()
890 if k == .typ_channel {
891 return true
892 }
893 if k == .stmt_fn_decl {
894 // stmt_fn_decl layout: edge0 = receiver, edge1 = fn type, edge2 = attrs,
895 // edge3 = body. Signature (receiver + fn type) is always active; the body
896 // only when comptime-active. Attributes never hold a channel type (matches
897 // the legacy FnDecl arm, which scans only receiver.typ + fn_type + body).
898 if flat_node_uses_channel(c.edge(0), options) {
899 return true
900 }
901 if flat_node_uses_channel(c.edge(1), options) {
902 return true
903 }
904 if flat_fn_decl_body_active_for_channel_scan(c, options) {
905 return flat_node_uses_channel(c.edge(3), options)
906 }
907 return false
908 }
909 if k == .expr_comptime && options.filter_comptime {
910 inner := c.edge(0)
911 if inner.kind() == .expr_if {
912 return flat_comptime_if_uses_channel(inner, options)
913 }
914 return flat_node_uses_channel(inner, options)
915 }
916 for i in 0 .. c.edge_count() {
917 if flat_node_uses_channel(c.edge(i), options) {
918 return true
919 }
920 }
921 return false
922}
923
924// flat_comptime_if_uses_channel mirrors
925// `comptime_if_expr_uses_channel_with_options`: only the branch selected by the
926// comptime condition is scanned for channel usage.
927fn flat_comptime_if_uses_channel(if_c ast.Cursor, options ChannelScanOptions) bool {
928 if flat_comptime_cond_matches(if_c.edge(0), options) {
929 for i in 2 .. if_c.edge_count() {
930 if flat_node_uses_channel(if_c.edge(i), options) {
931 return true
932 }
933 }
934 return false
935 }
936 else_c := if_c.edge(1)
937 if else_c.kind() == .expr_if {
938 if else_c.edge(0).kind() == .expr_empty {
939 for i in 2 .. else_c.edge_count() {
940 if flat_node_uses_channel(else_c.edge(i), options) {
941 return true
942 }
943 }
944 return false
945 }
946 return flat_comptime_if_uses_channel(else_c, options)
947 }
948 return flat_node_uses_channel(else_c, options)
949}
950
951// flat_fn_decl_body_active_for_channel_scan mirrors
952// `fn_decl_body_is_active_for_channel_scan`: a FnDecl whose comptime attributes
953// (`@[if ...]`) don't match the current target is inactive, so its body is
954// skipped. aux_attribute layout: edge0 = value, edge1 = comptime_cond.
955fn flat_fn_decl_body_active_for_channel_scan(fn_c ast.Cursor, options ChannelScanOptions) bool {
956 attrs := fn_c.list_at(2)
957 for i in 0 .. attrs.len() {
958 cond := attrs.at(i).edge(1)
959 if !cond.is_valid() || cond.kind() == .expr_empty {
960 continue
961 }
962 if !flat_comptime_cond_matches(cond, options) {
963 return false
964 }
965 }
966 return true
967}
968
969// flat_comptime_cond_matches evaluates a comptime condition from FlatAst
970// cursors. Call/pkgconfig conditions still fall back to the legacy expression
971// evaluator until call argument cursors are ported.
972fn flat_comptime_cond_matches(cond ast.Cursor, options ChannelScanOptions) bool {
973 if !cond.is_valid() {
974 return false
975 }
976 match cond.kind() {
977 .expr_ident {
978 return ast_comptime_flag_matches(cond.name(), options.user_defines, options.target_os)
979 }
980 .expr_comptime, .expr_paren {
981 return flat_comptime_cond_matches(cond.edge(0), options)
982 }
983 .expr_prefix {
984 op := unsafe { token.Token(int(cond.aux())) }
985 if op == .not {
986 return !flat_comptime_cond_matches(cond.edge(0), options)
987 }
988 }
989 .expr_infix {
990 op := unsafe { token.Token(int(cond.aux())) }
991 if op == .and {
992 return flat_comptime_cond_matches(cond.edge(0), options)
993 && flat_comptime_cond_matches(cond.edge(1), options)
994 }
995 if op == .logical_or {
996 return flat_comptime_cond_matches(cond.edge(0), options)
997 || flat_comptime_cond_matches(cond.edge(1), options)
998 }
999 }
1000 .expr_postfix {
1001 op := unsafe { token.Token(int(cond.aux())) }
1002 inner := cond.edge(0)
1003 if op == .question && inner.kind() == .expr_ident {
1004 return vpref.comptime_optional_define_value(inner.name(), options.user_defines,
1005 options.explicit_user_defines)
1006 }
1007 }
1008 .expr_call, .expr_call_or_cast {
1009 if pkg_name := flat_pkgconfig_call_name(cond) {
1010 if !options.allow_pkgconfig {
1011 return false
1012 }
1013 return vpref.comptime_pkgconfig_value(pkg_name)
1014 }
1015 }
1016 else {}
1017 }
1018
1019 return false
1020}
1021
1022fn flat_pkgconfig_call_name(expr ast.Cursor) ?string {
1023 match expr.kind() {
1024 .expr_call {
1025 lhs := expr.edge(0)
1026 if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig'
1027 && expr.edge_count() == 2 {
1028 return flat_string_literal_value(expr.edge(1))
1029 }
1030 }
1031 .expr_call_or_cast {
1032 lhs := expr.edge(0)
1033 if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' {
1034 return flat_string_literal_value(expr.edge(1))
1035 }
1036 }
1037 else {}
1038 }
1039
1040 return none
1041}
1042
1043fn flat_string_literal_value(expr ast.Cursor) ?string {
1044 if !expr.is_valid() || expr.kind() != .expr_string {
1045 return none
1046 }
1047 return unquote_ast_string_literal_value(expr.name())
1048}
1049
1050// parse_batch routes normal parsing through the streaming-into-shared-
1051// FlatBuilder path.
1052fn (mut b Builder) parse_batch(mut parser_reused parser.Parser, files []string) {
1053 b.ensure_flat_builder_inited()
1054 parser_reused.parse_files_into_flat(files, mut b.file_set, mut b.flat_builder)
1055}
1056
1057// ensure_flat_builder_inited lazily seeds b.flat_builder on first use.
1058// Use init_flat_builder_for_paths upfront when the input set is known to
1059// avoid arena reallocs; this fallback only kicks in when the streaming
1060// path is entered without that pre-sizing pass.
1061fn (mut b Builder) ensure_flat_builder_inited() {
1062 if b.flat_builder_inited {
1063 return
1064 }
1065 b.flat_builder = ast.new_flat_builder()
1066 b.flat_builder_inited = true
1067}
1068
1069// init_flat_builder_for_paths sizes the persistent flat_builder arenas
1070// from the total source bytes of the supplied paths. Used by parse_files
1071// to pre-size before any parse_batch call so the streaming path avoids
1072// the geometric realloc churn the one-shot flatten_files() side-steps.
1073// The 2x scale factor accounts for imports, which the caller does not
1074// yet know — empirically core+user is ~half of the final byte total.
1075fn (mut b Builder) init_flat_builder_for_paths(paths []string) {
1076 if b.flat_builder_inited {
1077 return
1078 }
1079 mut total_bytes := i64(0)
1080 for path in paths {
1081 if path == '' {
1082 continue
1083 }
1084 total_bytes += os.file_size(path)
1085 }
1086 nodes_cap, edges_cap, strings_cap := ast.arena_caps_for_bytes(total_bytes * 2)
1087 b.flat_builder = ast.new_flat_builder_with_capacity(nodes_cap, edges_cap, strings_cap)
1088 b.flat_builder_inited = true
1089}
1090
1091fn (mut b Builder) parse_files(files []string) []ast.File {
1092 mut parser_reused := parser.Parser.new(b.pref)
1093 skip_builtin := b.pref.skip_builtin
1094 target_os := b.pref.source_filter_target_os()
1095 allow_pkgconfig_imports := !b.pref.is_cross_target()
1096 mut use_core_headers := false
1097 // Resolve core-source paths upfront so the pre-size pass below can
1098 // stat them alongside user files. We still parse in the same batch
1099 // shape as before so module-init ordering is unchanged.
1100 mut core_module_files := [][]string{}
1101 mut cached_core_files := []string{}
1102 if !skip_builtin {
1103 use_core_headers = b.can_use_cached_core_headers_for_parse()
1104 b.used_vh_for_parse = use_core_headers
1105 if use_core_headers {
1106 cached_core_files = b.core_cached_parse_paths()
1107 } else {
1108 for module_path in core_cached_module_paths {
1109 vlib_path := b.pref.get_vlib_module_path(module_path)
1110 core_module_files << get_v_files_from_dir(vlib_path, b.pref.user_defines, target_os)
1111 }
1112 }
1113 }
1114 // Expand user input paths: allow compiling module directories (e.g. `v2 .`).
1115 mut expanded_user_files := []string{}
1116 mut seen_user_files := map[string]bool{}
1117 for input in files {
1118 if input == '' {
1119 continue
1120 }
1121 if os.is_dir(input) {
1122 dir_files := get_user_v_files_from_dir(input, b.pref.user_defines, target_os)
1123 for dir_file in dir_files {
1124 if dir_file != '' && dir_file !in seen_user_files {
1125 expanded_user_files << dir_file
1126 seen_user_files[dir_file] = true
1127 }
1128 }
1129 continue
1130 }
1131 if should_expand_single_file_input(input) {
1132 if input !in seen_user_files {
1133 expanded_user_files << input
1134 seen_user_files[input] = true
1135 }
1136 dir_files := get_v_files_from_dir(os.dir(input), b.pref.user_defines, target_os)
1137 for dir_file in dir_files {
1138 if dir_file != '' && dir_file !in seen_user_files {
1139 expanded_user_files << dir_file
1140 seen_user_files[dir_file] = true
1141 }
1142 }
1143 continue
1144 }
1145 if input !in seen_user_files {
1146 expanded_user_files << input
1147 seen_user_files[input] = true
1148 }
1149 }
1150 // Directory inputs and non-main module files were expanded above. Single-file
1151 // `main` programs stay isolated, so `v2 hello.v` parses only `hello.v`,
1152 // while `v2 .` and `v2 vlib/math/math_test.v` still parse their module files.
1153 virtual_main_modules := b.collect_virtual_main_modules_from_paths(expanded_user_files)
1154 if b.can_use_cached_virtual_headers_for_parse(virtual_main_modules) {
1155 expanded_user_files = b.replace_virtual_sources_with_headers(expanded_user_files,
1156 virtual_main_modules)
1157 b.used_virtual_vh_for_parse = true
1158 }
1159 mut pre_size_paths := []string{}
1160 pre_size_paths << cached_core_files
1161 for module_files in core_module_files {
1162 pre_size_paths << module_files
1163 }
1164 pre_size_paths << expanded_user_files
1165 b.init_flat_builder_for_paths(pre_size_paths)
1166 if !skip_builtin {
1167 if use_core_headers {
1168 b.parse_batch(mut parser_reused, cached_core_files)
1169 } else {
1170 for module_files in core_module_files {
1171 b.parse_batch(mut parser_reused, module_files)
1172 }
1173 }
1174 }
1175 b.parse_batch(mut parser_reused, expanded_user_files)
1176 skip_imports := b.pref.skip_imports
1177 if skip_imports {
1178 // Flat path: b.flat_builder is the canonical store; build() derives
1179 // b.flat from it after parse completes.
1180 return []ast.File{}
1181 }
1182 // parse imports
1183 use_import_headers := b.can_use_cached_import_headers_for_parse()
1184 b.used_import_vh_for_parse = use_import_headers
1185 mut parsed_imports := []string{}
1186 if !skip_builtin {
1187 parsed_imports << core_cached_module_paths
1188 }
1189 // Walk the flat store directly. parse_batch may append new files to
1190 // b.flat_builder, so re-read the length each iteration.
1191 for afi := 0; afi < b.flat_builder.flat.files.len; afi++ {
1192 ff := b.flat_builder.flat.files[afi]
1193 ast_file_name := b.flat_builder.flat.file_name(ff)
1194 for mod in active_file_imports_from_flat_with_options(&b.flat_builder.flat, ff,
1195 b.pref.user_defines, b.pref.explicit_user_defines, target_os, allow_pkgconfig_imports) {
1196 if mod.name in parsed_imports {
1197 continue
1198 }
1199 if use_core_headers || use_import_headers {
1200 if cached_path := b.cached_import_parse_path(mod.name) {
1201 b.parse_batch(mut parser_reused, [cached_path])
1202 parsed_imports << mod.name
1203 continue
1204 }
1205 }
1206 mod_path := b.pref.get_module_path(mod.name, ast_file_name)
1207 module_files := get_v_files_from_dir(mod_path, b.pref.user_defines, target_os)
1208 if module_files.len == 0 {
1209 continue
1210 }
1211 b.parse_batch(mut parser_reused, module_files)
1212 parsed_imports << mod.name
1213 }
1214 }
1215 return []ast.File{}
1216}
1217