diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index ac297986..885c2576 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -28,6 +28,7 @@ pub type UntypedModule = Module<(), UntypedDefinition>; pub enum ModuleKind { Lib, Validator, + Env, } impl ModuleKind { @@ -38,6 +39,10 @@ impl ModuleKind { pub fn is_lib(&self) -> bool { matches!(self, ModuleKind::Lib) } + + pub fn is_env(&self) -> bool { + matches!(self, ModuleKind::Env) + } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index 81753bf4..564afa19 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -785,7 +785,9 @@ impl<'a> Environment<'a> { })?; if module_info.kind.is_validator() - && (self.current_kind.is_lib() || !self.current_module.starts_with("tests")) + && (self.current_kind.is_lib() + || self.current_kind.is_env() + || !self.current_module.starts_with("tests")) { return Err(Error::ValidatorImported { location: *location, diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index e6f890c7..c544eb14 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -127,6 +127,9 @@ pub enum Error { #[error("I couldn't find any exportable function named '{name}' in module '{module}'.")] ExportNotFound { module: String, name: String }, + + #[error("I located conditional modules under 'env', but no default one!")] + NoDefaultEnvironment, } impl Error { @@ -195,6 +198,7 @@ impl ExtraData for Error { | Error::NoValidatorNotFound { .. } | Error::MoreThanOneValidatorFound { .. } | Error::Module { .. } + | Error::NoDefaultEnvironment { .. } | Error::ExportNotFound { .. } => None, Error::Type { error, .. } => error.extra_data(), } @@ -224,6 +228,7 @@ impl GetSource for Error { | Error::NoValidatorNotFound { .. } | Error::MoreThanOneValidatorFound { .. } | Error::ExportNotFound { .. } + | Error::NoDefaultEnvironment { .. } | Error::Module { .. } => None, Error::DuplicateModule { second: path, .. } | Error::MissingManifest { path } @@ -252,6 +257,7 @@ impl GetSource for Error { | Error::Json { .. } | Error::MalformedStakeAddress { .. } | Error::NoValidatorNotFound { .. } + | Error::NoDefaultEnvironment { .. } | Error::MoreThanOneValidatorFound { .. } | Error::ExportNotFound { .. } | Error::Module { .. } => None, @@ -307,6 +313,7 @@ impl Diagnostic for Error { Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, Error::ExportNotFound { .. } => None, + Error::NoDefaultEnvironment { .. } => None, Error::Module(e) => e.code().map(boxed), } } @@ -330,6 +337,9 @@ impl Diagnostic for Error { Error::MissingManifest { .. } => Some(Box::new( "Try running `aiken new ` to initialise a project with an example manifest.", )), + Error::NoDefaultEnvironment { .. } => Some(Box::new( + "Environment module names are free, but there must be at least one named 'default.ak'.", + )), Error::TomlLoading { .. } => None, Error::Format { .. } => None, Error::TestFailure { .. } => None, @@ -408,6 +418,7 @@ impl Diagnostic for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::NoDefaultEnvironment { .. } => None, Error::Module(e) => e.labels(), } } @@ -419,6 +430,7 @@ impl Diagnostic for Error { Error::ImportCycle { .. } => None, Error::ExportNotFound { .. } => None, Error::Blueprint(e) => e.source_code(), + Error::NoDefaultEnvironment { .. } => None, Error::Parse { named, .. } => Some(named), Error::Type { named, .. } => Some(named), Error::StandardIo(_) => None, @@ -462,6 +474,7 @@ impl Diagnostic for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::NoDefaultEnvironment { .. } => None, Error::Module(e) => e.url(), } } @@ -476,6 +489,7 @@ impl Diagnostic for Error { Error::Parse { .. } => None, Error::Type { error, .. } => error.related(), Error::StandardIo(_) => None, + Error::NoDefaultEnvironment { .. } => None, Error::MissingManifest { .. } => None, Error::TomlLoading { .. } => None, Error::Format { .. } => None, diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index f9200e6c..2af5f0eb 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -65,6 +65,8 @@ use uplc::{ PlutusData, }; +const DEFAULT_ENV_MODULE: &str = "default"; + #[derive(Debug)] pub struct Source { pub path: PathBuf, @@ -611,11 +613,13 @@ where } fn read_source_files(&mut self) -> Result<(), Error> { + let env = self.root.join("env"); let lib = self.root.join("lib"); let validators = self.root.join("validators"); self.aiken_files(&validators, ModuleKind::Validator)?; self.aiken_files(&lib, ModuleKind::Lib)?; + self.aiken_files(&env, ModuleKind::Env)?; Ok(()) } @@ -879,12 +883,18 @@ where } fn aiken_files(&mut self, dir: &Path, kind: ModuleKind) -> Result<(), Error> { + let mut has_default = None; + walkdir::WalkDir::new(dir) .follow_links(true) .into_iter() .filter_map(Result::ok) .filter(|e| e.file_type().is_file()) .try_for_each(|d| { + if has_default.is_none() { + has_default = Some(false); + } + let path = d.into_path(); let keep = is_aiken_path(&path, dir); let ext = path.extension(); @@ -895,11 +905,20 @@ where } if keep { + if self.module_name(dir, &path).as_str() == DEFAULT_ENV_MODULE { + has_default = Some(true); + } self.add_module(path, dir, kind) } else { Ok(()) } - }) + })?; + + if kind == ModuleKind::Env && has_default == Some(false) { + return Err(Error::NoDefaultEnvironment); + } + + Ok(()) } fn add_module(&mut self, path: PathBuf, dir: &Path, kind: ModuleKind) -> Result<(), Error> {