nickel_lang_core/transform/mod.rs
1//! Program transformations.
2use crate::{
3 cache::ImportResolver,
4 term::RichTerm,
5 traverse::{Traverse, TraverseOrder},
6 typ::UnboundTypeVariableError,
7};
8
9pub mod desugar_destructuring;
10pub mod free_vars;
11pub mod gen_pending_contracts;
12pub mod import_resolution;
13pub mod substitute_wildcards;
14
15/// RFC007: we can't use yet the new `typecheck::Wildcards` type, as they follow the new AST. We
16/// temporarily redefine a `Wildcards` type that matches the old definition.
17pub(crate) type Wildcards = Vec<crate::typ::Type>;
18
19/// Apply all program transformations, excepted import resolution that is currently performed
20/// earlier, as it needs to be done before typechecking.
21///
22/// Do not perform transformations on the imported files. If needed, either do it yourself using
23/// pending imports returned by [`resolve_imports`][import_resolution::strict::resolve_imports] or
24/// use the [cache][crate::cache::CacheHub].
25pub fn transform(
26 mut rt: RichTerm,
27 wildcards: Option<&Wildcards>,
28) -> Result<RichTerm, UnboundTypeVariableError> {
29 free_vars::transform(&mut rt);
30 transform_no_free_vars(rt, wildcards)
31}
32
33/// Same as [`transform`], but doesn't apply the free vars transformation.
34pub fn transform_no_free_vars(
35 rt: RichTerm,
36 wildcards: Option<&Wildcards>,
37) -> Result<RichTerm, UnboundTypeVariableError> {
38 let rt = rt.traverse(
39 &mut |mut rt: RichTerm| -> Result<RichTerm, UnboundTypeVariableError> {
40 // Start by substituting any wildcard with its inferred type
41 if let Some(wildcards) = wildcards {
42 rt = substitute_wildcards::transform_one(rt, wildcards);
43 }
44 // We desugar destructuring before other transformations, as this step generates new
45 // record contracts and terms that must be themselves transformed.
46 let rt = desugar_destructuring::transform_one(rt);
47 Ok(rt)
48 },
49 TraverseOrder::TopDown,
50 )?;
51
52 Ok(rt
53 .traverse(
54 &mut |rt: RichTerm| -> Result<RichTerm, UnboundTypeVariableError> {
55 // `gen_pending_contracts` is applied bottom-up, because it might generate
56 // additional terms down the AST (pending contracts pushed down the fields of a
57 // record). In a top-down workflow, we would then visit those new duplicated nodes
58 // (already visited as part of transforming the metadata), and do that potentially
59 // again one level down. This results in a potentially non-linear cost in the size
60 // of the AST. This was witnessed on Terraform-Nickel, causing examples using huge
61 // auto-generated contracts (several of MBs) to not terminate in reasonable time.
62 let rt = gen_pending_contracts::transform_one(rt)?;
63 Ok(rt)
64 },
65 TraverseOrder::BottomUp,
66 )
67 .unwrap())
68}