Skip to main content

seq_runtime/variant_ops/
modify.rs

1//! Mutating-by-clone variant helpers: `variant_append`, `variant_last`,
2//! `variant_init`, and the field-unpacker used for match expressions.
3
4use crate::stack::{Stack, peek_heap_mut_second, pop, push};
5use crate::value::Value;
6use std::sync::Arc;
7
8/// Append a value to a variant, returning a new variant
9///
10/// Stack effect: ( Variant Value -- Variant' )
11///
12/// Creates a new variant with the same tag as the input, but with
13/// the new value appended to its fields. The original variant is
14/// not modified (functional/immutable style).
15///
16/// Example: For arrays, `[1, 2] 3 variant-append` produces `[1, 2, 3]`
17/// Example: For objects, `{} "key" variant-append "val" variant-append` builds `{"key": "val"}`
18///
19/// # Safety
20/// Stack must have a Variant and a Value on top
21#[unsafe(no_mangle)]
22pub unsafe extern "C" fn patch_seq_variant_append(stack: Stack) -> Stack {
23    use crate::value::VariantData;
24
25    unsafe {
26        // Fast path: peek at the variant at sp-2 without popping.
27        // SAFETY: variant.append requires two values on the stack (enforced by
28        // the type checker), so stack.sub(2) is valid.
29        if let Some(Value::Variant(variant_arc)) = peek_heap_mut_second(stack)
30            && let Some(data) = Arc::get_mut(variant_arc)
31        {
32            // Sole owner all the way down — mutate in place.
33            // Safety: `data` points into the Value at sp-2. `pop` only
34            // touches sp-1 (decrements sp, reads that slot), so sp-2's
35            // memory is not accessed or invalidated by the pop.
36            let (stack, value) = pop(stack);
37            data.fields.push(value);
38            return stack; // Variant is still at sp-1, mutated in place
39        }
40
41        // Slow path: pop both, clone if shared, push result
42        let (stack, value) = pop(stack);
43        let (stack, variant_val) = pop(stack);
44
45        match variant_val {
46            Value::Variant(mut arc) => {
47                if let Some(data) = Arc::get_mut(&mut arc) {
48                    data.fields.push(value);
49                    push(stack, Value::Variant(arc))
50                } else {
51                    let mut new_fields = Vec::with_capacity(arc.fields.len() + 1);
52                    new_fields.extend(arc.fields.iter().cloned());
53                    new_fields.push(value);
54                    let new_variant =
55                        Value::Variant(Arc::new(VariantData::new(arc.tag.clone(), new_fields)));
56                    push(stack, new_variant)
57                }
58            }
59            _ => panic!("variant-append: expected Variant, got {:?}", variant_val),
60        }
61    }
62}
63
64/// Get the last field from a variant
65///
66/// Stack effect: ( Variant -- Value )
67///
68/// Returns a clone of the last field. Panics if the variant has no fields.
69/// This is the "peek" operation for using variants as stacks.
70///
71/// # Safety
72/// Stack must have a Variant on top
73#[unsafe(no_mangle)]
74pub unsafe extern "C" fn patch_seq_variant_last(stack: Stack) -> Stack {
75    unsafe {
76        let (stack, variant_val) = pop(stack);
77
78        match variant_val {
79            Value::Variant(variant_data) => {
80                if variant_data.fields.is_empty() {
81                    panic!("variant-last: variant has no fields");
82                }
83
84                let last = variant_data.fields.last().unwrap().clone();
85                push(stack, last)
86            }
87            _ => panic!("variant-last: expected Variant, got {:?}", variant_val),
88        }
89    }
90}
91
92/// Get all but the last field from a variant
93///
94/// Stack effect: ( Variant -- Variant' )
95///
96/// Returns a new variant with the same tag but without the last field.
97/// Panics if the variant has no fields.
98/// This is the "pop" operation for using variants as stacks (without returning the popped value).
99///
100/// # Safety
101/// Stack must have a Variant on top
102#[unsafe(no_mangle)]
103pub unsafe extern "C" fn patch_seq_variant_init(stack: Stack) -> Stack {
104    use crate::value::VariantData;
105
106    unsafe {
107        let (stack, variant_val) = pop(stack);
108
109        match variant_val {
110            Value::Variant(variant_data) => {
111                if variant_data.fields.is_empty() {
112                    panic!("variant-init: variant has no fields");
113                }
114
115                // Create new fields without the last element
116                let new_fields: Vec<Value> =
117                    variant_data.fields[..variant_data.fields.len() - 1].to_vec();
118
119                let new_variant = Value::Variant(Arc::new(VariantData::new(
120                    variant_data.tag.clone(),
121                    new_fields,
122                )));
123
124                push(stack, new_variant)
125            }
126            _ => panic!("variant-init: expected Variant, got {:?}", variant_val),
127        }
128    }
129}
130
131/// Unpack a variant's fields onto the stack
132///
133/// Takes a field count as parameter and:
134/// - Pops the variant from the stack
135/// - Pushes each field (0..field_count) in order
136///
137/// Stack effect: ( Variant -- field0 field1 ... fieldN-1 )
138///
139/// Used by match expression codegen to extract variant fields.
140///
141/// # Safety
142/// Stack must have a Variant on top with at least `field_count` fields
143#[unsafe(no_mangle)]
144pub unsafe extern "C" fn patch_seq_unpack_variant(stack: Stack, field_count: i64) -> Stack {
145    unsafe {
146        let (mut stack, variant_val) = pop(stack);
147
148        match variant_val {
149            Value::Variant(variant_data) => {
150                let count = field_count as usize;
151                if count > variant_data.fields.len() {
152                    panic!(
153                        "unpack-variant: requested {} fields but variant only has {}",
154                        count,
155                        variant_data.fields.len()
156                    );
157                }
158
159                // Push each field in order (field0 first, then field1, etc.)
160                for i in 0..count {
161                    stack = push(stack, variant_data.fields[i].clone());
162                }
163
164                stack
165            }
166            _ => panic!("unpack-variant: expected Variant, got {:?}", variant_val),
167        }
168    }
169}