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

+873
View File
@@ -0,0 +1,873 @@
module transform
import v3.flat
import v3.types
fn (mut t Transformer) make_array_new_call(elem_type string, len_expr flat.NodeId, cap_expr flat.NodeId) flat.NodeId {
return t.make_call_typed('array_new', arr3(t.make_sizeof_type(elem_type), len_expr, cap_expr),
'[]${elem_type}')
}
fn (mut t Transformer) lower_array_init_to_runtime(id flat.NodeId, node flat.Node) flat.NodeId {
if node.value.len == 0 || is_fixed_array_type(node.value) {
return id
}
elem_type := node.value
mut len_expr := t.make_int_literal(0)
mut cap_expr := t.make_int_literal(0)
mut init_expr := flat.empty_node
for i in 0 .. node.children_count {
child := t.a.child_node(&node, i)
if child.kind == .field_init && child.children_count > 0 {
val := t.transform_expr(t.a.child(child, 0))
if child.value == 'len' {
len_expr = val
} else if child.value == 'cap' {
cap_expr = val
} else if child.value == 'init' {
init_expr = val
}
}
}
new_call := t.make_array_new_call(elem_type, len_expr, cap_expr)
if int(init_expr) < 0 {
return new_call
}
tmp_name := t.new_temp('arr_init')
idx_name := t.new_temp('arr_idx')
t.pending_stmts << t.make_decl_assign_typed(tmp_name, new_call, '[]${elem_type}')
init_idx := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(t.make_ident(tmp_name),
'len', 'int'))
post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
elem_lhs := t.make_index(t.make_ident(tmp_name), t.make_ident(idx_name), elem_type)
assign := t.make_assign(elem_lhs, init_expr)
t.pending_stmts << t.make_for_stmt(init_idx, cond, post, arr1(assign), node)
return t.make_ident(tmp_name)
}
fn (mut t Transformer) lower_array_literal_to_runtime(id flat.NodeId, node flat.Node) flat.NodeId {
array_type := t.node_type(id)
if !array_type.starts_with('[]') {
return id
}
elem_type := array_type[2..]
tmp_name := t.new_temp('arr_lit')
t.pending_stmts << t.make_decl_assign_typed(tmp_name, t.make_array_new_call(elem_type,
t.make_int_literal(0), t.make_int_literal(node.children_count)), array_type)
for i in 0 .. node.children_count {
elem_id := t.a.child(&node, i)
elem := t.a.nodes[int(elem_id)]
if elem.kind == .prefix && elem.value == '...' && elem.children_count > 0 {
spread := t.transform_expr(t.a.child(&elem, 0))
call := t.make_call_typed('array_push_many', arr2(t.make_prefix(.amp,
t.make_ident(tmp_name)), spread), 'void')
t.pending_stmts << t.make_expr_stmt(call)
continue
}
value_name := t.new_temp('arr_val')
value := if elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types {
t.wrap_sum_value(elem_id, elem_type)
} else {
t.transform_expr(elem_id)
}
t.pending_stmts << t.make_decl_assign_typed(value_name, value, elem_type)
call := t.make_call_typed('array_push', arr2(t.make_prefix(.amp, t.make_ident(tmp_name)), t.make_prefix(.amp,
t.make_ident(value_name))), 'void')
t.pending_stmts << t.make_expr_stmt(call)
}
return t.make_ident(tmp_name)
}
fn (mut t Transformer) transform_array_literal_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
array_type := t.normalize_type_alias(target_type)
if !array_type.starts_with('[]') {
return none
}
elem_type := array_type[2..]
tmp_name := t.new_temp('arr_lit')
t.pending_stmts << t.make_decl_assign_typed(tmp_name, t.make_array_new_call(elem_type,
t.make_int_literal(0), t.make_int_literal(node.children_count)), array_type)
for i in 0 .. node.children_count {
elem_id := t.a.child(&node, i)
elem := t.a.nodes[int(elem_id)]
if elem.kind == .prefix && elem.value == '...' && elem.children_count > 0 {
spread := t.transform_expr_for_type(t.a.child(&elem, 0), array_type)
call := t.make_call_typed('array_push_many', arr2(t.make_prefix(.amp,
t.make_ident(tmp_name)), spread), 'void')
t.pending_stmts << t.make_expr_stmt(call)
continue
}
value_name := t.new_temp('arr_val')
value := if elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types {
t.wrap_sum_value(elem_id, elem_type)
} else {
t.transform_expr_for_type(elem_id, elem_type)
}
t.pending_stmts << t.make_decl_assign_typed(value_name, value, elem_type)
call := t.make_call_typed('array_push', arr2(t.make_prefix(.amp, t.make_ident(tmp_name)), t.make_prefix(.amp,
t.make_ident(value_name))), 'void')
t.pending_stmts << t.make_expr_stmt(call)
}
return t.make_ident(tmp_name)
}
fn (mut t Transformer) transform_empty_array_init_for_type(node flat.Node, target_type string) ?flat.NodeId {
if node.value.len > 0 || node.children_count > 0 {
return none
}
array_type := t.normalize_type_alias(target_type)
if !array_type.starts_with('[]') {
return none
}
elem_type := array_type[2..]
return t.make_array_new_call(elem_type, t.make_int_literal(0), t.make_int_literal(0))
}
fn (mut t Transformer) try_lower_array_append_stmt(id flat.NodeId) ?[]flat.NodeId {
if int(id) < 0 {
return none
}
node := t.a.nodes[int(id)]
if node.kind != .infix || node.op != .left_shift || node.children_count < 2 {
return none
}
lhs_id := t.a.child(&node, 0)
mut lhs_type := t.lvalue_type(lhs_id)
lhs_type = t.normalize_type_alias(lhs_type)
mut array_type := t.clean_array_append_lhs_type(lhs_type)
if !array_type.starts_with('[]') {
return none
}
elem_type := array_type[2..]
rhs_id := t.a.child(&node, 1)
mut rhs_type := t.normalize_type_alias(t.node_type(rhs_id))
rhs_node := t.a.nodes[int(rhs_id)]
mut push_many := t.array_append_rhs_is_push_many(lhs_id, rhs_id, rhs_type, elem_type)
if push_many && rhs_node.kind == .array_literal && !rhs_type.starts_with('[]') {
t.a.nodes[int(rhs_id)].typ = array_type
rhs_type = array_type
}
mut result := []flat.NodeId{}
lhs := t.transform_lvalue(lhs_id)
t.drain_pending(mut result)
mut rhs := if !push_many
&& (elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types) {
t.wrap_sum_value(rhs_id, elem_type)
} else {
t.transform_expr(rhs_id)
}
if !push_many {
rhs = t.coerce_transformed_expr_to_type(rhs, rhs_id, elem_type)
}
t.drain_pending(mut result)
if rhs_type.len == 0 {
rhs_type = t.node_type(rhs)
push_many = t.array_append_rhs_is_push_many(lhs_id, rhs_id, rhs_type, elem_type)
}
lhs_addr := t.runtime_addr(lhs, lhs_type)
if push_many {
call := if is_fixed_array_type(rhs_type) {
t.make_call_typed('array_push_many_ptr', arr3(lhs_addr, rhs,
t.make_fixed_array_len_expr(rhs_type)), 'void')
} else {
t.make_call_typed('array_push_many', arr2(lhs_addr, rhs), 'void')
}
result << t.make_expr_stmt(call)
return result
}
value_name := t.new_temp('arr_val')
result << t.make_decl_assign_typed(value_name, rhs, elem_type)
result << t.make_expr_stmt(t.make_call_typed('array_push', arr2(lhs_addr, t.make_prefix(.amp,
t.make_ident(value_name))), 'void'))
return result
}
fn (t &Transformer) clean_array_append_lhs_type(typ string) string {
mut clean := t.normalize_type_alias(typ).trim_space()
for {
if clean.starts_with('&') {
clean = clean[1..].trim_space()
continue
}
if clean.starts_with('shared ') {
clean = clean[7..].trim_space()
continue
}
if clean.starts_with('atomic ') {
clean = clean[7..].trim_space()
continue
}
break
}
return clean
}
fn (mut t Transformer) lower_array_prepend_call(node flat.Node, fn_node flat.Node, base_type string, elem_type string) ?flat.NodeId {
if node.children_count < 2 || fn_node.children_count == 0 {
return none
}
base_id := t.a.child(&fn_node, 0)
value_id := t.a.child(&node, 1)
base := t.transform_lvalue(base_id)
value := if elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types {
t.wrap_sum_value(value_id, elem_type)
} else {
t.transform_expr_for_type(value_id, elem_type)
}
value_name := t.new_temp('arr_val')
t.pending_stmts << t.make_decl_assign_typed(value_name, value, elem_type)
return t.make_call_typed('array__prepend', arr2(t.runtime_addr(base, base_type), t.make_prefix(.amp,
t.make_ident(value_name))), 'void')
}
fn (mut t Transformer) lower_array_insert_call(node flat.Node, fn_node flat.Node, base_type string, elem_type string) ?flat.NodeId {
if node.children_count < 3 || fn_node.children_count == 0 {
return none
}
base_id := t.a.child(&fn_node, 0)
index_id := t.a.child(&node, 1)
value_id := t.a.child(&node, 2)
base := t.transform_lvalue(base_id)
index := t.transform_expr_for_type(index_id, 'int')
value := if elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types {
t.wrap_sum_value(value_id, elem_type)
} else {
t.transform_expr_for_type(value_id, elem_type)
}
value_name := t.new_temp('arr_val')
t.pending_stmts << t.make_decl_assign_typed(value_name, value, elem_type)
return t.make_call_typed('array__insert', arr3(t.runtime_addr(base, base_type), index, t.make_prefix(.amp,
t.make_ident(value_name))), 'void')
}
fn (t &Transformer) array_append_rhs_is_push_many(lhs_id flat.NodeId, rhs_id flat.NodeId, rhs_type string, elem_type string) bool {
clean_rhs_type := rhs_type.trim_space()
if clean_rhs_type.starts_with('[]') {
if t.array_append_elem_types_match(clean_rhs_type[2..], elem_type) {
return true
}
if declared_rhs_type := t.array_append_ident_type(rhs_id) {
if declared_rhs_type.starts_with('[]') {
return t.array_append_elem_types_match(declared_rhs_type[2..], elem_type)
}
}
return false
}
if is_fixed_array_type(clean_rhs_type) {
return t.array_append_elem_types_match(fixed_array_elem_type(clean_rhs_type), elem_type)
}
if !isnil(t.tc) {
if rhs_resolved := t.tc.expr_type(rhs_id) {
rhs_clean := types.unwrap_pointer(rhs_resolved)
if rhs_clean is types.Array {
return t.array_append_elem_types_match(rhs_clean.elem_type.name(), elem_type)
}
if rhs_clean is types.ArrayFixed {
return t.array_append_elem_types_match(rhs_clean.elem_type.name(), elem_type)
}
}
if lhs_resolved := t.tc.expr_type(lhs_id) {
lhs_clean := types.unwrap_pointer(lhs_resolved)
if lhs_clean is types.Array && clean_rhs_type in ['array', 'Array'] {
return t.tc.c_type(lhs_clean.elem_type) == 'void*'
}
}
}
if clean_rhs_type in ['array', 'Array'] {
return t.array_append_elem_c_type(elem_type) !in ['array', 'Array']
}
return false
}
fn (t &Transformer) array_append_elem_types_match(rhs_elem_type string, lhs_elem_type string) bool {
rhs_raw := rhs_elem_type.trim_space()
lhs_raw := lhs_elem_type.trim_space()
if rhs_raw == lhs_raw {
return true
}
rhs_clean := t.normalize_type_alias(rhs_elem_type)
lhs_clean := t.normalize_type_alias(lhs_elem_type)
if rhs_clean == lhs_clean {
return true
}
if isnil(t.tc) {
return false
}
return t.array_append_elem_c_type(rhs_clean) == t.array_append_elem_c_type(lhs_clean)
}
fn (t &Transformer) array_append_ident_type(id flat.NodeId) ?string {
if int(id) < 0 {
return none
}
node := t.a.nodes[int(id)]
if node.kind != .ident || node.value.len == 0 {
return none
}
typ := t.var_type(node.value)
if typ.len == 0 {
return none
}
return typ
}
fn (t &Transformer) array_append_elem_c_type(typ string) string {
if isnil(t.tc) {
return typ
}
clean := typ.trim_space()
if clean.len == 0 {
return clean
}
if !clean.contains('.') {
for alias, target in t.tc.type_aliases {
if alias.all_after_last('.') == clean {
return t.tc.c_type(t.tc.parse_type(target))
}
}
}
return t.tc.c_type(t.tc.parse_type(clean))
}
fn (mut t Transformer) array_get_value(base flat.NodeId, index flat.NodeId, elem_type string) flat.NodeId {
get_call := t.make_call_typed('array_get', arr2(base, index), 'voidptr')
ptr := t.make_cast('&${elem_type}', get_call, '&${elem_type}')
value := t.make_prefix(.mul, ptr)
t.a.nodes[int(value)].typ = elem_type
return value
}
fn (mut t Transformer) array_get_ptr(base flat.NodeId, index flat.NodeId, elem_type string) flat.NodeId {
get_call := t.make_call_typed('array_get', arr2(base, index), 'voidptr')
return t.make_cast('&${elem_type}', get_call, '&${elem_type}')
}
fn (mut t Transformer) lower_array_filter_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
if node.children_count < 2 || !base_type.starts_with('[]') {
return none
}
elem_type := base_type[2..]
base_id := t.a.child(&fn_node, 0)
base := t.stable_expr_for_reuse(base_id)
mut prefix := []flat.NodeId{}
t.drain_pending(mut prefix)
out_name := t.new_temp('filter')
idx_name := t.new_temp('filter_idx')
prefix << t.make_decl_assign_typed(out_name, t.make_array_new_call(elem_type,
t.make_int_literal(0), t.make_selector(base, 'len', 'int')), base_type)
init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(base, 'len', 'int'))
post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
elem_name_default := t.new_temp('filter_it')
elem_expr := t.array_get_value(base, t.make_ident(idx_name), elem_type)
predicate_id := t.a.child(&node, 1)
predicate_node := t.a.nodes[int(predicate_id)]
mut predicate_expr_id := predicate_id
mut lambda_param := ''
if predicate_node.kind == .lambda_expr && predicate_node.children_count > 0 {
predicate_expr_id = t.a.child(&predicate_node, predicate_node.children_count - 1)
if predicate_node.children_count > 1 {
param := t.a.child_node(&predicate_node, 0)
if param.kind == .ident && param.value.len > 0 {
lambda_param = param.value
}
}
}
elem_name := if lambda_param.len > 0 { lambda_param } else { elem_name_default }
elem_decl := t.make_decl_assign_typed(elem_name, elem_expr, elem_type)
old_elem := t.var_type(elem_name)
t.set_var_type(elem_name, elem_type)
predicate_source := if lambda_param.len > 0 {
predicate_expr_id
} else {
t.substitute_ident(predicate_expr_id, 'it', elem_name)
}
predicate := t.transform_expr(predicate_source)
if old_elem.len > 0 {
t.set_var_type(elem_name, old_elem)
} else {
t.unset_var_type(elem_name)
}
mut loop_body := []flat.NodeId{}
loop_body << elem_decl
t.drain_pending(mut loop_body)
push_call := t.make_call_typed('array_push', arr2(t.make_prefix(.amp, t.make_ident(out_name)), t.make_prefix(.amp,
t.make_ident(elem_name))), 'void')
then_block := t.make_block(arr1(t.make_expr_stmt(push_call)))
loop_body << t.make_if(predicate, then_block, t.make_empty())
prefix << t.make_for_stmt(init, cond, post, loop_body, node)
for stmt in prefix {
t.pending_stmts << stmt
}
return t.make_ident(out_name)
}
fn (mut t Transformer) lower_array_map_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
if node.children_count < 2 || !base_type.starts_with('[]') {
return none
}
elem_type := base_type[2..]
map_expr_id := t.a.child(&node, 1)
map_expr := t.a.nodes[int(map_expr_id)]
mut map_fn_name := ''
elem_name := t.new_temp('map_it')
old_elem := t.var_type(elem_name)
t.set_var_type(elem_name, elem_type)
mapped_source := t.substitute_ident(map_expr_id, 'it', elem_name)
mut result_elem_type := t.node_type(map_expr_id)
mut direct_selector_type := ''
mapped_source_node := t.a.nodes[int(mapped_source)]
if mapped_source_node.kind == .selector {
selector_type := t.resolve_selector_type(mapped_source_node)
if selector_type.len > 0 {
direct_selector_type = selector_type
result_elem_type = selector_type
}
}
if map_expr.kind == .ident {
if fn_name := t.resolve_fn_value_ident(map_expr.value) {
map_fn_name = fn_name
if ret := t.fn_ret_types[fn_name] {
result_elem_type = ret
} else if !isnil(t.tc) {
if ret_type := t.tc.fn_ret_types[fn_name] {
result_elem_type = t.normalize_type_alias(ret_type.name())
}
}
}
}
if result_elem_type.len == 0 {
result_elem_type = t.reliable_stringify_type(map_expr_id)
}
saved_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
mapped_expr := if map_fn_name.len > 0 {
t.make_call_typed(map_fn_name, arr1(t.make_ident(elem_name)), result_elem_type)
} else {
t.transform_expr(mapped_source)
}
mapped_pending := t.pending_stmts.clone()
t.pending_stmts = saved_pending
if old_elem.len > 0 {
t.set_var_type(elem_name, old_elem)
} else {
t.unset_var_type(elem_name)
}
mapped_type := t.node_type(mapped_expr)
if mapped_type.len > 0 && mapped_type != 'unknown' {
result_elem_type = mapped_type
}
if direct_selector_type.len > 0 {
result_elem_type = direct_selector_type
}
if mapped_expr_node := t.selector_expr_node(mapped_expr) {
selector_type := t.resolve_selector_type(mapped_expr_node)
if selector_type.len > 0 {
result_elem_type = selector_type
}
}
if result_elem_type.len == 0 {
result_elem_type = elem_type
}
out_type := '[]${result_elem_type}'
base_id := t.a.child(&fn_node, 0)
base := t.stable_expr_for_reuse(base_id)
mut prefix := []flat.NodeId{}
t.drain_pending(mut prefix)
out_name := t.new_temp('map')
idx_name := t.new_temp('map_idx')
prefix << t.make_decl_assign_typed(out_name, t.make_array_new_call(result_elem_type,
t.make_int_literal(0), t.make_selector(base, 'len', 'int')), out_type)
init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(base, 'len', 'int'))
post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
elem_expr := t.array_get_value(base, t.make_ident(idx_name), elem_type)
elem_decl := t.make_decl_assign_typed(elem_name, elem_expr, elem_type)
mut loop_body := []flat.NodeId{}
loop_body << elem_decl
for stmt in mapped_pending {
loop_body << stmt
}
value_name := t.new_temp('map_val')
loop_body << t.make_decl_assign_typed(value_name, mapped_expr, result_elem_type)
loop_body << t.make_expr_stmt(t.make_call_typed('array_push', arr2(t.make_prefix(.amp,
t.make_ident(out_name)), t.make_prefix(.amp, t.make_ident(value_name))), 'void'))
prefix << t.make_for_stmt(init, cond, post, loop_body, node)
for stmt in prefix {
t.pending_stmts << stmt
}
result := t.make_ident(out_name)
t.a.nodes[int(result)].typ = out_type
return result
}
fn (t &Transformer) selector_expr_node(id flat.NodeId) ?flat.Node {
if int(id) < 0 {
return none
}
node := t.a.nodes[int(id)]
if node.kind == .selector {
return node
}
return none
}
fn (mut t Transformer) substitute_ident(id flat.NodeId, name string, replacement string) flat.NodeId {
if int(id) < 0 || name.len == 0 || replacement.len == 0 || name == replacement {
return id
}
node := t.a.nodes[int(id)]
if node.kind == .ident && node.value == name {
new_id := t.make_ident(replacement)
t.a.nodes[int(new_id)].typ = node.typ
return new_id
}
if node.kind == .lambda_expr && node.children_count > 1 {
first := t.a.child_node(&node, 0)
if first.kind == .ident && first.value == name {
return id
}
}
if node.kind == .call && node.children_count > 1 {
fn_id := t.a.child(&node, 0)
fn_node := t.a.nodes[int(fn_id)]
if fn_node.kind == .selector && fn_node.value in ['any', 'all', 'count', 'filter', 'map'] {
mut new_children := []flat.NodeId{cap: int(node.children_count)}
new_children << t.substitute_ident(fn_id, name, replacement)
for i in 1 .. node.children_count {
new_children << t.a.child(&node, i)
}
start := t.a.children.len
for child in new_children {
t.a.children << child
}
return t.a.add_node(flat.Node{
kind: node.kind
op: node.op
children_start: start
children_count: flat.child_count(new_children.len)
pos: node.pos
value: node.value
typ: node.typ
})
}
}
if node.children_count == 0 {
return id
}
mut new_children := []flat.NodeId{cap: int(node.children_count)}
for i in 0 .. node.children_count {
new_children << t.substitute_ident(t.a.child(&node, i), name, replacement)
}
start := t.a.children.len
for child in new_children {
t.a.children << child
}
return t.a.add_node(flat.Node{
kind: node.kind
op: node.op
children_start: start
children_count: flat.child_count(new_children.len)
pos: node.pos
value: node.value
typ: node.typ
})
}
fn (mut t Transformer) lower_array_count_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
if node.children_count < 2 || !base_type.starts_with('[]') {
return none
}
elem_type := base_type[2..]
base_id := t.a.child(&fn_node, 0)
base := t.stable_expr_for_reuse(base_id)
mut prefix := []flat.NodeId{}
t.drain_pending(mut prefix)
result_name := t.new_temp('count')
idx_name := t.new_temp('count_idx')
prefix << t.make_decl_assign_typed(result_name, t.make_int_literal(0), 'int')
init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(base, 'len', 'int'))
post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
elem_name := t.new_temp('count_it')
elem_expr := t.array_get_value(base, t.make_ident(idx_name), elem_type)
elem_decl := t.make_decl_assign_typed(elem_name, elem_expr, elem_type)
predicate_id := t.a.child(&node, 1)
old_elem := t.var_type(elem_name)
t.set_var_type(elem_name, elem_type)
predicate := t.transform_expr(t.substitute_ident(predicate_id, 'it', elem_name))
if old_elem.len > 0 {
t.set_var_type(elem_name, old_elem)
} else {
t.unset_var_type(elem_name)
}
mut loop_body := []flat.NodeId{}
loop_body << elem_decl
t.drain_pending(mut loop_body)
inc := t.make_assign_op(t.make_ident(result_name), t.make_int_literal(1), .plus_assign)
loop_body << t.make_if(predicate, t.make_block(arr1(inc)), t.make_empty())
prefix << t.make_for_stmt(init, cond, post, loop_body, node)
for stmt in prefix {
t.pending_stmts << stmt
}
return t.make_ident(result_name)
}
fn (mut t Transformer) lower_array_any_all_call(node flat.Node, fn_node flat.Node, base_type string, method string) ?flat.NodeId {
if node.children_count < 2 || !base_type.starts_with('[]') {
return none
}
elem_type := base_type[2..]
base_id := t.a.child(&fn_node, 0)
base := t.stable_expr_for_reuse(base_id)
mut prefix := []flat.NodeId{}
t.drain_pending(mut prefix)
result_name := t.new_temp(method)
idx_name := t.new_temp('${method}_idx')
default_value := if method == 'all' {
t.make_bool_literal(true)
} else {
t.make_bool_literal(false)
}
prefix << t.make_decl_assign_typed(result_name, default_value, 'bool')
init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(base, 'len', 'int'))
post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
elem_name := t.new_temp('${method}_it')
elem_expr := t.array_get_value(base, t.make_ident(idx_name), elem_type)
elem_decl := t.make_decl_assign_typed(elem_name, elem_expr, elem_type)
predicate_id := t.a.child(&node, 1)
old_elem := t.var_type(elem_name)
t.set_var_type(elem_name, elem_type)
predicate := t.transform_expr(t.substitute_ident(predicate_id, 'it', elem_name))
if old_elem.len > 0 {
t.set_var_type(elem_name, old_elem)
} else {
t.unset_var_type(elem_name)
}
mut loop_body := []flat.NodeId{}
loop_body << elem_decl
t.drain_pending(mut loop_body)
if method == 'all' {
not_predicate := t.make_prefix(.not, predicate)
assign_false := t.make_assign(t.make_ident(result_name), t.make_bool_literal(false))
loop_body << t.make_if(not_predicate, t.make_block(arr1(assign_false)), t.make_empty())
} else {
assign_true := t.make_assign(t.make_ident(result_name), t.make_bool_literal(true))
loop_body << t.make_if(predicate, t.make_block(arr1(assign_true)), t.make_empty())
}
prefix << t.make_for_stmt(init, cond, post, loop_body, node)
for stmt in prefix {
t.pending_stmts << stmt
}
return t.make_ident(result_name)
}
fn (mut t Transformer) lower_array_sort_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
if !base_type.starts_with('[]') && !(base_type.starts_with('&')
&& base_type[1..].starts_with('[]')) {
return none
}
if node.children_count > 2 {
return none
}
base_id := t.a.child(&fn_node, 0)
base := t.transform_lvalue(base_id)
clean_type := if base_type.starts_with('&') { base_type[1..] } else { base_type }
elem_type := clean_type[2..]
cmp_id := if node.children_count > 1 { t.a.child(&node, 1) } else { flat.empty_node }
t.pending_stmts << t.make_array_default_sort_stmt(base, elem_type, node, cmp_id)
return t.make_empty()
}
fn (mut t Transformer) lower_array_sorted_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
if node.children_count > 2 || !base_type.starts_with('[]') {
return none
}
base_id := t.a.child(&fn_node, 0)
clone_name := t.new_temp('sorted')
clone_call := t.make_call_typed('array_clone', arr1(t.transform_expr(base_id)), base_type)
t.pending_stmts << t.make_decl_assign_typed(clone_name, clone_call, base_type)
cmp_id := if node.children_count > 1 { t.a.child(&node, 1) } else { flat.empty_node }
t.pending_stmts << t.make_array_default_sort_stmt(t.make_ident(clone_name), base_type[2..],
node, cmp_id)
return t.make_ident(clone_name)
}
fn (mut t Transformer) lower_array_sort_with_compare_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
if node.children_count != 2
|| (!base_type.starts_with('[]') && !(base_type.starts_with('&')
&& base_type[1..].starts_with('[]'))) {
return none
}
base_id := t.a.child(&fn_node, 0)
base := t.transform_lvalue(base_id)
clean_type := if base_type.starts_with('&') { base_type[1..] } else { base_type }
elem_type := clean_type[2..]
cmp := t.stable_array_compare_fn(t.a.child(&node, 1), elem_type)
t.pending_stmts << t.make_array_compare_sort_stmt(base, elem_type, node, cmp)
return t.make_empty()
}
fn (mut t Transformer) lower_array_sorted_with_compare_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
if node.children_count != 2 || !base_type.starts_with('[]') {
return none
}
base_id := t.a.child(&fn_node, 0)
clone_name := t.new_temp('sorted')
clone_call := t.make_call_typed('array_clone', arr1(t.transform_expr(base_id)), base_type)
t.pending_stmts << t.make_decl_assign_typed(clone_name, clone_call, base_type)
elem_type := base_type[2..]
cmp := t.stable_array_compare_fn(t.a.child(&node, 1), elem_type)
t.pending_stmts << t.make_array_compare_sort_stmt(t.make_ident(clone_name), elem_type, node, cmp)
return t.make_ident(clone_name)
}
fn (mut t Transformer) stable_array_compare_fn(cmp_id flat.NodeId, elem_type string) flat.NodeId {
cmp := t.transform_expr(cmp_id)
cmp_type := 'fn (&${elem_type}, &${elem_type}) int'
return t.stable_transformed_expr_for_reuse(cmp, cmp_type, 'sort_cmp')
}
fn (mut t Transformer) make_array_default_sort_stmt(base flat.NodeId, elem_type string, src flat.Node, cmp_id flat.NodeId) flat.NodeId {
i_name := t.new_temp('sort_i')
j_name := t.new_temp('sort_j')
tmp_name := t.new_temp('sort_tmp')
t.set_var_type(i_name, 'int')
t.set_var_type(j_name, 'int')
t.set_var_type(tmp_name, elem_type)
init := t.make_decl_assign_typed(i_name, t.make_int_literal(1), 'int')
cond := t.make_infix(.lt, t.make_ident(i_name), t.make_selector(base, 'len', 'int'))
post := t.make_expr_stmt(t.make_postfix(t.make_ident(i_name), .inc))
j_decl := t.make_decl_assign_typed(j_name, t.make_ident(i_name), 'int')
inner_cond := t.make_infix(.logical_and, t.make_infix(.gt, t.make_ident(j_name),
t.make_int_literal(0)), t.array_sort_less_expr(base, elem_type, j_name, cmp_id))
tmp_decl := t.make_decl_assign_typed(tmp_name, t.make_index(base, t.make_ident(j_name),
elem_type), elem_type)
prev_idx := t.make_infix(.minus, t.make_ident(j_name), t.make_int_literal(1))
assign_cur := t.make_index_assign(t.make_index(base, t.make_ident(j_name), elem_type), t.make_index(base,
prev_idx, elem_type))
prev_idx2 := t.make_infix(.minus, t.make_ident(j_name), t.make_int_literal(1))
assign_prev := t.make_index_assign(t.make_index(base, prev_idx2, elem_type),
t.make_ident(tmp_name))
dec_j := t.make_expr_stmt(t.make_postfix(t.make_ident(j_name), .dec))
inner_body := [tmp_decl, assign_cur, assign_prev, dec_j]
inner_for := t.make_for_stmt(t.make_empty(), inner_cond, t.make_empty(), inner_body, src)
return t.make_for_stmt(init, cond, post, [j_decl, inner_for], src)
}
fn (mut t Transformer) make_array_compare_sort_stmt(base flat.NodeId, elem_type string, src flat.Node, cmp flat.NodeId) flat.NodeId {
i_name := t.new_temp('sort_i')
j_name := t.new_temp('sort_j')
tmp_name := t.new_temp('sort_tmp')
t.set_var_type(i_name, 'int')
t.set_var_type(j_name, 'int')
t.set_var_type(tmp_name, elem_type)
init := t.make_decl_assign_typed(i_name, t.make_int_literal(1), 'int')
cond := t.make_infix(.lt, t.make_ident(i_name), t.make_selector(base, 'len', 'int'))
post := t.make_expr_stmt(t.make_postfix(t.make_ident(i_name), .inc))
j_decl := t.make_decl_assign_typed(j_name, t.make_ident(i_name), 'int')
inner_cond := t.make_infix(.logical_and, t.make_infix(.gt, t.make_ident(j_name),
t.make_int_literal(0)), t.array_sort_compare_less_expr(base, elem_type, j_name, cmp))
tmp_decl := t.make_decl_assign_typed(tmp_name, t.make_index(base, t.make_ident(j_name),
elem_type), elem_type)
prev_idx := t.make_infix(.minus, t.make_ident(j_name), t.make_int_literal(1))
assign_cur := t.make_index_assign(t.make_index(base, t.make_ident(j_name), elem_type), t.make_index(base,
prev_idx, elem_type))
prev_idx2 := t.make_infix(.minus, t.make_ident(j_name), t.make_int_literal(1))
assign_prev := t.make_index_assign(t.make_index(base, prev_idx2, elem_type),
t.make_ident(tmp_name))
dec_j := t.make_expr_stmt(t.make_postfix(t.make_ident(j_name), .dec))
inner_body := [tmp_decl, assign_cur, assign_prev, dec_j]
inner_for := t.make_for_stmt(t.make_empty(), inner_cond, t.make_empty(), inner_body, src)
return t.make_for_stmt(init, cond, post, [j_decl, inner_for], src)
}
fn (mut t Transformer) array_sort_less_expr(base flat.NodeId, elem_type string, idx_name string, cmp_id flat.NodeId) flat.NodeId {
cur := t.make_index(base, t.make_ident(idx_name), elem_type)
prev := t.make_index(base, t.make_infix(.minus, t.make_ident(idx_name), t.make_int_literal(1)),
elem_type)
if int(cmp_id) >= 0 {
old_a := t.var_type('a')
old_b := t.var_type('b')
t.set_var_type('a', elem_type)
t.set_var_type('b', elem_type)
raw_cmp := t.substitute_array_sort_vars(cmp_id, cur, prev)
cmp := t.transform_expr(raw_cmp)
if old_a.len > 0 {
t.set_var_type('a', old_a)
} else {
t.unset_var_type('a')
}
if old_b.len > 0 {
t.set_var_type('b', old_b)
} else {
t.unset_var_type('b')
}
return cmp
}
if elem_type == 'string' {
return t.make_call_typed('string__lt', arr2(cur, prev), 'bool')
}
return t.make_infix(.lt, cur, prev)
}
fn (mut t Transformer) array_sort_compare_less_expr(base flat.NodeId, elem_type string, idx_name string, cmp flat.NodeId) flat.NodeId {
cur := t.make_index(base, t.make_ident(idx_name), elem_type)
prev := t.make_index(base, t.make_infix(.minus, t.make_ident(idx_name), t.make_int_literal(1)),
elem_type)
call := t.make_call_expr_typed(cmp, arr2(t.make_prefix(.amp, cur), t.make_prefix(.amp, prev)),
'int')
return t.make_infix(.lt, call, t.make_int_literal(0))
}
fn (mut t Transformer) substitute_array_sort_vars(id flat.NodeId, a_expr flat.NodeId, b_expr flat.NodeId) flat.NodeId {
if int(id) < 0 {
return id
}
node := t.a.nodes[int(id)]
if node.kind == .ident {
if node.value == 'a' {
return a_expr
}
if node.value == 'b' {
return b_expr
}
return id
}
if node.children_count == 0 {
return id
}
mut children := []flat.NodeId{cap: int(node.children_count)}
for i in 0 .. node.children_count {
children << t.substitute_array_sort_vars(t.a.child(&node, i), a_expr, b_expr)
}
start := t.a.children.len
for child in children {
t.a.children << child
}
return t.a.add_node(flat.Node{
kind: node.kind
op: node.op
children_start: start
children_count: node.children_count
pos: node.pos
value: node.value
typ: node.typ
})
}
fn (mut t Transformer) make_index_assign(lhs flat.NodeId, rhs flat.NodeId) flat.NodeId {
start := t.a.children.len
t.a.children << lhs
t.a.children << rhs
return t.a.add_node(flat.Node{
kind: .index_assign
op: .assign
children_start: start
children_count: 2
})
}
+21
View File
@@ -0,0 +1,21 @@
module transform
import os
fn (t &Transformer) vmod_root() string {
mut dir := if t.cur_file.len > 0 { os.dir(t.cur_file) } else { os.getwd() }
if dir.len == 0 {
dir = os.getwd()
}
for {
if os.exists(os.join_path(dir, 'v.mod')) {
return dir
}
parent := os.dir(dir)
if parent == dir || parent.len == 0 {
return if t.cur_file.len > 0 { os.dir(t.cur_file) } else { os.getwd() }
}
dir = parent
}
return dir
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+414
View File
@@ -0,0 +1,414 @@
module transform
import v3.flat
fn (mut t Transformer) transform_for_body(id flat.NodeId, node flat.Node) []flat.NodeId {
if node.children_count < 3 {
return arr1(id)
}
// child 0: init statement
init_id := t.a.child(&node, 0)
mut new_init := init_id
if int(init_id) >= 0 {
expanded := t.transform_stmt(init_id)
if expanded.len > 0 {
new_init = expanded[0]
}
}
// child 1: condition expression
cond_id := t.a.child(&node, 1)
cond_smartcasts := t.extract_all_is_exprs(cond_id)
new_cond := t.transform_and_chain_smartcasts(cond_id)
mut cond_prefix := []flat.NodeId{}
t.drain_pending(mut cond_prefix)
// child 2: post statement
post_id := t.a.child(&node, 2)
mut new_post := post_id
if int(post_id) >= 0 {
expanded := t.transform_stmt(post_id)
if expanded.len > 0 {
new_post = expanded[0]
}
}
// children 3..n: body statements
mut body_ids := []flat.NodeId{}
for i in 3 .. node.children_count {
body_ids << t.a.child(&node, i)
}
for info in cond_smartcasts {
t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name)
}
new_body := t.transform_stmts(body_ids)
for _ in cond_smartcasts {
t.pop_smartcast()
}
mut loop_cond := new_cond
mut loop_body := new_body.clone()
if cond_prefix.len > 0 {
mut guarded_body := []flat.NodeId{cap: cond_prefix.len + new_body.len + 1}
for stmt in cond_prefix {
guarded_body << stmt
}
not_cond := t.make_prefix(.not, t.make_paren(new_cond))
break_block := t.make_block(arr1(t.a.add(.break_stmt)))
guarded_body << t.make_if(not_cond, break_block, t.make_block([]flat.NodeId{}))
for stmt in new_body {
guarded_body << stmt
}
loop_cond = t.make_bool_literal(true)
loop_body = guarded_body.clone()
}
// Rebuild the for_stmt with transformed children
start := t.a.children.len
t.a.children << new_init
t.a.children << loop_cond
t.a.children << new_post
for bid in loop_body {
t.a.children << bid
}
count := t.a.children.len - start
new_id := t.a.add_node(flat.Node{
kind: .for_stmt
op: node.op
children_start: start
children_count: flat.child_count(count)
pos: node.pos
value: node.value
typ: node.typ
})
return arr1(new_id)
}
fn (mut t Transformer) transform_for_in_body(id flat.NodeId, node flat.Node) []flat.NodeId {
header_count := node.value.int()
if header_count < 3 || node.children_count < 3 {
return arr1(id)
}
key_id := t.a.child(&node, 0) // loop var ident — pass through (do not transform a binding)
val_id := t.a.child(&node, 1) // may be flat.empty_node (-1)
container_id := t.a.child(&node, 2)
iter_type := t.detect_for_in_type(node)
has_index := int(val_id) >= 0
container_is_range := if int(container_id) >= 0 {
t.a.nodes[int(container_id)].kind == .range
} else {
false
}
if header_count == 4 {
range_end_id := t.a.child(&node, 3)
body_ids := t.a.children_of(&node)[header_count..].clone()
return t.lower_range_for_in(id, node, key_id, container_id, range_end_id, body_ids)
}
if container_is_range {
range_node := t.a.nodes[int(container_id)]
if range_node.children_count >= 2 {
body_ids := t.a.children_of(&node)[header_count..].clone()
return t.lower_range_for_in(id, node, key_id, t.a.child(&range_node, 0), t.a.child(&range_node,
1), body_ids)
}
}
if iter_type.starts_with('map[') {
return t.rebuild_for_in_stmt(id, node)
}
if pool_iter := t.pool_get_results_iter_type(container_id) {
body_ids := t.a.children_of(&node)[header_count..].clone()
return t.lower_indexed_for_in(id, node, key_id, val_id, container_id, pool_iter, has_index,
body_ids)
}
effective_iter := if iter_type.starts_with('...') {
'[]' + iter_type[3..]
} else if iter_type.starts_with('&[]') {
iter_type[1..]
} else {
iter_type
}
if effective_iter.starts_with('[]') || effective_iter == 'string'
|| is_fixed_array_type(effective_iter) {
body_ids := t.a.children_of(&node)[header_count..].clone()
return t.lower_indexed_for_in(id, node, key_id, val_id, container_id, effective_iter,
has_index, body_ids)
}
return t.rebuild_for_in_stmt(id, node)
}
fn (t &Transformer) pool_get_results_iter_type(container_id flat.NodeId) ?string {
if int(container_id) < 0 {
return none
}
node := t.a.nodes[int(container_id)]
if node.kind != .call || node.children_count == 0 {
return none
}
callee_id := t.a.child(&node, 0)
if int(callee_id) < 0 {
return none
}
callee := t.a.nodes[int(callee_id)]
if callee.kind != .index || callee.children_count < 2 {
return none
}
base := t.a.child_node(&callee, 0)
if base.kind != .selector || base.value !in ['get_results', 'get_results_ref'] {
return none
}
elem_type := t.generic_call_type_arg_name(t.a.child(&callee, 1))
if elem_type.len == 0 {
return none
}
if base.value == 'get_results_ref' {
return '[]&${elem_type}'
}
return '[]${elem_type}'
}
fn (mut t Transformer) rebuild_for_in_stmt(_id flat.NodeId, node flat.Node) []flat.NodeId {
header_count := node.value.int()
key_id := t.a.child(&node, 0)
val_id := t.a.child(&node, 1)
container_id := t.a.child(&node, 2)
new_container := t.transform_expr(container_id)
iter_type := t.detect_for_in_type(node)
has_index := int(val_id) >= 0
container_is_range := if int(container_id) >= 0 {
t.a.nodes[int(container_id)].kind == .range
} else {
false
}
if header_count == 4 || container_is_range {
// range `for i in 0 .. n`: single loop var (child0) is an int
if int(key_id) >= 0 {
key_name := t.a.nodes[int(key_id)].value
if key_name.len > 0 {
t.set_var_type(key_name, 'int')
}
}
} else if has_index {
// two loop vars: child0 = key/index, child1 = value/element
key_name := if int(key_id) >= 0 { t.a.nodes[int(key_id)].value } else { '' }
val_name := if int(val_id) >= 0 { t.a.nodes[int(val_id)].value } else { '' }
if iter_type.starts_with('map[') {
// map[K]V: child0 (key) -> key type, child1 (val) -> value type
bracket_end := iter_type.index(']') or { 0 }
if key_name.len > 0 && bracket_end > 4 {
t.set_var_type(key_name, iter_type[4..bracket_end])
}
} else if iter_type.starts_with('[]') || iter_type == 'string' {
// []E: child0 (index) -> 'int'
if key_name.len > 0 {
t.set_var_type(key_name, 'int')
}
}
if val_name.len > 0 {
elem_type := t.infer_for_in_elem_type(iter_type, node)
if elem_type.len > 0 {
t.set_var_type(val_name, elem_type)
}
}
} else {
// single var, child0 is the element
if int(key_id) >= 0 {
key_name := t.a.nodes[int(key_id)].value
if key_name.len > 0 {
elem_type := t.infer_for_in_elem_type(iter_type, node)
if elem_type.len > 0 {
t.set_var_type(key_name, elem_type)
}
}
}
}
mut ids := []flat.NodeId{}
ids << key_id
ids << val_id
ids << new_container
if header_count == 4 {
range_end_id := t.a.child(&node, 3)
ids << t.transform_expr(range_end_id)
}
body_ids := t.a.children_of(&node)[header_count..].clone()
new_body := t.transform_stmts(body_ids)
for bid in new_body {
ids << bid
}
start := t.a.children.len
for cid in ids {
t.a.children << cid
}
return arr1(t.a.add_node(flat.Node{
kind: .for_in_stmt
op: node.op
children_start: start
children_count: flat.child_count(ids.len)
pos: node.pos
value: node.value
typ: node.typ
}))
}
fn (mut t Transformer) lower_range_for_in(id flat.NodeId, node flat.Node, key_id flat.NodeId, low_id flat.NodeId, high_id flat.NodeId, body_ids []flat.NodeId) []flat.NodeId {
if int(key_id) < 0 {
return arr1(id)
}
key := t.a.nodes[int(key_id)]
if key.kind != .ident || key.value.len == 0 {
return arr1(id)
}
t.set_var_type(key.value, 'int')
low := t.transform_expr(low_id)
high := t.stable_expr_for_reuse(high_id)
mut prefix := []flat.NodeId{}
t.drain_pending(mut prefix)
init := t.make_decl_assign_typed(key.value, low, 'int')
cond := t.make_infix(.lt, t.make_ident(key.value), high)
post := t.make_expr_stmt(t.make_postfix(t.make_ident(key.value), .inc))
new_body := t.transform_stmts(body_ids)
prefix << t.make_for_stmt(init, cond, post, new_body, node)
return prefix
}
fn (mut t Transformer) lower_indexed_for_in(id flat.NodeId, node flat.Node, key_id flat.NodeId, val_id flat.NodeId, container_id flat.NodeId, iter_type string, has_index bool, body_ids []flat.NodeId) []flat.NodeId {
if int(key_id) < 0 {
return arr1(id)
}
key := t.a.nodes[int(key_id)]
if key.kind != .ident || key.value.len == 0 {
return arr1(id)
}
mut container := t.stable_expr_for_reuse(container_id)
mut prefix := []flat.NodeId{}
t.drain_pending(mut prefix)
mut actual_iter_type := iter_type
container_type := t.node_type(container)
if container_type.len > 0 {
actual_iter_type = container_type
}
if actual_iter_type.starts_with('&[]') {
container = t.make_prefix(.mul, container)
t.a.nodes[int(container)].typ = actual_iter_type[1..]
actual_iter_type = actual_iter_type[1..]
}
elem_type := t.infer_for_in_elem_type(actual_iter_type, node)
if elem_type.len == 0 {
return arr1(id)
}
mut idx_name := key.value
if !has_index {
idx_name = t.new_temp('for_idx')
}
mut elem_name := key.value
if has_index {
if int(val_id) < 0 {
return arr1(id)
}
val := t.a.nodes[int(val_id)]
if val.kind != .ident || val.value.len == 0 {
return arr1(id)
}
elem_name = val.value
}
t.set_var_type(idx_name, 'int')
elem_is_mut := node.op == .amp && actual_iter_type != 'string'
elem_needs_ref := elem_is_mut && !elem_type.starts_with('&')
elem_var_type := if elem_needs_ref { '&${elem_type}' } else { elem_type }
t.set_var_type(elem_name, elem_var_type)
len_expr := if is_fixed_array_type(actual_iter_type) {
t.make_fixed_array_len_expr(actual_iter_type)
} else {
t.make_selector(container, 'len', 'int')
}
init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
cond := t.make_infix(.lt, t.make_ident(idx_name), len_expr)
post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
elem_expr := if elem_needs_ref && actual_iter_type.starts_with('[]') {
t.array_get_ptr(container, t.make_ident(idx_name), elem_type)
} else if elem_needs_ref {
t.make_prefix(.amp, t.make_index(container, t.make_ident(idx_name), elem_type))
} else if actual_iter_type.starts_with('[]') {
t.array_get_value(container, t.make_ident(idx_name), elem_type)
} else {
t.make_index(container, t.make_ident(idx_name), elem_type)
}
elem_decl := t.make_decl_assign_typed(elem_name, elem_expr, elem_var_type)
mut transformed_body := []flat.NodeId{}
if elem_needs_ref {
had_pointer_value_lvalue := t.pointer_value_lvalues[elem_name] or { false }
t.pointer_value_lvalues[elem_name] = true
transformed_body = t.transform_stmts(body_ids)
if had_pointer_value_lvalue {
t.pointer_value_lvalues[elem_name] = true
} else {
t.pointer_value_lvalues.delete(elem_name)
}
} else {
transformed_body = t.transform_stmts(body_ids)
}
mut new_body := []flat.NodeId{}
new_body << elem_decl
new_body << transformed_body
prefix << t.make_for_stmt(init, cond, post, new_body, node)
return prefix
}
fn (mut t Transformer) make_for_stmt(init flat.NodeId, cond flat.NodeId, post flat.NodeId, body []flat.NodeId, src flat.Node) flat.NodeId {
start := t.a.children.len
t.a.children << init
t.a.children << cond
t.a.children << post
for id in body {
t.a.children << id
}
return t.a.add_node(flat.Node{
kind: .for_stmt
op: src.op
children_start: start
children_count: flat.child_count(3 + body.len)
pos: src.pos
typ: src.typ
})
}
fn (mut t Transformer) detect_for_in_type(node flat.Node) string {
if node.typ.len > 0 {
return node.typ
}
header_count := node.value.int()
container_idx := if header_count >= 3 { header_count - 1 } else { 2 }
if node.children_count > container_idx {
iter_id := t.a.child(&node, container_idx)
return t.node_type(iter_id)
}
return ''
}
// Infer the element type for the loop variable from the iterable type.
fn (t &Transformer) infer_for_in_elem_type(iter_type string, node flat.Node) string {
if iter_type.starts_with('&[]') {
return iter_type[3..]
}
if iter_type.starts_with('[]') {
return iter_type[2..]
}
if iter_type.starts_with('map[') {
// map[K]V -> value type is everything after the closing ']'
bracket_end := iter_type.index(']') or { return '' }
if bracket_end + 1 < iter_type.len {
return iter_type[bracket_end + 1..]
}
return ''
}
if iter_type == 'string' {
return 'u8'
}
if is_fixed_array_type(iter_type) {
return fixed_array_elem_type(iter_type)
}
// Check if the iterable is a range expression
if node.children_count > 0 {
iter_id := t.a.child(&node, 0)
iter_node := t.a.nodes[int(iter_id)]
if iter_node.kind == .range {
return 'int'
}
}
return ''
}
File diff suppressed because it is too large. Load diff
+213
View File
@@ -0,0 +1,213 @@
module transform
import v3.flat
import v3.types
// is_interface_type checks if a type name is a known interface.
fn (t &Transformer) is_interface_type(name string) bool {
return t.resolve_interface_type_name(name).len > 0
}
fn (t &Transformer) resolve_interface_type_name(name string) string {
if name.len == 0 || isnil(t.tc) {
return ''
}
clean := t.trim_pointer_type(t.normalize_type_alias(name))
if clean in t.tc.interface_names {
return clean
}
if !clean.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
qname := '${t.cur_module}.${clean}'
if qname in t.tc.interface_names {
return qname
}
}
return ''
}
fn (mut t Transformer) transform_interface_value_for_type(id flat.NodeId, target_type string) ?flat.NodeId {
if int(id) < 0 || target_type.len == 0 || isnil(t.tc) {
return none
}
target_is_ptr := target_type.starts_with('&')
iface_name := t.resolve_interface_type_name(target_type)
if iface_name.len == 0 {
return none
}
// IError has bespoke handling (built via `error()`, fields accessed directly);
// do not route it through the generic interface boxing.
if iface_name.all_after_last('.') == 'IError' {
return none
}
source_type := t.node_type(id)
source_iface := t.resolve_interface_type_name(source_type)
if source_iface == iface_name {
return t.transform_expr(id)
}
if target_is_ptr && source_type.starts_with('&')
&& t.resolve_interface_type_name(source_type[1..]) == iface_name {
return t.transform_expr(id)
}
literal := t.make_interface_literal_from_expr(id, iface_name) or { return none }
if !target_is_ptr {
return literal
}
// A `&Interface` value must outlive the current scope (it is commonly returned
// or stored in a global, e.g. `default_rng = &PRNG(rng)`). Heap-allocate the
// interface box rather than taking the address of a local temporary, which
// would dangle.
tmp_name := t.new_temp('iface_box')
t.pending_stmts << t.make_decl_assign_typed(tmp_name, literal, iface_name)
addr := t.make_prefix(.amp, t.make_ident(tmp_name))
size := t.make_sizeof_type(iface_name)
dup := t.make_call_typed('memdup', arr2(addr, size), 'voidptr')
cast := t.make_cast(target_type, dup, target_type)
t.a.nodes[int(cast)].typ = target_type
return cast
}
fn (mut t Transformer) transform_global_amp_interface_cast(node flat.Node, target_type string) ?flat.NodeId {
if node.kind != .prefix || node.op != .amp || node.children_count != 1 {
return none
}
child_id := t.a.child(&node, 0)
child := t.a.nodes[int(child_id)]
if child.kind != .cast_expr || child.children_count == 0 {
return none
}
iface_name := t.resolve_interface_type_name(child.value)
if iface_name.len == 0 || iface_name.all_after_last('.') == 'IError' {
return none
}
old_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
literal := t.make_interface_literal_from_expr(t.a.child(&child, 0), iface_name) or {
t.pending_stmts = old_pending
return none
}
has_pending := t.pending_stmts.len > 0
t.pending_stmts.clear()
t.pending_stmts = old_pending
if has_pending {
return none
}
start := t.a.children.len
t.a.children << literal
ptr_type := if target_type.len > 0 { target_type } else { '&${iface_name}' }
return t.a.add_node(flat.Node{
kind: .prefix
op: .amp
children_start: start
children_count: 1
pos: node.pos
value: node.value
typ: ptr_type
})
}
fn (mut t Transformer) make_interface_literal_from_expr(id flat.NodeId, iface_name string) ?flat.NodeId {
fields := t.tc.interface_fields[iface_name] or { []types.StructField{} }
source_type := t.node_type(id)
if source_type.len == 0 {
return none
}
source_expr := t.transform_expr(id)
source := t.stable_transformed_expr_for_reuse(source_expr, source_type, 'iface_src')
is_ptr := source_type.starts_with('&')
concrete_type := if is_ptr { source_type[1..] } else { source_type }
// `_object` is a pointer to the boxed concrete value; method dispatch reads it
// back and casts it to the concrete type. For pointer sources we store the
// pointer directly; for value sources we heap-copy so the box can outlive the
// source scope. The pointer is typed (`&Concrete`) so codegen can recover the
// concrete type and emit the matching `_typ` dispatch id.
object_expr := if is_ptr {
source
} else {
addr := t.make_prefix(.amp, source)
size := t.make_sizeof_type(concrete_type)
dup := t.make_call_typed('memdup', arr2(addr, size), 'voidptr')
t.make_cast('&${concrete_type}', dup, '&${concrete_type}')
}
field_base := if is_ptr {
base := t.make_prefix(.mul, source)
t.a.nodes[int(base)].typ = concrete_type
base
} else {
source
}
mut field_ids := []flat.NodeId{cap: fields.len + 1}
field_ids << t.make_sum_literal_field('_object', object_expr, '&${concrete_type}')
for field in fields {
field_type := t.normalize_type_alias(field.typ.name())
field_value := t.make_selector(field_base, field.name, field_type)
field_ids << t.make_sum_literal_field(field.name, field_value, field_type)
}
start := t.a.children.len
for field_id in field_ids {
t.a.children << field_id
}
return t.a.add_node(flat.Node{
kind: .struct_init
children_start: start
children_count: flat.child_count(field_ids.len)
value: iface_name
typ: iface_name
})
}
// transform_interface_cast transforms interface-to-concrete type casts.
// This is a hook for future interface dispatch lowering where interface
// values need to be unwrapped to their concrete types.
// Currently passes through unchanged.
fn (mut t Transformer) transform_interface_cast(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count == 0 {
return id
}
mut new_children := []flat.NodeId{cap: int(node.children_count)}
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
new_children << t.transform_expr(child_id)
}
start := t.a.children.len
for nc in new_children {
t.a.children << nc
}
return t.a.add_node(flat.Node{
kind: node.kind
op: node.op
children_start: start
children_count: node.children_count
pos: node.pos
value: node.value
typ: node.typ
})
}
// transform_interface_method_call transforms method calls on interface values.
// This is a hook for vtable dispatch lowering where `iface.method(args)`
// needs to be rewritten to indirect calls through the interface vtable.
// Currently passes through unchanged.
fn (mut t Transformer) transform_interface_method_call(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count == 0 {
return id
}
mut new_children := []flat.NodeId{cap: int(node.children_count)}
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
new_children << t.transform_expr(child_id)
}
start := t.a.children.len
for nc in new_children {
t.a.children << nc
}
return t.a.add_node(flat.Node{
kind: node.kind
op: node.op
children_start: start
children_count: node.children_count
pos: node.pos
value: node.value
typ: node.typ
})
}
+7
View File
@@ -0,0 +1,7 @@
module transform
import v3.flat
fn (mut t Transformer) transform_live_fn(id flat.NodeId, _node flat.Node) flat.NodeId {
return id
}
+437
View File
@@ -0,0 +1,437 @@
module transform
import v3.flat
struct MapIndexInfo {
base_id flat.NodeId
key_id flat.NodeId
base_type string
key_type string
value_type string
}
fn (mut t Transformer) map_type_parts(map_type string) (string, string) {
clean := t.clean_map_type(map_type)
if !clean.starts_with('map[') {
return '', ''
}
return t.map_key_type(clean), t.map_value_type(clean)
}
fn (mut t Transformer) make_new_map_call(map_type string) flat.NodeId {
key_type, value_type := t.map_type_parts(map_type)
hash_fn, eq_fn, clone_fn, free_fn := map_callback_names(key_type)
mut args := []flat.NodeId{}
args << t.make_sizeof_type(key_type)
args << t.make_sizeof_type(value_type)
args << t.make_ident(hash_fn)
args << t.make_ident(eq_fn)
args << t.make_ident(clone_fn)
args << t.make_ident(free_fn)
return t.make_call_typed('new_map', args, map_type)
}
fn map_callback_names(key_type string) (string, string, string, string) {
if key_type == 'string' {
return 'v3_map_hash_string', 'v3_map_eq_string', 'v3_map_clone_string', 'v3_map_free_string'
}
size_suffix := match key_type {
'u8', 'i8', 'bool', 'char' { '1' }
'u16', 'i16' { '2' }
'i64', 'u64', 'isize', 'usize', 'voidptr' { '8' }
else { '4' }
}
return 'v3_map_hash_int_${size_suffix}', 'v3_map_eq_int_${size_suffix}', 'v3_map_clone_int_${size_suffix}', 'v3_map_free_nop'
}
fn (mut t Transformer) map_index_info(index_id flat.NodeId) ?MapIndexInfo {
if int(index_id) < 0 {
return none
}
lhs := t.a.nodes[int(index_id)]
if lhs.kind != .index || lhs.children_count < 2 || lhs.value == 'range' {
return none
}
base_id := t.a.child(&lhs, 0)
key_id := t.a.child(&lhs, 1)
base_type := t.node_type(base_id)
map_type := t.clean_map_type(base_type)
if !map_type.starts_with('map[') {
return none
}
key_type, value_type := t.map_type_parts(map_type)
if key_type.len == 0 || value_type.len == 0 {
return none
}
return MapIndexInfo{
base_id: base_id
key_id: key_id
base_type: base_type
key_type: key_type
value_type: value_type
}
}
fn (mut t Transformer) make_map_get_expr(map_expr flat.NodeId, base_type string, key_name string, zero_name string, value_type string) flat.NodeId {
call := t.make_call_typed('map__get', arr3(t.runtime_addr(map_expr, base_type), t.make_prefix(.amp,
t.make_ident(key_name)), t.make_prefix(.amp, t.make_ident(zero_name))), 'voidptr')
cast := t.make_cast('&${value_type}', call, '&${value_type}')
return t.make_prefix(.mul, cast)
}
fn (mut t Transformer) make_map_get_check_expr(map_expr flat.NodeId, base_type string, key_name string) flat.NodeId {
return t.make_call_typed('map__get_check', arr2(t.runtime_addr(map_expr, base_type), t.make_prefix(.amp,
t.make_ident(key_name))), 'voidptr')
}
fn (mut t Transformer) make_map_exists_expr(map_expr flat.NodeId, base_type string, key_name string) flat.NodeId {
return t.make_call_typed('map__exists', arr2(t.runtime_addr(map_expr, base_type), t.make_prefix(.amp,
t.make_ident(key_name))), 'bool')
}
fn (mut t Transformer) make_map_set_stmt(map_expr flat.NodeId, base_type string, key_name string, value_name string) flat.NodeId {
call := t.make_call_typed('map__set', arr3(t.runtime_addr(map_expr, base_type), t.make_prefix(.amp,
t.make_ident(key_name)), t.make_prefix(.amp, t.make_ident(value_name))), 'void')
return t.make_expr_stmt(call)
}
fn (t &Transformer) const_expr_for_ident(id flat.NodeId) ?flat.NodeId {
if int(id) < 0 || isnil(t.tc) {
return none
}
node := t.a.nodes[int(id)]
if node.kind != .ident || node.value.len == 0 {
return none
}
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
qname := '${t.cur_module}.${node.value}'
if expr_id := t.tc.const_exprs[qname] {
return expr_id
}
}
if expr_id := t.tc.const_exprs[node.value] {
return expr_id
}
return none
}
fn (mut t Transformer) lower_map_membership_expr(map_id flat.NodeId, key_id flat.NodeId, map_type string) ?flat.NodeId {
clean_type := t.clean_map_type(map_type)
mut key_type := ''
if clean_type.starts_with('map[') {
key_type = t.map_key_type(clean_type)
}
if key_type.len == 0 {
key_type = t.node_type(key_id)
}
if key_type.len == 0 {
key := t.a.nodes[int(key_id)]
if key.kind == .selector {
key_type = t.selector_field_type(key)
}
}
if key_type.len == 0 {
return none
}
map_source_id := t.const_expr_for_ident(map_id) or { map_id }
map_expr := t.stable_expr_for_reuse(map_source_id)
key_name := t.new_temp('map_key')
t.pending_stmts << t.make_decl_assign_typed(key_name, t.transform_expr(key_id), key_type)
return t.make_map_exists_expr(map_expr, map_type, key_name)
}
fn (mut t Transformer) try_lower_map_index_expr(_id flat.NodeId, node flat.Node) ?flat.NodeId {
if node.kind != .index || node.children_count < 2 || node.value == 'range' {
return none
}
base_id := t.a.child(&node, 0)
key_id := t.a.child(&node, 1)
base_type := t.node_type(base_id)
map_type := t.clean_map_type(base_type)
if !map_type.starts_with('map[') {
return none
}
key_type, value_type := t.map_type_parts(map_type)
if key_type.len == 0 || value_type.len == 0 {
return none
}
map_source_id := t.const_expr_for_ident(base_id) or { base_id }
map_expr := t.stable_expr_for_reuse(map_source_id)
key_name := t.new_temp('map_key')
zero_name := t.new_temp('map_zero')
t.pending_stmts << t.make_decl_assign_typed(key_name, t.transform_expr(key_id), key_type)
t.pending_stmts << t.make_decl_assign_typed(zero_name, t.zero_value_for_type(value_type),
value_type)
return t.make_map_get_expr(map_expr, base_type, key_name, zero_name, value_type)
}
fn (mut t Transformer) is_map_index_or_expr(node flat.Node) bool {
if node.kind != .or_expr || node.children_count < 2 {
return false
}
expr := t.a.child_node(&node, 0)
if expr.kind != .index || expr.children_count < 2 || expr.value == 'range' {
return false
}
base_id := t.a.child(expr, 0)
base_type := t.node_type(base_id)
return t.clean_map_type(base_type).starts_with('map[')
}
fn (mut t Transformer) transform_map_index_or_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count < 2 {
return id
}
expr_id := t.a.child(&node, 0)
body_id := t.a.child(&node, 1)
info := t.map_index_info(expr_id) or { return id }
map_expr := t.stable_expr_for_reuse(info.base_id)
key_name := t.new_temp('map_key')
ptr_name := t.new_temp('map_ptr')
val_name := t.new_temp('map_val')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
key_expr := t.transform_expr(info.key_id)
mut prelude := []flat.NodeId{}
t.drain_pending(mut prelude)
prelude << t.make_decl_assign_typed(key_name, key_expr, info.key_type)
prelude << t.make_decl_assign_typed(ptr_name, t.make_map_get_check_expr(map_expr,
info.base_type, key_name), 'voidptr')
prelude << t.make_decl_assign_typed(val_name, t.zero_value_for_type(info.value_type),
info.value_type)
ptr_ident := t.make_ident(ptr_name)
found_cond := t.make_infix(.ne, ptr_ident, t.a.add(.nil_literal))
ptr_value := t.make_prefix(.mul, t.make_cast('&${info.value_type}', t.make_ident(ptr_name),
'&${info.value_type}'))
then_block := t.make_block(arr1(t.make_assign(t.make_ident(val_name), ptr_value)))
else_block := t.make_block(t.lower_map_or_body_to_stmts(body_id, val_name, info.value_type,
node.value))
t.pending_stmts = outer_pending
for stmt in prelude {
t.pending_stmts << stmt
}
t.pending_stmts << t.make_if(found_cond, then_block, else_block)
return t.make_ident(val_name)
}
fn (mut t Transformer) lower_map_or_body_to_stmts(body_id flat.NodeId, target_name string, target_type string, mode string) []flat.NodeId {
if mode == '!' || mode == '?' {
if t.is_optional_type_name(t.cur_fn_ret_type) {
return arr1(t.make_none_return_stmt())
}
return arr1(t.make_panic_stmt('option/result propagation failed'))
}
if int(body_id) < 0 {
return []flat.NodeId{}
}
body := t.a.nodes[int(body_id)]
if body.kind != .block {
if body.kind == .none_expr && !t.is_optional_type_name(target_type)
&& t.is_optional_type_name(t.cur_fn_ret_type) {
return arr1(t.make_none_return_stmt())
}
return arr1(t.make_assign(t.make_ident(target_name), t.transform_expr(body_id)))
}
mut result := []flat.NodeId{}
for i in 0 .. body.children_count {
child_id := t.a.child(&body, i)
child := t.a.nodes[int(child_id)]
is_last := i == body.children_count - 1
if is_last && child.kind == .expr_stmt && child.children_count > 0 {
inner_id := t.a.child(&child, 0)
inner := t.a.nodes[int(inner_id)]
if inner.kind == .none_expr && !t.is_optional_type_name(target_type)
&& t.is_optional_type_name(t.cur_fn_ret_type) {
result << t.make_none_return_stmt()
continue
}
if t.node_type(inner_id) == 'void' {
expanded := t.transform_stmt(child_id)
t.drain_pending(mut result)
for eid in expanded {
result << eid
}
} else {
value := t.transform_expr(inner_id)
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
}
} else {
expanded := t.transform_stmt(child_id)
t.drain_pending(mut result)
for eid in expanded {
result << eid
}
}
}
_ = target_type
return result
}
fn (mut t Transformer) try_lower_map_index_assign(node flat.Node) ?[]flat.NodeId {
if node.kind !in [.assign, .index_assign] || node.children_count < 2 {
return none
}
info := t.map_index_info(t.a.child(&node, 0)) or { return none }
map_expr := t.stable_expr_for_reuse(info.base_id)
key_name := t.new_temp('map_key')
mut result := []flat.NodeId{}
t.drain_pending(mut result)
result << t.make_decl_assign_typed(key_name, t.transform_expr(info.key_id), info.key_type)
rhs_id := t.a.child(&node, 1)
if node.op == .assign {
value_name := t.new_temp('map_val')
value := if info.value_type.starts_with('&') && t.is_sum_type_name(info.value_type[1..]) {
t.transform_expr_for_type(rhs_id, info.value_type)
} else if info.value_type in t.sum_types
|| t.resolve_sum_name(info.value_type) in t.sum_types {
t.wrap_sum_value(rhs_id, info.value_type)
} else {
t.transform_expr(rhs_id)
}
result << t.make_decl_assign_typed(value_name, value, info.value_type)
result << t.make_map_set_stmt(map_expr, info.base_type, key_name, value_name)
return result
}
if node.op == .left_shift_assign && info.value_type.starts_with('[]') {
t.lower_map_index_append_with_info(info, map_expr, key_name, rhs_id, mut result)
return result
}
op := map_compound_to_infix_op(node.op) or { return none }
t.lower_map_index_compound_with_info(info, map_expr, key_name, op, rhs_id, mut result)
return result
}
fn map_compound_to_infix_op(op flat.Op) ?flat.Op {
match op {
.plus_assign { return flat.Op.plus }
.minus_assign { return flat.Op.minus }
.mul_assign { return flat.Op.mul }
.div_assign { return flat.Op.div }
.mod_assign { return flat.Op.mod }
.amp_assign { return flat.Op.amp }
.pipe_assign { return flat.Op.pipe }
.xor_assign { return flat.Op.xor }
.left_shift_assign { return flat.Op.left_shift }
.right_shift_assign { return flat.Op.right_shift }
else { return none }
}
}
fn (mut t Transformer) load_map_index_current(info MapIndexInfo, map_expr flat.NodeId, key_name string, mut result []flat.NodeId) string {
zero_name := t.new_temp('map_zero')
current_name := t.new_temp('map_val')
result << t.make_decl_assign_typed(zero_name, t.zero_value_for_type(info.value_type),
info.value_type)
get_expr := t.make_map_get_expr(map_expr, info.base_type, key_name, zero_name, info.value_type)
result << t.make_decl_assign_typed(current_name, get_expr, info.value_type)
return current_name
}
fn (mut t Transformer) lower_map_index_compound_with_info(info MapIndexInfo, map_expr flat.NodeId, key_name string, op flat.Op, rhs_id flat.NodeId, mut result []flat.NodeId) {
current_name := t.load_map_index_current(info, map_expr, key_name, mut result)
rhs := t.transform_expr(rhs_id)
new_value := if info.value_type == 'string' && op == .plus {
t.make_call_typed('string__plus', arr2(t.make_ident(current_name), rhs), 'string')
} else {
t.make_infix(op, t.make_ident(current_name), rhs)
}
result << t.make_assign(t.make_ident(current_name), new_value)
result << t.make_map_set_stmt(map_expr, info.base_type, key_name, current_name)
}
fn (mut t Transformer) lower_map_index_postfix_with_info(info MapIndexInfo, map_expr flat.NodeId, key_name string, op flat.Op, mut result []flat.NodeId) {
current_name := t.load_map_index_current(info, map_expr, key_name, mut result)
infix_op := if op == .dec { flat.Op.minus } else { flat.Op.plus }
new_value := t.make_infix(infix_op, t.make_ident(current_name), t.make_int_literal(1))
result << t.make_assign(t.make_ident(current_name), new_value)
result << t.make_map_set_stmt(map_expr, info.base_type, key_name, current_name)
}
fn (mut t Transformer) lower_map_index_append_with_info(info MapIndexInfo, map_expr flat.NodeId, key_name string, rhs_id flat.NodeId, mut result []flat.NodeId) {
current_name := t.load_map_index_current(info, map_expr, key_name, mut result)
append := t.make_infix(.left_shift, t.make_ident(current_name), t.transform_expr(rhs_id))
t.annotate_left_shift(append)
result << t.make_expr_stmt(append)
result << t.make_map_set_stmt(map_expr, info.base_type, key_name, current_name)
}
fn (mut t Transformer) try_lower_map_index_postfix_stmt(id flat.NodeId) ?[]flat.NodeId {
if int(id) < 0 {
return none
}
node := t.a.nodes[int(id)]
if node.kind != .postfix || node.children_count == 0 || node.op !in [.inc, .dec] {
return none
}
info := t.map_index_info(t.a.child(&node, 0)) or { return none }
map_expr := t.stable_expr_for_reuse(info.base_id)
key_name := t.new_temp('map_key')
mut result := []flat.NodeId{}
t.drain_pending(mut result)
result << t.make_decl_assign_typed(key_name, t.transform_expr(info.key_id), info.key_type)
t.lower_map_index_postfix_with_info(info, map_expr, key_name, node.op, mut result)
return result
}
fn (mut t Transformer) try_lower_map_index_append_stmt(id flat.NodeId) ?[]flat.NodeId {
if int(id) < 0 {
return none
}
node := t.a.nodes[int(id)]
if node.kind != .infix || node.op != .left_shift || node.children_count < 2 {
return none
}
info := t.map_index_info(t.a.child(&node, 0)) or { return none }
if !info.value_type.starts_with('[]') {
return none
}
map_expr := t.stable_expr_for_reuse(info.base_id)
key_name := t.new_temp('map_key')
mut result := []flat.NodeId{}
t.drain_pending(mut result)
result << t.make_decl_assign_typed(key_name, t.transform_expr(info.key_id), info.key_type)
t.lower_map_index_append_with_info(info, map_expr, key_name, t.a.child(&node, 1), mut result)
return result
}
fn (mut t Transformer) lower_map_init_to_runtime(id flat.NodeId, node flat.Node) flat.NodeId {
map_type := if node.value.len > 0 {
node.value
} else if node.typ.len > 0 {
node.typ
} else {
t.node_type(id)
}
if !map_type.starts_with('map[') {
return id
}
init_call := t.make_new_map_call(map_type)
if node.children_count == 0 {
return init_call
}
tmp_name := t.new_temp('map_lit')
t.pending_stmts << t.make_decl_assign_typed(tmp_name, init_call, map_type)
key_type, value_type := t.map_type_parts(map_type)
for i := 0; i + 1 < node.children_count; i += 2 {
key_name := t.new_temp('map_key')
value_name := t.new_temp('map_val')
t.pending_stmts << t.make_decl_assign_typed(key_name,
t.transform_expr(t.a.child(&node, i)), key_type)
value_id := t.a.child(&node, i + 1)
value := if value_type.starts_with('&') && t.is_sum_type_name(value_type[1..]) {
t.transform_expr_for_type(value_id, value_type)
} else if value_type in t.sum_types || t.resolve_sum_name(value_type) in t.sum_types {
t.wrap_sum_value(value_id, value_type)
} else {
t.transform_expr(value_id)
}
t.pending_stmts << t.make_decl_assign_typed(value_name, value, value_type)
call := t.make_call_typed('map__set', arr3(t.make_prefix(.amp, t.make_ident(tmp_name)), t.make_prefix(.amp,
t.make_ident(key_name)), t.make_prefix(.amp, t.make_ident(value_name))), 'void')
t.pending_stmts << t.make_expr_stmt(call)
}
return t.make_ident(tmp_name)
}
+75
View File
@@ -0,0 +1,75 @@
module transform
import v3.flat
// match_has_sum_type_subject checks if a match_stmt's subject expression
// resolves to a sum type. This determines whether the match needs to be
// lowered with is_expr conditions (sum type dispatch) or plain equality
// checks (value matching).
fn (mut t Transformer) match_has_sum_type_subject(node flat.Node) bool {
if node.children_count == 0 {
return false
}
subject_id := t.a.child(&node, 0)
subject_type := t.resolve_expr_type(subject_id)
if subject_type.len == 0 {
return false
}
return subject_type in t.sum_types
}
// resolve_match_subject_type determines the type of a match subject expression.
// Uses resolve_expr_type on child 0 (the match expression).
fn (mut t Transformer) resolve_match_subject_type(node flat.Node) string {
if node.children_count == 0 {
return ''
}
subject_id := t.a.child(&node, 0)
return t.resolve_expr_type(subject_id)
}
// transform_match_branch_body transforms a match branch body with smartcast
// awareness. When the branch matches a sum type variant, the subject variable
// should be narrowed to that variant's type within the branch body.
//
// For now, this performs basic body transformation without smartcast injection.
// When smartcast support is fully wired up, this will:
// 1. Push a smartcast context for the subject variable narrowed to the variant
// 2. Transform the body statements under that context
// 3. Pop the smartcast context
// 4. Rebuild the block with the transformed statements
fn (mut t Transformer) transform_match_branch_body(branch_id flat.NodeId, subject_name string, variant_name string) flat.NodeId {
if int(branch_id) < 0 {
return branch_id
}
branch := t.a.nodes[int(branch_id)]
if branch.children_count == 0 {
return branch_id
}
// Collect the body statement ids from the branch
mut body_ids := []flat.NodeId{}
// Determine where the body starts (after the condition values)
n_conds := t.count_conds(branch)
for i in n_conds .. branch.children_count {
body_ids << t.a.child(&branch, i)
}
if body_ids.len == 0 {
return branch_id
}
// Push smartcast if we have a sum type variant match
mut has_sc := subject_name.len > 0 && variant_name.len > 0
if has_sc {
sum_type_name := t.find_sum_type_for_variant(variant_name)
if sum_type_name.len > 0 {
t.push_smartcast(subject_name, variant_name, sum_type_name)
} else {
has_sc = false
}
}
// Transform the body statements under smartcast context
new_body := t.transform_stmts(body_ids)
if has_sc {
t.pop_smartcast()
}
return t.make_block(new_body)
}
+28
View File
@@ -0,0 +1,28 @@
module transform
// is_generic_fn checks if a function name refers to a generic function.
// Generic functions have type parameters that need to be monomorphized
// into concrete specializations before code generation.
// For now, returns false as generics are not yet supported in v3.
fn (mut t Transformer) is_generic_fn(_name string) bool {
return false
}
// is_generic_struct checks if a struct name is generic.
// Generic structs like `Array[T]` would need monomorphization into
// concrete types like `Array_int`, `Array_string`, etc.
// For now, returns false as generics are not yet supported in v3.
fn (mut t Transformer) is_generic_struct(_name string) bool {
return false
}
// monomorphize_pass is the top-level generic monomorphization pass.
// When generics support is added, this will:
// 1. Scan all call sites and struct inits for generic instantiations
// 2. For each unique set of type arguments, clone and specialize the
// generic function/struct definition
// 3. Rewrite call sites to reference the monomorphized specialization
// 4. Remove the original generic definitions
// Called from transform_all() in transform.v if generics support is added.
fn (mut t Transformer) monomorphize_pass() {
}
+503
View File
@@ -0,0 +1,503 @@
module transform
import v3.flat
import v3.types
struct ArrayIndexInfo {
base_id flat.NodeId
index_id flat.NodeId
base_type string
value_type string
}
struct EnumFromStringInfo {
enum_type string
fields []string
arg_id flat.NodeId
}
fn (t &Transformer) optional_base_type(typ string) string {
if typ.len > 1 && (typ[0] == `?` || typ[0] == `!`) {
return typ[1..]
}
return typ
}
fn (mut t Transformer) array_index_info(index_id flat.NodeId) ?ArrayIndexInfo {
if int(index_id) < 0 {
return none
}
expr := t.a.nodes[int(index_id)]
if expr.kind != .index || expr.children_count < 2 || expr.value == 'range' {
return none
}
base_id := t.a.child(&expr, 0)
index_expr_id := t.a.child(&expr, 1)
base_type := t.resolve_expr_type(base_id)
if !base_type.starts_with('[]') {
return none
}
value_type := t.normalize_type_alias(base_type[2..])
if value_type.len == 0 {
return none
}
return ArrayIndexInfo{
base_id: base_id
index_id: index_expr_id
base_type: base_type
value_type: value_type
}
}
fn (mut t Transformer) is_array_index_or_expr(node flat.Node) bool {
if node.kind != .or_expr || node.children_count < 2 {
return false
}
_ := t.array_index_info(t.a.child(&node, 0)) or { return false }
return true
}
fn (mut t Transformer) transform_array_index_or_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count < 2 {
return id
}
expr_id := t.a.child(&node, 0)
body_id := t.a.child(&node, 1)
info := t.array_index_info(expr_id) or { return id }
base_expr := t.stable_expr_for_reuse(info.base_id)
index_name := t.new_temp('arr_idx')
val_name := t.new_temp('arr_val')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
index_expr := t.transform_expr(info.index_id)
mut prelude := []flat.NodeId{}
t.drain_pending(mut prelude)
prelude << t.make_decl_assign_typed(index_name, index_expr, 'int')
prelude << t.make_decl_assign_typed(val_name, t.zero_value_for_type(info.value_type),
info.value_type)
idx_ident := t.make_ident(index_name)
lower_ok := t.make_infix(.ge, idx_ident, t.make_int_literal(0))
upper_ok := t.make_infix(.lt, t.make_ident(index_name),
t.make_selector(base_expr, 'len', 'int'))
found_cond := t.make_infix(.logical_and, lower_ok, upper_ok)
index_value := t.make_index(base_expr, t.make_ident(index_name), info.value_type)
then_block := t.make_block(arr1(t.make_assign(t.make_ident(val_name), index_value)))
else_block := t.make_block(t.lower_or_body_to_stmts(body_id, val_name, info.value_type,
node.value, ''))
t.pending_stmts = outer_pending
for stmt in prelude {
t.pending_stmts << stmt
}
t.pending_stmts << t.make_if(found_cond, then_block, else_block)
return t.make_ident(val_name)
}
fn (t &Transformer) or_body_is_nil(body_id flat.NodeId) bool {
if int(body_id) < 0 {
return false
}
body := t.a.nodes[int(body_id)]
if body.kind == .nil_literal {
return true
}
if body.kind != .block || body.children_count != 1 {
return false
}
stmt_id := t.a.child(&body, 0)
stmt := t.a.nodes[int(stmt_id)]
if stmt.kind == .expr_stmt && stmt.children_count == 1 {
return t.a.nodes[int(t.a.child(&stmt, 0))].kind == .nil_literal
}
return stmt.kind == .nil_literal
}
fn (t &Transformer) enum_from_string_info(expr_id flat.NodeId) ?EnumFromStringInfo {
if int(expr_id) < 0 {
return none
}
expr := t.a.nodes[int(expr_id)]
if expr.kind != .call || expr.children_count < 2 {
return none
}
fn_id := t.a.child(&expr, 0)
fn_node := t.a.nodes[int(fn_id)]
if fn_node.kind != .selector || fn_node.value != 'from_string' || fn_node.children_count == 0 {
return none
}
enum_id := t.a.child(&fn_node, 0)
enum_type := t.enum_type_from_node(enum_id) or { return none }
fields := t.enum_types[enum_type] or { return none }
if fields.len == 0 {
return none
}
return EnumFromStringInfo{
enum_type: enum_type
fields: fields.clone()
arg_id: t.a.child(&expr, 1)
}
}
fn (t &Transformer) enum_type_from_node(id flat.NodeId) ?string {
if int(id) < 0 {
return none
}
node := t.a.nodes[int(id)]
if node.kind == .ident {
if node.value in t.enum_types {
return node.value
}
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
qname := '${t.cur_module}.${node.value}'
if qname in t.enum_types {
return qname
}
}
for key, _ in t.enum_types {
if key.all_after_last('.') == node.value {
return key
}
}
}
if node.kind == .selector && node.children_count > 0 {
base := t.a.child_node(&node, 0)
if base.kind == .ident {
qname := '${base.value}.${node.value}'
if qname in t.enum_types {
return qname
}
}
}
return none
}
fn (mut t Transformer) is_enum_from_string_or_expr(node flat.Node) bool {
if node.kind != .or_expr || node.children_count < 2 {
return false
}
_ := t.enum_from_string_info(t.a.child(&node, 0)) or { return false }
return true
}
fn (mut t Transformer) transform_enum_from_string_or_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count < 2 {
return id
}
expr_id := t.a.child(&node, 0)
body_id := t.a.child(&node, 1)
info := t.enum_from_string_info(expr_id) or { return id }
str_name := t.new_temp('enum_str')
val_name := t.new_temp('enum_val')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
arg_expr := t.transform_expr(info.arg_id)
mut prelude := []flat.NodeId{}
t.drain_pending(mut prelude)
prelude << t.make_decl_assign_typed(str_name, arg_expr, 'string')
prelude << t.make_decl_assign_typed(val_name, t.zero_value_for_type(info.enum_type),
info.enum_type)
mut else_block := t.make_block(t.lower_or_body_to_stmts(body_id, val_name, info.enum_type,
node.value, ''))
for i := info.fields.len - 1; i >= 0; i-- {
cond := t.make_call_typed('string__eq', arr2(t.make_ident(str_name),
t.make_string_literal(info.fields[i])), 'bool')
assign := t.make_assign(t.make_ident(val_name), t.make_int_literal(i))
then_block := t.make_block(arr1(assign))
else_block = t.make_if(cond, then_block, else_block)
}
t.pending_stmts = outer_pending
for stmt in prelude {
t.pending_stmts << stmt
}
t.pending_stmts << else_block
return t.make_ident(val_name)
}
fn (t &Transformer) or_expr_types(expr_id flat.NodeId, fallback_type string) (string, string) {
if !isnil(t.tc) {
if typ := t.tc.expr_type(expr_id) {
if typ is types.OptionType {
base_name := t.value_type_name(typ.base_type)
source_name := if base_name == 'int' && typ.base_type is types.Void {
'?void'
} else {
'?${base_name}'
}
return source_name, base_name
}
if typ is types.ResultType {
base_name := t.value_type_name(typ.base_type)
source_name := if base_name == 'int' && typ.base_type is types.Void {
'!void'
} else {
'!${base_name}'
}
return source_name, base_name
}
}
}
mut expr_type := t.node_type(expr_id)
if expr_type.len == 0 || expr_type == 'Optional' {
if fallback_type.len > 0 && !t.is_optional_type_name(fallback_type) {
expr_type = '?${fallback_type}'
} else {
expr_type = fallback_type
}
}
base_type := t.optional_base_type(expr_type)
mut value_type := if base_type.len > 0 { base_type } else { fallback_type }
if value_type.len == 0 || value_type == 'void' || value_type == '!' || value_type == '?' {
value_type = 'int'
}
return expr_type, value_type
}
fn (t &Transformer) value_type_name(typ types.Type) string {
if typ is types.Void {
return 'void'
}
return typ.name()
}
fn (t &Transformer) is_optional_type_name(typ string) bool {
return typ.len > 0 && (typ[0] == `?` || typ[0] == `!`)
}
fn (t &Transformer) qualify_optional_type(typ string) string {
if typ.len < 2 || (typ[0] != `?` && typ[0] != `!`) {
return typ
}
base := typ[1..]
if base.contains('.') || base.len == 0 {
return typ
}
qualified := t.qualify_type(base)
if qualified != base {
return typ[..1] + qualified
}
return typ
}
fn (t &Transformer) qualify_type(name string) string {
if name.contains('.') || name.len == 0 {
return name
}
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
qname := '${t.cur_module}.${name}'
if qname in t.sum_types || qname in t.structs {
return qname
}
}
if qualified := t.qualified_types[name] {
return qualified
}
return name
}
fn (mut t Transformer) make_decl_assign_typed(name string, rhs flat.NodeId, typ string) flat.NodeId {
decl := t.make_decl_assign(name, rhs)
if typ.len > 0 {
t.a.nodes[int(decl)].typ = typ
t.set_var_type(name, typ)
}
return decl
}
fn (mut t Transformer) zero_value_for_type(typ string) flat.NodeId {
mut clean := typ
if clean.starts_with('&') {
return t.a.add(.nil_literal)
}
if clean.len > 1 && (clean[0] == `?` || clean[0] == `!`) {
return t.make_optional_none(clean)
}
clean = t.normalize_type_alias(clean)
if clean.starts_with('fn(') || clean.starts_with('fn (') || clean.starts_with('fn_ptr:') {
return t.make_cast(clean, t.make_int_literal(0), clean)
}
if clean.starts_with('[]') {
return t.make_array_init(clean[2..])
}
if clean.starts_with('map[') {
return t.make_map_init(clean)
}
if clean == 'string' {
return t.make_string_literal('')
}
if clean == 'bool' {
return t.make_bool_literal(false)
}
if clean in ['f32', 'f64'] {
return t.make_float_literal('0.0')
}
if clean in ['void', ''] {
return t.make_int_literal(0)
}
if clean in ['int', 'i8', 'i16', 'i32', 'i64', 'isize', 'usize', 'u8', 'byte', 'u16', 'u32', 'u64', 'rune', 'char']
|| clean in t.enum_types {
return t.make_int_literal(0)
}
return t.make_struct_init(clean)
}
fn (mut t Transformer) make_panic_stmt(message string) flat.NodeId {
call := t.make_call('panic', arr1(t.make_string_literal(message)))
return t.make_expr_stmt(call)
}
fn (mut t Transformer) make_none_return_stmt() flat.NodeId {
return t.make_return(t.a.add(.none_expr), t.cur_fn_ret_type)
}
fn (mut t Transformer) make_none_return_stmt_with_err(err_source string) flat.NodeId {
if err_source.len == 0 {
return t.make_none_return_stmt()
}
err_expr := t.make_selector(t.make_ident(err_source), 'err', 'IError')
return t.make_return(t.make_optional_none_with_err(t.cur_fn_ret_type, err_expr),
t.cur_fn_ret_type)
}
fn (mut t Transformer) lower_or_expr_to_temp(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count < 2 {
return id
}
expr_id := t.a.child(&node, 0)
body_id := t.a.child(&node, 1)
expr_type, value_type := t.or_expr_types(expr_id, node.typ)
if !t.is_optional_type_name(expr_type) {
return t.transform_expr(expr_id)
}
is_void := value_type.len == 0 || value_type == 'void'
opt_tmp := t.new_temp('or_opt')
val_tmp := t.new_temp('or_val')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
new_expr := t.transform_expr(expr_id)
mut prelude := []flat.NodeId{}
t.drain_pending(mut prelude)
if is_void {
prelude << t.make_decl_assign_typed(opt_tmp, new_expr, expr_type)
opt_ident := t.make_ident(opt_tmp)
not_ok := t.make_prefix(.not, t.make_selector(opt_ident, 'ok', 'bool'))
else_block := t.make_block(t.lower_or_body_to_stmts(body_id, '', '', node.value, opt_tmp))
if_stmt := t.make_if(not_ok, else_block, t.make_empty())
t.pending_stmts = outer_pending
for stmt in prelude {
t.pending_stmts << stmt
}
t.pending_stmts << if_stmt
return t.make_int_literal(0)
}
prelude << t.make_decl_assign_typed(opt_tmp, new_expr, expr_type)
prelude << t.make_decl_assign_typed(val_tmp, t.zero_value_for_type(value_type), value_type)
opt_ident := t.make_ident(opt_tmp)
ok_cond := t.make_selector(opt_ident, 'ok', 'bool')
value_expr := t.make_selector(t.make_ident(opt_tmp), 'value', value_type)
then_assign := t.make_assign(t.make_ident(val_tmp), value_expr)
then_block := t.make_block(arr1(then_assign))
else_block := t.make_block(t.lower_or_body_to_stmts(body_id, val_tmp, value_type, node.value,
opt_tmp))
if_stmt := t.make_if(ok_cond, then_block, else_block)
t.pending_stmts = outer_pending
for stmt in prelude {
t.pending_stmts << stmt
}
t.pending_stmts << if_stmt
return t.make_ident(val_tmp)
}
fn (mut t Transformer) lower_or_body_to_stmts(body_id flat.NodeId, target_name string, target_type string, mode string, err_source string) []flat.NodeId {
if mode == '!' || mode == '?' {
if t.is_optional_type_name(t.cur_fn_ret_type) {
return arr1(t.make_none_return_stmt_with_err(err_source))
}
return arr1(t.make_panic_stmt('option/result propagation failed'))
}
if int(body_id) < 0 {
return []flat.NodeId{}
}
body := t.a.nodes[int(body_id)]
if body.kind != .block {
if target_name.len == 0 {
value := t.transform_expr(body_id)
mut result := []flat.NodeId{}
t.drain_pending(mut result)
if t.node_type(body_id) != 'void' {
result << t.make_expr_stmt(value)
}
return result
}
return arr1(t.make_assign(t.make_ident(target_name), t.transform_expr(body_id)))
}
mut result := []flat.NodeId{}
if body.children_count == 0 {
return result
}
t.set_var_type('err', 'IError')
err_value := if err_source.len > 0 {
t.make_selector(t.make_ident(err_source), 'err', 'IError')
} else {
t.make_struct_init('IError')
}
result << t.make_decl_assign_typed('err', err_value, 'IError')
for i in 0 .. body.children_count {
child_id := t.a.child(&body, i)
child := t.a.nodes[int(child_id)]
is_last := i == body.children_count - 1
if is_last && child.kind == .expr_stmt && child.children_count > 0 {
inner_id := t.a.child(&child, 0)
inner := t.a.nodes[int(inner_id)]
if inner.kind == .call && t.is_noreturn_call(inner) {
expanded := t.transform_stmt(child_id)
t.drain_pending(mut result)
for eid in expanded {
result << eid
}
} else if t.node_type(inner_id) == 'void' {
expanded := t.transform_stmt(child_id)
t.drain_pending(mut result)
for eid in expanded {
result << eid
}
} else if target_name.len == 0 {
expanded := t.transform_stmt(child_id)
t.drain_pending(mut result)
for eid in expanded {
result << eid
}
} else {
value := if target_type in t.sum_types
|| t.resolve_sum_name(target_type) in t.sum_types {
t.wrap_sum_value(inner_id, target_type)
} else {
t.transform_expr_for_type(inner_id, target_type)
}
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
}
} else {
expanded := t.transform_stmt(child_id)
t.drain_pending(mut result)
for eid in expanded {
result << eid
}
}
}
_ = target_type
return result
}
fn (t &Transformer) is_noreturn_call(node flat.Node) bool {
if node.kind != .call || node.children_count == 0 {
return false
}
fn_node := t.a.child_node(&node, 0)
return fn_node.value in ['panic', 'exit']
}
+18
View File
@@ -0,0 +1,18 @@
module transform
import v3.flat
// transform_sql_expr transforms SQL/ORM expressions into function calls.
// V's ORM allows writing SQL-like queries directly in code:
// users := sql db { select from User where age > 18 order by name }
// This would be lowered into the appropriate database driver function calls.
// For now, returns the node unchanged as a hook for future ORM support.
fn (mut t Transformer) transform_sql_expr(id flat.NodeId, _node flat.Node) flat.NodeId {
// TODO: When ORM support is implemented:
// 1. Parse the SQL expression structure from children
// 2. Determine the target table and struct type
// 3. Build the appropriate db.exec() or db.select() call
// 4. Generate result mapping from rows to struct instances
// 5. Handle where clauses, order by, limit, etc.
return id
}
+435
View File
@@ -0,0 +1,435 @@
module transform
import v3.flat
// transform_return_with_sumtype_wrap checks if a return statement returns a
// variant value where the function's return type is a sum type. In that case,
// the variant value may need to be wrapped in the sum type's tagged union.
//
// For now, passes through unchanged since the C gen handles wrapping currently.
// This is a hook for future sum type return wrapping at the transform level.
fn (mut t Transformer) transform_return_with_sumtype_wrap(id flat.NodeId, node flat.Node) []flat.NodeId {
if node.children_count == 0 {
return arr1(id)
}
// Check if current function returns a sum type
if t.cur_fn_ret_type.len == 0 || t.cur_fn_ret_type !in t.sum_types {
return arr1(id)
}
// Check if the return value is a struct init whose type is a variant
child_id := t.a.child(&node, 0)
child := t.a.nodes[int(child_id)]
if child.kind == .struct_init && child.value.len > 0 {
variants := t.sum_types[t.cur_fn_ret_type]
for v in variants {
if v == child.value {
// This is a variant being returned as a sum type.
// For now, pass through - C gen handles the wrapping.
// TODO: Generate explicit sum type wrapping here.
return arr1(id)
}
}
}
return arr1(id)
}
// branch_tail_expr extracts the tail EXPRESSION id from a branch block,
// unwrapping a trailing `.expr_stmt` so we get the expression inside it
// rather than the statement wrapper.
fn (t &Transformer) branch_tail_expr(block_id flat.NodeId) flat.NodeId {
if int(block_id) < 0 {
return block_id
}
block := t.a.nodes[int(block_id)]
if block.kind != .block || block.children_count == 0 {
return block_id
}
last_id := t.a.child(&block, block.children_count - 1)
last := t.a.nodes[int(last_id)]
if last.kind == .expr_stmt && last.children_count > 0 {
return t.a.child(&last, 0)
}
return last_id
}
// make_return builds a `return <val>` statement node with the given type.
fn (mut t Transformer) make_return(val flat.NodeId, ret_typ string) flat.NodeId {
start := t.a.children.len
t.a.children << val
return t.a.add_node(flat.Node{
kind: .return_stmt
children_start: start
children_count: 1
typ: ret_typ
})
}
fn (t &Transformer) return_expr_is_err(id flat.NodeId) bool {
if int(id) < 0 {
return false
}
node := t.a.nodes[int(id)]
return node.kind == .ident && node.value == 'err'
}
fn (mut t Transformer) try_expand_return_optional_expr(node flat.Node) ?[]flat.NodeId {
if node.children_count != 1 || !t.is_optional_type_name(t.cur_fn_ret_type) {
return none
}
child_id := t.a.child(&node, 0)
child := t.a.nodes[int(child_id)]
if child.kind == .none_expr {
return none
}
if !t.return_expr_is_optional_result(child_id) {
return none
}
expr_type0 := t.optional_result_expr_type_name(child_id)
if !t.is_optional_type_name(expr_type0) {
return none
}
ret_type := t.qualify_optional_type(t.cur_fn_ret_type)
expr_type := t.qualify_optional_type(expr_type0)
new_expr := t.transform_expr(child_id)
mut result := []flat.NodeId{}
t.drain_pending(mut result)
tmp_name := t.new_temp('return_opt')
result << t.make_decl_assign_typed(tmp_name, new_expr, expr_type)
tmp_ident := t.make_ident(tmp_name)
ok_cond := t.make_selector(tmp_ident, 'ok', 'bool')
value := t.make_selector(t.make_ident(tmp_name), 'value', t.optional_base_type(expr_type))
then_block := t.make_block(arr1(t.make_return(value, ret_type)))
else_block := t.make_block(arr1(t.make_none_return_stmt()))
result << t.make_if(ok_cond, then_block, else_block)
return result
}
fn (t &Transformer) return_expr_is_optional_result(id flat.NodeId) bool {
if int(id) < 0 {
return false
}
node := t.a.nodes[int(id)]
if node.kind == .call && !isnil(t.tc) {
if name := t.tc.resolved_call_name(id) {
if ret := t.tc.fn_ret_types[name] {
return t.is_optional_type_name(ret.name())
}
}
return t.is_optional_type_name(t.get_call_return_type(id, node))
}
if node.kind == .ident {
typ := t.var_type(node.value)
return t.is_optional_type_name(typ) || t.is_optional_type_name(node.typ)
}
if node.kind in [.selector, .index, .if_expr, .match_stmt, .or_expr] {
return t.is_optional_type_name(t.node_type(id))
}
return t.is_optional_type_name(node.typ)
}
// return_block_from_branch builds a block that keeps leading statements
// (transformed) and turns the tail expression of the branch into a `return`.
fn (mut t Transformer) return_block_from_branch(branch_id flat.NodeId, ret_typ string) flat.NodeId {
branch := t.a.nodes[int(branch_id)]
if branch.kind == .return_stmt {
mut all := []flat.NodeId{}
ret_stmts := t.transform_stmt(branch_id)
t.drain_pending(mut all)
for stmt in ret_stmts {
all << stmt
}
return t.make_block(all)
}
if branch.kind != .block {
// single expression branch: just `return <expr>`
mut all := []flat.NodeId{}
ret_val := t.wrap_sum_return_expr(branch_id)
t.drain_pending(mut all)
all << t.make_return(ret_val, ret_typ)
return t.make_block(all)
}
mut stmt_ids := []flat.NodeId{}
for i in 0 .. branch.children_count {
stmt_ids << t.a.child(&branch, i)
}
if stmt_ids.len == 0 {
return t.make_block([]flat.NodeId{})
}
// all but the last are kept as statements (transformed); the last becomes a return
lead := stmt_ids[..stmt_ids.len - 1].clone()
new_lead := t.transform_stmts(lead)
tail_id := stmt_ids[stmt_ids.len - 1]
tail := t.a.nodes[int(tail_id)]
tail_expr := t.branch_tail_expr(branch_id)
mut all := []flat.NodeId{}
for s in new_lead {
all << s
}
if tail.kind == .return_stmt {
ret_stmts := t.transform_stmt(tail_id)
t.drain_pending(mut all)
for stmt in ret_stmts {
all << stmt
}
return t.make_block(all)
}
ret_val := t.wrap_sum_return_expr(tail_expr)
t.drain_pending(mut all)
ret := t.make_return(ret_val, ret_typ)
all << ret
return t.make_block(all)
}
// build_return_if_chain recursively converts an if_expr (possibly an else-if
// chain) into an if-statement whose branch tails are `return` statements.
fn (mut t Transformer) build_return_if_chain(if_id flat.NodeId, ret_typ string) flat.NodeId {
if_node := t.a.nodes[int(if_id)]
cond_id := t.a.child(&if_node, 0)
cond_smartcasts := t.extract_all_is_exprs(cond_id)
new_cond := t.transform_and_chain_smartcasts(cond_id)
then_id := t.a.child(&if_node, 1)
for info in cond_smartcasts {
t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name)
}
then_block := t.return_block_from_branch(then_id, ret_typ)
for _ in cond_smartcasts {
t.pop_smartcast()
}
mut else_block := flat.empty_node
if if_node.children_count >= 3 {
else_id := t.a.child(&if_node, 2)
else_node := t.a.nodes[int(else_id)]
if else_node.kind == .if_expr {
// else-if chain: recurse, wrap resulting if-stmt in a block
inner := t.build_return_if_chain(else_id, ret_typ)
else_block = t.make_block(arr1(inner))
} else {
else_block = t.return_block_from_branch(else_id, ret_typ)
}
}
return t.make_if(new_cond, then_block, else_block)
}
// try_expand_return_if detects a `return if cond { a } else { b }` pattern
// and expands it into `if cond { return a } else { return b }`.
// This simplification makes the C backend's job easier since it avoids
// needing statement-expressions for the if-as-value in the return position.
//
// Leading statements in each branch are preserved, and a trailing `.expr_stmt`
// is unwrapped so the inner expression (not the statement) is returned.
//
// Returns the expanded if-statement as a single-element array, or none if the
// pattern does not match. A plain `return if x {..}` with no else (if_expr with
// fewer than 3 children) is left unexpanded.
fn (mut t Transformer) try_expand_return_if(_id flat.NodeId, node flat.Node) ?[]flat.NodeId {
if node.children_count == 0 {
return none
}
val_id := t.a.child(&node, 0)
val_node := t.a.nodes[int(val_id)]
if val_node.kind != .if_expr || val_node.children_count < 3 {
return none
}
return arr1(t.build_return_if_chain(val_id, node.typ))
}
fn (mut t Transformer) match_branch_return_block(branch flat.Node, body_start_idx int, ret_typ string) flat.NodeId {
mut body_ids := []flat.NodeId{}
for i in body_start_idx .. branch.children_count {
body_ids << t.a.child(&branch, i)
}
if body_ids.len == 0 {
return t.make_block([]flat.NodeId{})
}
mut all := []flat.NodeId{}
if body_ids.len > 1 {
lead := body_ids[..body_ids.len - 1].clone()
for stmt in t.transform_stmts(lead) {
all << stmt
}
}
tail_id := body_ids[body_ids.len - 1]
tail := t.a.nodes[int(tail_id)]
if tail.kind == .return_stmt {
for stmt in t.transform_stmt(tail_id) {
all << stmt
}
return t.make_block(all)
}
tail_expr := if tail.kind == .expr_stmt && tail.children_count > 0 {
t.a.child(&tail, 0)
} else {
tail_id
}
tail_expr_node := t.a.nodes[int(tail_expr)]
if tail_expr_node.kind == .match_stmt {
nested_return := t.make_return(tail_expr, ret_typ)
for stmt in t.transform_stmt(nested_return) {
all << stmt
}
return t.make_block(all)
}
expected_ret := t.optional_base_type(ret_typ)
actual_tail := if tail_expr_node.kind == .enum_val {
t.transform_enum_shorthand(tail_expr, tail_expr_node, expected_ret)
} else {
tail_expr
}
ret_val := t.wrap_sum_return_expr(actual_tail)
t.drain_pending(mut all)
all << t.make_return(ret_val, ret_typ)
return t.make_block(all)
}
fn (mut t Transformer) build_return_match_chain(match_expr_id flat.NodeId, orig_expr_id flat.NodeId, branches []flat.NodeId, idx int, ret_typ string) flat.NodeId {
if idx >= branches.len {
return t.a.add(flat.NodeKind.empty)
}
branch := t.a.nodes[int(branches[idx])]
is_else := branch.value == 'else'
body_start_idx := if is_else { 0 } else { t.count_conds(branch) }
if !is_else && t.match_branch_all_type_patterns(branch) && t.count_conds(branch) > 1 {
return t.build_return_match_type_branch_chain(match_expr_id, orig_expr_id, branch,
branches, idx, 0, ret_typ)
}
mut sc_pushed := 0
if !is_else {
n_conds := t.count_conds(branch)
if n_conds == 1 {
cond_val_id := t.a.child(&branch, 0)
if variant_name := t.match_type_pattern(cond_val_id) {
subj := t.expr_key(match_expr_id)
sum_name := t.find_sum_type_for_variant(variant_name)
if subj.len > 0 && sum_name.len > 0 {
t.push_smartcast(subj, variant_name, sum_name)
sc_pushed++
}
orig_subj := t.expr_key(orig_expr_id)
if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
t.push_smartcast(orig_subj, variant_name, sum_name)
sc_pushed++
}
}
}
}
body_block := t.match_branch_return_block(branch, body_start_idx, ret_typ)
for _ in 0 .. sc_pushed {
t.pop_smartcast()
}
if is_else {
return body_block
}
cond_id := t.build_match_cond(match_expr_id, branch)
mut if_ids := []flat.NodeId{}
if_ids << cond_id
if_ids << body_block
if idx + 1 < branches.len {
if_ids << t.build_return_match_chain(match_expr_id, orig_expr_id, branches, idx + 1,
ret_typ)
}
if_start := t.a.children.len
for id in if_ids {
t.a.children << id
}
return t.a.add_node(flat.Node{
kind: .if_expr
children_start: if_start
children_count: flat.child_count(if_ids.len)
})
}
fn (mut t Transformer) build_return_match_type_branch_chain(match_expr_id flat.NodeId, orig_expr_id flat.NodeId, branch flat.Node, branches []flat.NodeId, idx int, cond_idx int, ret_typ string) flat.NodeId {
n_conds := t.count_conds(branch)
if cond_idx >= n_conds {
return if idx + 1 < branches.len {
t.build_return_match_chain(match_expr_id, orig_expr_id, branches, idx + 1, ret_typ)
} else {
t.a.add(flat.NodeKind.empty)
}
}
cond_val_id := t.a.child(&branch, cond_idx)
variant_name := t.match_type_pattern(cond_val_id) or {
return t.build_return_match_chain(match_expr_id, orig_expr_id, branches, idx + 1, ret_typ)
}
is_start := t.a.children.len
t.a.children << match_expr_id
is_id := t.a.add_node(flat.Node{
kind: .is_expr
value: variant_name
children_start: is_start
children_count: 1
})
cond_id := t.transform_is_expr(is_id, t.a.nodes[int(is_id)])
mut sc_pushed := 0
subj := t.expr_key(match_expr_id)
sum_name := t.find_sum_type_for_variant(variant_name)
if subj.len > 0 && sum_name.len > 0 {
t.push_smartcast(subj, variant_name, sum_name)
sc_pushed++
}
orig_subj := t.expr_key(orig_expr_id)
if orig_subj.len > 0 && orig_subj != subj && sum_name.len > 0 {
t.push_smartcast(orig_subj, variant_name, sum_name)
sc_pushed++
}
body_block := t.match_branch_return_block(branch, n_conds, ret_typ)
for _ in 0 .. sc_pushed {
t.pop_smartcast()
}
else_part := t.build_return_match_type_branch_chain(match_expr_id, orig_expr_id, branch,
branches, idx, cond_idx + 1, ret_typ)
start := t.a.children.len
t.a.children << cond_id
t.a.children << body_block
t.a.children << else_part
return t.a.add_node(flat.Node{
kind: .if_expr
children_start: start
children_count: 3
})
}
// try_expand_return_match detects a `return match x { ... }` pattern and expands
// it into an if/else-if chain where every branch tail is an explicit return.
fn (mut t Transformer) try_expand_return_match(_id flat.NodeId, node flat.Node) ?[]flat.NodeId {
if node.children_count == 0 {
return none
}
val_id := t.a.child(&node, 0)
val := t.a.nodes[int(val_id)]
if val.kind != .match_stmt || val.children_count < 2 {
return none
}
match_expr_id := t.a.child(&val, 0)
match_expr := t.a.nodes[int(match_expr_id)]
needs_temp := match_expr.kind !in [.ident, .int_literal, .bool_literal, .string_literal,
.char_literal]
mut result := []flat.NodeId{}
mut actual_expr_id := flat.empty_node
if needs_temp {
tmp_name := t.new_temp('match')
match_type := t.node_type(match_expr_id)
new_expr := t.transform_expr(match_expr_id)
t.drain_pending(mut result)
result << t.make_decl_assign_typed(tmp_name, new_expr, match_type)
actual_expr_id = t.make_ident(tmp_name)
t.a.nodes[int(actual_expr_id)].typ = match_type
t.set_var_type(tmp_name, match_type)
} else {
actual_expr_id = t.transform_expr(match_expr_id)
t.drain_pending(mut result)
}
mut branches := []flat.NodeId{}
for i in 1 .. val.children_count {
branches << t.a.child(&val, i)
}
result << t.build_return_match_chain(actual_expr_id, match_expr_id, branches, 0, node.typ)
return result
}
+571
View File
@@ -0,0 +1,571 @@
module transform
import v3.flat
fn (mut t Transformer) transform_field_init_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count == 0 {
return id
}
val_id := t.a.child(&node, 0)
val_node := t.a.nodes[int(val_id)]
new_val := if inferred_sum := t.sum_type_for_field_variant(node.value, val_id, val_node) {
t.wrap_sum_value(val_id, inferred_sum)
} else {
t.transform_expr(val_id)
}
start := t.a.children.len
t.a.children << new_val
return t.a.add_node(flat.Node{
kind: .field_init
op: node.op
children_start: start
children_count: 1
pos: node.pos
value: node.value
typ: node.typ
})
}
// transform_struct_fields transforms struct initialization fields with enum resolution.
// For each .field_init child, transforms the value expression. If the struct field type
// is a known enum, resolves shorthand enum values (e.g. `.red` -> `Color.red`).
fn (mut t Transformer) transform_struct_fields(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count == 0 {
return t.add_missing_struct_defaults(id, node)
}
info := t.lookup_struct_info(node.value) or {
// Unknown struct: fall back to generic child transform
return t.transform_struct_children(id, node)
}
// Build a field name -> type lookup from the struct definition
mut field_types := map[string]string{}
mut field_order := []string{cap: info.fields.len}
for f in info.fields {
field_types[f.name] = f.typ
field_order << f.name
}
mut field_ids := []flat.NodeId{}
mut promoted_fields := map[string][]flat.NodeId{}
mut promoted_types := map[string]string{}
mut prelude := []flat.NodeId{}
t.drain_pending(mut prelude)
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
child := t.a.nodes[int(child_id)]
if child.kind == .field_init && child.children_count > 0 {
val_id := t.a.child(&child, 0)
val_node := t.a.nodes[int(val_id)]
field_name := if child.value.len > 0 {
child.value
} else if i < field_order.len {
field_order[i]
} else {
''
}
mut target_field_name := field_name
mut field_type := field_types[field_name] or { '' }
mut promoted_parent := ''
if field_type.len == 0 {
if embedded := t.embedded_field_for_promoted_field(info, field_name) {
promoted_parent = embedded.name
promoted_types[promoted_parent] = embedded.typ
if embedded_info := t.lookup_struct_info(embedded.typ) {
if promoted_type := t.struct_field_type(embedded_info, field_name) {
field_type = promoted_type
}
}
}
}
if val_node.kind == .array_literal && is_fixed_array_type(field_type) {
t.a.nodes[int(val_id)].typ = field_type
}
value_type := t.node_type(val_id)
sum_field_type := t.struct_field_sum_type(field_type, info.module)
enum_field_type := t.enum_type_name_for_expected(field_type, info.module)
// Check if the value is an enum shorthand and the field type is an enum
mut new_val := if val_node.kind == .enum_val && enum_field_type.len > 0 {
t.transform_enum_shorthand(val_id, val_node, enum_field_type)
} else if field_type.starts_with('[]') && is_fixed_array_type(value_type) {
t.fixed_array_value_to_array(val_id, value_type, field_type)
} else if sum_field_type.len > 0 {
t.wrap_sum_value(val_id, sum_field_type)
} else if inferred_sum := t.sum_type_for_field_variant(field_name, val_id, val_node) {
t.wrap_sum_value(val_id, inferred_sum)
} else if field_type.starts_with('&') {
t.transform_expr_for_type(val_id, field_type)
} else if field_type.len > 0 {
t.transform_expr_for_type(val_id, field_type)
} else {
t.transform_expr(val_id)
}
if sum_field_type.len == 0 && field_type.len > 0 {
new_val = t.coerce_transformed_expr_to_type(new_val, val_id, field_type)
}
t.drain_pending(mut prelude)
fi_start := t.a.children.len
t.a.children << new_val
new_field := t.a.add_node(flat.Node{
kind: .field_init
op: child.op
children_start: fi_start
children_count: 1
pos: child.pos
value: target_field_name
typ: child.typ
})
if promoted_parent.len > 0 {
mut promoted := promoted_fields[promoted_parent] or { []flat.NodeId{} }
promoted << new_field
promoted_fields[promoted_parent] = promoted
} else {
field_ids << new_field
}
} else {
field_ids << child_id
}
}
for parent, promoted in promoted_fields {
promoted_start := t.a.children.len
for fid in promoted {
t.a.children << fid
}
embedded_type := promoted_types[parent] or { parent }
embedded_init := t.a.add_node(flat.Node{
kind: .struct_init
children_start: promoted_start
children_count: flat.child_count(promoted.len)
value: embedded_type
typ: embedded_type
})
fi_start := t.a.children.len
t.a.children << embedded_init
field_ids << t.a.add_node(flat.Node{
kind: .field_init
children_start: fi_start
children_count: 1
value: parent
typ: embedded_type
})
}
start := t.a.children.len
for fid in field_ids {
t.a.children << fid
}
new_id := t.a.add_node(flat.Node{
kind: .struct_init
op: node.op
children_start: start
children_count: flat.child_count(field_ids.len)
pos: node.pos
value: node.value
typ: node.typ
})
final_id := t.add_missing_struct_defaults(new_id, t.a.nodes[int(new_id)])
for stmt in prelude {
t.pending_stmts << stmt
}
return final_id
}
// transform_struct_children is a fallback for struct inits where the struct type is unknown.
// Transforms all field_init value expressions without enum resolution.
fn (mut t Transformer) transform_struct_children(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count == 0 {
return id
}
mut field_ids := []flat.NodeId{}
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
child := t.a.nodes[int(child_id)]
if child.kind == .field_init && child.children_count > 0 {
val_id := t.a.child(&child, 0)
new_val := t.transform_expr(val_id)
fi_start := t.a.children.len
t.a.children << new_val
field_ids << t.a.add_node(flat.Node{
kind: .field_init
op: child.op
children_start: fi_start
children_count: 1
pos: child.pos
value: child.value
typ: child.typ
})
} else {
field_ids << child_id
}
}
start := t.a.children.len
for fid in field_ids {
t.a.children << fid
}
return t.a.add_node(flat.Node{
kind: .struct_init
op: node.op
children_start: start
children_count: flat.child_count(field_ids.len)
pos: node.pos
value: node.value
typ: node.typ
})
}
// add_missing_struct_defaults checks if any fields with default values are missing
// from the struct initialization. This is a hook point for future default-fill logic.
// Currently returns the node unchanged because StructInfo does not yet store default values.
fn (mut t Transformer) add_missing_struct_defaults(id flat.NodeId, node flat.Node) flat.NodeId {
if node.value.len == 0 {
return id
}
info := t.lookup_struct_info(node.value) or { return id }
mut provided := map[string]bool{}
mut field_ids := []flat.NodeId{cap: int(node.children_count) + info.fields.len}
mut prelude := []flat.NodeId{}
t.drain_pending(mut prelude)
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
child := t.a.nodes[int(child_id)]
if child.kind == .field_init {
provided[child.value] = true
}
field_ids << child_id
}
old_module := t.cur_module
if info.module.len > 0 {
t.cur_module = info.module
}
mut added := false
for field in info.fields {
if field.name in provided || int(field.default_expr) < 0 {
continue
}
default_node := t.a.nodes[int(field.default_expr)]
enum_field_type := t.enum_type_name_for_expected(field.typ, info.module)
new_val := if default_node.kind == .enum_val && enum_field_type.len > 0 {
t.transform_enum_shorthand(field.default_expr, default_node, enum_field_type)
} else if t.is_sum_type_name(field.typ) {
// A sum-type field default (e.g. `typ_expr Expr = EmptyExpr{}`) must be
// wrapped into the sum, not emitted as the bare variant. wrap_sum_value
// is a no-op when the value already is the sum type.
t.wrap_sum_value(field.default_expr, field.typ)
} else {
t.transform_expr_for_type(field.default_expr, field.typ)
}
t.drain_pending(mut prelude)
fi_start := t.a.children.len
t.a.children << new_val
field_ids << t.a.add_node(flat.Node{
kind: .field_init
children_start: fi_start
children_count: 1
value: field.name
typ: field.typ
})
provided[field.name] = true
added = true
}
t.cur_module = old_module
if !added {
for stmt in prelude {
t.pending_stmts << stmt
}
return id
}
start := t.a.children.len
for fid in field_ids {
t.a.children << fid
}
final_id := t.a.add_node(flat.Node{
kind: .struct_init
op: node.op
children_start: start
children_count: flat.child_count(field_ids.len)
pos: node.pos
value: node.value
typ: node.typ
})
for stmt in prelude {
t.pending_stmts << stmt
}
return final_id
}
fn (t &Transformer) lookup_struct_info(name string) ?StructInfo {
if name.contains('.') {
if name in t.structs {
return t.structs[name]
}
} else if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
qname := '${t.cur_module}.${name}'
if qname in t.structs {
return t.structs[qname]
}
}
if name in t.structs {
return t.structs[name]
}
if name.contains('.') {
short_name := name.all_after_last('.')
if short_name in t.structs {
return t.structs[short_name]
}
}
return none
}
fn (t &Transformer) struct_field_type(info StructInfo, field_name string) ?string {
for field in info.fields {
if field.name == field_name {
return field.typ
}
}
return none
}
fn (t &Transformer) embedded_field_for_promoted_field(info StructInfo, field_name string) ?FieldInfo {
for field in info.fields {
if !t.is_embedded_field(field) {
continue
}
embedded_info := t.lookup_struct_info(field.typ) or { continue }
if _ := t.struct_field_type(embedded_info, field_name) {
return field
}
}
return none
}
fn (t &Transformer) is_embedded_field(field FieldInfo) bool {
if field.name.len == 0 || field.typ.len == 0 {
return false
}
short_typ := if field.typ.contains('.') { field.typ.all_after_last('.') } else { field.typ }
short_name := if field.name.contains('.') { field.name.all_after_last('.') } else { field.name }
return short_name == short_typ
}
fn (mut t Transformer) transform_assoc_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.kind != .assoc || node.children_count == 0 {
return id
}
base_id := t.a.child(&node, 0)
base_type := t.node_type(base_id)
mut assoc_type := node.value
if assoc_type.len == 0 {
assoc_type = base_type
}
if assoc_type.starts_with('&') {
assoc_type = assoc_type[1..]
}
mut field_types := map[string]string{}
mut assoc_module := ''
if info := t.lookup_struct_info(assoc_type) {
assoc_module = info.module
for field in info.fields {
field_types[field.name] = field.typ
}
}
tmp_name := t.new_temp('assoc')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
mut prelude := []flat.NodeId{}
transformed_base := t.transform_expr(base_id)
base := if base_type.starts_with('&') {
t.make_prefix(.mul, transformed_base)
} else {
transformed_base
}
t.drain_pending(mut prelude)
prelude << t.make_decl_assign_typed(tmp_name, base, assoc_type)
for i in 1 .. node.children_count {
field_id := t.a.child(&node, i)
field := t.a.nodes[int(field_id)]
if field.kind != .field_init || field.children_count == 0 {
continue
}
value_id := t.a.child(&field, 0)
value_node := t.a.nodes[int(value_id)]
field_type := field_types[field.value] or { '' }
if value_node.kind == .array_literal && is_fixed_array_type(field_type) {
t.a.nodes[int(value_id)].typ = field_type
}
value_type := t.node_type(value_id)
enum_field_type := t.enum_type_name_for_expected(field_type, assoc_module)
value := if value_node.kind == .enum_val && enum_field_type.len > 0 {
t.transform_enum_shorthand(value_id, value_node, enum_field_type)
} else if field_type.starts_with('[]') && is_fixed_array_type(value_type) {
t.fixed_array_value_to_array(value_id, value_type, field_type)
} else if t.is_sum_type_name(field_type) {
t.wrap_sum_value(value_id, field_type)
} else if field_type.len > 0 {
t.transform_expr_for_type(value_id, field_type)
} else {
t.transform_expr(value_id)
}
t.drain_pending(mut prelude)
prelude << t.make_assign(t.make_selector(t.make_ident(tmp_name), field.value, field_type),
value)
}
t.pending_stmts = outer_pending
for stmt in prelude {
t.pending_stmts << stmt
}
result := t.make_ident(tmp_name)
t.a.nodes[int(result)].typ = assoc_type
return result
}
fn (mut t Transformer) transform_amp_assoc_expr_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
if node.kind != .prefix || node.op != .amp || node.children_count != 1 {
return none
}
child_id := t.a.child(&node, 0)
child := t.a.nodes[int(child_id)]
if child.kind != .assoc {
return none
}
value := t.transform_assoc_expr(child_id, child)
mut value_type := t.node_type(value)
if value_type.len == 0 {
value_type = t.node_type(child_id)
}
if value_type.starts_with('&') {
value_type = value_type[1..]
}
if value_type.len == 0 {
return none
}
addr := t.make_prefix(.amp, value)
size := t.make_sizeof_type(value_type)
dup := t.make_call_typed('memdup', arr2(addr, size), 'voidptr')
mut ptr_type := target_type
if ptr_type.len == 0 {
ptr_type = node.typ
}
if !ptr_type.starts_with('&') {
ptr_type = '&${value_type}'
}
cast := t.make_cast(ptr_type, dup, ptr_type)
t.a.nodes[int(cast)].typ = ptr_type
return cast
}
fn (t &Transformer) struct_field_sum_type(field_type string, owner_module string) string {
if t.is_sum_type_name(field_type) {
return field_type
}
if field_type.len == 0 || field_type.contains('.') || owner_module.len == 0 {
return ''
}
qname := '${owner_module}.${field_type}'
if qname in t.sum_types {
return qname
}
return ''
}
fn (t &Transformer) sum_type_for_field_variant(field_name string, val_id flat.NodeId, val_node flat.Node) ?string {
if field_name != 'info' {
return none
}
mut variant := if val_node.kind in [.struct_init, .cast_expr, .assoc] && val_node.value.len > 0 {
val_node.value
} else if val_node.kind == .assoc && val_node.children_count > 0 {
t.node_type(t.a.child(&val_node, 0))
} else {
t.node_type(val_id)
}
if variant.starts_with('&') {
variant = variant[1..]
}
if variant.len == 0 {
return none
}
short_variant := if variant.contains('.') { variant.all_after_last('.') } else { variant }
for sum_name, variants in t.sum_types {
if sum_name != 'TypeInfo' && !sum_name.ends_with('.TypeInfo') {
continue
}
for v in variants {
short_v := if v.contains('.') { v.all_after_last('.') } else { v }
if v == variant || short_v == short_variant {
return sum_name
}
}
}
return none
}
fn (mut t Transformer) fixed_array_value_to_array(value_id flat.NodeId, fixed_type string, array_type string) flat.NodeId {
elem_type := fixed_array_elem_type(fixed_type)
len_expr := t.make_fixed_array_len_expr(fixed_type)
return t.make_call_typed('new_array_from_c_array', [
len_expr,
len_expr,
t.make_sizeof_type(elem_type),
t.transform_expr(value_id),
], array_type)
}
// transform_array_init_expr transforms .array_init nodes (e.g. `[]int{len: n}`).
// Recursively transforms any child expressions (len, cap, init values).
fn (mut t Transformer) transform_array_init_expr(id flat.NodeId, node flat.Node) flat.NodeId {
lowered := t.lower_array_init_to_runtime(id, node)
if lowered != id {
return lowered
}
if node.children_count == 0 {
return id
}
mut new_children := []flat.NodeId{cap: int(node.children_count)}
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
new_children << t.transform_expr(child_id)
}
start := t.a.children.len
for nc in new_children {
t.a.children << nc
}
return t.a.add_node(flat.Node{
kind: .array_init
op: node.op
children_start: start
children_count: node.children_count
pos: node.pos
value: node.value
typ: node.typ
})
}
// transform_map_init_expr transforms .map_init nodes.
// Recursively transforms all child key/value expressions.
fn (mut t Transformer) transform_map_init_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.value.starts_with('map[') || node.typ.starts_with('map[') {
return t.lower_map_init_to_runtime(id, node)
}
if node.children_count == 0 {
return id
}
mut new_children := []flat.NodeId{cap: int(node.children_count)}
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
new_children << t.transform_expr(child_id)
}
start := t.a.children.len
for nc in new_children {
t.a.children << nc
}
return t.a.add_node(flat.Node{
kind: .map_init
op: node.op
children_start: start
children_count: node.children_count
pos: node.pos
value: node.value
typ: node.typ
})
}
+533
View File
@@ -0,0 +1,533 @@
module transform
import v3.flat
fn (t &Transformer) trim_pointer_type(typ string) string {
if typ.starts_with('&') {
return typ[1..]
}
return typ
}
fn (t &Transformer) resolve_variant(sum_name string, variant string) string {
if variant.contains('.') {
return variant
}
resolved_sum := t.resolve_sum_name(sum_name)
if resolved_variant := t.sum_variant_name(resolved_sum, variant) {
return resolved_variant
}
if resolved_sum.contains('.') {
return '${resolved_sum.all_before_last('.')}.${variant}'
}
if sum_name.contains('.') {
return '${sum_name.all_before_last('.')}.${variant}'
}
return variant
}
fn (t &Transformer) resolve_sum_name(sum_name string) string {
if sum_name.len == 0 {
return sum_name
}
if isnil(t.sum_cache) {
return t.resolve_sum_name_uncached(sum_name)
}
mut c := t.sum_cache
if c.module != t.cur_module {
c.module = t.cur_module
c.entries.clear()
}
if cached := c.entries[sum_name] {
return cached
}
result := t.resolve_sum_name_uncached(sum_name)
c.entries[sum_name] = result
return result
}
fn (t &Transformer) resolve_sum_name_uncached(sum_name string) string {
if sum_name in t.sum_types {
return sum_name
}
if sum_name.contains('.') {
short_sum := sum_name.all_after_last('.')
if short_sum in t.sum_types {
return short_sum
}
}
if !sum_name.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
qsum := '${t.cur_module}.${sum_name}'
if qsum in t.sum_types {
return qsum
}
}
if !sum_name.contains('.') {
mut found := ''
for key, _ in t.sum_types {
if key.contains('.') && key.all_after_last('.') == sum_name {
if found.len > 0 && found != key {
found = ''
break
}
found = key
}
}
if found.len > 0 {
return found
}
}
if !isnil(t.tc) {
if sum_name in t.tc.sum_types {
return sum_name
}
if sum_name.contains('.') {
short_sum := sum_name.all_after_last('.')
if short_sum in t.tc.sum_types {
return short_sum
}
}
if !sum_name.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
qsum := '${t.cur_module}.${sum_name}'
if qsum in t.tc.sum_types {
return qsum
}
}
if !sum_name.contains('.') {
mut found := ''
for key, _ in t.tc.sum_types {
if key.contains('.') && key.all_after_last('.') == sum_name {
if found.len > 0 && found != key {
found = ''
break
}
found = key
}
}
if found.len > 0 {
return found
}
}
}
return sum_name
}
fn (t &Transformer) is_sum_type_name(name string) bool {
if name.len == 0 {
return false
}
resolved := t.resolve_sum_name(name)
return resolved in t.sum_types
}
fn (t &Transformer) is_interface_type_name(name string) bool {
if name.len == 0 || isnil(t.tc) {
return false
}
clean := t.trim_pointer_type(t.normalize_type_alias(name))
if clean == 'IError' || clean in t.tc.interface_names {
return true
}
if !clean.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
return '${t.cur_module}.${clean}' in t.tc.interface_names
}
return false
}
fn (t &Transformer) interface_variant_type(variant string) string {
if variant.contains('.') {
return variant
}
if variant in t.structs {
return variant
}
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
qvariant := '${t.cur_module}.${variant}'
if qvariant in t.structs {
return qvariant
}
}
for name, _ in t.structs {
if name.contains('.') && name.all_after_last('.') == variant {
return name
}
}
return variant
}
fn (t &Transformer) smartcast_target_type(sc SmartcastContext) string {
if t.is_interface_type_name(sc.sum_type_name) {
return t.interface_variant_type(sc.variant_name)
}
return t.resolve_variant(sc.sum_type_name, sc.variant_name)
}
fn (t &Transformer) sum_type_index(sum_name string, variant string) int {
resolved_sum := t.resolve_sum_name(sum_name)
variants := t.sum_types[resolved_sum] or {
if !isnil(t.tc) {
tc_variants := t.tc.sum_types[resolved_sum] or { return 0 }
return sum_type_index_in_variants(tc_variants, variant)
}
return 0
}
return sum_type_index_in_variants(variants, variant)
}
fn sum_type_index_in_variants(variants []string, variant string) int {
short_variant := if variant.contains('.') { variant.all_after_last('.') } else { variant }
for i, v in variants {
short_v := if v.contains('.') { v.all_after_last('.') } else { v }
if v == variant || short_v == short_variant {
return i + 1
}
}
return 0
}
fn (mut t Transformer) transform_is_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count == 0 {
return id
}
expr_id := t.a.child(&node, 0)
expr_type := t.node_type(expr_id)
clean_type0 := t.trim_pointer_type(expr_type)
clean_type := if clean_type0 in t.sum_types {
clean_type0
} else {
t.find_sum_type_for_variant(node.value)
}
if t.is_interface_type_name(clean_type0) {
new_expr := t.transform_expr(expr_id)
object := t.make_selector_op(new_expr, '_object', 'voidptr', if expr_type.starts_with('&') {
.arrow
} else {
.dot
})
return t.make_infix(.ne, object, t.a.add(.nil_literal))
}
if clean_type.len == 0 || clean_type !in t.sum_types {
return t.make_bool_literal(true)
}
new_expr := t.transform_expr(expr_id)
if check := t.make_sum_type_pattern_check(new_expr, expr_type, clean_type, node.value) {
return check
}
return t.make_bool_literal(true)
}
fn (mut t Transformer) make_sum_is_check(expr flat.NodeId, expr_type string, sum_name string, variant string) flat.NodeId {
tag := t.make_selector_op(expr, 'typ', 'int', if expr_type.starts_with('&') {
.arrow
} else {
.dot
})
idx := t.make_int_literal(t.sum_type_index(sum_name, variant))
return t.make_infix(.eq, tag, idx)
}
fn (t &Transformer) sum_variant_path(sum_name string, variant string) []string {
mut visited := map[string]bool{}
return t.sum_variant_path_inner(sum_name, variant, mut visited)
}
fn (t &Transformer) sum_variant_path_inner(sum_name string, variant string, mut visited map[string]bool) []string {
resolved_sum := t.resolve_sum_name(t.trim_pointer_type(sum_name))
if resolved_sum.len == 0 || resolved_sum in visited {
return []string{}
}
visited[resolved_sum] = true
if direct := t.sum_variant_name(resolved_sum, variant) {
return [direct]
}
variants := t.sum_types[resolved_sum] or { return []string{} }
for direct in variants {
direct_sum := t.resolve_sum_name(t.trim_pointer_type(direct))
if direct_sum == resolved_sum || direct_sum !in t.sum_types {
continue
}
nested := t.sum_variant_path_inner(direct_sum, variant, mut visited)
if nested.len == 0 {
continue
}
mut path := []string{cap: nested.len + 1}
path << direct
path << nested
return path
}
return []string{}
}
fn (mut t Transformer) make_sum_type_pattern_check(expr flat.NodeId, expr_type string, sum_name string, variant string) ?flat.NodeId {
resolved_sum := t.resolve_sum_name(t.trim_pointer_type(sum_name))
path := t.sum_variant_path(resolved_sum, variant)
if path.len == 0 {
return none
}
mut current := expr
mut current_type := expr_type
mut current_sum := resolved_sum
mut chain := flat.empty_node
for i, path_variant in path {
cmp := t.make_sum_is_check(current, current_type, current_sum, path_variant)
if int(chain) < 0 {
chain = cmp
} else {
chain = t.make_infix(.logical_and, chain, cmp)
}
if i == path.len - 1 {
break
}
qv := t.resolve_variant(current_sum, path_variant)
use_ptr := t.variant_references_sum(qv, current_sum)
field_type := if use_ptr { '&${qv}' } else { qv }
current = t.make_selector_op(current, t.sum_field_name(qv), field_type, if current_type.starts_with('&') {
.arrow
} else {
.dot
})
current_type = field_type
current_sum = t.resolve_sum_name(t.trim_pointer_type(qv))
}
return chain
}
fn (mut t Transformer) transform_as_expr(id flat.NodeId, node flat.Node) flat.NodeId {
if node.children_count == 0 {
return id
}
expr_id := t.a.child(&node, 0)
expr_type := t.node_type(expr_id)
clean_type0 := t.trim_pointer_type(expr_type)
clean_type := if clean_type0 in t.sum_types {
clean_type0
} else {
t.find_sum_type_for_variant(node.value)
}
if clean_type.len == 0 || clean_type !in t.sum_types {
return t.transform_expr(expr_id)
}
qv := t.resolve_variant(clean_type, node.value)
if qv.len > 0 && t.normalize_type_alias(clean_type0) == t.normalize_type_alias(qv) {
return t.transform_expr(expr_id)
}
sc_key := t.expr_key(expr_id)
if sc_key.len > 0 {
contexts := t.smartcasts_for(sc_key)
mut matched_contexts := []SmartcastContext{}
for i, sc in contexts {
mut sc_variant := sc.variant_name
if sc.variant_name.contains('.') {
sc_variant = sc.variant_name.all_after_last('.')
}
mut target_variant := node.value
if node.value.contains('.') {
target_variant = node.value.all_after_last('.')
}
if sc_variant == target_variant {
end := i + 1
matched_contexts = contexts[..end].clone()
}
}
if matched_contexts.len > 0 {
base := t.make_plain_expr_for_smartcast(expr_id)
return t.apply_smartcast_contexts(base, t.original_expr_type(expr_id), matched_contexts)
}
}
field := t.sum_field_name(qv)
new_expr := t.transform_expr(expr_id)
use_ptr := t.variant_references_sum(qv, clean_type)
field_typ := if use_ptr { '&${qv}' } else { qv }
field_sel := t.make_selector_op(new_expr, field, field_typ, if expr_type.starts_with('&') {
.arrow
} else {
.dot
})
if use_ptr {
return t.make_prefix(.mul, field_sel)
}
return field_sel
}
fn (mut t Transformer) wrap_sum_return_expr(expr_id flat.NodeId) flat.NodeId {
was_return_expr := t.in_return_expr
t.in_return_expr = true
result := if t.cur_fn_ret_type.len == 0 || t.cur_fn_ret_type !in t.sum_types {
t.transform_expr(expr_id)
} else {
t.wrap_sum_value(expr_id, t.cur_fn_ret_type)
}
t.in_return_expr = was_return_expr
return result
}
fn (mut t Transformer) wrap_sum_value(expr_id flat.NodeId, target_sum string) flat.NodeId {
resolved_sum := t.resolve_sum_name(target_sum)
if resolved_sum.len == 0 || resolved_sum !in t.sum_types {
return t.transform_expr(expr_id)
}
expr := t.a.nodes[int(expr_id)]
if expr.kind == .if_expr {
branch_type := t.if_expr_branch_result_type(expr)
if t.if_expr_branch_overrides_sum_target(branch_type, resolved_sum) {
return t.transform_expr(expr_id)
}
if lowered := t.try_expand_if_expr_value_for_type(expr_id, expr, resolved_sum) {
return lowered
}
}
expr_type := t.node_type(expr_id)
mut variant := expr_type
mut expr_smartcast := SmartcastContext{}
key := t.expr_key(expr_id)
if key.len > 0 {
if sc := t.find_smartcast(key) {
expr_smartcast = sc
}
}
has_expr_smartcast := expr_smartcast.expr_name.len > 0
if has_expr_smartcast && t.resolve_sum_name(expr_smartcast.sum_type_name) == resolved_sum {
variant = t.resolve_variant(expr_smartcast.sum_type_name, expr_smartcast.variant_name)
}
if expr.kind == .prefix && expr.op == .mul && expr.children_count > 0 {
inner_type := t.node_type(t.a.child(&expr, 0))
if inner_type.starts_with('&') {
variant = inner_type[1..]
}
}
if expr.kind == .struct_init || expr.kind == .cast_expr {
variant = expr.value
} else if expr.kind == .as_expr && expr.value.len > 0 {
variant = expr.value
} else if expr.kind == .assoc && expr.value.len > 0 {
variant = expr.value
} else if expr.kind == .assoc && expr.children_count > 0 {
variant = t.node_type(t.a.child(&expr, 0))
}
if expr.kind !in [.assoc, .as_expr, .prefix] && !has_expr_smartcast
&& t.resolve_sum_name(expr_type) == resolved_sum {
return t.transform_expr(expr_id)
}
if expr_type.starts_with('&') && t.resolve_sum_name(expr_type[1..]) == resolved_sum {
inner := t.transform_expr(expr_id)
deref := t.make_prefix(.mul, inner)
t.a.nodes[int(deref)].typ = resolved_sum
return deref
}
if variant.len == 0 {
return t.transform_expr(expr_id)
}
mut clean_variant := if variant.starts_with('&') { variant[1..] } else { variant }
if clean_variant.starts_with('ptr') && clean_variant.len > 3 && clean_variant[3..].contains('.') {
clean_variant = clean_variant[3..]
}
if clean_variant.starts_with('ptr') && clean_variant.len > 3
&& clean_variant[3..].contains('__') {
clean_variant = clean_variant[3..].replace('__', '.')
}
short_variant := if clean_variant.contains('.') {
clean_variant.all_after_last('.')
} else {
clean_variant
}
mut matches := false
mut matched_variant := clean_variant
for v in t.sum_types[resolved_sum] {
short_v := if v.contains('.') { v.all_after_last('.') } else { v }
if v == clean_variant || short_v == short_variant {
matches = true
matched_variant = v
break
}
}
if !matches {
return t.transform_expr(expr_id)
}
ref_variant := t.variant_references_sum(matched_variant, resolved_sum)
mut pointer_variant_child := flat.empty_node
if expr.kind == .prefix && expr.op == .mul && expr.children_count > 0 {
pointer_variant_child = t.a.child(&expr, 0)
}
inner := if int(pointer_variant_child) >= 0 && ref_variant {
t.transform_expr(pointer_variant_child)
} else {
t.transform_expr(expr_id)
}
if expr_type.starts_with('&') {
return t.make_sum_literal(resolved_sum, matched_variant, inner)
}
if int(pointer_variant_child) >= 0 && ref_variant {
return t.make_sum_literal(resolved_sum, matched_variant, inner)
}
if ref_variant {
return t.make_sum_literal(resolved_sum, matched_variant, inner)
}
start := t.a.children.len
t.a.children << inner
return t.a.add_node(flat.Node{
kind: .cast_expr
value: resolved_sum
children_start: start
children_count: 1
typ: resolved_sum
})
}
fn (mut t Transformer) ensure_sum_variant_ref(value flat.NodeId, variant string) flat.NodeId {
mut value_type := t.node_type(value)
if value_type.starts_with('&') {
return value
}
clean_variant := if variant.starts_with('&') { variant[1..] } else { variant }
if value_type.len == 0 {
value_type = clean_variant
}
if t.expr_can_take_address(value) || t.a.nodes[int(value)].kind == .struct_init {
ref := t.make_prefix(.amp, value)
t.a.nodes[int(ref)].typ = '&${clean_variant}'
return ref
}
tmp_name := t.new_temp('sum_val')
t.pending_stmts << t.make_decl_assign_typed(tmp_name, value, value_type)
ref := t.make_prefix(.amp, t.make_ident(tmp_name))
t.a.nodes[int(ref)].typ = '&${clean_variant}'
return ref
}
fn (mut t Transformer) make_sum_literal(sum_name string, variant string, value flat.NodeId) flat.NodeId {
qvariant := t.resolve_variant(sum_name, variant)
typ_field := t.make_sum_literal_field('typ', t.make_int_literal(t.sum_type_index(sum_name,
qvariant)), 'int')
raw_value_type := t.node_type(value)
value_type := if raw_value_type.starts_with('&') {
raw_value_type
} else {
qvariant
}
value_field := t.make_sum_literal_field(t.sum_field_name(qvariant), value, value_type)
start := t.a.children.len
t.a.children << typ_field
t.a.children << value_field
return t.a.add_node(flat.Node{
kind: .struct_init
children_start: start
children_count: 2
value: sum_name
typ: sum_name
})
}
fn (mut t Transformer) make_sum_literal_field(name string, value flat.NodeId, typ string) flat.NodeId {
start := t.a.children.len
t.a.children << value
return t.a.add_node(flat.Node{
kind: .field_init
children_start: start
children_count: 1
value: name
typ: typ
})
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,626 @@
module transform
import v3.flat
// propagate_decl_type infers the declared variable type from a .decl_assign node
// and registers it in var_types so downstream transforms can resolve it.
fn (mut t Transformer) propagate_decl_type(node flat.Node) {
if node.kind != .decl_assign || node.children_count < 2 {
return
}
lhs_id := t.a.child(&node, 0)
lhs := t.a.nodes[int(lhs_id)]
if lhs.kind != .ident || lhs.value.len == 0 {
return
}
mut typ := ''
if node.typ.len > 0 {
typ = node.typ
} else {
rhs_id := t.a.child(&node, 1)
typ = t.resolve_expr_type(rhs_id)
}
if typ.len > 0 {
t.set_var_type(lhs.value, typ)
}
}
// resolve_selector_type resolves the type of a .selector node (e.g. `obj.field`).
// Looks up the base expression type, then finds the field in the struct definition.
fn (t &Transformer) resolve_selector_type(node flat.Node) string {
if node.kind != .selector || node.children_count == 0 {
return ''
}
base_id := t.a.child(&node, 0)
base_node := t.a.nodes[int(base_id)]
if base_node.kind == .ident && base_node.value == 'C' {
// `C.<name>` references a C symbol (e.g. the `C.stdout &C.FILE` global).
// Resolve it via the recorded C global/const type and never fall through to
// the V struct field-name heuristics below: an unrelated V struct may have a
// field with the same name (e.g. `os.Pipe.stdout`), which would otherwise
// mistype `C.stdout` and trigger a bogus auto-reference (`&stdout`).
if is_c_int_selector(node.value) {
return 'int'
}
cname := 'C.${node.value}'
if gt := t.globals[cname] {
if gt.len > 0 {
return t.normalize_type_alias(gt)
}
}
if typ := t.const_type_name(cname) {
return typ
}
return ''
}
field_name := node.value
if field_name.len == 0 {
return ''
}
if enum_type := t.enum_type_name_from_expr(base_id) {
if fields := t.enum_types[enum_type] {
if field_name in fields {
return enum_type
}
}
}
if type_name := t.qualified_enum_type_selector_name(base_id, field_name) {
return type_name
}
if base_node.kind == .ident {
if typ := t.const_type_name('${base_node.value}.${field_name}') {
return typ
}
if fields := t.enum_types[base_node.value] {
if field_name in fields {
return base_node.value
}
}
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
qenum := '${t.cur_module}.${base_node.value}'
if fields := t.enum_types[qenum] {
if field_name in fields {
return qenum
}
}
}
}
base_type := t.resolve_expr_type(base_id)
if base_type.len == 0 {
if ftyp := t.lookup_unique_field_type(field_name) {
return ftyp
}
return ''
}
base_key := t.expr_key(base_id)
if base_key.len > 0 {
full_key := '${base_key}.${field_name}'
if sc := t.find_smartcast(full_key) {
return t.smartcast_target_type(sc)
}
}
if base_key.len > 0 {
if sc := t.find_smartcast(base_key) {
variant_type := t.qualify_variant(sc.variant_name, sc.sum_type_name)
if ftyp := t.lookup_struct_field_type(variant_type, field_name) {
return ftyp
}
if ftyp := t.lookup_struct_field_type(sc.variant_name, field_name) {
return ftyp
}
}
}
// Strip pointer prefix if present (e.g. "&MyStruct" -> "MyStruct")
lookup_type := if base_type.starts_with('&') { base_type[1..] } else { base_type }
if ftyp := t.lookup_struct_field_type(lookup_type, field_name) {
return ftyp
}
if info := t.lookup_struct_info(lookup_type) {
if embedded := t.embedded_field_for_promoted_field(info, field_name) {
if embedded_info := t.lookup_struct_info(embedded.typ) {
if ftyp := t.struct_field_type(embedded_info, field_name) {
return ftyp
}
}
}
}
if ftyp := t.lookup_unique_field_type(field_name) {
return ftyp
}
return ''
}
fn is_c_int_selector(name string) bool {
return name in ['errno', 'EINTR', 'STDOUT_FILENO', 'STDERR_FILENO', 'EINVAL']
}
fn (t &Transformer) selector_expr_name(id flat.NodeId) string {
if int(id) < 0 {
return ''
}
node := t.a.nodes[int(id)]
if node.kind == .ident {
return node.value
}
if node.kind == .selector && node.children_count > 0 {
base := t.selector_expr_name(t.a.child(&node, 0))
if base.len > 0 && node.value.len > 0 {
return '${base}.${node.value}'
}
}
return ''
}
fn (t &Transformer) enum_type_name_from_expr(id flat.NodeId) ?string {
name := t.selector_expr_name(id)
if name.len == 0 {
return none
}
if name in t.enum_types {
return name
}
if !name.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
qname := '${t.cur_module}.${name}'
if qname in t.enum_types {
return qname
}
}
return none
}
fn (t &Transformer) qualified_enum_type_selector_name(base_id flat.NodeId, field_name string) ?string {
base := t.selector_expr_name(base_id)
if base.len == 0 || field_name.len == 0 {
return none
}
name := '${base}.${field_name}'
if name in t.enum_types {
return name
}
if !name.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
qname := '${t.cur_module}.${name}'
if qname in t.enum_types {
return qname
}
}
return none
}
fn (t &Transformer) lookup_struct_field_type(type_name string, field_name string) ?string {
lookup := t.lookup_struct_info_for_field(type_name, field_name) or { return none }
for f in lookup.info.fields {
if f.name == field_name {
return t.normalize_field_type(f.typ, lookup.owner_type)
}
}
return none
}
fn (t &Transformer) lookup_struct_field_raw_type(type_name string, field_name string) ?string {
lookup := t.lookup_struct_info_for_field(type_name, field_name) or { return none }
for f in lookup.info.fields {
if f.name == field_name {
if f.raw_typ.len > 0 {
return f.raw_typ
}
return f.typ
}
}
return none
}
fn (t &Transformer) lookup_struct_info_for_field(type_name string, field_name string) ?StructFieldLookup {
if type_name.len == 0 || field_name.len == 0 {
return none
}
mut lookup_type := if type_name.starts_with('&') { type_name[1..] } else { type_name }
owner_type := lookup_type
if lookup_type !in t.structs && lookup_type.contains('.') {
short_type := lookup_type.all_after_last('.')
if short_type in t.structs {
lookup_type = short_type
}
}
if lookup_type !in t.structs && !lookup_type.contains('.') && t.cur_module.len > 0
&& t.cur_module != 'main' && t.cur_module != 'builtin' {
qtype := '${t.cur_module}.${lookup_type}'
if qtype in t.structs {
lookup_type = qtype
}
}
info := t.structs[lookup_type] or { return none }
return StructFieldLookup{
info: info
owner_type: owner_type
}
}
fn (t &Transformer) lookup_unique_field_type(field_name string) ?string {
match field_name {
'name' {
return 'string'
}
'generic_types' {
return '[]u32'
}
'obj' {
return t.resolve_sum_name('ScopeObject')
}
else {}
}
if ftyp := t.unique_fields[field_name] {
if ftyp.len > 0 {
return ftyp
}
}
return none
}
fn (t &Transformer) normalize_field_type(typ string, owner_type string) string {
if typ.len == 0 {
return typ
}
if typ.starts_with('&') {
return '&' + t.normalize_field_type(typ[1..], owner_type)
}
if typ.starts_with('[]') {
return '[]' + t.normalize_field_type(typ[2..], owner_type)
}
if typ.starts_with('?') {
return '?' + t.normalize_field_type(typ[1..], owner_type)
}
if typ.starts_with('!') {
return '!' + t.normalize_field_type(typ[1..], owner_type)
}
if typ.starts_with('map[') {
bracket_end := typ.index(']') or { return t.normalize_type_alias(typ) }
key_type := t.normalize_field_type(typ[4..bracket_end], owner_type)
value_type := t.normalize_field_type(typ[bracket_end + 1..], owner_type)
return 'map[${key_type}]${value_type}'
}
if typ.starts_with('[') {
bracket_end := typ.index(']') or { return t.normalize_type_alias(typ) }
return typ[..bracket_end + 1] + t.normalize_field_type(typ[bracket_end + 1..], owner_type)
}
if typ.contains('.') || !owner_type.contains('.') {
return t.normalize_type_alias(typ)
}
owner_mod := owner_type.all_before_last('.')
qtyp := '${owner_mod}.${typ}'
if qtyp in t.structs || qtyp in t.sum_types || qtyp in t.enum_types {
return t.normalize_type_alias(qtyp)
}
if !isnil(t.tc) {
if qtyp in t.tc.type_aliases {
return t.normalize_type_alias(qtyp)
}
}
return t.normalize_type_alias(typ)
}
fn (t &Transformer) normalize_type_alias(typ string) string {
if typ.len == 0 || isnil(t.tc) {
return typ
}
if is_plain_builtin_alias_type(typ) {
return typ
}
if isnil(t.alias_cache) {
return t.normalize_type_alias_uncached(typ)
}
mut c := t.alias_cache
if c.module != t.cur_module {
c.module = t.cur_module
c.entries.clear()
}
if cached := c.entries[typ] {
return cached
}
result := t.normalize_type_alias_uncached(typ)
c.entries[typ] = result
return result
}
fn (t &Transformer) normalize_type_alias_uncached(typ string) string {
if is_generic_placeholder_type_name(typ) {
return 'int'
}
if typ.starts_with('mut ') {
return '&' + t.normalize_type_alias(typ[4..])
}
if typ.starts_with('&') {
return '&' + t.normalize_type_alias(typ[1..])
}
if typ.starts_with('[]') {
return '[]' + t.normalize_type_alias(typ[2..])
}
if typ.starts_with('?') {
return '?' + t.normalize_type_alias(typ[1..])
}
if typ.starts_with('!') {
return '!' + t.normalize_type_alias(typ[1..])
}
if !typ.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
qtyp := '${t.cur_module}.${typ}'
if qtyp in t.structs || qtyp in t.sum_types || qtyp in t.enum_types {
return qtyp
}
}
if typ in t.structs || typ in t.sum_types || typ in t.enum_types {
return typ
}
if target := t.tc.type_aliases[typ] {
return target
}
if !typ.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
&& t.cur_module != 'builtin' {
qtyp := '${t.cur_module}.${typ}'
if target := t.tc.type_aliases[qtyp] {
return target
}
}
return typ
}
fn is_plain_builtin_alias_type(typ string) bool {
return match typ {
'bool', 'string', 'void', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
'f32', 'f64', 'rune', 'isize', 'usize', 'voidptr', 'byteptr', 'charptr' {
true
}
else {
false
}
}
}
fn is_generic_placeholder_type_name(typ string) bool {
if typ.contains('.') {
return is_generic_placeholder_type_name(typ.all_after_last('.'))
}
return typ in ['T', 'U', 'V', 'K']
}
fn (t &Transformer) normalize_type_in_module(typ string, mod string) string {
clean := typ.trim_space()
if clean.len == 0 {
return clean
}
if clean.starts_with('&') {
return '&' + t.normalize_type_in_module(clean[1..], mod)
}
if clean.starts_with('mut ') {
return '&' + t.normalize_type_in_module(clean[4..], mod)
}
if clean.starts_with('shared ') {
return 'shared ' + t.normalize_type_in_module(clean[7..], mod)
}
if clean.starts_with('atomic ') {
return 'atomic ' + t.normalize_type_in_module(clean[7..], mod)
}
if clean.starts_with('?') {
return '?' + t.normalize_type_in_module(clean[1..], mod)
}
if clean.starts_with('!') {
return '!' + t.normalize_type_in_module(clean[1..], mod)
}
if clean.starts_with('...') {
return '...' + t.normalize_type_in_module(clean[3..], mod)
}
if clean.starts_with('[]') {
return '[]' + t.normalize_type_in_module(clean[2..], mod)
}
if clean.contains('.') || mod.len == 0 || mod == 'main' || mod == 'builtin' {
return t.normalize_type_alias(clean)
}
qtyp := '${mod}.${clean}'
if qtyp in t.structs || qtyp in t.sum_types || qtyp in t.enum_types {
return t.normalize_type_alias(qtyp)
}
if !isnil(t.tc) {
if qtyp in t.tc.type_aliases {
return t.normalize_type_alias(qtyp)
}
}
return t.normalize_type_alias(clean)
}
// resolve_index_elem_type determines the element type of an .index expression.
// For `[]T` arrays returns `T`, for `map[K]V` returns `V`, for `string` returns `u8`.
fn (t &Transformer) resolve_index_elem_type(node flat.Node) string {
if node.kind != .index || node.children_count == 0 {
return ''
}
base_id := t.a.child(&node, 0)
mut base_type := t.resolve_expr_type(base_id)
if base_type.len == 0 {
return ''
}
base_type = t.normalize_type_alias(base_type)
if base_type.starts_with('&') {
ptr_elem_type := t.normalize_type_alias(base_type[1..])
if !ptr_elem_type.starts_with('[]') {
return ptr_elem_type
}
base_type = ptr_elem_type
}
if node.value == 'range' {
if base_type == 'string' {
return 'string'
}
if base_type.starts_with('[]') {
return base_type
}
}
if base_type.starts_with('[]') {
return t.normalize_type_alias(base_type[2..])
}
if base_type.starts_with('map[') {
bracket_end := base_type.index(']') or { return '' }
if bracket_end + 1 < base_type.len {
return t.normalize_type_alias(base_type[bracket_end + 1..])
}
return ''
}
if base_type == 'string' {
return 'u8'
}
return ''
}
// node_type returns the v-type string for an expression node, preferring the
// precise type recorded by the type checker (tc.expr_types, populated before
// transform) and falling back to the transformer's local heuristics. This is the
// bridge that makes the transformer type-aware, so type-dependent lowering lives
// here rather than in the backend.
fn (t &Transformer) node_type(id flat.NodeId) string {
if int(id) < 0 {
return ''
}
resolved := t.resolve_expr_type(id)
if resolved.len > 0 {
return resolved
}
node := t.a.nodes[int(id)]
if node.typ.len > 0 {
return t.normalize_type_alias(node.typ)
}
if node.kind == .selector {
sel_type := t.resolve_selector_type(node)
if sel_type.len > 0 {
return sel_type
}
}
if node.kind == .index {
elem_type := t.resolve_index_elem_type(node)
if elem_type.len > 0 {
return elem_type
}
}
if node.kind == .infix {
infix_type := t.infix_struct_operator_result_type(node)
if infix_type.len > 0 {
return infix_type
}
}
if node.kind == .struct_init && node.value.len > 0 {
if node.value in t.structs {
return node.value
}
if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
qname := '${t.cur_module}.${node.value}'
if qname in t.structs {
return qname
}
}
return node.value
}
if !isnil(t.tc) {
if typ := t.tc.expr_type(id) {
name := typ.name()
if name.len > 0 && name != 'void' && (name != 'int'
|| node.kind in [.ident, .int_literal, .infix, .prefix, .paren, .selector, .index, .call]) {
return name
}
}
}
return ''
}
// lvalue_type returns the v-type string for an assignable expression, handling
// idents/calls (via node_type), selectors (struct field types) and index
// expressions (array/map element types). Used by array-append lowering so that
// `obj.field << x` and `m[k] << x` are typed, not just plain `arr << x`.
fn (t &Transformer) lvalue_type(id flat.NodeId) string {
if int(id) < 0 {
return ''
}
node := t.a.nodes[int(id)]
if node.kind == .selector {
sel_type := t.resolve_selector_type(node)
if !isnil(t.tc) {
if checker_type := t.tc.expr_type(id) {
checker_name := checker_type.name()
if checker_name.starts_with('&') && !sel_type.starts_with('&') {
return checker_name
}
}
}
if sel_type.len > 0 {
return sel_type
}
}
if node.kind == .index {
elem_type := t.resolve_index_elem_type(node)
if elem_type.len > 0 {
return elem_type
}
}
nt := t.node_type(id)
if nt.len > 0 {
return nt
}
return ''
}
// is_string_type checks if an expression resolves to string type.
// Handles string literals, interpolations, and ident/call expressions typed as string.
fn (t &Transformer) is_string_type(id flat.NodeId) bool {
if int(id) < 0 {
return false
}
node := t.a.nodes[int(id)]
if node.kind == .string_literal || node.kind == .string_interp {
return true
}
return t.node_type(id) == 'string'
}
// is_array_type checks if a type string represents an array type (starts with `[]`).
fn (t &Transformer) is_array_type(type_str string) bool {
return type_str.starts_with('[]')
}
// is_map_type checks if a type string represents a map type (starts with `map[`).
fn (t &Transformer) is_map_type(type_str string) bool {
return type_str.starts_with('map[')
}
// array_elem_type extracts the element type from an array type string.
// `[]int` -> `int`, `[][]string` -> `[]string`.
fn (t &Transformer) array_elem_type(type_str string) string {
if type_str.starts_with('[]') {
return type_str[2..]
}
return ''
}
// map_value_type extracts the value type from a map type string.
// `map[string]int` -> `int`, `map[string][]bool` -> `[]bool`.
fn (t &Transformer) map_value_type(type_str string) string {
if !type_str.starts_with('map[') {
return ''
}
bracket_end := type_str.index(']') or { return '' }
if bracket_end + 1 < type_str.len {
return type_str[bracket_end + 1..]
}
return ''
}
// map_key_type extracts the key type from a map type string.
// `map[string]int` -> `string`, `map[int]bool` -> `int`.
fn (t &Transformer) map_key_type(type_str string) string {
if !type_str.starts_with('map[') {
return ''
}
bracket_end := type_str.index(']') or { return '' }
if bracket_end > 4 {
return type_str[4..bracket_end]
}
return ''
}