From 4130e0f2c3dc3c3bdabf80c5894d35e1c42498a4 Mon Sep 17 00:00:00 2001 From: rvcas Date: Tue, 25 Oct 2022 21:00:28 -0400 Subject: [PATCH] feat: validate if scripts return Bool --- crates/lang/src/ast.rs | 18 +++---- crates/project/src/error.rs | 15 +++++- crates/project/src/lib.rs | 91 ++++++++++++++++++++++++++++++------ crates/project/src/module.rs | 43 ++++++++++++++++- 4 files changed, 143 insertions(+), 24 deletions(-) diff --git a/crates/lang/src/ast.rs b/crates/lang/src/ast.rs index f263043e..b284d24f 100644 --- a/crates/lang/src/ast.rs +++ b/crates/lang/src/ast.rs @@ -39,6 +39,16 @@ pub struct Module { pub kind: ModuleKind, } +impl Module { + pub fn definitions(&self) -> impl Iterator { + self.definitions.iter() + } + + pub fn into_definitions(self) -> impl Iterator { + self.definitions.into_iter() + } +} + impl UntypedModule { pub fn dependencies(&self) -> Vec<(String, Span)> { self.definitions() @@ -54,14 +64,6 @@ impl UntypedModule { }) .collect() } - - pub fn definitions(&self) -> impl Iterator { - self.definitions.iter() - } - - pub fn into_definitions(self) -> impl Iterator { - self.definitions.into_iter() - } } pub type TypedDefinition = Definition, TypedExpr, String, String>; diff --git a/crates/project/src/error.rs b/crates/project/src/error.rs index 287fb2ec..98783a7f 100644 --- a/crates/project/src/error.rs +++ b/crates/project/src/error.rs @@ -4,7 +4,7 @@ use std::{ path::{Path, PathBuf}, }; -use aiken_lang::{parser::error::ParseError, tipo}; +use aiken_lang::{error::ParseError, parser::ast::Span, tipo}; use miette::{Diagnostic, EyreContext, LabeledSpan, MietteHandlerOpts, RgbColors, SourceCode}; #[allow(dead_code)] @@ -50,6 +50,13 @@ pub enum Error { #[source] error: tipo::error::Error, }, + + #[error("validator functions must return Bool")] + ValidatorMustReturnBool { + path: PathBuf, + src: String, + location: Span, + }, } impl Error { @@ -143,6 +150,7 @@ impl Diagnostic for Error { Error::Type { .. } => Some(Box::new("aiken::typecheck")), Error::StandardIo(_) => None, Error::Format { .. } => None, + Error::ValidatorMustReturnBool { .. } => Some(Box::new("aiken::scripts")), } } @@ -163,6 +171,7 @@ impl Diagnostic for Error { Error::Type { error, .. } => error.help(), Error::StandardIo(_) => None, Error::Format { .. } => None, + Error::ValidatorMustReturnBool { .. } => Some(Box::new("try annotating the validator's return type with Bool")), } } @@ -176,6 +185,9 @@ impl Diagnostic for Error { Error::Type { error, .. } => error.labels(), Error::StandardIo(_) => None, Error::Format { .. } => None, + Error::ValidatorMustReturnBool { location, .. } => Some(Box::new( + vec![LabeledSpan::new_with_span(None, *location)].into_iter(), + )), } } @@ -189,6 +201,7 @@ impl Diagnostic for Error { Error::Type { src, .. } => Some(src), Error::StandardIo(_) => None, Error::Format { .. } => None, + Error::ValidatorMustReturnBool { src, .. } => Some(src), } } } diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 4b0b4c4c..2d8245f7 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -9,12 +9,17 @@ pub mod error; pub mod format; pub mod module; -use aiken_lang::{ast::ModuleKind, builtins, tipo::TypeInfo, IdGenerator}; +use aiken_lang::{ + ast::{Definition, ModuleKind}, + builtins, + tipo::{Type, TypeInfo}, + IdGenerator, +}; use crate::{ config::Config, error::{Error, Warning}, - module::{CheckedModule, ParsedModule, ParsedModules}, + module::{CheckedModule, CheckedModules, ParsedModule, ParsedModules}, }; #[derive(Debug)] @@ -25,6 +30,12 @@ pub struct Source { pub kind: ModuleKind, } +pub const SPEND: &str = "spend"; +pub const CERT: &str = "cert"; +pub const MINT: &str = "mint"; +pub const WITHDRAWL: &str = "withdrawl"; +pub const VALIDATOR_NAMES: [&str; 4] = [SPEND, CERT, MINT, WITHDRAWL]; + pub struct Project { config: Config, defined_modules: HashMap, @@ -70,7 +81,9 @@ impl Project { let processing_sequence = parsed_modules.sequence()?; - let _checked_modules = self.type_check(parsed_modules, processing_sequence)?; + let mut checked_modules = self.type_check(parsed_modules, processing_sequence)?; + + let scripts = self.validate_scripts(&mut checked_modules)?; Ok(()) } @@ -157,8 +170,8 @@ impl Project { &mut self, mut parsed_modules: ParsedModules, processing_sequence: Vec, - ) -> Result, Error> { - let mut modules = Vec::with_capacity(parsed_modules.len() + 1); + ) -> Result { + let mut modules = HashMap::with_capacity(parsed_modules.len() + 1); for name in processing_sequence { if let Some(ParsedModule { @@ -199,18 +212,68 @@ impl Project { self.module_types .insert(name.clone(), ast.type_info.clone()); - modules.push(CheckedModule { - kind, - // extra, - name, - code, - ast, - input_path: path, - }); + modules.insert( + name.clone(), + CheckedModule { + kind, + // extra, + name, + code, + ast, + input_path: path, + }, + ); } } - Ok(modules) + Ok(modules.into()) + } + + fn validate_scripts( + &self, + checked_modules: &mut CheckedModules, + ) -> Result, Error> { + let mut errors = Vec::new(); + let mut scripts = Vec::new(); + + for module in checked_modules.scripts() { + scripts.push(module.clone()); + + for def in module.ast.definitions() { + if let Definition::Fn { + arguments, + location, + name, + return_type, + .. + } = def + { + if VALIDATOR_NAMES.contains(&name.as_str()) { + // validators must return a Bool + if !return_type.is_bool() { + errors.push(Error::ValidatorMustReturnBool { + location: *location, + src: module.code.clone(), + path: module.input_path.clone(), + }) + } + + // depending on name, validate the minimum number of arguments + // if too low, push a new error on to errors + } + } + } + } + + if errors.is_empty() { + for script in &scripts { + checked_modules.remove(&script.name); + } + + Ok(scripts) + } else { + Err(Error::List(errors)) + } } fn aiken_files(&mut self, dir: &Path, kind: ModuleKind) -> Result<(), Error> { diff --git a/crates/project/src/module.rs b/crates/project/src/module.rs index 204b7e7f..467d363a 100644 --- a/crates/project/src/module.rs +++ b/crates/project/src/module.rs @@ -155,7 +155,7 @@ fn find_cycle( false } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CheckedModule { pub name: String, pub code: String, @@ -164,3 +164,44 @@ pub struct CheckedModule { pub ast: TypedModule, // pub extra: ModuleExtra, } + +#[derive(Debug, Clone)] +pub struct CheckedModules(HashMap); + +impl From> for CheckedModules { + fn from(checked_modules: HashMap) -> Self { + CheckedModules(checked_modules) + } +} + +impl From for HashMap { + fn from(checked_modules: CheckedModules) -> Self { + checked_modules.0 + } +} + +impl CheckedModules { + pub fn scripts(&self) -> impl Iterator { + self.0.values().filter(|module| module.kind.is_script()) + } + + pub fn into_scripts(self) -> impl Iterator { + self.0 + .into_values() + .filter(|module| module.kind.is_script()) + } +} + +impl Deref for CheckedModules { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for CheckedModules { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +}