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(), ) }) };