Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -0,0 +1,510 @@
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
// vtest build: macos
//
// Differential test harness for the flat-native markused walker.
//
// Markused decides which functions/methods the backend must emit. A wrong
// answer is silent — too few keys → linker errors, too many keys → bloated
// binary and slow build. The cursor-based walker that will replace the
// recursive walker must produce a bit-identical key set on every program
// that exercises the dispatch surface.
//
// This file provides the comparison machinery and a growing fixture catalog.
// The fixtures are intentionally small, one per language construct, so that
// when the diff fails the failing fixture immediately localises the bug.
// Until mark_used_flat lands, the "differential" tests assert determinism
// (legacy == legacy across two runs) — they prove the harness itself works
// without depending on the new walker.
module markused
import os
import time
import v2.ast
import v2.parser
import v2.pref
import v2.token
import v2.types
// ParsedFixture bundles every artefact a markused walker (legacy or flat)
// needs to run on a source string. It is returned by parse_fixture so each
// test fixture writes the source once and gets everything else for free.
struct ParsedFixture {
files []ast.File
flat ast.FlatAst
env &types.Environment
}
fn parse_fixture(src string) ParsedFixture {
tmp := '/tmp/v2_markused_diff_${os.getpid()}_${rand_suffix()}.v'
os.write_file(tmp, src) or { panic('write_file: ${err}') }
defer {
os.rm(tmp) or {}
}
prefs := &pref.Preferences{
skip_builtin: true
}
mut fs := token.FileSet.new()
mut par := parser.Parser.new(prefs)
files := par.parse_files([tmp], mut fs)
flat := ast.flatten_files(files)
// Skip the type checker entirely — fixtures use println / etc. which
// would require pulling in builtin. The walker treats a near-empty env
// as "no type info available" and falls back to syntactic receiver
// candidates. Determinism is preserved (env in == env out for both runs)
// which is all the differential harness needs.
env := types.Environment.new()
return ParsedFixture{
files: files
flat: flat
env: env
}
}
// rand_suffix produces a short pseudo-unique tag so concurrent test runs
// don't trample each other's tempfiles. We avoid pulling in `rand` so the
// harness has no dependency on RNG seeding inside the test binary.
fn rand_suffix() string {
t := u64(time.now().unix_milli()) & 0xFFFFFFFF
return '${t:x}'
}
// DiffReport captures the symmetric difference between two used_keys maps.
// It is a struct (not a bool) so failing tests can print exactly which keys
// drifted, which is essential for debugging walker bugs in 100+-key fixtures.
struct DiffReport {
only_in_a []string
only_in_b []string
equal bool
}
fn diff_used_keys(a map[string]bool, b map[string]bool) DiffReport {
mut only_a := []string{}
mut only_b := []string{}
for k, _ in a {
if k !in b {
only_a << k
}
}
for k, _ in b {
if k !in a {
only_b << k
}
}
only_a.sort()
only_b.sort()
return DiffReport{
only_in_a: only_a
only_in_b: only_b
equal: only_a.len == 0 && only_b.len == 0
}
}
fn assert_used_keys_equal(label string, a map[string]bool, b map[string]bool) {
d := diff_used_keys(a, b)
if d.equal {
return
}
eprintln('[${label}] used_keys diverged:')
if d.only_in_a.len > 0 {
eprintln(' only in A (${d.only_in_a.len}):')
for k in d.only_in_a {
eprintln(' ${k}')
}
}
if d.only_in_b.len > 0 {
eprintln(' only in B (${d.only_in_b.len}):')
for k in d.only_in_b {
eprintln(' ${k}')
}
}
assert false, '${label}: used_keys differ (see above)'
}
// run_legacy is a thin wrapper around mark_used so individual fixtures read
// as "legacy = run_legacy(p); flat = run_flat(p); assert_used_keys_equal".
fn run_legacy(p ParsedFixture) map[string]bool {
return mark_used(p.files, p.env)
}
fn run_flat(p ParsedFixture) map[string]bool {
return mark_used_flat(&p.flat, p.env)
}
// fixtures — each test exercises one slice of the walker dispatch surface.
// Adding a new fixture means: (1) write the smallest V program that hits the
// dispatch arm, (2) assert legacy determinism here, (3) when flat lands,
// flip to assert_used_keys_equal(name, run_legacy(p), run_flat(p)).
const fixture_plain_fn = 'module main
fn add(a int, b int) int {
return a + b
}
fn main() {
println(add(1, 2))
}
'
const fixture_method_call = 'module main
struct Counter {
mut:
n int
}
fn (mut c Counter) inc() {
c.n++
}
fn (c Counter) value() int {
return c.n
}
fn main() {
mut c := Counter{}
c.inc()
println(c.value())
}
'
const fixture_infix_operator = 'module main
struct Vec {
x int
y int
}
fn (a Vec) + (b Vec) Vec {
return Vec{a.x + b.x, a.y + b.y}
}
fn main() {
v := Vec{1, 2} + Vec{3, 4}
println(v.x)
}
'
const fixture_array_and_loop = 'module main
fn sum(xs []int) int {
mut s := 0
for x in xs {
s += x
}
return s
}
fn main() {
xs := [1, 2, 3]
println(sum(xs))
}
'
const fixture_match_expr = 'module main
fn describe(n int) string {
return match n {
0 { "zero" }
1 { "one" }
else { "many" }
}
}
fn main() {
println(describe(1))
}
'
const fixture_if_guard = 'module main
fn maybe(n int) ?int {
if n > 0 {
return n
}
return none
}
fn main() {
if v := maybe(3) {
println(v)
}
}
'
const fixture_global_init = 'module main
__global g_counter = 0
fn bump() {
g_counter++
}
fn main() {
bump()
println(g_counter)
}
'
const fixture_global_init_calls_fn = 'module main
struct Value {
n int
}
fn make_value() Value {
return Value{n: 1}
}
__global state = make_value()
fn main() {
_ = state.n
}
'
// fixture_generic_fn exercises walker dispatch on generic function calls.
// The walker must mark both the generic base and any concrete bindings
// reachable through env.generic_types — the closest analogue of a real
// codebase\'s "[]T → []int" specialization path.
const fixture_generic_fn = 'module main
fn identity[T](x T) T {
return x
}
fn main() {
a := identity[int](42)
b := identity[string]("hi")
println(a)
println(b)
}
'
// fixture_closure exercises closure capture: the walker must not lose
// reachability when the called fn is the value of a local variable that
// itself was assigned a fn literal closing over outer state.
const fixture_closure = 'module main
fn make_counter() fn () int {
mut n := 0
return fn [mut n] () int {
n++
return n
}
}
fn main() {
c := make_counter()
println(c())
println(c())
}
'
// fixture_sumtype_is_as covers the .is / as / smart-cast dispatch arms
// in walk_expr that resolve variant-specific method calls.
const fixture_sumtype_is_as = 'module main
type Shape = Circle | Square
struct Circle {
r f64
}
struct Square {
side f64
}
fn area(s Shape) f64 {
return match s {
Circle { 3.14 * s.r * s.r }
Square { s.side * s.side }
}
}
fn main() {
c := Shape(Circle{r: 2.0})
if c is Circle {
println(c.r)
}
println(area(c))
}
'
// fixture_defer_and_map exercises defer statement walking plus map literal
// init / index / "in" lookup — three distinct walk_stmt / walk_expr arms
// that share no helpers with the simpler fixtures.
const fixture_defer_and_map = 'module main
fn lookup() int {
mut m := {"a": 1, "b": 2}
defer {
m["a"] = 9
}
if "b" in m {
return m["b"]
}
return 0
}
fn main() {
println(lookup())
}
'
// fixture_string_interp_and_array_slice covers string-interpolation
// expressions and array slicing — both have their own walk_expr arms
// with their own sub-expression edges. Uses a raw string (r'...') so
// the outer V parser does not try to interpolate the inner ${...}.
const fixture_string_interp_and_array_slice = r'module main
fn slice_sum(xs []int) string {
tail := xs[1..]
mut s := 0
for x in tail {
s += x
}
return "sum=${s} count=${tail.len}"
}
fn main() {
println(slice_sum([10, 20, 30, 40]))
}
'
fn all_fixtures() []string {
return [
fixture_plain_fn,
fixture_method_call,
fixture_infix_operator,
fixture_array_and_loop,
fixture_match_expr,
fixture_if_guard,
fixture_global_init,
fixture_global_init_calls_fn,
fixture_generic_fn,
fixture_closure,
fixture_sumtype_is_as,
fixture_defer_and_map,
fixture_string_interp_and_array_slice,
]
}
// --- flat-vs-legacy diff tests ---
// These are the real safety net for the markused migration. Each PR that
// rewrites part of mark_used_flat must keep every fixture green. While the
// shim still calls flat.to_files() + the legacy walker, both runs must
// trivially produce the same map — once the body diverges, these fixtures
// catch regressions immediately and the DiffReport pinpoints which key set
// drifted.
fn test_flat_matches_legacy_plain_fn() {
p := parse_fixture(fixture_plain_fn)
assert_used_keys_equal('plain_fn', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_method_call() {
p := parse_fixture(fixture_method_call)
assert_used_keys_equal('method_call', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_infix_operator() {
p := parse_fixture(fixture_infix_operator)
assert_used_keys_equal('infix_operator', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_array_and_loop() {
p := parse_fixture(fixture_array_and_loop)
assert_used_keys_equal('array_and_loop', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_match_expr() {
p := parse_fixture(fixture_match_expr)
assert_used_keys_equal('match_expr', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_if_guard() {
p := parse_fixture(fixture_if_guard)
assert_used_keys_equal('if_guard', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_global_init() {
p := parse_fixture(fixture_global_init)
assert_used_keys_equal('global_init', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_global_init_calls_fn() {
p := parse_fixture(fixture_global_init_calls_fn)
assert_used_keys_equal('global_init_calls_fn', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_generic_fn() {
p := parse_fixture(fixture_generic_fn)
assert_used_keys_equal('generic_fn', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_closure() {
p := parse_fixture(fixture_closure)
assert_used_keys_equal('closure', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_sumtype_is_as() {
p := parse_fixture(fixture_sumtype_is_as)
assert_used_keys_equal('sumtype_is_as', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_defer_and_map() {
p := parse_fixture(fixture_defer_and_map)
assert_used_keys_equal('defer_and_map', run_legacy(p), run_flat(p))
}
fn test_flat_matches_legacy_string_interp_and_array_slice() {
p := parse_fixture(fixture_string_interp_and_array_slice)
assert_used_keys_equal('string_interp_and_array_slice', run_legacy(p), run_flat(p))
}
// test_flat_is_deterministic — two runs of mark_used_flat on the same fixture
// must produce the same map. Guards against accidental nondeterminism
// (e.g. iteration order over a map of types) creeping in as the body
// of the shim is progressively replaced.
fn test_flat_is_deterministic_all_fixtures() {
for i, src in all_fixtures() {
p := parse_fixture(src)
assert_used_keys_equal('fixture-${i}-determinism', run_flat(p), run_flat(p))
}
}
// test_fixtures_produce_nonempty_results guards against silently broken
// harness setup: every fixture has at least main() reachable, so a zero-key
// result means our parse/typecheck/walk pipeline broke before we even got
// to compare anything.
fn test_fixtures_produce_nonempty_results() {
for i, src in all_fixtures() {
p := parse_fixture(src)
keys := run_legacy(p)
assert keys.len > 0, 'fixture #${i} produced empty used_keys — pipeline broken before walker'
}
}
// test_flat_ast_round_trips_every_fixture verifies the cursor-side machinery
// works end-to-end on the fixture surface: every fixture parses, flattens,
// and is reachable via the cursor API from each file root. This is what
// the future flat walker will consume, so it must not blow up here.
fn test_flat_ast_round_trips_every_fixture() {
for i, src in all_fixtures() {
p := parse_fixture(src)
assert p.flat.files.len > 0, 'fixture #${i}: flat has no files'
for fi in 0 .. p.flat.files.len {
fc := p.flat.file_cursor(fi)
assert fc.root().is_valid(), 'fixture #${i} file ${fi}: invalid root'
assert fc.root().kind() == .file, 'fixture #${i} file ${fi}: root not .file'
// Touch each top-level list so an aux_list miswiring trips here
// rather than deep inside the future walker port.
_ = fc.attrs().len()
_ = fc.imports().len()
_ = fc.stmts().len()
}
}
}
@@ -0,0 +1,28 @@
// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module markused
import v2.ast
import v2.types
// mark_used_flat is the flat-input entry point for the markused pass. It
// accepts a FlatAst — the canonical post-parse representation — instead of
// a rehydrated []ast.File.
//
// The collect_defs phase reads each file's imports and top-level stmts via
// FlatAst.read_file_imports / FileCursor.stmts, so the per-file File struct
// (with its attribute/import/stmt slices and selector_names map) is never
// materialized. Function bodies are still decoded as legacy ast.Stmt and
// stored in FnInfo for walk_collected — that phase is the next slice to
// port. The differential harness in markused_diff_test.v guards
// bit-for-bit equality with the legacy walker at every step.
pub fn mark_used_flat(flat &ast.FlatAst, env &types.Environment) map[string]bool {
return mark_used_flat_with_options(flat, env, MarkUsedOptions{})
}
pub fn mark_used_flat_with_options(flat &ast.FlatAst, env &types.Environment, opts MarkUsedOptions) map[string]bool {
mut w := new_walker([]ast.File{}, env, opts)
w.collect_defs_from_flat(flat)
return w.walk_collected_from_flat(flat)
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff