aiken_project/
export.rs

1use crate::{
2    blueprint::{
3        self,
4        definitions::Definitions,
5        parameter::Parameter,
6        schema::{Annotated, Declaration, Schema},
7    },
8    module::{CheckedModule, CheckedModules},
9};
10use aiken_lang::{
11    ast::{ArgName, Span, TypedArg, TypedFunction},
12    gen_uplc::CodeGenerator,
13    plutus_version::PlutusVersion,
14};
15use miette::NamedSource;
16use uplc::ast::SerializableProgram;
17
18#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
19pub struct Export {
20    pub name: String,
21
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub doc: Option<String>,
24
25    #[serde(skip_serializing_if = "Vec::is_empty")]
26    #[serde(default)]
27    pub parameters: Vec<Parameter>,
28
29    pub return_type: Parameter,
30
31    #[serde(flatten)]
32    pub program: SerializableProgram,
33
34    #[serde(skip_serializing_if = "Definitions::is_empty")]
35    #[serde(default)]
36    pub definitions: Definitions<Annotated<Schema>>,
37}
38
39impl Export {
40    pub fn from_function(
41        func: &TypedFunction,
42        module: &CheckedModule,
43        generator: &mut CodeGenerator,
44        modules: &CheckedModules,
45        plutus_version: &PlutusVersion,
46    ) -> Result<Export, blueprint::Error> {
47        let mut definitions = Definitions::new();
48
49        let parameters = func
50            .arguments
51            .iter()
52            .map(|param| {
53                Annotated::from_type(
54                    modules.into(),
55                    blueprint::validator::tipo_or_annotation(module, param),
56                    &mut definitions,
57                )
58                .map(|schema| Parameter {
59                    title: Some(param.arg_name.get_label()),
60                    schema: Declaration::Referenced(schema),
61                })
62                .map_err(|error| blueprint::Error::Schema {
63                    error,
64                    location: param.location,
65                    source_code: NamedSource::new(
66                        module.input_path.display().to_string(),
67                        module.code.clone(),
68                    ),
69                })
70            })
71            .collect::<Result<_, _>>()?;
72
73        let return_type = Annotated::from_type(
74            modules.into(),
75            blueprint::validator::tipo_or_annotation(
76                module,
77                &TypedArg {
78                    arg_name: ArgName::Discarded {
79                        name: "".to_string(),
80                        label: "".to_string(),
81                        location: Span::empty(),
82                    },
83                    location: Span::empty(),
84                    annotation: func.return_annotation.clone(),
85                    doc: None,
86                    is_validator_param: false,
87                    tipo: func.return_type.clone(),
88                },
89            ),
90            &mut definitions,
91        )
92        .map(|schema| Parameter {
93            title: Some("return_type".to_string()),
94            schema: Declaration::Referenced(schema),
95        })
96        .map_err(|error| blueprint::Error::Schema {
97            error,
98            location: func.location,
99            source_code: NamedSource::new(
100                module.input_path.display().to_string(),
101                module.code.clone(),
102            ),
103        })?;
104
105        let program = generator
106            .generate_raw(&func.body, &func.arguments, &module.name)
107            .to_debruijn()
108            .unwrap();
109
110        let program = match plutus_version {
111            PlutusVersion::V1 => SerializableProgram::PlutusV1Program(program),
112            PlutusVersion::V2 => SerializableProgram::PlutusV2Program(program),
113            PlutusVersion::V3 => SerializableProgram::PlutusV3Program(program),
114        };
115
116        Ok(Export {
117            name: format!("{}.{}", &module.name, &func.name),
118            doc: func.doc.clone(),
119            parameters,
120            return_type,
121            program,
122            definitions,
123        })
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::{CheckedModules, Export};
130    use crate::tests::TestProject;
131    use aiken_lang::{
132        self,
133        ast::{TraceLevel, Tracing},
134        plutus_version::PlutusVersion,
135    };
136
137    macro_rules! assert_export {
138        ($code:expr) => {
139            let mut project = TestProject::new();
140
141            let modules = CheckedModules::singleton(project.check(project.parse(indoc::indoc! { $code })));
142
143            let mut generator = project.new_generator(
144                Tracing::All(TraceLevel::Verbose),
145            );
146
147            let (module, func) = modules
148                .functions()
149                .next()
150                .expect("source code did no yield any exports");
151
152            let export = Export::from_function(func, module, &mut generator, &modules, &PlutusVersion::default());
153
154            match export {
155                Err(e) => insta::with_settings!({
156                    description => concat!("Code:\n\n", indoc::indoc! { $code }),
157                    omit_expression => true
158                }, {
159                    insta::assert_debug_snapshot!(e);
160                }),
161
162                Ok(validator) => insta::with_settings!({
163                    description => concat!("Code:\n\n", indoc::indoc! { $code }),
164                    omit_expression => true
165                }, {
166                    insta::assert_json_snapshot!(validator);
167                }),
168            };
169        };
170    }
171
172    #[test]
173    fn basic_export() {
174        assert_export!(
175            r#"
176            pub fn add(a: Int, b: Int) -> Int {
177                a + b
178            }
179            "#
180        );
181    }
182
183    #[test]
184    fn illegal_opaque_type() {
185        assert_export!(
186            r#"
187            pub opaque type Thing {
188              a: Int
189            }
190
191            pub fn add(a: Thing, b: Int) -> Int {
192                a.a + b
193            }
194            "#
195        );
196    }
197
198    #[test]
199    fn recursive_types() {
200        assert_export!(
201            r#"
202            pub type Foo<a> {
203              Empty
204              Bar(a, Foo<a>)
205            }
206
207            pub fn add(a: Foo<Int>, b: Foo<Int>) -> Int {
208              when (a, b) is {
209                (Empty, Empty) -> 0
210                (Bar(x, y), Bar(c, d)) -> x + c + add(y, d)
211                (Empty, Bar(c, d)) -> c + add(Empty, d)
212                (Bar(x, y), Empty) -> x + add(y, Empty)
213              }
214            }
215            "#
216        );
217    }
218
219    #[test]
220    fn cannot_export_generics() {
221        assert_export!(
222            r#"
223            pub fn add(_a: a, _b: b) -> Bool {
224                True
225            }
226            "#
227        );
228    }
229}