Files

1247 lines
38 KiB
V

module transform
import v3.flat
// try_expand_if_guard detects an if-guard pattern where the condition is a
// decl_assign whose RHS is a call returning an optional (?T) or result (!T).
// When detected it expands:
// if val := maybe_call() { body }
// into:
// __or_tmp_N := maybe_call()
// if !__or_tmp_N.is_error { val := __or_tmp_N.data; body... }
//
fn (mut t Transformer) try_expand_if_guard(_id flat.NodeId, node flat.Node) ?[]flat.NodeId {
if node.kind != .if_expr || node.children_count < 2 {
return none
}
cond_id := t.a.child(&node, 0)
cond := t.a.nodes[int(cond_id)]
if cond.kind != .decl_assign || cond.children_count < 2 {
return none
}
lhs_id := t.a.child(&cond, 0)
rhs_id := t.a.child(&cond, 1)
lhs := t.a.nodes[int(lhs_id)]
if lhs.kind != .ident || lhs.value.len == 0 {
return none
}
lhs_ids := t.multi_assign_lhs_ids(cond)
if lhs_ids.len == 1 {
if info := t.map_index_info(rhs_id) {
return t.expand_map_index_if_guard(node, lhs.value, info)
}
if info := t.array_index_info(rhs_id) {
return t.expand_array_index_if_guard(node, lhs.value, info)
}
}
mut rhs_type := t.optional_result_expr_type_name(rhs_id)
if !t.is_optional_type_name(rhs_type) {
return none
}
rhs_type = t.qualify_optional_type(rhs_type)
value_type := t.optional_base_type(rhs_type)
tmp_name := t.new_temp('if_guard')
rhs_expr := t.transform_expr(rhs_id)
mut prelude := []flat.NodeId{}
t.drain_pending(mut prelude)
tmp_decl := t.make_decl_assign_typed(tmp_name, rhs_expr, rhs_type)
ok_cond := t.make_selector(t.make_ident(tmp_name), 'ok', 'bool')
mut value_decls := []flat.NodeId{}
if lhs_ids.len > 1 {
if rhs_types := t.multi_return_types_for_expr(rhs_id, lhs_ids.len) {
for i, lhs_item_id in lhs_ids {
lhs_item := t.a.nodes[int(lhs_item_id)]
if lhs_item.kind != .ident || lhs_item.value == '_' {
continue
}
field_type := rhs_types[i].name()
payload := t.make_selector(t.make_ident(tmp_name), 'value', value_type)
field := t.make_selector(payload, 'arg${i}', field_type)
value_decls << t.make_decl_assign_typed(lhs_item.value, field, field_type)
}
}
}
if value_decls.len == 0 {
value_decls << t.make_decl_assign_typed(lhs.value, t.make_selector(t.make_ident(tmp_name),
'value', value_type), value_type)
}
then_id := t.a.child(&node, 1)
then_node := t.a.nodes[int(then_id)]
if lhs_ids.len > 1 {
if rhs_types := t.multi_return_types_for_expr(rhs_id, lhs_ids.len) {
for i, lhs_item_id in lhs_ids {
lhs_item := t.a.nodes[int(lhs_item_id)]
if lhs_item.kind == .ident && lhs_item.value.len > 0 && lhs_item.value != '_' {
t.set_var_type(lhs_item.value, rhs_types[i].name())
}
}
}
} else {
t.set_var_type(lhs.value, value_type)
}
mut then_children := []flat.NodeId{}
for value_decl in value_decls {
then_children << value_decl
}
if then_node.kind == .block {
then_children << t.transform_stmts(t.a.children_of(&then_node))
} else {
then_children << t.transform_stmt(then_id)
}
then_block := t.make_block(then_children)
mut else_block := flat.empty_node
if node.children_count >= 3 {
else_id := t.a.child(&node, 2)
else_node := t.a.nodes[int(else_id)]
else_block = t.transform_if_guard_else_block(else_id, else_node, tmp_name)
}
mut expanded := []flat.NodeId{cap: prelude.len + 2}
for stmt in prelude {
expanded << stmt
}
expanded << tmp_decl
expanded << t.make_if(ok_cond, then_block, else_block)
return expanded
}
fn (t &Transformer) optional_result_expr_type_name(id flat.NodeId) string {
if int(id) < 0 {
return ''
}
if !isnil(t.tc) {
node := t.a.nodes[int(id)]
if node.kind == .call {
if name := t.tc.resolved_call_name(id) {
if ret := t.tc.fn_ret_types[name] {
ret_name := ret.name()
if t.is_optional_type_name(ret_name) {
return ret_name
}
}
}
ret_name := t.get_call_return_type(id, node)
if t.is_optional_type_name(ret_name) {
return ret_name
}
}
if typ := t.tc.expr_type(id) {
typ_name := typ.name()
if t.is_optional_type_name(typ_name) {
return typ_name
}
}
}
return t.node_type(id)
}
fn (mut t Transformer) transform_if_guard_else_block(else_id flat.NodeId, else_node flat.Node, err_source string) flat.NodeId {
saved_var_types := t.var_types.clone()
t.set_var_type('err', 'IError')
mut children := []flat.NodeId{}
err_value := if err_source.len > 0 {
t.make_selector(t.make_ident(err_source), 'err', 'IError')
} else {
t.make_struct_init('IError')
}
children << t.make_decl_assign_typed('err', err_value, 'IError')
if else_node.kind == .block {
children << t.transform_stmts(t.a.children_of(&else_node))
} else if else_node.kind == .if_expr {
children << t.transform_else_if_expr(else_id, else_node)
} else {
children << t.transform_stmt(else_id)
}
t.var_types = saved_var_types
return t.make_block(children)
}
fn (mut t Transformer) transform_else_if_expr(else_id flat.NodeId, else_node flat.Node) flat.NodeId {
if expanded := t.try_expand_if_guard(else_id, else_node) {
return t.make_block(expanded)
}
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
new_if := t.transform_if_branches_with_smartcast(else_id, else_node)
mut local_pending := t.pending_stmts.clone()
t.pending_stmts = outer_pending
if local_pending.len == 0 {
return new_if
}
local_pending << new_if
return t.make_block(local_pending)
}
fn (mut t Transformer) expand_map_index_if_guard(node flat.Node, lhs_name string, info MapIndexInfo) ?[]flat.NodeId {
map_expr := t.stable_expr_for_reuse(info.base_id)
key_name := t.new_temp('map_key')
ptr_name := t.new_temp('map_ptr')
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')
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}'))
value_decl := t.make_decl_assign_typed(lhs_name, ptr_value, info.value_type)
then_id := t.a.child(&node, 1)
then_node := t.a.nodes[int(then_id)]
t.set_var_type(lhs_name, info.value_type)
mut then_children := []flat.NodeId{}
then_children << value_decl
if then_node.kind == .block {
then_children << t.transform_stmts(t.a.children_of(&then_node))
} else {
then_children << t.transform_stmt(then_id)
}
then_block := t.make_block(then_children)
mut else_block := flat.empty_node
if node.children_count >= 3 {
else_id := t.a.child(&node, 2)
else_node := t.a.nodes[int(else_id)]
else_block = if else_node.kind == .block {
t.make_block(t.transform_stmts(t.a.children_of(&else_node)))
} else if else_node.kind == .if_expr {
t.transform_else_if_expr(else_id, else_node)
} else {
t.make_block(t.transform_stmt(else_id))
}
}
t.pending_stmts = outer_pending
mut expanded := []flat.NodeId{cap: prelude.len + 1}
for stmt in prelude {
expanded << stmt
}
expanded << t.make_if(found_cond, then_block, else_block)
return expanded
}
fn (mut t Transformer) expand_array_index_if_guard(node flat.Node, lhs_name string, info ArrayIndexInfo) ?[]flat.NodeId {
array_expr := t.stable_expr_for_reuse(info.base_id)
index_name := t.new_temp('arr_idx')
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')
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(array_expr, 'len',
'int'))
found_cond := t.make_infix(.logical_and, lower_ok, upper_ok)
value_decl := t.make_decl_assign_typed(lhs_name, t.make_index(array_expr,
t.make_ident(index_name), info.value_type), info.value_type)
then_id := t.a.child(&node, 1)
then_node := t.a.nodes[int(then_id)]
t.set_var_type(lhs_name, info.value_type)
mut then_children := []flat.NodeId{}
then_children << value_decl
if then_node.kind == .block {
then_children << t.transform_stmts(t.a.children_of(&then_node))
} else {
then_children << t.transform_stmt(then_id)
}
then_block := t.make_block(then_children)
mut else_block := flat.empty_node
if node.children_count >= 3 {
else_id := t.a.child(&node, 2)
else_node := t.a.nodes[int(else_id)]
else_block = if else_node.kind == .block {
t.make_block(t.transform_stmts(t.a.children_of(&else_node)))
} else if else_node.kind == .if_expr {
t.transform_else_if_expr(else_id, else_node)
} else {
t.make_block(t.transform_stmt(else_id))
}
}
t.pending_stmts = outer_pending
mut expanded := []flat.NodeId{cap: prelude.len + 1}
for stmt in prelude {
expanded << stmt
}
expanded << t.make_if(found_cond, then_block, else_block)
return expanded
}
// try_expand_if_expr_value detects an if-expression used as a value and
// lowers it to a mutable temporary so that the C backend sees a simple
// variable instead of a gcc statement-expression.
//
// x := if cond { a } else { b }
// becomes:
// mut __if_tmp_N := zero_value
// if cond { __if_tmp_N = a } else { __if_tmp_N = b }
// x := __if_tmp_N
fn (mut t Transformer) try_expand_if_expr_value(id flat.NodeId, node flat.Node) ?flat.NodeId {
if node.kind != .if_expr {
return none
}
// An if-expression used as a value must have both then and else branches.
if node.children_count < 3 {
return none
}
mut result_type := t.if_expr_result_type(id, node)
if result_type.len == 0 || result_type == 'void' {
return none
}
return t.try_expand_if_expr_value_for_type(id, node, result_type)
}
fn (mut t Transformer) try_expand_if_expr_value_for_type(id flat.NodeId, node flat.Node, result_type string) ?flat.NodeId {
if node.kind != .if_expr || node.children_count < 3 || result_type.len == 0
|| result_type == 'void' {
return none
}
mut actual_result_type := result_type
branch_type := t.if_expr_branch_result_type(node)
if t.if_expr_branch_overrides_sum_target(branch_type, result_type) {
actual_result_type = branch_type
}
tmp_name := t.new_temp('if_val')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
mut prelude := []flat.NodeId{}
prelude << t.make_decl_assign_typed(tmp_name, t.zero_value_for_type(actual_result_type),
actual_result_type)
for stmt in t.build_if_value_chain(id, tmp_name, actual_result_type) {
prelude << stmt
}
t.pending_stmts = outer_pending
for stmt in prelude {
t.pending_stmts << stmt
}
tmp := t.make_ident(tmp_name)
t.a.nodes[int(tmp)].typ = actual_result_type
return tmp
}
fn (t &Transformer) if_expr_branch_overrides_sum_target(branch_type string, target_type string) bool {
if branch_type.len == 0 || target_type.len == 0 {
return false
}
if branch_type.starts_with('[]') {
branch_elem := branch_type[2..]
target_short := if target_type.contains('.') {
target_type.all_after_last('.')
} else {
target_type
}
branch_short := if branch_elem.contains('.') {
branch_elem.all_after_last('.')
} else {
branch_elem
}
if target_short == branch_short {
return true
}
}
resolved_target := t.resolve_sum_name(target_type)
if resolved_target.len == 0 || resolved_target !in t.sum_types {
return false
}
clean_branch := t.trim_pointer_type(branch_type)
branch_sum := t.resolve_sum_name(clean_branch)
variant_sum := t.resolve_sum_name(t.find_sum_type_for_variant(clean_branch))
return branch_sum != resolved_target && variant_sum != resolved_target
}
fn (t &Transformer) if_expr_result_type(id flat.NodeId, node flat.Node) string {
mut node_typ := ''
if node.typ.len > 0 {
typ := t.normalize_type_alias(node.typ)
if typ !in ['array', 'map'] {
node_typ = typ
}
}
mut checked_typ := ''
if !isnil(t.tc) {
if typ := t.tc.expr_type(id) {
name := typ.name()
if name.len > 0 {
checked_typ = t.normalize_type_alias(name)
}
}
}
branch_typ := t.if_expr_branch_result_type(node)
if branch_typ.starts_with('[]') && is_fixed_array_type(checked_typ)
&& fixed_array_elem_type(checked_typ) == branch_typ[2..] {
return branch_typ
}
if branch_typ.starts_with('[]') && is_fixed_array_type(node_typ)
&& fixed_array_elem_type(node_typ) == branch_typ[2..] {
return branch_typ
}
if branch_typ.starts_with('[]') && !checked_typ.starts_with('[]') && !node_typ.starts_with('[]') {
return branch_typ
}
if t.if_expr_branch_type_overrides(branch_typ, checked_typ) {
return branch_typ
}
if t.if_expr_branch_type_overrides(branch_typ, node_typ) {
return branch_typ
}
if checked_typ.len > 0 && checked_typ !in ['array', 'map'] {
return checked_typ
}
if node_typ.len > 0 {
return node_typ
}
if branch_typ.len > 0 {
return branch_typ
}
return ''
}
fn (t &Transformer) if_expr_branch_type_overrides(branch_typ string, stale_typ string) bool {
if branch_typ.len == 0 || stale_typ.len == 0 || branch_typ == stale_typ {
return false
}
if stale_typ in ['array', 'map', 'unknown'] {
return true
}
if stale_typ in t.enum_types && branch_typ == 'int' {
return false
}
if stale_typ.starts_with('&') && branch_typ.starts_with('&') {
stale_inner := stale_typ[1..]
branch_inner := branch_typ[1..]
if stale_inner in ['int', 'void', 'unknown'] && branch_inner !in ['int', 'void', 'unknown'] {
return true
}
}
if stale_typ.starts_with('[]') || is_fixed_array_type(stale_typ) {
return !branch_typ.starts_with('[]') && !is_fixed_array_type(branch_typ)
}
if stale_typ.starts_with('map[') {
return !branch_typ.starts_with('map[')
}
return false
}
fn (t &Transformer) if_expr_branch_result_type(node flat.Node) string {
mut result := ''
if node.children_count >= 2 {
then_smartcasts :=
t.smartcast_contexts_from_is_exprs(t.extract_all_is_exprs(t.a.child(&node, 0)))
then_type := t.stmt_value_type_with_smartcasts(t.a.child(&node, 1), then_smartcasts)
if then_type.len > 0 {
result = t.merge_if_expr_types(result, t.normalize_type_alias(then_type))
}
}
if node.children_count >= 3 {
else_id := t.a.child(&node, 2)
else_node := t.a.nodes[int(else_id)]
else_type := if else_node.kind == .if_expr {
t.if_expr_result_type(else_id, else_node)
} else {
t.stmt_value_type(else_id)
}
if else_type.len > 0 {
result = t.merge_if_expr_types(result, t.normalize_type_alias(else_type))
}
}
return result
}
fn (t &Transformer) smartcast_contexts_from_is_exprs(infos []IsExprInfo) []SmartcastContext {
mut result := []SmartcastContext{cap: infos.len}
for info in infos {
result << SmartcastContext{
expr_name: info.expr_name
variant_name: info.variant_name
sum_type_name: info.sum_type_name
}
}
return result
}
fn (t &Transformer) stmt_value_type_with_smartcasts(id flat.NodeId, contexts []SmartcastContext) string {
if int(id) < 0 {
return ''
}
node := t.a.nodes[int(id)]
match node.kind {
.expr_stmt {
if node.children_count > 0 {
return t.node_type_with_smartcasts(t.a.child(&node, node.children_count - 1),
contexts)
}
return ''
}
.block {
for i := node.children_count - 1; i >= 0; i-- {
typ := t.stmt_value_type_with_smartcasts(t.a.child(&node, i), contexts)
if typ.len > 0 {
return typ
}
}
return ''
}
else {
return t.node_type_with_smartcasts(id, contexts)
}
}
}
fn (t &Transformer) node_type_with_smartcasts(id flat.NodeId, contexts []SmartcastContext) string {
if int(id) < 0 {
return ''
}
node := t.a.nodes[int(id)]
match node.kind {
.ident {
if sc := t.find_smartcast_in_context(node.value, contexts) {
return t.smartcast_target_type(sc)
}
return t.node_type(id)
}
.selector {
if node.children_count == 0 {
return t.node_type(id)
}
full_key := t.expr_key(id)
if full_key.len > 0 {
if sc := t.find_smartcast_in_context(full_key, contexts) {
return t.smartcast_target_type(sc)
}
}
base_id := t.a.child(&node, 0)
base_key := t.expr_key(base_id)
if base_key.len > 0 {
if sc := t.find_smartcast_in_context(base_key, contexts) {
variant_type := t.qualify_variant(sc.variant_name, sc.sum_type_name)
if ftyp := t.lookup_struct_field_type(variant_type, node.value) {
return ftyp
}
if ftyp := t.lookup_struct_field_type(sc.variant_name, node.value) {
return ftyp
}
}
}
base_type := t.node_type_with_smartcasts(base_id, contexts)
clean_base := if base_type.starts_with('&') { base_type[1..] } else { base_type }
if ftyp := t.lookup_struct_field_type(clean_base, node.value) {
return ftyp
}
return t.node_type(id)
}
.prefix {
if node.children_count == 0 {
return t.node_type(id)
}
child_type := t.node_type_with_smartcasts(t.a.child(&node, 0), contexts)
if node.op == .amp && child_type.len > 0 {
return '&${child_type}'
}
if node.op == .mul && child_type.starts_with('&') {
return child_type[1..]
}
if node.op == .not {
return 'bool'
}
return t.node_type(id)
}
.paren {
if node.children_count > 0 {
return t.node_type_with_smartcasts(t.a.child(&node, 0), contexts)
}
return t.node_type(id)
}
.array_literal {
if node.typ.len > 0 {
typ := t.normalize_type_alias(node.typ)
if typ != 'array' {
return typ
}
}
if node.children_count > 0 {
elem_type := t.node_type_with_smartcasts(t.a.child(&node, 0), contexts)
if elem_type.len > 0 {
return '[]${elem_type}'
}
}
return t.node_type(id)
}
.index {
base_type := if node.children_count > 0 {
t.node_type_with_smartcasts(t.a.child(&node, 0), contexts)
} else {
''
}
if node.value == 'range' && base_type.starts_with('[]') {
return base_type
}
if base_type.starts_with('[]') {
return base_type[2..]
}
return t.node_type(id)
}
.if_expr {
return t.if_expr_result_type(id, node)
}
.match_stmt {
return t.match_expr_type(node)
}
else {
return t.node_type(id)
}
}
}
fn (t &Transformer) find_smartcast_in_context(expr_name string, contexts []SmartcastContext) ?SmartcastContext {
mut i := contexts.len - 1
for i >= 0 {
if contexts[i].expr_name == expr_name {
return contexts[i]
}
i--
}
return t.find_smartcast(expr_name)
}
fn (t &Transformer) merge_if_expr_types(current string, next string) string {
if current.len == 0 {
return next
}
if next.len == 0 || current == next {
return current
}
if current == 'array' && next.starts_with('[]') {
return next
}
if next == 'array' && current.starts_with('[]') {
return current
}
if current.starts_with('[]') && is_fixed_array_type(next)
&& current[2..] == fixed_array_elem_type(next) {
return current
}
if next.starts_with('[]') && is_fixed_array_type(current)
&& next[2..] == fixed_array_elem_type(current) {
return next
}
if current.starts_with('[]') && !next.starts_with('[]') && current[2..] == next {
return current
}
if next.starts_with('[]') && !current.starts_with('[]') && next[2..] == current {
return next
}
return current
}
fn (mut t Transformer) build_if_value_chain(if_id flat.NodeId, target_name string, target_type string) []flat.NodeId {
if_node := t.a.nodes[int(if_id)]
if if_node.kind != .if_expr || if_node.children_count < 2 {
return []flat.NodeId{}
}
if guard_chain := t.build_if_value_guard_chain(if_node, target_name, target_type) {
return guard_chain
}
cond_id := t.a.child(&if_node, 0)
then_id := t.a.child(&if_node, 1)
has_else := if_node.children_count >= 3
all_is := t.extract_all_is_exprs(cond_id)
new_cond := t.transform_and_chain_smartcasts(cond_id)
mut result := []flat.NodeId{}
t.drain_pending(mut result)
for info in all_is {
t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name)
}
then_block := t.if_value_branch_block(then_id, target_name, target_type)
for _ in all_is {
t.pop_smartcast()
}
mut else_block := flat.empty_node
if has_else {
else_id := t.a.child(&if_node, 2)
else_node := t.a.nodes[int(else_id)]
if else_node.kind == .if_expr {
else_block = t.make_block(t.build_if_value_chain(else_id, target_name, target_type))
} else {
else_block = t.if_value_branch_block(else_id, target_name, target_type)
}
}
result << t.make_if(new_cond, then_block, else_block)
return result
}
fn (mut t Transformer) build_if_value_guard_chain(if_node flat.Node, target_name string, target_type string) ?[]flat.NodeId {
if if_node.children_count < 3 {
return none
}
cond_id := t.a.child(&if_node, 0)
cond := t.a.nodes[int(cond_id)]
if cond.kind != .decl_assign || cond.children_count < 2 {
return none
}
lhs_id := t.a.child(&cond, 0)
rhs_id := t.a.child(&cond, 1)
lhs := t.a.nodes[int(lhs_id)]
if lhs.kind != .ident || lhs.value.len == 0 {
return none
}
if info := t.map_index_info(rhs_id) {
return t.build_map_index_if_value_guard_chain(if_node, lhs.value, info, target_name,
target_type)
}
if info := t.array_index_info(rhs_id) {
return t.build_array_index_if_value_guard_chain(if_node, lhs.value, info, target_name,
target_type)
}
mut rhs_type := t.optional_result_expr_type_name(rhs_id)
if !t.is_optional_type_name(rhs_type) {
return none
}
rhs_type = t.qualify_optional_type(rhs_type)
value_type := t.optional_base_type(rhs_type)
tmp_name := t.new_temp('if_guard')
rhs_expr := t.transform_expr(rhs_id)
mut result := []flat.NodeId{}
t.drain_pending(mut result)
result << t.make_decl_assign_typed(tmp_name, rhs_expr, rhs_type)
ok_cond := t.make_selector(t.make_ident(tmp_name), 'ok', 'bool')
value_decl := t.make_decl_assign_typed(lhs.value, t.make_selector(t.make_ident(tmp_name),
'value', value_type), value_type)
saved_var_types := t.var_types.clone()
t.set_var_type(lhs.value, value_type)
then_id := t.a.child(&if_node, 1)
then_block0 := t.if_value_branch_block(then_id, target_name, target_type)
mut then_children := []flat.NodeId{cap: int(t.a.nodes[int(then_block0)].children_count) + 1}
then_children << value_decl
then_children << t.a.children_of(&t.a.nodes[int(then_block0)])
then_block := t.make_block(then_children)
t.var_types = saved_var_types
else_id := t.a.child(&if_node, 2)
else_node := t.a.nodes[int(else_id)]
else_block := if else_node.kind == .if_expr {
t.make_block(t.build_if_value_chain(else_id, target_name, target_type))
} else {
t.if_value_branch_block(else_id, target_name, target_type)
}
result << t.make_if(ok_cond, then_block, else_block)
return result
}
fn (mut t Transformer) build_map_index_if_value_guard_chain(if_node flat.Node, lhs_name string, info MapIndexInfo, target_name string, target_type string) []flat.NodeId {
map_expr := t.stable_expr_for_reuse(info.base_id)
key_name := t.new_temp('map_key')
ptr_name := t.new_temp('map_ptr')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
key_expr := t.transform_expr(info.key_id)
mut result := []flat.NodeId{}
t.drain_pending(mut result)
result << t.make_decl_assign_typed(key_name, key_expr, info.key_type)
result << t.make_decl_assign_typed(ptr_name, t.make_map_get_check_expr(map_expr,
info.base_type, key_name), 'voidptr')
ptr_ident := t.make_ident(ptr_name)
found_cond := t.make_infix(.ne, ptr_ident, t.a.add(.nil_literal))
saved_var_types := t.var_types.clone()
mut then_children := []flat.NodeId{}
if lhs_name != '_' {
ptr_value := t.make_prefix(.mul, t.make_cast('&${info.value_type}', t.make_ident(ptr_name),
'&${info.value_type}'))
then_children << t.make_decl_assign_typed(lhs_name, ptr_value, info.value_type)
t.set_var_type(lhs_name, info.value_type)
}
then_id := t.a.child(&if_node, 1)
then_block0 := t.if_value_branch_block(then_id, target_name, target_type)
then_children << t.a.children_of(&t.a.nodes[int(then_block0)])
then_block := t.make_block(then_children)
t.var_types = saved_var_types
else_id := t.a.child(&if_node, 2)
else_node := t.a.nodes[int(else_id)]
else_block := if else_node.kind == .if_expr {
t.make_block(t.build_if_value_chain(else_id, target_name, target_type))
} else {
t.if_value_branch_block(else_id, target_name, target_type)
}
t.pending_stmts = outer_pending
result << t.make_if(found_cond, then_block, else_block)
return result
}
fn (mut t Transformer) build_array_index_if_value_guard_chain(if_node flat.Node, lhs_name string, info ArrayIndexInfo, target_name string, target_type string) []flat.NodeId {
array_expr := t.stable_expr_for_reuse(info.base_id)
index_name := t.new_temp('arr_idx')
outer_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
index_expr := t.transform_expr(info.index_id)
mut result := []flat.NodeId{}
t.drain_pending(mut result)
result << t.make_decl_assign_typed(index_name, index_expr, 'int')
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(array_expr, 'len',
'int'))
found_cond := t.make_infix(.logical_and, lower_ok, upper_ok)
saved_var_types := t.var_types.clone()
mut then_children := []flat.NodeId{}
if lhs_name != '_' {
value := t.make_index(array_expr, t.make_ident(index_name), info.value_type)
then_children << t.make_decl_assign_typed(lhs_name, value, info.value_type)
t.set_var_type(lhs_name, info.value_type)
}
then_id := t.a.child(&if_node, 1)
then_block0 := t.if_value_branch_block(then_id, target_name, target_type)
then_children << t.a.children_of(&t.a.nodes[int(then_block0)])
then_block := t.make_block(then_children)
t.var_types = saved_var_types
else_id := t.a.child(&if_node, 2)
else_node := t.a.nodes[int(else_id)]
else_block := if else_node.kind == .if_expr {
t.make_block(t.build_if_value_chain(else_id, target_name, target_type))
} else {
t.if_value_branch_block(else_id, target_name, target_type)
}
t.pending_stmts = outer_pending
result << t.make_if(found_cond, then_block, else_block)
return result
}
fn (mut t Transformer) if_value_branch_block(branch_id flat.NodeId, target_name string, target_type string) flat.NodeId {
if int(branch_id) < 0 {
return t.make_block([]flat.NodeId{})
}
branch := t.a.nodes[int(branch_id)]
if branch.kind == .if_expr {
return t.make_block(t.build_if_value_chain(branch_id, target_name, target_type))
}
if branch.kind != .block {
mut result := []flat.NodeId{}
value := t.transform_if_branch_value(branch_id, target_type)
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
return t.make_block(result)
}
if branch.children_count == 0 {
return t.make_block([]flat.NodeId{})
}
mut stmt_ids := []flat.NodeId{cap: int(branch.children_count)}
for i in 0 .. branch.children_count {
stmt_ids << t.a.child(&branch, i)
}
mut result := []flat.NodeId{}
if stmt_ids.len > 1 {
for stmt in t.transform_stmts(stmt_ids[..stmt_ids.len - 1]) {
result << stmt
}
}
tail_id := stmt_ids[stmt_ids.len - 1]
tail := t.a.nodes[int(tail_id)]
if tail.kind == .return_stmt {
tail_stmts := t.transform_stmt(tail_id)
t.drain_pending(mut result)
for stmt in tail_stmts {
result << stmt
}
return t.make_block(result)
}
if tail.kind == .expr_stmt && tail.children_count > 0 {
inner_id := t.a.child(&tail, 0)
inner := t.a.nodes[int(inner_id)]
if inner.kind == .call && t.is_noreturn_call(inner) {
tail_stmts := t.transform_stmt(tail_id)
t.drain_pending(mut result)
for stmt in tail_stmts {
result << stmt
}
return t.make_block(result)
}
if t.node_type(inner_id) == 'void' {
tail_stmts := t.transform_stmt(tail_id)
t.drain_pending(mut result)
for stmt in tail_stmts {
result << stmt
}
return t.make_block(result)
}
value := t.transform_if_branch_value(inner_id, target_type)
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
return t.make_block(result)
}
if tail.kind == .block && t.stmt_value_type(tail_id).len > 0 {
value := t.transform_if_branch_value(tail_id, target_type)
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
return t.make_block(result)
}
if tail.kind == .match_stmt {
value := t.transform_if_branch_value(tail_id, target_type)
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
return t.make_block(result)
}
if tail.kind == .if_expr {
value := t.transform_if_branch_value(tail_id, target_type)
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
return t.make_block(result)
}
if t.is_stmt_kind(tail.kind) {
tail_stmts := t.transform_stmt(tail_id)
t.drain_pending(mut result)
for stmt in tail_stmts {
result << stmt
}
return t.make_block(result)
}
value := t.transform_if_branch_value(tail_id, target_type)
t.drain_pending(mut result)
result << t.make_assign(t.make_ident(target_name), value)
return t.make_block(result)
}
fn (mut t Transformer) transform_if_branch_value(id flat.NodeId, target_type string) flat.NodeId {
if t.is_sum_type_name(target_type) {
return t.wrap_sum_value(id, target_type)
}
return t.transform_expr_for_type(id, target_type)
}
// transform_is_condition transforms an `x is Type` condition node into the
// common sum-type tag comparison used by all C emission paths.
fn (mut t Transformer) transform_is_condition(cond_id flat.NodeId) flat.NodeId {
if int(cond_id) < 0 {
return cond_id
}
cond := t.a.nodes[int(cond_id)]
if cond.kind != .is_expr {
return cond_id
}
// is_expr: child[0] is the expression being checked, value is the type name.
if cond.children_count < 1 {
return cond_id
}
variant_name := cond.value
if variant_name.len == 0 {
return cond_id
}
return t.transform_is_expr(cond_id, cond)
}
// transform_and_chain_smartcasts handles conditions like
// `x is T && x.field > 0` where the second operand should see x
// narrowed to T through a smartcast.
//
// It walks a left-associative chain of .logical_and nodes. When a
// term is an is_expr it pushes a smartcast so subsequent terms
// (and the then-branch) see the narrowed type.
//
// Currently passes through unchanged but is structured to detect
// the pattern for future expansion.
fn (mut t Transformer) transform_and_chain_smartcasts(cond_id flat.NodeId) flat.NodeId {
if int(cond_id) < 0 {
return cond_id
}
cond := t.a.nodes[int(cond_id)]
if cond.kind == .decl_assign {
return t.transform_if_guard_condition(cond)
}
if cond.kind != .infix || cond.op !in [.logical_and, .logical_or] {
// Not an && chain -- preserve bare smartcast checks and fully transform
// ordinary conditions.
if cond.kind == .is_expr {
return t.transform_is_condition(cond_id)
}
return t.transform_expr(cond_id)
}
if cond.children_count < 2 {
return cond_id
}
lhs_id := t.a.child(&cond, 0)
rhs_id := t.a.child(&cond, 1)
lhs := t.a.nodes[int(lhs_id)]
new_lhs := t.transform_and_chain_smartcasts(lhs_id)
lhs_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
lhs_smartcasts := if cond.op == .logical_and {
t.extract_all_is_exprs(lhs_id)
} else {
[]IsExprInfo{}
}
for info in lhs_smartcasts {
t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name)
}
mut new_rhs := t.transform_and_chain_smartcasts(rhs_id)
rhs_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
for _ in lhs_smartcasts {
t.pop_smartcast()
}
// The left operand always evaluates, so its pending statements may run before
// the `&&`/`||`. The right operand is conditional, so any statements its
// lowering produced (e.g. an `in [...]` / `or {}` temporary) must run only when
// the right operand is actually reached — wrap them in a `({ ...; value; })`
// statement-expression to preserve short-circuit evaluation.
for stmt in lhs_pending {
t.pending_stmts << stmt
}
if rhs_pending.len > 0 {
mut block_stmts := rhs_pending.clone()
block_stmts << t.make_expr_stmt(new_rhs)
new_rhs = t.make_block(block_stmts)
t.a.nodes[int(new_rhs)].typ = 'bool'
}
if lhs.kind == .is_expr && new_lhs == lhs_id && new_rhs == rhs_id {
return cond_id
}
return t.make_infix(cond.op, new_lhs, new_rhs)
}
fn (mut t Transformer) transform_if_guard_condition(node flat.Node) flat.NodeId {
if node.kind != .decl_assign || node.children_count < 2 {
return flat.empty_node
}
mut new_children := []flat.NodeId{cap: int(node.children_count)}
for i in 0 .. node.children_count {
child_id := t.a.child(&node, i)
if i == 1 {
new_children << t.transform_expr(child_id)
} else {
new_children << t.transform_lvalue(child_id)
}
}
start := t.a.children.len
for child in new_children {
t.a.children << child
}
return t.a.add_node(flat.Node{
kind: .decl_assign
op: node.op
children_start: start
children_count: flat.child_count(new_children.len)
pos: node.pos
value: node.value
typ: node.typ
})
}
// transform_if_branches_with_smartcast is the main if-expr handler that
// integrates smartcasting. When the condition contains an is_expr, it
// pushes a smartcast for the then-branch, transforms the body under
// that context, pops the smartcast, then transforms the else-block.
fn (mut t Transformer) transform_if_branches_with_smartcast(id flat.NodeId, node flat.Node) flat.NodeId {
if node.kind != .if_expr || node.children_count < 2 {
return id
}
cond_id := t.a.child(&node, 0)
then_id := t.a.child(&node, 1)
has_else := node.children_count >= 3
else_id := if has_else { t.a.child(&node, 2) } else { flat.empty_node }
all_is := t.extract_all_is_exprs(cond_id)
new_cond_id := t.transform_and_chain_smartcasts(cond_id)
cond_pending := t.pending_stmts.clone()
t.pending_stmts.clear()
// Transform then-block children under the smartcast context.
saved_var_types := t.var_types.clone()
for info in all_is {
t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name)
}
then_node := t.a.nodes[int(then_id)]
mut new_then_id := then_id
if then_node.kind == .block {
child_ids := t.a.children_of(&then_node)
new_children := t.transform_stmts(child_ids)
block_start := t.a.children.len
for c in new_children {
t.a.children << c
}
new_then_id = t.a.add_node(flat.Node{
kind: .block
children_start: block_start
children_count: flat.child_count(new_children.len)
})
}
for _ in all_is {
t.pop_smartcast()
}
t.var_types = saved_var_types
// Transform else-block (no smartcast -- the is_expr was false here).
mut new_else_id := flat.empty_node
if has_else {
else_node := t.a.nodes[int(else_id)]
if else_node.kind == .if_expr {
// else-if chain: recurse.
new_else_id = t.transform_else_if_expr(else_id, else_node)
} else if else_node.kind == .block {
child_ids := t.a.children_of(&else_node)
new_children := t.transform_stmts(child_ids)
block_start := t.a.children.len
for c in new_children {
t.a.children << c
}
new_else_id = t.a.add_node(flat.Node{
kind: .block
children_start: block_start
children_count: flat.child_count(new_children.len)
})
} else {
new_else_id = else_id
}
}
// Rebuild the if_expr with (possibly) new children.
if_start := t.a.children.len
t.a.children << new_cond_id
t.a.children << new_then_id
mut child_count := 2
if has_else {
t.a.children << new_else_id
child_count = 3
}
new_if := t.a.add_node(flat.Node{
kind: .if_expr
children_start: if_start
children_count: flat.child_count(child_count)
typ: node.typ
pos: node.pos
})
for pending in cond_pending {
t.pending_stmts << pending
}
return new_if
}
// --- helpers ---
struct IsExprInfo {
expr_name string
variant_name string
sum_type_name string
}
fn (t &Transformer) extract_all_is_exprs(cond_id flat.NodeId) []IsExprInfo {
mut result := []IsExprInfo{}
t.collect_is_exprs(cond_id, mut result)
return result
}
fn (t &Transformer) collect_is_exprs(cond_id flat.NodeId, mut result []IsExprInfo) {
if int(cond_id) < 0 {
return
}
cond := t.a.nodes[int(cond_id)]
if cond.kind == .is_expr && cond.children_count >= 1 {
expr_id := t.a.child(&cond, 0)
ek := t.expr_key(expr_id)
if ek.len > 0 && cond.value.len > 0 {
expr_type := t.original_expr_type(expr_id)
stn := t.sum_type_for_is_expr(expr_type, cond.value)
if stn.len > 0 {
result << IsExprInfo{
expr_name: ek
variant_name: cond.value
sum_type_name: stn
}
} else {
if t.is_interface_type_name(expr_type) {
result << IsExprInfo{
expr_name: ek
variant_name: cond.value
sum_type_name: expr_type
}
}
}
}
}
if cond.kind == .infix && cond.op == .logical_and && cond.children_count >= 2 {
t.collect_is_exprs(t.a.child(&cond, 0), mut result)
t.collect_is_exprs(t.a.child(&cond, 1), mut result)
}
}
fn (t &Transformer) extract_is_expr(cond_id flat.NodeId) IsExprInfo {
if int(cond_id) < 0 {
return IsExprInfo{}
}
cond := t.a.nodes[int(cond_id)]
if cond.kind == .is_expr && cond.children_count >= 1 {
expr_id := t.a.child(&cond, 0)
ek := t.expr_key(expr_id)
if ek.len > 0 && cond.value.len > 0 {
expr_type := t.original_expr_type(expr_id)
return IsExprInfo{
expr_name: ek
variant_name: cond.value
sum_type_name: t.sum_type_for_is_expr(expr_type, cond.value)
}
}
}
if cond.kind == .infix && cond.op == .logical_and && cond.children_count >= 2 {
// Check left side first (is_expr is typically on the left of &&).
lhs_id := t.a.child(&cond, 0)
info := t.extract_is_expr(lhs_id)
if info.expr_name.len > 0 {
return info
}
// Check right side as fallback.
rhs_id := t.a.child(&cond, 1)
return t.extract_is_expr(rhs_id)
}
return IsExprInfo{}
}
fn (t &Transformer) sum_type_for_is_expr(expr_type string, variant string) string {
clean_expr_type := t.trim_pointer_type(expr_type)
resolved_expr_sum := t.resolve_sum_name(clean_expr_type)
if resolved_expr_sum in t.sum_types {
if _ := t.sum_variant_name(resolved_expr_sum, variant) {
return clean_expr_type
}
}
return t.find_sum_type_for_variant(variant)
}
// find_sum_type_for_variant returns the sum type name that contains
// the given variant, or '' if none is found.
fn (t &Transformer) find_sum_type_for_variant(variant string) string {
short := if variant.contains('.') { variant.all_after_last('.') } else { variant }
mut best := ''
for sum_name, variants in t.sum_types {
for v in variants {
short_v := if v.contains('.') { v.all_after_last('.') } else { v }
if v == variant || short_v == short {
if sum_name.contains('.') {
return sum_name
}
if best.len == 0 {
best = sum_name
}
}
}
}
return best
}