Merge pull request #1129 from aiken-lang/blueprint-apply-better-errors

Provide better errors when failing to match validators in blueprint apply.
This commit is contained in:
Matthias Benkort 2025-03-21 10:40:20 +01:00 committed by GitHub
commit 37220241e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 39 deletions

View File

@ -14,12 +14,10 @@
- **aiken-lang**: Change default placeholder for `trace` to `Void` instead of `todo`. @KtorZ - **aiken-lang**: Change default placeholder for `trace` to `Void` instead of `todo`. @KtorZ
- **aiken-lang**: Disallow (parse error) dangling colon `:` in traces. See [#1113](https://github.com/aiken-lang/aiken/issues/1113). @KtorZ - **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**: 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
### Fixed
- **aiken-lang**: Formatter was removing comments from function type annotation args @rvcas - **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**: 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-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 ## v1.1.13 - 2025-02-26

View File

@ -13,7 +13,7 @@ use aiken_lang::gen_uplc::CodeGenerator;
use definitions::Definitions; use definitions::Definitions;
pub use error::Error; pub use error::Error;
use schema::{Annotated, Schema}; use schema::{Annotated, Schema};
use std::fmt::Debug; use std::{collections::BTreeSet, fmt::Debug};
use validator::Validator; use validator::Validator;
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
@ -99,15 +99,7 @@ impl Blueprint {
let mut validator = None; let mut validator = None;
for v in self.validators.iter() { for v in self.validators.iter() {
let mut split = v.title.split('.'); let (known_module_name, known_validator_name) = v.get_module_and_name();
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 is_target = match (want_module_name, want_validator_name) { let is_target = match (want_module_name, want_validator_name) {
(None, None) => true, (None, None) => true,
@ -143,8 +135,8 @@ impl Blueprint {
&self, &self,
module_name: Option<&str>, module_name: Option<&str>,
validator_name: Option<&str>, validator_name: Option<&str>,
when_too_many: fn(Vec<String>) -> E, when_too_many: fn(BTreeSet<(String, String, bool)>) -> E,
when_missing: fn(Vec<String>) -> E, when_missing: fn(BTreeSet<(String, String, bool)>) -> E,
action: F, action: F,
) -> Result<A, E> ) -> Result<A, E>
where where
@ -153,10 +145,35 @@ impl Blueprint {
match self.lookup(module_name, validator_name) { match self.lookup(module_name, validator_name) {
Some(LookupResult::One(_, validator)) => action(validator), Some(LookupResult::One(_, validator)) => action(validator),
Some(LookupResult::Many) => Err(when_too_many( 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( 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(),
)), )),
} }
} }

View File

@ -46,6 +46,20 @@ pub struct Validator {
} }
impl 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( pub fn from_checked_module(
modules: &CheckedModules, modules: &CheckedModules,
generator: &mut CodeGenerator, generator: &mut CodeGenerator,

View File

@ -15,6 +15,7 @@ use owo_colors::{
Stream::{Stderr, Stdout}, Stream::{Stderr, Stdout},
}; };
use std::{ use std::{
collections::BTreeSet,
fmt::{self, Debug, Display}, fmt::{self, Debug, Display},
io, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -137,10 +138,14 @@ pub enum Error {
}, },
#[error("I didn't find any validator matching your criteria.")] #[error("I didn't find any validator matching your criteria.")]
NoValidatorNotFound { known_validators: Vec<String> }, NoValidatorNotFound {
known_validators: BTreeSet<(String, String, bool)>,
},
#[error("I found multiple suitable validators and I need you to tell me which one to pick.")] #[error("I found multiple suitable validators and I need you to tell me which one to pick.")]
MoreThanOneValidatorFound { known_validators: Vec<String> }, MoreThanOneValidatorFound {
known_validators: BTreeSet<(String, String, bool)>,
},
#[error("I couldn't find any exportable function named '{name}' in module '{module}'.")] #[error("I couldn't find any exportable function named '{name}' in module '{module}'.")]
ExportNotFound { module: String, name: String }, ExportNotFound { module: String, name: String },
@ -423,27 +428,13 @@ impl Diagnostic for Error {
None => String::new(), None => String::new(),
} }
))), ))),
Error::NoValidatorNotFound { known_validators } => Some(Box::new(format!( Error::NoValidatorNotFound { known_validators } => Some(Box::new(hint_validators(
"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,
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."
.iter()
.map(|title| format!(
"→ {title}",
title = title.if_supports_color(Stdout, |s| s.purple())
))
.collect::<Vec<String>>()
.join("\n")
))), ))),
Error::MoreThanOneValidatorFound { known_validators } => Some(Box::new(format!( Error::MoreThanOneValidatorFound { known_validators } => Some(Box::new(hint_validators(
"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,
known_validators "Here's a list of matching validators I've found in your project.\nPlease narrow the selection using additional options.",
.iter()
.map(|title| format!(
"→ {title}",
title = title.if_supports_color(Stdout, |s| s.purple())
))
.collect::<Vec<String>>()
.join("\n")
))), ))),
Error::Module(e) => e.help(), Error::Module(e) => e.help(),
} }
@ -809,3 +800,53 @@ fn default_miette_handler(context_lines: usize) -> MietteHandler {
.context_lines(context_lines) .context_lines(context_lines)
.build() .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$} {:<pad_validator$}\n\
{:<pad_module$} {:<pad_validator$}\n\
{}\n\nFor convenience, I have highlighted in {bold_green} suitable candidates that {has_params}.",
"module(s)",
"validator(s)",
"",
"",
{
known_validators
.iter()
.map(|(module, validator, has_params)| {
let title = format!(
"{:>pad_module$} . {:<pad_validator$}",
module,
validator,
);
if *has_params {
title
.if_supports_color(Stderr, |s| s.bold())
.if_supports_color(Stderr, |s| s.green())
.to_string()
} else {
title
}
})
.collect::<Vec<String>>()
.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()),
)
}