brillig/
foreign_call.rs

1use acir_field::AcirField;
2use serde::{Deserialize, Serialize};
3
4/// Single input or output of a [foreign call][crate::Opcode::ForeignCall].
5///
6/// This enum can represent either a single field element or an array of field elements.
7///
8/// The `#[serde(untagged)]` attribute uses the natural JSON representation:
9/// `Single(5)` serializes as `5`, and `Array([1,2,3])` serializes as `[1,2,3]`.
10/// This allows external systems to pass values directly without needing to know about
11/// Rust enum variants, relying on JSON's native distinction between single values and arrays.
12#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
13#[serde(untagged)]
14pub enum ForeignCallParam<F> {
15    /// A single field element value.
16    Single(F),
17    /// Multiple field element values (array or vector passed by value).
18    Array(Vec<F>),
19}
20
21impl<F> From<F> for ForeignCallParam<F> {
22    fn from(value: F) -> Self {
23        ForeignCallParam::Single(value)
24    }
25}
26
27impl<F> From<Vec<F>> for ForeignCallParam<F> {
28    fn from(values: Vec<F>) -> Self {
29        ForeignCallParam::Array(values)
30    }
31}
32
33impl<F: AcirField> ForeignCallParam<F> {
34    /// Convert the parameter into a uniform vector representation.
35    ///
36    /// This is useful for flattening data when processing foreign call results,
37    /// allowing both `Single` and `Array` variants to be handled uniformly as `Vec<F>`.
38    pub fn fields(&self) -> Vec<F> {
39        match self {
40            ForeignCallParam::Single(value) => vec![*value],
41            ForeignCallParam::Array(values) => values.to_vec(),
42        }
43    }
44
45    /// Unwrap the field in a `Single` input. Panics if it's an `Array`.
46    pub fn unwrap_field(&self) -> F {
47        match self {
48            ForeignCallParam::Single(value) => *value,
49            ForeignCallParam::Array(_) => panic!("Expected single value, found array"),
50        }
51    }
52}
53
54/// Represents the full output of a [foreign call][crate::Opcode::ForeignCall].
55///
56/// A foreign call can return multiple outputs, where each output can be either
57/// a single field element or an array. This struct wraps a vector of [ForeignCallParam]
58/// to represent all outputs from a single foreign call.
59#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default)]
60pub struct ForeignCallResult<F> {
61    /// Resolved output values of the foreign call.
62    ///
63    /// Each element represents one output, which can be either a single value or an array.
64    pub values: Vec<ForeignCallParam<F>>,
65}
66
67/// Convenience conversion for a foreign call returning a single field element value.
68///
69/// Creates a `ForeignCallResult` with one output containing the single value.
70impl<F> From<F> for ForeignCallResult<F> {
71    fn from(value: F) -> Self {
72        ForeignCallResult { values: vec![value.into()] }
73    }
74}
75
76/// Conversion for a foreign call returning a single array output.
77/// Creates a `ForeignCallResult` with one output, where that output is an array.
78/// This represents one array output, not multiple single-value outputs:
79///
80/// ```ignore
81/// let result: ForeignCallResult<F> = vec![1, 2, 3].into();
82/// // result.values.len() == 1
83/// // result.values[0] == ForeignCallParam::Array([1, 2, 3])
84/// ```
85///
86/// For multiple single-value outputs, use `From<Vec<ForeignCallParam<F>>>` instead:
87/// ```ignore
88/// let result: ForeignCallResult<F> = vec![
89///     ForeignCallParam::Single(1),
90///     ForeignCallParam::Single(2),
91///     ForeignCallParam::Single(3)
92/// ].into();
93/// // result.values.len() == 3
94/// ```
95impl<F> From<Vec<F>> for ForeignCallResult<F> {
96    fn from(values: Vec<F>) -> Self {
97        ForeignCallResult { values: vec![values.into()] }
98    }
99}
100
101/// Conversion for a foreign call returning multiple outputs.
102///
103/// Each element in the vector represents a separate output, which can be
104/// either a single value or an array.
105impl<F> From<Vec<ForeignCallParam<F>>> for ForeignCallResult<F> {
106    fn from(values: Vec<ForeignCallParam<F>>) -> Self {
107        ForeignCallResult { values }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use acir_field::FieldElement;
115
116    #[test]
117    fn test_foreign_call_param_from_single() {
118        let value = FieldElement::from(42u128);
119        let param = ForeignCallParam::from(value);
120
121        assert_eq!(param, ForeignCallParam::Single(value));
122        assert_eq!(param.fields(), vec![value]);
123        assert_eq!(param.unwrap_field(), value);
124    }
125
126    #[test]
127    fn test_foreign_call_param_from_array() {
128        let values =
129            vec![FieldElement::from(1u128), FieldElement::from(2u128), FieldElement::from(3u128)];
130        let param = ForeignCallParam::from(values.clone());
131
132        assert_eq!(param, ForeignCallParam::Array(values.clone()));
133        assert_eq!(param.fields(), values);
134    }
135
136    #[test]
137    fn test_foreign_call_param_array_roundtrip() {
138        // Test that Array variant round trips through From<Vec<F>> and fields()
139        let original = vec![
140            FieldElement::from(10u128),
141            FieldElement::from(20u128),
142            FieldElement::from(30u128),
143        ];
144
145        // Need explicit type annotation to disambiguate from From<F> impl
146        let param: ForeignCallParam<FieldElement> = original.clone().into();
147        let roundtrip = param.fields();
148
149        assert_eq!(roundtrip, original);
150    }
151
152    #[test]
153    fn test_foreign_call_param_single_to_array() {
154        // Note: Single doesn't roundtrip perfectly because fields() returns Vec
155        // This test documents the expected behavior
156        let value = FieldElement::from(42u128);
157        let param = ForeignCallParam::Single(value);
158
159        // fields() converts Single to a Vec with one element
160        assert_eq!(param.fields(), vec![value]);
161    }
162
163    #[test]
164    #[should_panic(expected = "Expected single value, found array")]
165    fn test_foreign_call_param_unwrap_field_panics_on_array() {
166        let param =
167            ForeignCallParam::Array(vec![FieldElement::from(1u128), FieldElement::from(2u128)]);
168
169        // This should panic
170        param.unwrap_field();
171    }
172
173    #[test]
174    fn test_foreign_call_result_from_single_value() {
175        let value = FieldElement::from(42u128);
176        let result = ForeignCallResult::from(value);
177
178        assert_eq!(result.values.len(), 1);
179        assert_eq!(result.values[0], ForeignCallParam::Single(value));
180    }
181
182    #[test]
183    fn test_foreign_call_result_from_vec_creates_single_array_output() {
184        let values =
185            vec![FieldElement::from(1u128), FieldElement::from(2u128), FieldElement::from(3u128)];
186        let result = ForeignCallResult::from(values.clone());
187
188        // From<Vec<F>> creates one output that is an Array
189        assert_eq!(result.values.len(), 1);
190        assert_eq!(result.values[0], ForeignCallParam::Array(values));
191    }
192
193    #[test]
194    fn test_foreign_call_result_from_params_creates_multiple_outputs() {
195        let params = vec![
196            ForeignCallParam::Single(FieldElement::from(1u128)),
197            ForeignCallParam::Single(FieldElement::from(2u128)),
198            ForeignCallParam::Single(FieldElement::from(3u128)),
199        ];
200        let result = ForeignCallResult::from(params.clone());
201
202        // From<Vec<ForeignCallParam<F>>> creates multiple outputs
203        assert_eq!(result.values.len(), 3);
204        assert_eq!(result.values, params);
205    }
206}