Skip to content

Commit 2f13152

Browse files
committed
v2: move more logic from cleanc to transformer
1 parent 557138a commit 2f13152

7 files changed

Lines changed: 321 additions & 127 deletions

File tree

‎cmd/v2/test_all.sh‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
cd "$(dirname "$0")"
5+
6+
echo "=== 1/6: Self-host test ==="
7+
bash test_v2_self.sh
8+
9+
echo ""
10+
echo "=== 2/6: Rebuild v2 and run builtin test files ==="
11+
v self && v -o v2 v2.v
12+
./v2 ../../vlib/builtin/array_test.v
13+
./v2 ../../vlib/builtin/string_test.v
14+
./v2 ../../vlib/builtin/map_test.v
15+
16+
echo ""
17+
echo "=== 3/6: SSA backends test (arm64) ==="
18+
v -gc none run test_ssa_backends.v arm64
19+
20+
echo ""
21+
echo "=== 4/6: SSA backends test (cleanc) ==="
22+
v -gc none run test_ssa_backends.v cleanc
23+
24+
echo ""
25+
echo "=== 5/6: Transformer v2 nix test ==="
26+
v ../../vlib/v2/transformer/transformer_v2_nix_test.v
27+
28+
echo ""
29+
echo "=== ALL TESTS PASSED ==="

‎vlib/v2/gen/cleanc/cleanc.v‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -930,9 +930,9 @@ fn (mut g Gen) gen_keyword_operator(node ast.KeywordOperator) {
930930
}
931931
}
932932
.key_typeof {
933+
// typeof should be lowered to StringLiteral by the transformer.
934+
// Fallback: emit a placeholder string.
933935
if node.exprs.len > 0 {
934-
// typeof needs V type names (e.g. "map[rune]int"), not C type names.
935-
// Try to get the raw types.Type from the checker and format as V string.
936936
mut type_name := ''
937937
if raw_type := g.get_raw_type(node.exprs[0]) {
938938
type_name = g.types_type_to_v(raw_type)

‎vlib/v2/gen/cleanc/expr.v‎

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -739,53 +739,6 @@ fn (mut g Gen) expr(node ast.Expr) {
739739
g.sb.write_string(')')
740740
return
741741
}
742-
// Map comparison: use Map_K_V_map_eq function.
743-
// Exclude pointer-to-map types (e.g. Map_string_int*) — those are pointer comparisons.
744-
is_lhs_map := (lhs_type == 'map' || lhs_type.starts_with('Map_'))
745-
&& !lhs_type.ends_with('*')
746-
is_rhs_map := (rhs_type == 'map' || rhs_type.starts_with('Map_'))
747-
&& !rhs_type.ends_with('*')
748-
if node.op in [.eq, .ne] && (is_lhs_map || is_rhs_map) {
749-
// Determine specific map type name for the eq function.
750-
// get_expr_type often returns generic 'map', so use get_raw_type
751-
// to resolve the specific Map_K_V type for proper comparison.
752-
mut map_type_name := if lhs_type.starts_with('Map_') {
753-
lhs_type
754-
} else if rhs_type.starts_with('Map_') {
755-
rhs_type
756-
} else {
757-
'map'
758-
}
759-
if map_type_name == 'map' {
760-
if raw := g.get_raw_type(node.lhs) {
761-
// Unwrap pointer types to get the base map type
762-
base := if raw is types.Pointer { raw.base_type } else { raw }
763-
c_type := g.types_type_to_c(base)
764-
if c_type.starts_with('Map_') {
765-
map_type_name = c_type
766-
}
767-
}
768-
}
769-
if map_type_name == 'map' {
770-
if raw := g.get_raw_type(node.rhs) {
771-
base := if raw is types.Pointer { raw.base_type } else { raw }
772-
c_type := g.types_type_to_c(base)
773-
if c_type.starts_with('Map_') {
774-
map_type_name = c_type
775-
}
776-
}
777-
}
778-
eq_fn := '${map_type_name}_map_eq'
779-
if node.op == .ne {
780-
g.sb.write_string('!')
781-
}
782-
g.sb.write_string('${eq_fn}(')
783-
g.expr(node.lhs)
784-
g.sb.write_string(', ')
785-
g.expr(node.rhs)
786-
g.sb.write_string(')')
787-
return
788-
}
789742
mut cmp_type := ''
790743
if g.should_use_memcmp_eq(lhs_type, rhs_type) {
791744
cmp_type = lhs_type
@@ -1066,12 +1019,6 @@ fn (mut g Gen) expr(node ast.Expr) {
10661019
panic('bug in v2 compiler: CallOrCastExpr should have been lowered in v2.transformer')
10671020
}
10681021
ast.SelectorExpr {
1069-
// typeof(x).name -> just emit the typeof string directly (already a string)
1070-
if node.lhs is ast.KeywordOperator && node.lhs.op == .key_typeof
1071-
&& node.rhs.name == 'name' {
1072-
g.gen_keyword_operator(node.lhs)
1073-
return
1074-
}
10751022
// C.<ident> references C macros/constants directly (e.g. C.EOF -> EOF).
10761023
if node.lhs is ast.Ident && node.lhs.name == 'C' {
10771024
g.sb.write_string(node.rhs.name)
@@ -1244,30 +1191,6 @@ fn (mut g Gen) expr(node ast.Expr) {
12441191
}
12451192
return
12461193
}
1247-
// Nested module reference: rand.seed.time_seed_array => seed__time_seed_array
1248-
// Only applies when the resolved name is a known function.
1249-
if node.lhs is ast.SelectorExpr {
1250-
inner_sel := node.lhs as ast.SelectorExpr
1251-
if inner_sel.lhs is ast.Ident {
1252-
inner_ident := inner_sel.lhs as ast.Ident
1253-
if g.is_module_ident(inner_ident.name) {
1254-
sub_mod := inner_sel.rhs.name
1255-
short_name := '${sub_mod}__${node.rhs.name}'
1256-
if short_name in g.fn_return_types || short_name in g.fn_param_is_ptr {
1257-
g.sb.write_string(short_name)
1258-
return
1259-
}
1260-
mod_name := g.resolve_module_name(inner_ident.name)
1261-
full_name := '${mod_name}__${sub_mod}__${node.rhs.name}'
1262-
if full_name in g.fn_return_types || full_name in g.fn_param_is_ptr {
1263-
g.sb.write_string(full_name)
1264-
return
1265-
}
1266-
// Not a known function — fall through to normal SelectorExpr
1267-
// handling (e.g. os.args.len is a field access, not a module call).
1268-
}
1269-
}
1270-
}
12711194
// Check if LHS is an enum type name -> emit EnumName__field
12721195
if node.lhs is ast.Ident && g.is_enum_type(node.lhs.name) {
12731196
enum_name := g.get_qualified_name(node.lhs.name)

‎vlib/v2/gen/cleanc/fn.v‎

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -987,31 +987,6 @@ fn (mut g Gen) resolve_container_method_name(receiver ast.Expr, method_name stri
987987
return ''
988988
}
989989

990-
// resolve_nested_module_call checks if a SelectorExpr represents a nested module
991-
// function call (e.g. rand.seed.time_seed_array) and writes the resolved name.
992-
fn (mut g Gen) resolve_nested_module_call(sel ast.SelectorExpr, method string, name &string) bool {
993-
if sel.lhs is ast.SelectorExpr {
994-
inner := sel.lhs as ast.SelectorExpr
995-
if inner.lhs is ast.Ident && g.is_module_ident(inner.lhs.name) {
996-
sub_mod := inner.rhs.name
997-
fn_name := sanitize_fn_ident(method)
998-
short_name := '${sub_mod}__${fn_name}'
999-
if short_name in g.fn_return_types || short_name in g.fn_param_is_ptr {
1000-
unsafe {
1001-
*name = short_name
1002-
}
1003-
return true
1004-
}
1005-
mod_name := g.resolve_module_name(inner.lhs.name)
1006-
unsafe {
1007-
*name = '${mod_name}__${sub_mod}__${fn_name}'
1008-
}
1009-
return true
1010-
}
1011-
}
1012-
return false
1013-
}
1014-
1015990
fn (mut g Gen) resolve_call_name(lhs ast.Expr, arg_count int) string {
1016991
mut name := ''
1017992
if lhs is ast.Ident {
@@ -1027,8 +1002,6 @@ fn (mut g Gen) resolve_call_name(lhs ast.Expr, arg_count int) string {
10271002
if lhs.lhs is ast.Ident && g.is_module_ident(lhs.lhs.name) {
10281003
mod_name := g.resolve_module_name(lhs.lhs.name)
10291004
name = '${mod_name}__${sanitize_fn_ident(lhs.rhs.name)}'
1030-
} else if g.resolve_nested_module_call(lhs, lhs.rhs.name, &name) {
1031-
// Nested module reference resolved (e.g. rand.seed.time_seed_array)
10321005
} else {
10331006
method_name := sanitize_fn_ident(lhs.rhs.name)
10341007
base_type := g.method_receiver_base_type(lhs.lhs)
@@ -1166,24 +1139,6 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) {
11661139
}
11671140
}
11681141
}
1169-
// Nested module function call: rand.seed.time_seed_array(2) => seed__time_seed_array(2)
1170-
// Must be checked before the fn_pointer_expr check, because the checker resolves
1171-
// the nested module function reference as a FnType, which would trigger the
1172-
// function pointer cast path.
1173-
if lhs is ast.SelectorExpr && lhs.lhs is ast.SelectorExpr {
1174-
mut nested_name := ''
1175-
if g.resolve_nested_module_call(lhs, lhs.rhs.name, &nested_name) {
1176-
g.sb.write_string('${nested_name}(')
1177-
for i, arg in args {
1178-
if i > 0 {
1179-
g.sb.write_string(', ')
1180-
}
1181-
g.expr(arg)
1182-
}
1183-
g.sb.write_string(')')
1184-
return
1185-
}
1186-
}
11871142
if lhs is ast.SelectorExpr && g.is_fn_pointer_expr(lhs) {
11881143
mut should_emit_fnptr_call := true
11891144
resolved := g.resolve_call_name(lhs, args.len)
@@ -1299,9 +1254,6 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) {
12991254
} else if lhs.lhs is ast.Ident && g.is_module_ident(lhs.lhs.name) {
13001255
mod_name := g.resolve_module_name(lhs.lhs.name)
13011256
name = '${mod_name}__${sanitize_fn_ident(lhs.rhs.name)}'
1302-
} else if g.resolve_nested_module_call(lhs, lhs.rhs.name, &name) {
1303-
// Nested module reference (e.g. rand.seed.time_seed_array)
1304-
// Don't add lhs.lhs as receiver - it's a module path, not a value.
13051257
} else {
13061258
// value.method(args...) => ReceiverType__method(value, args...)
13071259
name = g.resolve_call_name(lhs, args.len)

‎vlib/v2/transformer/expr.v‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,19 @@ fn (mut t Transformer) transform_expr(expr ast.Expr) ast.Expr {
294294
pos: expr.pos
295295
})
296296
}
297+
ast.KeywordOperator {
298+
if expr.op == .key_typeof && expr.exprs.len > 0 {
299+
type_name := t.resolve_typeof_expr(expr.exprs[0])
300+
if type_name != '' {
301+
return ast.StringLiteral{
302+
kind: .v
303+
value: quote_v_string_literal(type_name)
304+
pos: expr.pos
305+
}
306+
}
307+
}
308+
expr
309+
}
297310
else {
298311
expr
299312
}
@@ -577,6 +590,19 @@ fn (mut t Transformer) transform_slice_index_expr(lhs ast.Expr, orig_lhs ast.Exp
577590

578591
// transform_selector_expr transforms a selector expression, applying smart cast if applicable
579592
fn (mut t Transformer) transform_selector_expr(expr ast.SelectorExpr) ast.Expr {
593+
// typeof(x).name -> string literal with V type name
594+
if expr.lhs is ast.KeywordOperator && expr.lhs.op == .key_typeof && expr.rhs.name == 'name' {
595+
if expr.lhs.exprs.len > 0 {
596+
type_name := t.resolve_typeof_expr(expr.lhs.exprs[0])
597+
if type_name != '' {
598+
return ast.StringLiteral{
599+
kind: .v
600+
value: quote_v_string_literal(type_name)
601+
pos: expr.pos
602+
}
603+
}
604+
}
605+
}
580606
// Check for smart cast field access: check ALL contexts in the stack
581607
if t.has_active_smartcast() {
582608
full_str := t.expr_to_string(expr)
@@ -595,6 +621,7 @@ fn (mut t Transformer) transform_selector_expr(expr ast.SelectorExpr) ast.Expr {
595621
}
596622
}
597623
// Handle module-qualified enum value access: module.EnumType.value -> module__EnumType__value
624+
// Also handle nested module references: rand.seed.time_seed_array -> seed__time_seed_array
598625
if expr.lhs is ast.SelectorExpr {
599626
lhs_sel := expr.lhs as ast.SelectorExpr
600627
if lhs_sel.lhs is ast.Ident {
@@ -609,6 +636,28 @@ fn (mut t Transformer) transform_selector_expr(expr ast.SelectorExpr) ast.Expr {
609636
}
610637
}
611638
}
639+
// Nested module reference: rand.seed.time_seed_array -> seed__time_seed_array
640+
// Only resolve when both the outer ident is a module AND the inner name
641+
// is also a sub-module (not a variable like os.args.len).
642+
if t.is_module_ident(module_name) {
643+
sub_mod := lhs_sel.rhs.name
644+
fn_name := expr.rhs.name
645+
// Check if sub_mod is actually a module scope
646+
if t.get_module_scope(sub_mod) != none {
647+
return ast.Ident{
648+
name: '${sub_mod}__${fn_name}'
649+
pos: expr.pos
650+
}
651+
}
652+
// Try full qualified name (module__sub_mod as scope key)
653+
full_mod := '${module_name}__${sub_mod}'
654+
if t.get_module_scope(full_mod) != none {
655+
return ast.Ident{
656+
name: '${full_mod}__${fn_name}'
657+
pos: expr.pos
658+
}
659+
}
660+
}
612661
}
613662
}
614663
// Default transformation
@@ -2382,6 +2431,45 @@ fn (mut t Transformer) transform_infix_expr(expr ast.InfixExpr) ast.Expr {
23822431
// arr1 == arr2 -> array__eq(arr1, arr2)
23832432
return eq_call
23842433
}
2434+
// Check for map comparisons: map1 == map2 or map1 != map2
2435+
// Exclude pointer-to-map types (those should be pointer comparisons)
2436+
if expr.op in [.eq, .ne] {
2437+
mut is_lhs_ptr_map := false
2438+
if lhs_type := t.get_expr_type(expr.lhs) {
2439+
if lhs_type is types.Pointer {
2440+
is_lhs_ptr_map = true
2441+
}
2442+
}
2443+
mut is_rhs_ptr_map := false
2444+
if rhs_type := t.get_expr_type(expr.rhs) {
2445+
if rhs_type is types.Pointer {
2446+
is_rhs_ptr_map = true
2447+
}
2448+
}
2449+
if !is_lhs_ptr_map && !is_rhs_ptr_map {
2450+
lhs_map_type := t.get_map_type_for_expr(expr.lhs)
2451+
rhs_map_type := t.get_map_type_for_expr(expr.rhs)
2452+
if lhs_map_type != none || rhs_map_type != none {
2453+
map_type_name := lhs_map_type or { rhs_map_type or { 'map' } }
2454+
eq_fn := '${map_type_name}_map_eq'
2455+
map_eq_call := ast.CallExpr{
2456+
lhs: ast.Ident{
2457+
name: eq_fn
2458+
}
2459+
args: [t.transform_expr(expr.lhs), t.transform_expr(expr.rhs)]
2460+
pos: expr.pos
2461+
}
2462+
if expr.op == .ne {
2463+
return ast.PrefixExpr{
2464+
op: .not
2465+
expr: map_eq_call
2466+
pos: expr.pos
2467+
}
2468+
}
2469+
return map_eq_call
2470+
}
2471+
}
2472+
}
23852473
}
23862474
// Check for struct operator overloading (e.g., time.Time - time.Time)
23872475
// This transforms t1 - t2 into time__Time__minus(t1, t2) for structs with operator overloading

‎vlib/v2/transformer/fn.v‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,37 @@ fn (mut t Transformer) transform_call_expr(expr ast.CallExpr) ast.Expr {
874874
// Method call resolution: rewrite receiver.method(args) -> Type__method(receiver, args)
875875
if expr.lhs is ast.SelectorExpr {
876876
sel := expr.lhs as ast.SelectorExpr
877+
// Nested module call: rand.seed.time_seed_array() -> seed__time_seed_array()
878+
if sel.lhs is ast.SelectorExpr {
879+
inner := sel.lhs as ast.SelectorExpr
880+
if inner.lhs is ast.Ident && t.is_module_ident(inner.lhs.name) {
881+
sub_mod := inner.rhs.name
882+
fn_name := sel.rhs.name
883+
// Check if sub_mod is actually a module scope (not a variable like os.args)
884+
mut resolved_name := ''
885+
if t.get_module_scope(sub_mod) != none {
886+
resolved_name = '${sub_mod}__${fn_name}'
887+
} else {
888+
full_mod := '${inner.lhs.name}__${sub_mod}'
889+
if t.get_module_scope(full_mod) != none {
890+
resolved_name = '${full_mod}__${fn_name}'
891+
}
892+
}
893+
if resolved_name != '' {
894+
mut args := []ast.Expr{cap: expr.args.len}
895+
for arg in expr.args {
896+
args << t.transform_expr(arg)
897+
}
898+
return ast.CallExpr{
899+
lhs: ast.Ident{
900+
name: resolved_name
901+
}
902+
args: args
903+
pos: expr.pos
904+
}
905+
}
906+
}
907+
}
877908
is_module_call := sel.lhs is ast.Ident && t.get_module_scope(sel.lhs.name) != none
878909
&& t.lookup_var_type(sel.lhs.name) == none
879910
if !is_module_call {

0 commit comments

Comments
 (0)