From fbe2f8258282e20c135821410682c6caf73cf960 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sun, 4 Aug 2024 10:27:32 +0200 Subject: [PATCH] Thread down environment module from cli down to the type-checker We simply provide a flag with a free-form output which acts as the module to lookup in the 'env' folder. The strategy is to replace the environment module name on-the-fly when a user tries to import 'env'. If the environment isn't found, an 'UnknownModule' error is raised (which I will slightly adjust in a following commits to something more related to environment) There are few important consequences to this design which may not seem immediately obvious: 1. We parse and type-check every env modules, even if they aren't used. This ensures that code doesn't break with a compilation error simply because people forgot to type-check a given env. Note that compilation could still fail because the env module itself could provide an invalid API. So it only prevents each modules to be independently wrong when taken in isolation. 2. Technically, this also means that one can import env modules in other env modules by their names. I don't know if it's a good or bad idea at this point but it doesn't really do any wrong; dependencies and cycles are handlded all-the-same. --- crates/aiken-lang/src/ast.rs | 3 ++ crates/aiken-lang/src/lib.rs | 1 + crates/aiken-lang/src/tests/check.rs | 2 ++ crates/aiken-lang/src/tipo/environment.rs | 40 +++++++++++++++------- crates/aiken-lang/src/tipo/infer.rs | 17 +++------ crates/aiken-lsp/src/server/lsp_project.rs | 1 + crates/aiken-project/src/lib.rs | 22 ++++++++---- crates/aiken-project/src/module.rs | 2 ++ crates/aiken-project/src/options.rs | 2 ++ crates/aiken-project/src/test_framework.rs | 1 + crates/aiken-project/src/tests/mod.rs | 1 + crates/aiken/src/cmd/blueprint/address.rs | 10 ------ crates/aiken/src/cmd/blueprint/hash.rs | 10 ------ crates/aiken/src/cmd/blueprint/policy.rs | 10 ------ crates/aiken/src/cmd/build.rs | 7 ++++ crates/aiken/src/cmd/check.rs | 7 ++++ 16 files changed, 73 insertions(+), 63 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 885c2576..dae941c2 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -21,6 +21,9 @@ pub const BACKPASS_VARIABLE: &str = "_backpass"; pub const CAPTURE_VARIABLE: &str = "_capture"; pub const PIPE_VARIABLE: &str = "_pipe"; +pub const ENV_MODULE: &str = "env"; +pub const DEFAULT_ENV_MODULE: &str = "default"; + pub type TypedModule = Module; pub type UntypedModule = Module<(), UntypedDefinition>; diff --git a/crates/aiken-lang/src/lib.rs b/crates/aiken-lang/src/lib.rs index 2910c3f3..c0ed6894 100644 --- a/crates/aiken-lang/src/lib.rs +++ b/crates/aiken-lang/src/lib.rs @@ -49,6 +49,7 @@ macro_rules! aiken_fn { $module_types, $crate::ast::Tracing::silent(), &mut warnings, + None, ) .unwrap(); diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index ecf1c528..906240e7 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -38,6 +38,7 @@ fn check_module( &module_types, Tracing::All(TraceLevel::Verbose), &mut warnings, + None, ) .expect("extra dependency did not compile"); module_types.insert(package.clone(), typed_module.type_info.clone()); @@ -50,6 +51,7 @@ fn check_module( &module_types, tracing, &mut warnings, + None, ); result diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index 564afa19..4548bc7a 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -7,7 +7,7 @@ use super::{ }; use crate::{ ast::{ - Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, + self, Annotation, CallArg, DataType, Definition, Function, ModuleConstant, ModuleKind, RecordConstructor, RecordConstructorArg, Span, TypeAlias, TypedDefinition, TypedFunction, TypedPattern, UnqualifiedImport, UntypedArg, UntypedDefinition, UntypedFunction, Use, Validator, PIPE_VARIABLE, @@ -80,11 +80,33 @@ pub struct Environment<'a> { /// A mapping from known annotations to their resolved type. pub annotations: HashMap>, + /// The user-defined target environment referred to as the module 'env'. + pub target_env: Option<&'a str>, + /// Warnings pub warnings: &'a mut Vec, } impl<'a> Environment<'a> { + pub fn find_module(&self, fragments: &[String], location: Span) -> Result<&'a TypeInfo, Error> { + let mut name = fragments.join("/"); + + if name == ast::ENV_MODULE { + name = self + .target_env + .unwrap_or(ast::DEFAULT_ENV_MODULE) + .to_string() + } + + self.importable_modules + .get(&name) + .ok_or_else(|| Error::UnknownModule { + location, + name, + imported_modules: self.imported_modules.keys().cloned().collect(), + }) + } + pub fn close_scope(&mut self, data: ScopeResetData) { let unused = self .entity_usages @@ -705,6 +727,7 @@ impl<'a> Environment<'a> { current_kind: &'a ModuleKind, importable_modules: &'a HashMap, warnings: &'a mut Vec, + target_env: Option<&'a str>, ) -> Self { let prelude = importable_modules .get("aiken") @@ -731,6 +754,7 @@ impl<'a> Environment<'a> { annotations: HashMap::new(), warnings, entity_usages: vec![HashMap::new()], + target_env, } } @@ -772,17 +796,7 @@ impl<'a> Environment<'a> { location, package: _, }) => { - let name = module.join("/"); - - // Find imported module - let module_info = - self.importable_modules - .get(&name) - .ok_or_else(|| Error::UnknownModule { - location: *location, - name: name.clone(), - imported_modules: self.imported_modules.keys().cloned().collect(), - })?; + let module_info = self.find_module(module, *location)?; if module_info.kind.is_validator() && (self.current_kind.is_lib() @@ -791,7 +805,7 @@ impl<'a> Environment<'a> { { return Err(Error::ValidatorImported { location: *location, - name, + name: module.join("/"), }); } diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index b9a35b0e..8f9a37a6 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -19,6 +19,7 @@ use crate::{ use std::{borrow::Borrow, collections::HashMap, ops::Deref, rc::Rc}; impl UntypedModule { + #[allow(clippy::too_many_arguments)] pub fn infer( mut self, id_gen: &IdGenerator, @@ -27,11 +28,12 @@ impl UntypedModule { modules: &HashMap, tracing: Tracing, warnings: &mut Vec, + env: Option<&str>, ) -> Result { let module_name = self.name.clone(); let docs = std::mem::take(&mut self.docs); let mut environment = - Environment::new(id_gen.clone(), &module_name, &kind, modules, warnings); + Environment::new(id_gen.clone(), &module_name, &kind, modules, warnings, env); let mut type_names = HashMap::with_capacity(self.definitions.len()); let mut value_names = HashMap::with_capacity(self.definitions.len()); @@ -574,18 +576,7 @@ fn infer_definition( unqualified, package: _, }) => { - let name = module.join("/"); - - // Find imported module - let module_info = - environment - .importable_modules - .get(&name) - .ok_or_else(|| Error::UnknownModule { - location, - name, - imported_modules: environment.imported_modules.keys().cloned().collect(), - })?; + let module_info = environment.find_module(&module, location)?; Ok(Definition::Use(Use { location, diff --git a/crates/aiken-lsp/src/server/lsp_project.rs b/crates/aiken-lsp/src/server/lsp_project.rs index eda2d020..efd36ab6 100644 --- a/crates/aiken-lsp/src/server/lsp_project.rs +++ b/crates/aiken-lsp/src/server/lsp_project.rs @@ -40,6 +40,7 @@ impl LspProject { u32::default(), PropertyTest::DEFAULT_MAX_SUCCESS, Tracing::silent(), + None, ); self.project.restore(checkpoint); diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index 2af5f0eb..e3e7607c 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -32,7 +32,7 @@ use crate::{ }; use aiken_lang::{ ast::{ - DataTypeKey, Definition, FunctionAccessKey, ModuleKind, Tracing, TypedDataType, + self, DataTypeKey, Definition, FunctionAccessKey, ModuleKind, Tracing, TypedDataType, TypedFunction, }, builtins, @@ -65,8 +65,6 @@ use uplc::{ PlutusData, }; -const DEFAULT_ENV_MODULE: &str = "default"; - #[derive(Debug)] pub struct Source { pub path: PathBuf, @@ -186,10 +184,16 @@ where self.defined_modules = checkpoint.defined_modules; } - pub fn build(&mut self, uplc: bool, tracing: Tracing) -> Result<(), Vec> { + pub fn build( + &mut self, + uplc: bool, + tracing: Tracing, + env: Option, + ) -> Result<(), Vec> { let options = Options { code_gen_mode: CodeGenMode::Build(uplc), tracing, + env, }; self.compile(options) @@ -211,7 +215,7 @@ where let mut modules = self.parse_sources(self.config.name.clone())?; - self.type_check(&mut modules, Tracing::silent(), false)?; + self.type_check(&mut modules, Tracing::silent(), None, false)?; let destination = destination.unwrap_or_else(|| self.root.join("docs")); @@ -252,9 +256,11 @@ where seed: u32, property_max_success: usize, tracing: Tracing, + env: Option, ) -> Result<(), Vec> { let options = Options { tracing, + env, code_gen_mode: if skip_tests { CodeGenMode::NoOp } else { @@ -307,7 +313,7 @@ where let mut modules = self.parse_sources(self.config.name.clone())?; - self.type_check(&mut modules, options.tracing, true)?; + self.type_check(&mut modules, options.tracing, options.env.as_deref(), true)?; match options.code_gen_mode { CodeGenMode::Build(uplc_dump) => { @@ -725,6 +731,7 @@ where &mut self, modules: &mut ParsedModules, tracing: Tracing, + env: Option<&str>, validate_module_name: bool, ) -> Result<(), Vec> { let our_modules: BTreeSet = modules.keys().cloned().collect(); @@ -737,6 +744,7 @@ where &self.id_gen, &self.config.name.to_string(), tracing, + env, validate_module_name, &mut self.module_sources, &mut self.module_types, @@ -905,7 +913,7 @@ where } if keep { - if self.module_name(dir, &path).as_str() == DEFAULT_ENV_MODULE { + if self.module_name(dir, &path).as_str() == ast::DEFAULT_ENV_MODULE { has_default = Some(true); } self.add_module(path, dir, kind) diff --git a/crates/aiken-project/src/module.rs b/crates/aiken-project/src/module.rs index 49aad2a5..804d5c6b 100644 --- a/crates/aiken-project/src/module.rs +++ b/crates/aiken-project/src/module.rs @@ -51,6 +51,7 @@ impl ParsedModule { id_gen: &IdGenerator, package: &str, tracing: Tracing, + env: Option<&str>, validate_module_name: bool, module_sources: &mut HashMap, module_types: &mut HashMap, @@ -68,6 +69,7 @@ impl ParsedModule { module_types, tracing, &mut warnings, + env, ) .map_err(|error| Error::Type { path: self.path.clone(), diff --git a/crates/aiken-project/src/options.rs b/crates/aiken-project/src/options.rs index aa0a8d33..c1551706 100644 --- a/crates/aiken-project/src/options.rs +++ b/crates/aiken-project/src/options.rs @@ -3,6 +3,7 @@ use aiken_lang::ast::Tracing; pub struct Options { pub code_gen_mode: CodeGenMode, pub tracing: Tracing, + pub env: Option, } impl Default for Options { @@ -10,6 +11,7 @@ impl Default for Options { Self { code_gen_mode: CodeGenMode::NoOp, tracing: Tracing::silent(), + env: None, } } } diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index 2ca9e5ba..60e005c4 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -1314,6 +1314,7 @@ mod test { &module_types, Tracing::All(TraceLevel::Verbose), &mut warnings, + None, ) .expect("Failed to type-check module."); diff --git a/crates/aiken-project/src/tests/mod.rs b/crates/aiken-project/src/tests/mod.rs index d0967542..6039c89c 100644 --- a/crates/aiken-project/src/tests/mod.rs +++ b/crates/aiken-project/src/tests/mod.rs @@ -99,6 +99,7 @@ impl TestProject { &self.module_types, Tracing::All(TraceLevel::Verbose), &mut warnings, + None, ) .expect("Failed to type-check module"); diff --git a/crates/aiken/src/cmd/blueprint/address.rs b/crates/aiken/src/cmd/blueprint/address.rs index 285b30d3..df54879e 100644 --- a/crates/aiken/src/cmd/blueprint/address.rs +++ b/crates/aiken/src/cmd/blueprint/address.rs @@ -1,4 +1,3 @@ -use aiken_lang::ast::Tracing; use aiken_project::watch::with_project; use std::path::PathBuf; @@ -20,10 +19,6 @@ pub struct Args { #[clap(long)] delegated_to: Option, - /// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json) - #[clap(long)] - rebuild: bool, - /// Output the address for mainnet (this command defaults to testnet) #[clap(long)] mainnet: bool, @@ -35,15 +30,10 @@ pub fn exec( module, validator, delegated_to, - rebuild, mainnet, }: Args, ) -> miette::Result<()> { with_project(directory.as_deref(), false, |p| { - if rebuild { - p.build(false, Tracing::silent())?; - } - let title = module.as_ref().map(|m| { format!( "{m}{}", diff --git a/crates/aiken/src/cmd/blueprint/hash.rs b/crates/aiken/src/cmd/blueprint/hash.rs index ef67e702..c7370890 100644 --- a/crates/aiken/src/cmd/blueprint/hash.rs +++ b/crates/aiken/src/cmd/blueprint/hash.rs @@ -1,4 +1,3 @@ -use aiken_lang::ast::Tracing; use aiken_project::watch::with_project; use std::path::PathBuf; @@ -15,10 +14,6 @@ pub struct Args { /// Name of the validator within the module. Optional if there's only one validator #[clap(short, long)] validator: Option, - - /// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json) - #[clap(long)] - rebuild: bool, } pub fn exec( @@ -26,14 +21,9 @@ pub fn exec( directory, module, validator, - rebuild, }: Args, ) -> miette::Result<()> { with_project(directory.as_deref(), false, |p| { - if rebuild { - p.build(false, Tracing::silent())?; - } - let title = module.as_ref().map(|m| { format!( "{m}{}", diff --git a/crates/aiken/src/cmd/blueprint/policy.rs b/crates/aiken/src/cmd/blueprint/policy.rs index 80c90d4c..e23f118c 100644 --- a/crates/aiken/src/cmd/blueprint/policy.rs +++ b/crates/aiken/src/cmd/blueprint/policy.rs @@ -1,4 +1,3 @@ -use aiken_lang::ast::Tracing; use aiken_project::watch::with_project; use std::path::PathBuf; @@ -15,10 +14,6 @@ pub struct Args { /// Name of the validator within the module. Optional if there's only one validator #[clap(short, long)] validator: Option, - - /// Force the project to be rebuilt, otherwise relies on existing artifacts (i.e. plutus.json) - #[clap(long)] - rebuild: bool, } pub fn exec( @@ -26,14 +21,9 @@ pub fn exec( directory, module, validator, - rebuild, }: Args, ) -> miette::Result<()> { with_project(directory.as_deref(), false, |p| { - if rebuild { - p.build(false, Tracing::silent())?; - } - let title = module.as_ref().map(|m| { format!( "{m}{}", diff --git a/crates/aiken/src/cmd/build.rs b/crates/aiken/src/cmd/build.rs index a5b60499..fbe89fba 100644 --- a/crates/aiken/src/cmd/build.rs +++ b/crates/aiken/src/cmd/build.rs @@ -21,6 +21,10 @@ pub struct Args { #[clap(short, long)] uplc: bool, + /// Environment to build against. + #[clap(long)] + env: Option, + /// Filter traces to be included in the generated program(s). /// /// - user-defined: @@ -63,6 +67,7 @@ pub fn exec( uplc, filter_traces, trace_level, + env, }: Args, ) -> miette::Result<()> { let result = if watch { @@ -73,6 +78,7 @@ pub fn exec( Some(filter_traces) => filter_traces(trace_level), None => Tracing::All(trace_level), }, + env.clone(), ) }) } else { @@ -83,6 +89,7 @@ pub fn exec( Some(filter_traces) => filter_traces(trace_level), None => Tracing::All(trace_level), }, + env.clone(), ) }) }; diff --git a/crates/aiken/src/cmd/check.rs b/crates/aiken/src/cmd/check.rs index 92f89d3a..d83c437f 100644 --- a/crates/aiken/src/cmd/check.rs +++ b/crates/aiken/src/cmd/check.rs @@ -48,6 +48,10 @@ pub struct Args { #[clap(short, long)] exact_match: bool, + /// Environment to build against. + #[clap(long)] + env: Option, + /// Filter traces to be included in the generated program(s). /// /// - user-defined: @@ -95,6 +99,7 @@ pub fn exec( trace_level, seed, max_success, + env, }: Args, ) -> miette::Result<()> { let mut rng = rand::thread_rng(); @@ -114,6 +119,7 @@ pub fn exec( Some(filter_traces) => filter_traces(trace_level), None => Tracing::All(trace_level), }, + env.clone(), ) }) } else { @@ -129,6 +135,7 @@ pub fn exec( Some(filter_traces) => filter_traces(trace_level), None => Tracing::All(trace_level), }, + env.clone(), ) }) };