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