show multiple errors and mark tick/load annotation incompatible with parameters

This commit is contained in:
Moritz Hölting 2024-11-11 23:19:36 +01:00
parent eb595bc28b
commit 7e96a43e5f
2 changed files with 101 additions and 24 deletions

View File

@ -29,6 +29,8 @@ pub enum Error {
InvalidNamespaceName(#[from] InvalidNamespaceName), InvalidNamespaceName(#[from] InvalidNamespaceName),
#[error(transparent)] #[error(transparent)]
UnresolvedMacroUsage(#[from] UnresolvedMacroUsage), UnresolvedMacroUsage(#[from] UnresolvedMacroUsage),
#[error(transparent)]
IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation),
} }
/// An error that occurs when a function declaration is missing. /// An error that occurs when a function declaration is missing.
@ -212,3 +214,30 @@ impl Display for UnresolvedMacroUsage {
} }
impl std::error::Error for UnresolvedMacroUsage {} impl std::error::Error for UnresolvedMacroUsage {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IncompatibleFunctionAnnotation {
pub span: Span,
pub reason: String,
}
impl Display for IncompatibleFunctionAnnotation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
Message::new(
Severity::Error,
format!(
"Annotation `{}` cannot be used here, because {}.",
self.span.str(),
self.reason
)
)
)?;
write!(f, "\n{}", SourceCodeDisplay::new(&self.span, None::<u8>))
}
}
impl std::error::Error for IncompatibleFunctionAnnotation {}

View File

@ -5,7 +5,8 @@
use std::collections::HashSet; use std::collections::HashSet;
use error::{ use error::{
InvalidNamespaceName, MissingFunctionDeclaration, UnexpectedExpression, UnresolvedMacroUsage, IncompatibleFunctionAnnotation, InvalidNamespaceName, MissingFunctionDeclaration,
UnexpectedExpression, UnresolvedMacroUsage,
}; };
use crate::{ use crate::{
@ -39,13 +40,21 @@ impl ProgramFile {
) -> Result<(), error::Error> { ) -> Result<(), error::Error> {
self.namespace().analyze_semantics(handler)?; self.namespace().analyze_semantics(handler)?;
let mut errs = Vec::new();
let function_names = extract_all_function_names(self.declarations(), handler)?; let function_names = extract_all_function_names(self.declarations(), handler)?;
for declaration in self.declarations() { for declaration in self.declarations() {
declaration.analyze_semantics(&function_names, handler)?; if let Err(err) = declaration.analyze_semantics(&function_names, handler) {
errs.push(err);
}
} }
Ok(()) #[expect(clippy::option_if_let_else)]
if let Some(err) = errs.first() {
Err(err.clone())
} else {
Ok(())
}
} }
} }
@ -143,11 +152,30 @@ impl Function {
function_names: &HashSet<String>, function_names: &HashSet<String>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Result<(), error::Error> { ) -> Result<(), error::Error> {
let macro_names = self let macro_names = if let Some(parameters) = self.parameters() {
.parameters() if let Some(incompatible) = self
.iter() .annotations()
.flat_map(|l| l.elements().map(|el| el.span.str().to_string())) .iter()
.collect(); .find(|a| ["tick", "load"].contains(&a.identifier().span.str()))
{
let err =
error::Error::IncompatibleFunctionAnnotation(IncompatibleFunctionAnnotation {
span: incompatible.identifier().span(),
reason:
"functions with the `tick` or `load` annotation cannot have parameters"
.to_string(),
});
handler.receive(err.clone());
return Err(err);
}
parameters
.elements()
.map(|el| el.span.str().to_string())
.collect()
} else {
HashSet::new()
};
self.block() self.block()
.analyze_semantics(function_names, &macro_names, handler) .analyze_semantics(function_names, &macro_names, handler)
@ -162,28 +190,34 @@ impl Block {
macro_names: &HashSet<String>, macro_names: &HashSet<String>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Result<(), error::Error> { ) -> Result<(), error::Error> {
let mut errs = Vec::new();
for statement in &self.statements { for statement in &self.statements {
match statement { if let Err(err) = match statement {
Statement::Block(block) => { Statement::Block(block) => {
block.analyze_semantics(function_names, macro_names, handler)?; block.analyze_semantics(function_names, macro_names, handler)
} }
Statement::DocComment(_) | Statement::LiteralCommand(_) => {} Statement::DocComment(_) | Statement::LiteralCommand(_) => Ok(()),
Statement::ExecuteBlock(ex) => { Statement::ExecuteBlock(ex) => {
ex.analyze_semantics(function_names, macro_names, handler)?; ex.analyze_semantics(function_names, macro_names, handler)
} }
Statement::Grouping(group) => { Statement::Grouping(group) => {
group.analyze_semantics(function_names, macro_names, handler)?; group.analyze_semantics(function_names, macro_names, handler)
}
Statement::Run(run) => {
run.analyze_semantics(function_names, macro_names, handler)?;
} }
Statement::Run(run) => run.analyze_semantics(function_names, macro_names, handler),
Statement::Semicolon(sem) => { Statement::Semicolon(sem) => {
sem.analyze_semantics(function_names, macro_names, handler)?; sem.analyze_semantics(function_names, macro_names, handler)
} }
} } {
errs.push(err);
};
} }
Ok(()) #[expect(clippy::option_if_let_else)]
if let Some(err) = errs.first() {
Err(err.clone())
} else {
Ok(())
}
} }
} }
@ -197,13 +231,27 @@ impl ExecuteBlock {
) -> Result<(), error::Error> { ) -> Result<(), error::Error> {
match self { match self {
Self::HeadTail(head, tail) => { Self::HeadTail(head, tail) => {
head.analyze_semantics(function_names, macro_names, handler)?; let head_res = head.analyze_semantics(function_names, macro_names, handler);
tail.analyze_semantics(function_names, macro_names, handler) let tail_res = tail.analyze_semantics(function_names, macro_names, handler);
if head_res.is_err() {
head_res
} else {
tail_res
}
} }
Self::IfElse(cond, then, el) => { Self::IfElse(cond, then, el) => {
cond.analyze_semantics(function_names, macro_names, handler)?; let cond_res = cond.analyze_semantics(function_names, macro_names, handler);
then.analyze_semantics(function_names, macro_names, handler)?; let then_res = then.analyze_semantics(function_names, macro_names, handler);
el.analyze_semantics(function_names, macro_names, handler) let else_res = el.analyze_semantics(function_names, macro_names, handler);
if cond_res.is_err() {
cond_res
} else if then_res.is_err() {
then_res
} else {
else_res
}
} }
} }
} }