Skip to main content

leo_ast/stub/
function_stub.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::{
18    Annotation,
19    CompositeType,
20    Function,
21    FutureType,
22    Identifier,
23    Input,
24    Location,
25    Mode,
26    Node,
27    NodeID,
28    Output,
29    Path,
30    ProgramId,
31    TupleType,
32    Type,
33    Variant,
34};
35use leo_span::{Span, Symbol, sym};
36
37use itertools::Itertools;
38use serde::{Deserialize, Serialize};
39use snarkvm::{
40    console::program::RegisterType,
41    prelude::{FinalizeType, Network, ValueType},
42    synthesizer::program::{ClosureCore, FunctionCore, ViewCore},
43};
44use std::fmt;
45
46/// A function stub definition.
47#[derive(Clone, Serialize, Deserialize)]
48pub struct FunctionStub {
49    /// Annotations on the function.
50    pub annotations: Vec<Annotation>,
51    /// Is this function a transition, inlined, or a regular function?.
52    pub variant: Variant,
53    /// The function identifier, e.g., `foo` in `function foo(...) { ... }`.
54    pub identifier: Identifier,
55    /// The function's input parameters.
56    pub input: Vec<Input>,
57    /// The function's output declarations.
58    pub output: Vec<Output>,
59    /// The function's output type.
60    pub output_type: Type,
61    /// The entire span of the function definition.
62    pub span: Span,
63    /// The ID of the node.
64    pub id: NodeID,
65}
66
67impl PartialEq for FunctionStub {
68    fn eq(&self, other: &Self) -> bool {
69        self.identifier == other.identifier
70    }
71}
72
73impl Eq for FunctionStub {}
74
75impl FunctionStub {
76    /// Initialize a new function.
77    #[allow(clippy::too_many_arguments)]
78    pub fn new(
79        annotations: Vec<Annotation>,
80        _is_async: bool,
81        variant: Variant,
82        identifier: Identifier,
83        input: Vec<Input>,
84        output: Vec<Output>,
85        span: Span,
86        id: NodeID,
87    ) -> Self {
88        let output_type = match output.len() {
89            0 => Type::Unit,
90            1 => output[0].type_.clone(),
91            _ => Type::Tuple(TupleType::new(output.iter().map(|o| o.type_.clone()).collect())),
92        };
93
94        FunctionStub { annotations, variant, identifier, input, output, output_type, span, id }
95    }
96
97    /// Returns function name.
98    pub fn name(&self) -> Symbol {
99        self.identifier.name
100    }
101
102    /// Returns `true` if the function name is `main`.
103    pub fn is_main(&self) -> bool {
104        self.name() == sym::main
105    }
106
107    /// Returns `true` if any output of the function is a `Final`
108    pub fn has_final_output(&self) -> bool {
109        self.output.iter().any(|o| matches!(o.type_, Type::Future(_)))
110    }
111
112    /// Private formatting method used for optimizing [fmt::Debug] and [fmt::Display] implementations.
113    fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
114        match self.variant {
115            Variant::FinalFn => write!(f, "final fn ")?,
116            Variant::Finalize => write!(f, "finalize ")?,
117            Variant::Fn => write!(f, "fn ")?,
118            Variant::EntryPoint => write!(f, "entry ")?,
119            Variant::View => write!(f, "view fn ")?,
120        }
121        write!(f, "{}", self.identifier)?;
122
123        let parameters = self.input.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(",");
124        let returns = match self.output.len() {
125            0 => "()".to_string(),
126            1 => self.output[0].to_string(),
127            _ => self.output.iter().map(|x| x.to_string()).collect::<Vec<_>>().join(","),
128        };
129        write!(f, "({parameters}) -> {returns}")?;
130
131        Ok(())
132    }
133
134    /// Converts from snarkvm function type to leo FunctionStub, while also carrying the parent program name.
135    pub fn from_function_core<N: Network>(function: &FunctionCore<N>, program_id: ProgramId) -> Self {
136        let outputs = function
137            .outputs()
138            .iter()
139            .map(|output| match output.value_type() {
140                ValueType::Constant(val) => vec![Output {
141                    mode: Mode::Constant,
142                    type_: Type::from_snarkvm(val, program_id),
143                    span: Default::default(),
144                    id: Default::default(),
145                }],
146                ValueType::Public(val) => vec![Output {
147                    mode: Mode::Public,
148                    type_: Type::from_snarkvm(val, program_id),
149                    span: Default::default(),
150                    id: Default::default(),
151                }],
152                ValueType::Private(val) => vec![Output {
153                    mode: Mode::Private,
154                    type_: Type::from_snarkvm(val, program_id),
155                    span: Default::default(),
156                    id: Default::default(),
157                }],
158                ValueType::Record(id) => vec![Output {
159                    mode: Mode::None,
160                    type_: Type::Composite(CompositeType {
161                        path: {
162                            let ident = Identifier::from(id);
163                            Path::from(ident)
164                                .to_global(Location::new(program_id.as_symbol(), vec![ident.name]))
165                                .with_user_program(program_id)
166                        },
167                        const_arguments: Vec::new(),
168                    }),
169                    span: Default::default(),
170                    id: Default::default(),
171                }],
172                ValueType::ExternalRecord(loc) => {
173                    let external_program_id = ProgramId::from(loc.program_id());
174                    vec![Output {
175                        mode: Mode::None,
176                        span: Default::default(),
177                        id: Default::default(),
178                        type_: Type::Composite(CompositeType {
179                            path: {
180                                let ident = Identifier::from(loc.resource());
181                                Path::from(ident)
182                                    .to_global(Location::new(external_program_id.as_symbol(), vec![ident.name]))
183                                    .with_user_program(external_program_id)
184                            },
185                            const_arguments: Vec::new(),
186                        }),
187                    }]
188                }
189                ValueType::Future(_) => vec![Output {
190                    mode: Mode::None,
191                    span: Default::default(),
192                    id: Default::default(),
193                    type_: Type::Future(FutureType::new(
194                        Vec::new(),
195                        Some(Location::new(program_id.as_symbol(), vec![Symbol::intern(&function.name().to_string())])),
196                        false,
197                    )),
198                }],
199                ValueType::DynamicRecord => vec![Output {
200                    mode: Mode::None,
201                    span: Default::default(),
202                    id: Default::default(),
203                    type_: Type::DynRecord,
204                }],
205                ValueType::DynamicFuture => vec![Output {
206                    mode: Mode::None,
207                    span: Default::default(),
208                    id: Default::default(),
209                    type_: Type::Future(FutureType::new(
210                        Vec::new(),
211                        Some(Location::new(program_id.as_symbol(), vec![Symbol::intern(&function.name().to_string())])),
212                        false,
213                    )),
214                }],
215            })
216            .collect_vec()
217            .concat();
218        let output_vec = outputs.iter().map(|output| output.type_.clone()).collect_vec();
219        let output_type = match output_vec.len() {
220            0 => Type::Unit,
221            1 => output_vec[0].clone(),
222            _ => Type::Tuple(TupleType::new(output_vec)),
223        };
224
225        Self {
226            annotations: Vec::new(),
227            variant: Variant::EntryPoint,
228            identifier: Identifier::from(function.name()),
229            input: function
230                .inputs()
231                .iter()
232                .enumerate()
233                .map(|(index, input)| {
234                    let arg_name = Identifier::new(Symbol::intern(&format!("arg{}", index + 1)), Default::default());
235                    match input.value_type() {
236                        ValueType::Constant(val) => Input {
237                            identifier: arg_name,
238                            mode: Mode::Constant,
239                            type_: Type::from_snarkvm(val, program_id),
240                            span: Default::default(),
241                            id: Default::default(),
242                        },
243                        ValueType::Public(val) => Input {
244                            identifier: arg_name,
245                            mode: Mode::Public,
246                            type_: Type::from_snarkvm(val, program_id),
247                            span: Default::default(),
248                            id: Default::default(),
249                        },
250                        ValueType::Private(val) => Input {
251                            identifier: arg_name,
252                            mode: Mode::Private,
253                            type_: Type::from_snarkvm(val, program_id),
254                            span: Default::default(),
255                            id: Default::default(),
256                        },
257                        ValueType::Record(id) => Input {
258                            identifier: arg_name,
259                            mode: Mode::None,
260                            type_: Type::Composite(CompositeType {
261                                path: {
262                                    let ident = Identifier::from(id);
263                                    Path::from(ident)
264                                        .to_global(Location::new(program_id.as_symbol(), vec![ident.name]))
265                                        .with_user_program(program_id)
266                                },
267                                const_arguments: Vec::new(),
268                            }),
269                            span: Default::default(),
270                            id: Default::default(),
271                        },
272                        ValueType::ExternalRecord(loc) => {
273                            let external_program = ProgramId::from(loc.program_id());
274                            Input {
275                                identifier: arg_name,
276                                mode: Mode::None,
277                                span: Default::default(),
278                                id: Default::default(),
279                                type_: Type::Composite(CompositeType {
280                                    path: {
281                                        let ident = Identifier::from(loc.resource());
282                                        Path::from(ident)
283                                            .to_global(Location::new(external_program.as_symbol(), vec![ident.name]))
284                                            .with_user_program(external_program)
285                                    },
286                                    const_arguments: Vec::new(),
287                                }),
288                            }
289                        }
290                        ValueType::Future(_) | ValueType::DynamicFuture => {
291                            panic!("Functions do not contain futures as inputs")
292                        }
293
294                        ValueType::DynamicRecord => Input {
295                            identifier: arg_name,
296                            mode: Mode::None,
297                            span: Default::default(),
298                            id: Default::default(),
299                            type_: Type::DynRecord,
300                        },
301                    }
302                })
303                .collect_vec(),
304            output: outputs,
305            output_type,
306            span: Default::default(),
307            id: Default::default(),
308        }
309    }
310
311    pub fn from_finalize<N: Network>(function: &FunctionCore<N>, key_name: Symbol, program_id: ProgramId) -> Self {
312        Self {
313            annotations: Vec::new(),
314            variant: Variant::Finalize,
315            identifier: Identifier::new(key_name, Default::default()),
316            input: function
317                .finalize_logic()
318                .unwrap()
319                .inputs()
320                .iter()
321                .enumerate()
322                .map(|(index, input)| Input {
323                    identifier: Identifier::new(Symbol::intern(&format!("arg{}", index + 1)), Default::default()),
324                    mode: Mode::None,
325                    type_: match input.finalize_type() {
326                        FinalizeType::Plaintext(val) => Type::from_snarkvm(val, program_id),
327                        FinalizeType::Future(val) => Type::Future(FutureType::new(
328                            Vec::new(),
329                            Some(Location::new(ProgramId::from(val.program_id()).as_symbol(), vec![Symbol::intern(
330                                &format!("finalize/{}", val.resource()),
331                            )])),
332                            false,
333                        )),
334                        FinalizeType::DynamicFuture => Type::Future(FutureType::new(Vec::new(), None, false)),
335                    },
336                    span: Default::default(),
337                    id: Default::default(),
338                })
339                .collect_vec(),
340            output: Vec::new(),
341            output_type: Type::Unit,
342            span: Default::default(),
343            id: 0,
344        }
345    }
346
347    /// Construct a Leo `FunctionStub` from a snarkVM `ViewCore` (V15 read-only entry).
348    /// snarkVM's `ViewCore::add_input` and `ViewCore::add_output` enforce
349    /// `matches!(finalize_type, FinalizeType::Plaintext(_))` — see
350    /// `snarkvm/synthesizer/program/src/view/mod.rs` — so by the time a `ViewCore<N>` reaches
351    /// this constructor, non-plaintext finalize types are unreachable. The panic below matches
352    /// the defense-in-depth pattern used by `from_function_core` (`Functions do not contain
353    /// futures as inputs`) — `disassemble_from_str` will surface a clean validation error long
354    /// before this is hit, so reaching the panic indicates a bug in snarkVM or a caller that
355    /// bypassed validation entirely.
356    pub fn from_view<N: Network>(view: &ViewCore<N>, program_id: ProgramId) -> Self {
357        let plaintext_or_panic = |finalize_type: &FinalizeType<N>| match finalize_type {
358            FinalizeType::Plaintext(val) => Type::from_snarkvm(val, program_id),
359            FinalizeType::Future(_) | FinalizeType::DynamicFuture => {
360                panic!("Views do not contain futures as inputs or outputs")
361            }
362        };
363
364        let outputs = view
365            .outputs()
366            .iter()
367            .map(|output| Output {
368                mode: Mode::None,
369                type_: plaintext_or_panic(output.finalize_type()),
370                span: Default::default(),
371                id: Default::default(),
372            })
373            .collect_vec();
374        let output_vec = outputs.iter().map(|o| o.type_.clone()).collect_vec();
375        let output_type = match output_vec.len() {
376            0 => Type::Unit,
377            1 => output_vec[0].clone(),
378            _ => Type::Tuple(TupleType::new(output_vec)),
379        };
380
381        Self {
382            annotations: Vec::new(),
383            variant: Variant::View,
384            identifier: Identifier::from(view.name()),
385            input: view
386                .inputs()
387                .iter()
388                .enumerate()
389                .map(|(index, input)| Input {
390                    identifier: Identifier::new(Symbol::intern(&format!("arg{}", index + 1)), Default::default()),
391                    mode: Mode::None,
392                    type_: plaintext_or_panic(input.finalize_type()),
393                    span: Default::default(),
394                    id: Default::default(),
395                })
396                .collect_vec(),
397            output: outputs,
398            output_type,
399            span: Default::default(),
400            id: 0,
401        }
402    }
403
404    pub fn from_closure<N: Network>(closure: &ClosureCore<N>, program_id: ProgramId) -> Self {
405        let outputs = closure
406            .outputs()
407            .iter()
408            .map(|output| match output.register_type() {
409                RegisterType::Plaintext(val) => Output {
410                    mode: Mode::None,
411                    type_: Type::from_snarkvm(val, program_id),
412                    span: Default::default(),
413                    id: Default::default(),
414                },
415                RegisterType::Record(_) | RegisterType::DynamicRecord => panic!("Closures do not return records"),
416                RegisterType::ExternalRecord(_) => panic!("Closures do not return external records"),
417                RegisterType::Future(_) | RegisterType::DynamicFuture => panic!("Closures do not return futures"),
418            })
419            .collect_vec();
420        let output_vec = outputs.iter().map(|output| output.type_.clone()).collect_vec();
421        let output_type = match output_vec.len() {
422            0 => Type::Unit,
423            1 => output_vec[0].clone(),
424            _ => Type::Tuple(TupleType::new(output_vec)),
425        };
426        Self {
427            annotations: Vec::new(),
428            variant: Variant::Fn,
429            identifier: Identifier::from(closure.name()),
430            input: closure
431                .inputs()
432                .iter()
433                .enumerate()
434                .map(|(index, input)| {
435                    let arg_name = Identifier::new(Symbol::intern(&format!("arg{}", index + 1)), Default::default());
436                    match input.register_type() {
437                        RegisterType::Plaintext(val) => Input {
438                            identifier: arg_name,
439                            mode: Mode::None,
440                            type_: Type::from_snarkvm(val, program_id),
441                            span: Default::default(),
442                            id: Default::default(),
443                        },
444                        RegisterType::Record(_) | RegisterType::DynamicRecord => {
445                            panic!("Closures do not contain records as inputs")
446                        }
447                        RegisterType::ExternalRecord(_) => panic!("Closures do not contain external records as inputs"),
448                        RegisterType::Future(_) | RegisterType::DynamicFuture => {
449                            panic!("Closures do not contain futures as inputs")
450                        }
451                    }
452                })
453                .collect_vec(),
454            output: outputs,
455            output_type,
456            span: Default::default(),
457            id: Default::default(),
458        }
459    }
460}
461
462impl From<Function> for FunctionStub {
463    fn from(function: Function) -> Self {
464        Self {
465            annotations: function.annotations,
466            variant: function.variant,
467            identifier: function.identifier,
468            input: function.input,
469            output: function.output,
470            output_type: function.output_type,
471            span: function.span,
472            id: function.id,
473        }
474    }
475}
476
477impl fmt::Debug for FunctionStub {
478    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
479        self.format(f)
480    }
481}
482
483impl fmt::Display for FunctionStub {
484    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
485        self.format(f)
486    }
487}
488
489crate::simple_node_impl!(FunctionStub);