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}