diff --git a/CHANGELOG.md b/CHANGELOG.md index 561c3364..259a5aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - **aiken**: New `-S` flag on `check` and `build` that blocks the printing of warnings but it still shows the total warning count. @rvcas - **aiken-lang**: Allow types to be used as namespaces for constructors. Importing each constructor variants independently is no longer required in neither pattern-matches nor value construction. One can simply use the type name as a prefix/namespace now. @KtorZ +- **aiken-lang**: Allow capture on constructor calls. @KtorZ ### Changed @@ -14,12 +15,10 @@ - **aiken-lang**: Disallow (parse error) dangling colon `:` in traces. See [#1113](https://github.com/aiken-lang/aiken/issues/1113). @KtorZ - **aiken-lang**: Fix `aiken blueprint apply` wrongly overriding all validators handlers names & ABI to the mint's one. See [#1099](https://github.com/aiken-lang/aiken/issues/1099). @KtorZ - **aiken-lang**: Always type-check trace label irrespective of the trace level, to avoid unnecessary warnings in compact or silent mode. See [#1122](https://github.com/aiken-lang/aiken/issues/1122). @KtorZ - -### Fixed - - **aiken-lang**: Formatter was removing comments from function type annotation args @rvcas - **aiken-lang**: Parser wrongly merged two adjacent sequences together, effectively fusioning scopes. @KtorZ - **aiken-lang**: Fix hint when suggesting to use named fields, wrongly suggesting args in lexicographical order instead of definition order. @KtorZ +- **aiken-project**: Better errors on `blueprint apply` when matching multiple or no validators. See [#1127](https://github.com/aiken-lang/aiken/issues/1127) @KtorZ ## v1.1.13 - 2025-02-26 diff --git a/crates/aiken-lang/src/parser/expr/record.rs b/crates/aiken-lang/src/parser/expr/record.rs index 2e654f69..02f53369 100644 --- a/crates/aiken-lang/src/parser/expr/record.rs +++ b/crates/aiken-lang/src/parser/expr/record.rs @@ -29,24 +29,16 @@ pub fn parser( .then_ignore(just(Token::Colon)) .then(choice(( r.clone(), - select! {Token::DiscardName {name} => name }.validate( - |_name, span, emit| { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Discard), - )); - - UntypedExpr::Var { - location: span, - name: ast::CAPTURE_VARIABLE.to_string(), - } + choice((select! {Token::DiscardName {name} => name }.map_with_span( + |name, span| UntypedExpr::Var { + location: span, + name, }, - ), + ),)), ))) .map_with_span(|(label, value), span| ast::CallArg { location: span, - value, + value: Some(value), label: Some(label), }), choice(( @@ -111,7 +103,7 @@ pub fn parser( ) .map(|(value, name)| ast::CallArg { location: value.location(), - value, + value: Some(value), label: Some(name), }), )) @@ -144,24 +136,16 @@ pub fn parser( .or_not() .then(choice(( r.clone(), - select! {Token::DiscardName {name} => name }.validate( - |_name, span, emit| { - emit(ParseError::expected_input_found( - span, - None, - Some(error::Pattern::Discard), - )); - - UntypedExpr::Var { - location: span, - name: ast::CAPTURE_VARIABLE.to_string(), - } - }, - ), + select! {Token::DiscardName {name} => name }.map_with_span(|name, span| { + UntypedExpr::Var { + location: span, + name, + } + }), ))) .map(|(_label, value)| ast::CallArg { location: value.location(), - value, + value: Some(value), label: None, }) .separated_by(just(Token::Comma)) @@ -208,11 +192,7 @@ pub fn parser( }, }; - UntypedExpr::Call { - arguments, - fun: Box::new(fun), - location: span, - } + fun.call(arguments, span) }, ) } diff --git a/crates/aiken-project/src/blueprint/mod.rs b/crates/aiken-project/src/blueprint/mod.rs index 40219199..33bf6cf9 100644 --- a/crates/aiken-project/src/blueprint/mod.rs +++ b/crates/aiken-project/src/blueprint/mod.rs @@ -13,7 +13,7 @@ use aiken_lang::gen_uplc::CodeGenerator; use definitions::Definitions; pub use error::Error; use schema::{Annotated, Schema}; -use std::fmt::Debug; +use std::{collections::BTreeSet, fmt::Debug}; use validator::Validator; #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] @@ -99,15 +99,7 @@ impl Blueprint { let mut validator = None; for v in self.validators.iter() { - let mut split = v.title.split('.'); - - let known_module_name = split - .next() - .expect("validator's name must have two dot-separated components."); - - let known_validator_name = split - .next() - .expect("validator's name must have two dot-separated components."); + let (known_module_name, known_validator_name) = v.get_module_and_name(); let is_target = match (want_module_name, want_validator_name) { (None, None) => true, @@ -143,8 +135,8 @@ impl Blueprint { &self, module_name: Option<&str>, validator_name: Option<&str>, - when_too_many: fn(Vec) -> E, - when_missing: fn(Vec) -> E, + when_too_many: fn(BTreeSet<(String, String, bool)>) -> E, + when_missing: fn(BTreeSet<(String, String, bool)>) -> E, action: F, ) -> Result where @@ -153,10 +145,35 @@ impl Blueprint { match self.lookup(module_name, validator_name) { Some(LookupResult::One(_, validator)) => action(validator), Some(LookupResult::Many) => Err(when_too_many( - self.validators.iter().map(|v| v.title.clone()).collect(), + self.validators + .iter() + .filter_map(|v| { + let (l, r) = v.get_module_and_name(); + + if let Some(module_name) = module_name { + if l != module_name { + return None; + } + } + + if let Some(validator_name) = validator_name { + if r != validator_name { + return None; + } + } + + Some((l.to_string(), r.to_string(), !v.parameters.is_empty())) + }) + .collect(), )), None => Err(when_missing( - self.validators.iter().map(|v| v.title.clone()).collect(), + self.validators + .iter() + .map(|v| { + let (l, r) = v.get_module_and_name(); + (l.to_string(), r.to_string(), !v.parameters.is_empty()) + }) + .collect(), )), } } diff --git a/crates/aiken-project/src/blueprint/validator.rs b/crates/aiken-project/src/blueprint/validator.rs index 36b476b8..6a9a22d1 100644 --- a/crates/aiken-project/src/blueprint/validator.rs +++ b/crates/aiken-project/src/blueprint/validator.rs @@ -46,6 +46,20 @@ pub struct Validator { } impl Validator { + pub fn get_module_and_name(&self) -> (&str, &str) { + let mut split = self.title.split('.'); + + let known_module_name = split + .next() + .expect("validator's name must have two dot-separated components."); + + let known_validator_name = split + .next() + .expect("validator's name must have two dot-separated components."); + + (known_module_name, known_validator_name) + } + pub fn from_checked_module( modules: &CheckedModules, generator: &mut CodeGenerator, diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index 0180e831..0098ca0d 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -15,6 +15,7 @@ use owo_colors::{ Stream::{Stderr, Stdout}, }; use std::{ + collections::BTreeSet, fmt::{self, Debug, Display}, io, path::{Path, PathBuf}, @@ -137,10 +138,14 @@ pub enum Error { }, #[error("I didn't find any validator matching your criteria.")] - NoValidatorNotFound { known_validators: Vec }, + NoValidatorNotFound { + known_validators: BTreeSet<(String, String, bool)>, + }, #[error("I found multiple suitable validators and I need you to tell me which one to pick.")] - MoreThanOneValidatorFound { known_validators: Vec }, + MoreThanOneValidatorFound { + known_validators: BTreeSet<(String, String, bool)>, + }, #[error("I couldn't find any exportable function named '{name}' in module '{module}'.")] ExportNotFound { module: String, name: String }, @@ -423,27 +428,13 @@ impl Diagnostic for Error { None => String::new(), } ))), - Error::NoValidatorNotFound { known_validators } => Some(Box::new(format!( - "Here's a list of all validators I've found in your project. Please double-check this list against the options that you've provided:\n\n{}", - known_validators - .iter() - .map(|title| format!( - "→ {title}", - title = title.if_supports_color(Stdout, |s| s.purple()) - )) - .collect::>() - .join("\n") + Error::NoValidatorNotFound { known_validators } => Some(Box::new(hint_validators( + known_validators, + "Here's a list of all validators I've found in your project.\nPlease double-check this list against the options that you've provided." ))), - Error::MoreThanOneValidatorFound { known_validators } => Some(Box::new(format!( - "Here's a list of all validators I've found in your project. Select one of them using the appropriate options:\n\n{}", - known_validators - .iter() - .map(|title| format!( - "→ {title}", - title = title.if_supports_color(Stdout, |s| s.purple()) - )) - .collect::>() - .join("\n") + Error::MoreThanOneValidatorFound { known_validators } => Some(Box::new(hint_validators( + known_validators, + "Here's a list of matching validators I've found in your project.\nPlease narrow the selection using additional options.", ))), Error::Module(e) => e.help(), } @@ -809,3 +800,53 @@ fn default_miette_handler(context_lines: usize) -> MietteHandler { .context_lines(context_lines) .build() } + +fn hint_validators(known_validators: &BTreeSet<(String, String, bool)>, hint: &str) -> String { + let (pad_module, pad_validator) = known_validators.iter().fold( + (9, 12), + |(module_len, validator_len), (module, validator, _)| { + ( + module_len.max(module.len()), + validator_len.max(validator.len()), + ) + }, + ); + + format!( + "{hint}\n\n\ + {:pad_module$} . {:>() + .join("\n") + }, + bold_green = "bold green" + .if_supports_color(Stderr, |s| s.bold()) + .if_supports_color(Stderr, |s| s.green()), + has_params = "can take parameters" + .if_supports_color(Stderr, |s| s.bold()) + .if_supports_color(Stderr, |s| s.green()), + ) +} diff --git a/examples/acceptance_tests/121/aiken.toml b/examples/acceptance_tests/121/aiken.toml new file mode 100644 index 00000000..4f7f7fee --- /dev/null +++ b/examples/acceptance_tests/121/aiken.toml @@ -0,0 +1,9 @@ +name = "aiken-lang/121" +version = "0.0.0" +license = "Apache-2.0" +description = "Aiken contracts for project 'aiken-lang/121'" + +[repository] +user = "aiken-lang" +project = "121" +platform = "github" diff --git a/examples/acceptance_tests/121/lib/tests.ak b/examples/acceptance_tests/121/lib/tests.ak new file mode 100644 index 00000000..a69da0f1 --- /dev/null +++ b/examples/acceptance_tests/121/lib/tests.ak @@ -0,0 +1,34 @@ +pub type Foo { + i: Int, + b: Bool, +} + +const give_i: fn(Int) -> Foo = Foo { i: _, b: True } + +const give_b: fn(Bool) -> Foo = Foo(1337, _) + +fn foo_i(i: Int) -> fn(Bool) -> Foo { + Foo { i: i, b: _bool } +} + +fn foo_b(b: Bool) -> fn(Int) -> Foo { + Foo(_, b) +} + +test test_1() { + let bar = foo_i(14) + and { + foo_i(42)(True) == Foo(42, True), + bar(False) == Foo { i: 14, b: False }, + give_i(1337) == Foo { i: 1337, b: True }, + } +} + +test test_2() { + let bar = foo_b(False) + and { + foo_b(True)(42) == Foo(42, True), + bar(14) == Foo { i: 14, b: False }, + give_b(False) == Foo { i: 1337, b: False }, + } +}