From 0838d48f7c49b465db9c9bd283dc901b4f5f2ade Mon Sep 17 00:00:00 2001 From: KtorZ Date: Wed, 22 Feb 2023 11:51:32 +0100 Subject: [PATCH] Move module name validation outside of type-checking And disable it for documentation generation. This way, we can generate documentation for aiken/builtins and aiken (prelude) --- crates/aiken-lang/src/ast.rs | 74 ++++++++++++++++++++ crates/aiken-lang/src/tipo/error.rs | 11 --- crates/aiken-lang/src/tipo/infer.rs | 48 ------------- crates/aiken-project/src/blueprint/schema.rs | 2 +- crates/aiken-project/src/error.rs | 13 +++- crates/aiken-project/src/lib.rs | 11 ++- 6 files changed, 95 insertions(+), 64 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index bcfb7545..933fabbe 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -3,8 +3,11 @@ use std::{fmt, ops::Range, sync::Arc}; use crate::{ builtins::{self, bool}, expr::{TypedExpr, UntypedExpr}, + parser::token::Token, tipo::{PatternConstructor, Type, TypeInfo}, }; +use miette::Diagnostic; +use owo_colors::OwoColorize; pub const ASSERT_VARIABLE: &str = "_try"; pub const CAPTURE_VARIABLE: &str = "_capture"; @@ -72,6 +75,51 @@ impl TypedModule { .iter() .find_map(|definition| definition.find_node(byte_index)) } + + pub fn validate_module_name(&self) -> Result<(), Error> { + if self.name == "aiken" || self.name == "aiken/builtin" { + return Err(Error::ReservedModuleName { + name: self.name.to_string(), + }); + }; + + for segment in self.name.split('/') { + if str_to_keyword(segment).is_some() { + return Err(Error::KeywordInModuleName { + name: self.name.to_string(), + keyword: segment.to_string(), + }); + } + } + + Ok(()) + } +} + +fn str_to_keyword(word: &str) -> Option { + // Alphabetical keywords: + match word { + "assert" => Some(Token::Expect), + "expect" => Some(Token::Expect), + "else" => Some(Token::Else), + "is" => Some(Token::Is), + "as" => Some(Token::As), + "when" => Some(Token::When), + "const" => Some(Token::Const), + "fn" => Some(Token::Fn), + "if" => Some(Token::If), + "use" => Some(Token::Use), + "let" => Some(Token::Let), + "opaque" => Some(Token::Opaque), + "pub" => Some(Token::Pub), + "todo" => Some(Token::Todo), + "type" => Some(Token::Type), + "trace" => Some(Token::Trace), + "test" => Some(Token::Test), + "error" => Some(Token::ErrorTerm), + "validator" => Some(Token::Validator), + _ => None, + } } pub type TypedFunction = Function, TypedExpr>; @@ -1137,3 +1185,29 @@ impl chumsky::Span for Span { self.end } } + +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum Error { + #[error( + "I realized the module '{}' contains the keyword '{}', which is forbidden.\n", + name.purple(), + keyword.purple() + )] + #[diagnostic(url("https://aiken-lang.org/language-tour/modules"))] + #[diagnostic(code("illegal::module_name"))] + #[diagnostic(help(r#"You cannot use keywords as part of a module path name. As a quick reminder, here's a list of all the keywords (and thus, of invalid module path names): + + as, expect, check, const, else, fn, if, is, let, opaque, pub, test, todo, trace, type, use, when"#))] + KeywordInModuleName { name: String, keyword: String }, + + #[error("I realized you used '{}' as a module name, which is reserved (and not available).\n", name.purple())] + #[diagnostic(code("illegal::module_name"))] + #[diagnostic(help(r#"Some module names are reserved for internal use. This the case of: + +- aiken: where the prelude is located; +- aiken/builtin: where I store low-level Plutus builtins. + +Note that 'aiken' is also imported by default; but you can refer to it explicitly to disambiguate with a local value that would clash with one from that module."# + ))] + ReservedModuleName { name: String }, +} diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index f39328ac..4bb87529 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -521,17 +521,6 @@ You can help me by providing a type-annotation for 'x', as such: location: Span, }, - #[error("I realized you used '{}' as a module name, which is reserved (and not available).\n", name.purple())] - #[diagnostic(code("illegal::module_name"))] - #[diagnostic(help(r#"Some module names are reserved for internal use. This the case of: - -- aiken: where the prelude is located; -- aiken/builtin: where I store low-level Plutus builtins. - -Note that 'aiken' is also imported by default; but you can refer to it explicitly to disambiguate with a local value that would clash with one from that module."# - ))] - ReservedModuleName { name: String }, - #[error( "I discovered an attempt to access the {} element of a {}-tuple.\n", Ordinal(*index + 1).to_string().purple(), diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index df8f3a29..53a395c6 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -8,7 +8,6 @@ use crate::{ }, builtins, builtins::function, - parser::token::Token, IdGenerator, }; @@ -36,8 +35,6 @@ impl UntypedModule { let docs = std::mem::take(&mut self.docs); let mut environment = Environment::new(id_gen.clone(), &name, modules, warnings); - validate_module_name(&name)?; - let mut type_names = HashMap::with_capacity(self.definitions.len()); let mut value_names = HashMap::with_capacity(self.definitions.len()); let mut hydrators = HashMap::with_capacity(self.definitions.len()); @@ -549,48 +546,3 @@ fn infer_definition( } } } - -fn validate_module_name(name: &str) -> Result<(), Error> { - if name == "aiken" || name == "aiken/builtin" { - return Err(Error::ReservedModuleName { - name: name.to_string(), - }); - }; - - for segment in name.split('/') { - if str_to_keyword(segment).is_some() { - return Err(Error::KeywordInModuleName { - name: name.to_string(), - keyword: segment.to_string(), - }); - } - } - - Ok(()) -} - -fn str_to_keyword(word: &str) -> Option { - // Alphabetical keywords: - match word { - "assert" => Some(Token::Expect), - "expect" => Some(Token::Expect), - "else" => Some(Token::Else), - "is" => Some(Token::Is), - "as" => Some(Token::As), - "when" => Some(Token::When), - "const" => Some(Token::Const), - "fn" => Some(Token::Fn), - "if" => Some(Token::If), - "use" => Some(Token::Use), - "let" => Some(Token::Let), - "opaque" => Some(Token::Opaque), - "pub" => Some(Token::Pub), - "todo" => Some(Token::Todo), - "type" => Some(Token::Type), - "trace" => Some(Token::Trace), - "test" => Some(Token::Test), - "error" => Some(Token::ErrorTerm), - "validator" => Some(Token::Validator), - _ => None, - } -} diff --git a/crates/aiken-project/src/blueprint/schema.rs b/crates/aiken-project/src/blueprint/schema.rs index af03eb7f..aca47dbd 100644 --- a/crates/aiken-project/src/blueprint/schema.rs +++ b/crates/aiken-project/src/blueprint/schema.rs @@ -277,7 +277,7 @@ impl Annotated { Ok(Annotated { title: Some("Tuple".to_owned()), description: None, - annotated: Schema::Data(Some(Data::List(Items::Many(elems)))).into(), + annotated: Schema::Data(Some(Data::List(Items::Many(elems)))), }) } }, diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index 1b6c1ca1..18d4bedf 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -3,7 +3,7 @@ use crate::{ script::EvalHint, }; use aiken_lang::{ - ast::{BinOp, Span}, + ast::{self, BinOp, Span}, parser::error::ParseError, tipo, }; @@ -54,6 +54,9 @@ pub enum Error { #[error(transparent)] Json(#[from] serde_json::Error), + #[error(transparent)] + Module(#[from] ast::Error), + #[error("{help}")] TomlLoading { path: PathBuf, @@ -189,6 +192,7 @@ impl GetSource for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::Module { .. } => None, } } @@ -213,6 +217,7 @@ impl GetSource for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::Module { .. } => None, } } } @@ -246,6 +251,7 @@ impl Diagnostic for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::Module(e) => e.code(), } } @@ -317,6 +323,7 @@ impl Diagnostic for Error { known_validators.iter().map(|title| format!("→ {title}", title = title.purple().bold())).collect::>().join("\n") ))) }, + Error::Module(e) => e.help(), } } @@ -349,6 +356,7 @@ impl Diagnostic for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::Module(e) => e.labels(), } } @@ -373,6 +381,7 @@ impl Diagnostic for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::Module(e) => e.source_code(), } } @@ -397,6 +406,7 @@ impl Diagnostic for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::Module(e) => e.url(), } } @@ -421,6 +431,7 @@ impl Diagnostic for Error { Error::MalformedStakeAddress { .. } => None, Error::NoValidatorNotFound { .. } => None, Error::MoreThanOneValidatorFound { .. } => None, + Error::Module(e) => e.related(), } } } diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index e672d287..354263ad 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -166,7 +166,7 @@ where let parsed_modules = self.parse_sources(self.config.name.clone())?; - self.type_check(parsed_modules, Tracing::NoTraces)?; + self.type_check(parsed_modules, Tracing::NoTraces, false)?; self.event_listener.handle_event(Event::GeneratingDocFiles { output_path: destination.clone(), @@ -247,7 +247,7 @@ where let parsed_modules = self.parse_sources(self.config.name.clone())?; - self.type_check(parsed_modules, options.tracing)?; + self.type_check(parsed_modules, options.tracing, true)?; match options.code_gen_mode { CodeGenMode::Build(uplc_dump) => { @@ -432,7 +432,7 @@ where let parsed_modules = self.parse_sources(package.name)?; - self.type_check(parsed_modules, Tracing::NoTraces)?; + self.type_check(parsed_modules, Tracing::NoTraces, true)?; } Ok(()) @@ -518,6 +518,7 @@ where &mut self, mut parsed_modules: ParsedModules, tracing: Tracing, + validate_module_name: bool, ) -> Result<(), Error> { let processing_sequence = parsed_modules.sequence()?; @@ -550,6 +551,10 @@ where error, })?; + if validate_module_name { + ast.validate_module_name()?; + } + // Register any warnings emitted as type warnings let type_warnings = type_warnings .into_iter()