Skip to main content

morphir_core/ir/v4/
package.rs

1//! Package types for Morphir IR V4
2//!
3//! This module contains PackageDefinition, PackageSpecification, and related types.
4
5use indexmap::IndexMap;
6use serde::{Deserialize, Serialize};
7
8use super::access::{Access, AccessControlled};
9use super::module::{Documented, ModuleDefinition, ModuleSpecification};
10use super::types::{
11    ConstructorArgSpec, ConstructorSpecification, TypeDefinition, TypeSpecification,
12};
13use super::value::{ValueDefinition, ValueSpecification};
14
15/// Package specification (for dependencies)
16#[derive(Debug, Clone, PartialEq, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct PackageSpecification {
19    pub modules: IndexMap<String, ModuleSpecification>,
20}
21
22impl<'de> Deserialize<'de> for PackageSpecification {
23    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
24    where
25        D: serde::Deserializer<'de>,
26    {
27        super::serde_document::deserialize_standalone_with(
28            deserializer,
29            super::serde_document::decode_package_specification,
30        )
31    }
32}
33
34/// Package definition
35#[derive(Debug, Clone, PartialEq, Serialize)]
36#[serde(rename_all = "camelCase")]
37pub struct PackageDefinition {
38    pub modules: IndexMap<String, AccessControlled<ModuleDefinition>>,
39}
40
41impl PackageDefinition {
42    /// The public face of this package: the specification a reader of it sees.
43    ///
44    /// Only public modules, types and values appear, and each loses what a specification does not
45    /// carry: a custom type whose constructors are private becomes opaque, as does an incomplete
46    /// type definition, since neither has a public shape to publish. A definition carries no
47    /// annotations, so the specification derived from one has none.
48    ///
49    /// This is infallible, so a public value definition with no output type is skipped rather
50    /// than refused: a specification states a value's type, and one still being inferred has
51    /// nothing to state yet.
52    pub fn to_specification(&self) -> PackageSpecification {
53        PackageSpecification {
54            modules: self
55                .modules
56                .iter()
57                .filter(|(_, controlled)| controlled.access == Access::Public)
58                .map(|(name, controlled)| {
59                    (name.clone(), module_to_specification(&controlled.value))
60                })
61                .collect(),
62        }
63    }
64}
65
66fn module_to_specification(definition: &ModuleDefinition) -> ModuleSpecification {
67    ModuleSpecification {
68        // A definition carries no annotations, so the specification derived from one has none.
69        annotations: Vec::new().into(),
70        types: definition
71            .types
72            .iter()
73            .filter(|(_, controlled)| controlled.access == Access::Public)
74            .map(|(name, controlled)| {
75                let Documented { doc, value } = &controlled.value;
76                (
77                    name.clone(),
78                    Documented::new(doc.clone(), type_to_specification(value)),
79                )
80            })
81            .collect(),
82        values: definition
83            .values
84            .iter()
85            .filter(|(_, controlled)| controlled.access == Access::Public)
86            .filter_map(|(name, controlled)| {
87                let Documented { doc, value } = &controlled.value;
88                let specification = value_to_specification(value)?;
89                Some((name.clone(), Documented::new(doc.clone(), specification)))
90            })
91            .collect(),
92        doc: definition.doc.clone(),
93    }
94}
95
96/// The specification a value definition states, or nothing while its output type is unknown.
97fn value_to_specification(definition: &ValueDefinition) -> Option<ValueSpecification> {
98    Some(ValueSpecification {
99        annotations: Vec::new().into(),
100        inputs: definition.input_types.clone(),
101        output: definition.output_type.clone()?,
102    })
103}
104
105fn type_to_specification(definition: &TypeDefinition) -> TypeSpecification {
106    match definition {
107        TypeDefinition::TypeAliasDefinition {
108            type_params,
109            type_expr,
110        } => TypeSpecification::TypeAliasSpecification {
111            annotations: Vec::new().into(),
112            type_params: type_params.clone(),
113            type_expr: type_expr.clone(),
114        },
115        TypeDefinition::CustomTypeDefinition {
116            type_params,
117            constructors,
118        } => match constructors.access {
119            Access::Public => TypeSpecification::CustomTypeSpecification {
120                annotations: Vec::new().into(),
121                type_params: type_params.clone(),
122                constructors: constructors
123                    .value
124                    .iter()
125                    .map(|constructor| ConstructorSpecification {
126                        name: constructor.name.clone(),
127                        args: constructor
128                            .args
129                            .iter()
130                            .map(|argument| ConstructorArgSpec {
131                                name: argument.name.clone(),
132                                arg_type: argument.arg_type.clone(),
133                            })
134                            .collect(),
135                    })
136                    .collect(),
137            },
138            Access::Private => TypeSpecification::OpaqueTypeSpecification {
139                annotations: Vec::new().into(),
140                type_params: type_params.clone(),
141            },
142        },
143        // A type still being written publishes no shape, so it is opaque until it has one.
144        TypeDefinition::IncompleteTypeDefinition { type_params, .. } => {
145            TypeSpecification::OpaqueTypeSpecification {
146                annotations: Vec::new().into(),
147                type_params: type_params.clone(),
148            }
149        }
150    }
151}
152
153impl<'de> Deserialize<'de> for PackageDefinition {
154    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
155    where
156        D: serde::Deserializer<'de>,
157    {
158        super::serde_document::deserialize_standalone_with(
159            deserializer,
160            super::serde_document::decode_package_definition,
161        )
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::super::attributes::ValueAttributes;
168    use super::*;
169    use crate::ir::v4::module::Documentation;
170    use crate::ir::v4::types::{ConstructorArg, ConstructorDefinition, Incompleteness};
171    use crate::ir::v4::value::{Value, ValueBody};
172    use crate::naming::Name;
173
174    fn attrs() -> super::super::attributes::TypeAttributes {
175        super::super::attributes::TypeAttributes::default()
176    }
177
178    fn unit_type() -> super::super::types::Type {
179        super::super::types::Type::unit(attrs())
180    }
181
182    fn public<T>(value: T) -> AccessControlled<T> {
183        AccessControlled {
184            access: Access::Public,
185            value,
186        }
187    }
188
189    fn private<T>(value: T) -> AccessControlled<T> {
190        AccessControlled {
191            access: Access::Private,
192            value,
193        }
194    }
195
196    fn undocumented<T>(value: T) -> Documented<T> {
197        Documented::new(None, value)
198    }
199
200    fn documented<T>(doc: &str, value: T) -> Documented<T> {
201        Documented::new(Some(Documentation::from(doc)), value)
202    }
203
204    fn value_definition(output_type: Option<super::super::types::Type>) -> ValueDefinition {
205        ValueDefinition {
206            input_types: IndexMap::new(),
207            output_type,
208            body: ValueBody::Expression(Value::Unit(ValueAttributes::default())),
209        }
210    }
211
212    /// Builds a package with one private module (dropped entirely) and one public module whose
213    /// types and values cover every filtering and translation rule `to_specification` documents:
214    /// private members are dropped, a custom type with private constructors and an incomplete
215    /// type both become opaque, a public alias keeps its `doc`, and a value with no output type
216    /// is skipped.
217    fn sample_package() -> PackageDefinition {
218        let mut public_module_types = IndexMap::new();
219        public_module_types.insert(
220            "PrivType".to_string(),
221            private(undocumented(TypeDefinition::TypeAliasDefinition {
222                type_params: vec![],
223                type_expr: unit_type(),
224            })),
225        );
226        public_module_types.insert(
227            "OpaqueFromPrivateConstructors".to_string(),
228            public(undocumented(TypeDefinition::CustomTypeDefinition {
229                type_params: vec![],
230                constructors: private(vec![ConstructorDefinition {
231                    name: Name::from("hidden"),
232                    args: vec![ConstructorArg {
233                        name: Name::from("value"),
234                        arg_type: unit_type(),
235                    }],
236                }]),
237            })),
238        );
239        public_module_types.insert(
240            "Incomplete".to_string(),
241            public(undocumented(TypeDefinition::IncompleteTypeDefinition {
242                type_params: vec![],
243                incompleteness: Incompleteness::Draft,
244                partial_type_expr: None,
245            })),
246        );
247        public_module_types.insert(
248            "PublicAlias".to_string(),
249            public(documented(
250                "An alias for unit.",
251                TypeDefinition::TypeAliasDefinition {
252                    type_params: vec![],
253                    type_expr: unit_type(),
254                },
255            )),
256        );
257
258        let mut public_module_values = IndexMap::new();
259        public_module_values.insert(
260            "privValue".to_string(),
261            private(undocumented(value_definition(Some(unit_type())))),
262        );
263        public_module_values.insert(
264            "noOutputYet".to_string(),
265            public(undocumented(value_definition(None))),
266        );
267        public_module_values.insert(
268            "publicValue".to_string(),
269            public(undocumented(value_definition(Some(unit_type())))),
270        );
271
272        let mut modules = IndexMap::new();
273        modules.insert(
274            "PrivateModule".to_string(),
275            private(ModuleDefinition {
276                types: IndexMap::new(),
277                values: IndexMap::new(),
278                doc: None,
279            }),
280        );
281        modules.insert(
282            "PublicModule".to_string(),
283            public(ModuleDefinition {
284                types: public_module_types,
285                values: public_module_values,
286                doc: None,
287            }),
288        );
289
290        PackageDefinition { modules }
291    }
292
293    #[test]
294    fn to_specification_pins_every_filtering_and_translation_rule() {
295        let specification = sample_package().to_specification();
296
297        // A private module has no public face at all, so it does not appear.
298        assert_eq!(specification.modules.len(), 1);
299        let module = specification.modules.get("PublicModule").unwrap();
300
301        // Order is preserved: types and values keep the order they were declared in, and the
302        // private type/value is dropped rather than merely hidden.
303        assert_eq!(
304            module.types.keys().collect::<Vec<_>>(),
305            vec!["OpaqueFromPrivateConstructors", "Incomplete", "PublicAlias"]
306        );
307        assert_eq!(
308            module.values.keys().collect::<Vec<_>>(),
309            vec!["publicValue"]
310        );
311
312        // A custom type whose constructors are private has nothing public to publish, so it
313        // becomes opaque.
314        match &module
315            .types
316            .get("OpaqueFromPrivateConstructors")
317            .unwrap()
318            .value
319        {
320            TypeSpecification::OpaqueTypeSpecification { annotations, .. } => {
321                assert!(annotations.is_empty());
322            }
323            other => panic!("expected an opaque specification, got {other:?}"),
324        }
325
326        // An incomplete type definition has no shape to publish either, so it is also opaque.
327        match &module.types.get("Incomplete").unwrap().value {
328            TypeSpecification::OpaqueTypeSpecification { .. } => {}
329            other => panic!("expected an opaque specification, got {other:?}"),
330        }
331
332        // A public alias becomes an alias specification, carrying the doc from the definition.
333        let alias = module.types.get("PublicAlias").unwrap();
334        assert_eq!(
335            alias.doc.as_ref().map(Documentation::text),
336            Some("An alias for unit.")
337        );
338        match &alias.value {
339            TypeSpecification::TypeAliasSpecification { annotations, .. } => {
340                assert!(annotations.is_empty());
341            }
342            other => panic!("expected an alias specification, got {other:?}"),
343        }
344
345        // A value definition with no output type is skipped rather than refused.
346        assert!(!module.values.contains_key("noOutputYet"));
347        assert!(!module.values.contains_key("privValue"));
348
349        // A definition carries no annotations, so nothing derived from one has any either.
350        assert!(module.annotations.is_empty());
351        assert_eq!(
352            module.values.get("publicValue").unwrap().value.annotations,
353            Vec::new()
354        );
355    }
356}