From c3e55631d1bb5407f60227fa6fd0ccaf3f842e44 Mon Sep 17 00:00:00 2001 From: Volodymyr Herashchenko Date: Mon, 16 Feb 2026 13:48:23 +0200 Subject: [PATCH 1/2] add error states for parsing tree and AST This adds error states for `parse::Expression`, `ast::Expression` `ResolvedType` and `AliasedType`. Error recovery introduced by `chumsky` parser require some default states to recover to, which was represented before with dummy values, like unit type for expression or alias with `error` name. However, this kind of recovery is not desirable in case of further analyzing of parse tree, because it would lead to unrecognazible errors on analyzer. --- src/ast.rs | 12 ++++++++++ src/compile/mod.rs | 4 ++++ src/parse.rs | 57 +++++++++++++++++++--------------------------- src/types.rs | 21 +++++++++++++++++ src/value.rs | 3 +++ 5 files changed, 63 insertions(+), 34 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 6cda4851..715c98c1 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -160,6 +160,13 @@ impl Expression { pub fn span(&self) -> &Span { &self.span } + pub fn error(span: Span) -> Self { + Self { + inner: ExpressionInner::Error, + ty: ResolvedType::error(), + span, + } + } } /// Variant of an expression. @@ -171,6 +178,9 @@ pub enum ExpressionInner { /// Then, the block returns the value of its final expression. /// The block returns nothing (unit) if there is no final expression. Block(Arc<[Statement]>, Option>), + /// An error expression state, which indicates that parser didn't recognize this expression or + /// analyzer failed to analyze this expression. + Error, } /// A single expression directly returns its value. @@ -474,6 +484,7 @@ impl TreeLike for ExprTree<'_> { Tree::Unary(Self::Block(statements, maybe_expr)) } ExpressionInner::Single(single) => Tree::Unary(Self::Single(single)), + ExpressionInner::Error => Tree::Nullary, }, Self::Block(statements, maybe_expr) => Tree::Nary( statements @@ -913,6 +924,7 @@ impl AbstractSyntaxTree for Expression { span: *from.as_ref(), }) } + parse::ExpressionInner::Error => Ok(Self::error(*from.as_ref())), } } } diff --git a/src/compile/mod.rs b/src/compile/mod.rs index 2af17e6f..b95d6697 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -294,6 +294,10 @@ impl Expression { res } ExpressionInner::Single(e) => e.compile(scope), + ExpressionInner::Error => Err(Error::CannotCompile( + "Compiled from poisoned tree".to_string(), + ) + .with_span(*self.as_ref())), } } } diff --git a/src/parse.rs b/src/parse.rs index f47dda5e..605a8954 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -288,6 +288,13 @@ impl Expression { span, } } + + pub fn error(span: Span) -> Self { + Self { + inner: ExpressionInner::Error, + span, + } + } } impl_eq_hash!(Expression; inner); @@ -301,6 +308,10 @@ pub enum ExpressionInner { /// Then, the block returns the value of its final expression. /// The block returns nothing (unit) if there is no final expression. Block(Arc<[Statement]>, Option>), + /// An error expression state, which indicates that parser cannot recognize this expression. + /// Also tells an analyzer that we should skip analyzing this expression to not emmit more + /// errors. + Error, } /// A single expression directly returns a value. @@ -399,7 +410,7 @@ impl Match { } (MatchPattern::None, MatchPattern::Some(_, ty_r)) => AliasedType::option(ty_r.clone()), (MatchPattern::False, MatchPattern::True) => AliasedType::boolean(), - _ => unreachable!("Match expressions have valid left and right arms"), + _ => AliasedType::error(), } } } @@ -614,6 +625,7 @@ impl TreeLike for ExprTree<'_> { Tree::Unary(Self::Block(statements, maybe_expr)) } ExpressionInner::Single(single) => Tree::Unary(Self::Single(single)), + ExpressionInner::Error => Tree::Nullary, }, Self::Block(statements, maybe_expr) => Tree::Nary( statements @@ -935,13 +947,7 @@ impl ParseFromStrWithErrors for A { handler.update(parse_errs); - // TODO: We should return parsed result if we found errors, but because analyzing in `ast` module - // is not handling poisoned tree right now, we don't return parsed result - if handler.get().is_empty() { - ast - } else { - None - } + ast } } @@ -1042,12 +1048,7 @@ impl ChumskyParse for AliasedType { .then(ty.clone()), Token::LAngle, Token::RAngle, - |_| { - ( - AliasedType::alias(AliasName::from_str_unchecked("error")), - AliasedType::alias(AliasName::from_str_unchecked("error")), - ) - }, + |_| (AliasedType::error(), AliasedType::error()), ); let sum_type = just(Token::Ident("Either")) @@ -1073,7 +1074,7 @@ impl ChumskyParse for AliasedType { .map(|s: Vec| AliasedType::tuple(s)), Token::LParen, Token::RParen, - |_| AliasedType::tuple(Vec::new()), + |_| AliasedType::error(), ) .labelled("tuple"); @@ -1086,12 +1087,7 @@ impl ChumskyParse for AliasedType { }), Token::LBracket, Token::RBracket, - |_| { - AliasedType::array( - AliasedType::alias(AliasName::from_str_unchecked("error")), - 0, - ) - }, + |_| AliasedType::error(), ) .labelled("array"); @@ -1113,19 +1109,12 @@ impl ChumskyParse for AliasedType { })), Token::LAngle, Token::RAngle, - |_| { - ( - AliasedType::alias(AliasName::from_str_unchecked("error")), - NonZeroPow2Usize::TWO, - ) - }, + |_| (AliasedType::error(), NonZeroPow2Usize::TWO), )) .map(|(ty, size)| AliasedType::list(ty, size)) .labelled("List"); - choice((sum_type, option_type, tuple, array, list, atom)) - .map_with(|inner, _| inner) - .labelled("type") + choice((sum_type, option_type, tuple, array, list, atom)).labelled("type") }) } } @@ -1164,7 +1153,7 @@ impl ChumskyParse for Item { let type_parser = TypeAlias::parser().map(Item::TypeAlias); let mod_parser = Module::parser().map(|_| Item::Module); - choice((func_parser, type_parser, mod_parser)) + choice((func_parser, type_parser, mod_parser)).labelled("item") } } @@ -1200,7 +1189,7 @@ impl ChumskyParse for Function { (Token::LParen, Token::RParen), (Token::LBracket, Token::RBracket), ], - Expression::empty, + Expression::error, ))) .labelled("function body"); @@ -1493,7 +1482,7 @@ impl ChumskyParse for Expression { (Token::RAngle, Token::RAngle), (Token::LBracket, Token::RBracket), ], - |span| Expression::empty(span).inner().clone(), + |span| Expression::error(span).inner().clone(), ); let statements = statement @@ -1754,7 +1743,7 @@ impl Match { } _ => { let match_arm_fallback = MatchArm { - expression: Arc::new(Expression::empty(Span::new(0, 0))), + expression: Arc::new(Expression::error(Span::new(0, 0))), pattern: MatchPattern::False, }; diff --git a/src/types.rs b/src/types.rs index e68c9f06..45b65c87 100644 --- a/src/types.rs +++ b/src/types.rs @@ -27,6 +27,8 @@ pub enum TypeInner { Array(A, usize), /// List of the same type List(A, NonZeroPow2Usize), + /// Error type + Error, } impl TypeInner { @@ -85,6 +87,7 @@ impl TypeInner { write!(f, ", {bound}>") } }, + TypeInner::Error => write!(f, "ERROR"), } } } @@ -324,6 +327,14 @@ impl ResolvedType { pub fn as_inner(&self) -> &TypeInner> { &self.0 } + + pub const fn error() -> Self { + Self(TypeInner::Error) + } + + pub fn is_error(&self) -> bool { + matches!(self.as_inner(), TypeInner::Error) + } } impl TypeConstructible for ResolvedType { @@ -409,6 +420,7 @@ impl TreeLike for &ResolvedType { TypeInner::Option(l) | TypeInner::Array(l, _) | TypeInner::List(l, _) => Tree::Unary(l), TypeInner::Either(l, r) => Tree::Binary(l, r), TypeInner::Tuple(elements) => Tree::Nary(elements.iter().map(Arc::as_ref).collect()), + TypeInner::Error => Tree::Nullary, } } } @@ -548,6 +560,10 @@ impl AliasedType { } } + pub fn error() -> Self { + Self(AliasedInner::Inner(TypeInner::Error)) + } + /// Create a type alias from the given `identifier`. pub const fn alias(name: AliasName) -> Self { Self(AliasedInner::Alias(name)) @@ -600,6 +616,7 @@ impl AliasedType { let element = output.pop().unwrap(); output.push(ResolvedType::list(element, *bound)); } + TypeInner::Error => return Ok(ResolvedType::error()), }, } } @@ -711,6 +728,7 @@ impl TreeLike for &AliasedType { TypeInner::Tuple(elements) => { Tree::Nary(elements.iter().map(Arc::as_ref).collect()) } + TypeInner::Error => Tree::Nullary, }, } } @@ -1004,6 +1022,9 @@ impl From<&ResolvedType> for StructuralType { let element = output.pop().unwrap(); output.push(StructuralType::list(element, *bound)); } + // Not sure what to put here, but it should not be unreachable in correctly builded + // AST. + TypeInner::Error => output.push(StructuralType::unit()), } } debug_assert_eq!(output.len(), 1); diff --git a/src/value.rs b/src/value.rs index 3ca7fdca..a67fbc40 100644 --- a/src/value.rs +++ b/src/value.rs @@ -775,6 +775,7 @@ impl Value { } } } + TypeInner::Error => return None, } } debug_assert_eq!(output.len(), 1); @@ -836,6 +837,7 @@ impl crate::ArbitraryOfType for Value { .collect::>>()?; Ok(Self::list(elements, ty.as_ref().clone(), *bound)) } + TypeInner::Error => Self::arbitrary_of_type(u, &ResolvedType::error()), } } } @@ -1159,6 +1161,7 @@ impl TreeLike for Destructor<'_> { ), None => Tree::Unary(Self::WrongType), }, + TypeInner::Error => Tree::Nullary, } } } From 66b913e9b72fbc9876cbca09b5da14ea89c92e3e Mon Sep 17 00:00:00 2001 From: Volodymyr Herashchenko Date: Wed, 18 Feb 2026 11:53:33 +0200 Subject: [PATCH 2/2] add error reporting for AST Add error reporting and remade `Program::analyze` function, so it would return `Option` like it is done right now with parsing. All errors should be return from `Result` or be reported to the scope. --- src/ast.rs | 238 +++++++++++++++++++++++++++++++++---------------- src/lib.rs | 10 +-- src/witness.rs | 12 +-- 3 files changed, 172 insertions(+), 88 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 715c98c1..7a9cff81 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -9,7 +9,7 @@ use miniscript::iter::{Tree, TreeLike}; use simplicity::jet::Elements; use crate::debug::{CallTracker, DebugSymbols, TrackedCallName}; -use crate::error::{Error, RichError, Span, WithSpan}; +use crate::error::{Error, ErrorCollector, RichError, Span, WithSpan}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::parse::MatchPattern; use crate::pattern::Pattern; @@ -160,6 +160,7 @@ impl Expression { pub fn span(&self) -> &Span { &self.span } + pub fn error(span: Span) -> Self { Self { inner: ExpressionInner::Error, @@ -540,6 +541,7 @@ struct Scope { functions: HashMap, is_main: bool, call_tracker: CallTracker, + errors: Vec, } impl Scope { @@ -674,11 +676,13 @@ impl Scope { /// 1. The map of parameter types. /// 2. The map of witness types. /// 3. The function call tracker. - pub fn destruct(self) -> (Parameters, WitnessTypes, CallTracker) { + /// 4. Collected errors. + pub fn destruct(self) -> (Parameters, WitnessTypes, CallTracker, Vec) { ( Parameters::from(self.parameters), WitnessTypes::from(self.witnesses), self.call_tracker, + self.errors, ) } @@ -710,6 +714,42 @@ impl Scope { pub fn track_call>(&mut self, span: &S, name: TrackedCallName) { self.call_tracker.track_call(*span.as_ref(), name); } + + /// Push an error into vector of errors. + pub fn push_error(&mut self, err: RichError) { + self.errors.push(err); + } +} + +/// Helper trait to report errors. +trait ScopeReport { + /// Update the scope with error and return an `Option`. + fn report(self, scope: &mut Scope) -> Option; + + /// Update the scope with error and return `T`, either original or fallback. + fn report_and_fallback(self, scope: &mut Scope, fallback: T) -> T; +} + +impl> ScopeReport for Result { + fn report(self, scope: &mut Scope) -> Option { + match self { + Ok(t) => Some(t), + Err(err) => { + scope.push_error(err.into()); + None + } + } + } + + fn report_and_fallback(self, scope: &mut Scope, fallback: T) -> T { + match self { + Ok(ty) => ty, + Err(err) => { + scope.push_error(err.into()); + fallback + } + } + } } /// Part of the abstract syntax tree that can be generated from a precursor in the parse tree. @@ -726,25 +766,42 @@ trait AbstractSyntaxTree: Sized { } impl Program { - pub fn analyze(from: &parse::Program) -> Result { + pub fn analyze(from: &parse::Program, error_collector: &mut ErrorCollector) -> Option { let unit = ResolvedType::unit(); let mut scope = Scope::default(); let items = from .items() .iter() - .map(|s| Item::analyze(s, &unit, &mut scope)) - .collect::, RichError>>()?; + .filter_map(|s| { + Item::analyze(s, &unit, &mut scope) + .map(Some) + .report_and_fallback(&mut scope, None) + }) + .collect::>(); debug_assert!(scope.is_topmost()); - let (parameters, witness_types, call_tracker) = scope.destruct(); + let (parameters, witness_types, call_tracker, mut errors) = scope.destruct(); let mut iter = items.into_iter().filter_map(|item| match item { Item::Function(Function::Main(expr)) => Some(expr), _ => None, }); - let main = iter.next().ok_or(Error::MainRequired).with_span(from)?; + let main = match iter.next() { + Some(expr) => expr, + None => { + errors.push(Error::MainRequired.with_span(*from.as_ref())); + Expression::error(*from.as_ref()) + } + }; + if iter.next().is_some() { - return Err(Error::FunctionRedefined(FunctionName::main())).with_span(from); + errors.push(Error::FunctionRedefined(FunctionName::main()).with_span(*from.as_ref())); } - Ok(Self { + + if !errors.is_empty() { + error_collector.update(errors); + return None; + } + + Some(Self { main, parameters, witness_types, @@ -764,7 +821,8 @@ impl AbstractSyntaxTree for Item { parse::Item::TypeAlias(alias) => { scope .insert_alias(alias.name().clone(), alias.ty().clone()) - .with_span(alias)?; + .with_span(alias) + .report(scope); Ok(Self::TypeAlias) } parse::Item::Function(function) => { @@ -788,39 +846,48 @@ impl AbstractSyntaxTree for Function { .iter() .map(|param| { let identifier = param.identifier().clone(); - let ty = scope.resolve(param.ty())?; - Ok(FunctionParam { identifier, ty }) + let ty = scope + .resolve(param.ty()) + .with_span(from) + .report_and_fallback(scope, ResolvedType::error()); + FunctionParam { identifier, ty } }) - .collect::, Error>>() - .with_span(from)?; - let ret = from - .ret() - .as_ref() - .map(|aliased| scope.resolve(aliased).with_span(from)) - .transpose()? - .unwrap_or_else(ResolvedType::unit); + .collect::>(); + let ret = from.ret().as_ref().map_or(ResolvedType::unit(), |aliased| { + scope + .resolve(aliased) + .with_span(from) + .report_and_fallback(scope, ResolvedType::error()) + }); scope.push_scope(); for param in params.iter() { scope.insert_variable(param.identifier().clone(), param.ty().clone()); } - let body = Expression::analyze(from.body(), &ret, scope).map(Arc::new)?; + let body = Arc::new( + Expression::analyze(from.body(), &ret, scope) + .report_and_fallback(scope, Expression::error(*from.as_ref())), + ); scope.pop_scope(); debug_assert!(scope.is_topmost()); let function = CustomFunction { params, body }; scope .insert_function(from.name().clone(), function) - .with_span(from)?; + .with_span(from) + .report(scope); return Ok(Self::Custom); } if !from.params().is_empty() { - return Err(Error::MainNoInputs).with_span(from); + scope.push_error((Error::MainNoInputs).with_span(*from.as_ref())); } if let Some(aliased) = from.ret() { - let resolved = scope.resolve(aliased).with_span(from)?; + let resolved = scope + .resolve(aliased) + .with_span(from) + .report_and_fallback(scope, ResolvedType::error()); if !resolved.is_unit() { - return Err(Error::MainNoOutput).with_span(from); + scope.push_error((Error::MainNoOutput).with_span(*from.as_ref())); } } @@ -890,32 +957,44 @@ impl AbstractSyntaxTree for Expression { type From = parse::Expression; fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result { + if ty.is_error() { + return Ok(Self::error(*from.as_ref())); + } + match from.inner() { parse::ExpressionInner::Single(single) => { - let ast_single = SingleExpression::analyze(single, ty, scope)?; - Ok(Self { - ty: ty.clone(), - inner: ExpressionInner::Single(ast_single), - span: *from.as_ref(), - }) + if let Some(ast_single) = SingleExpression::analyze(single, ty, scope).report(scope) + { + Ok(Self { + ty: ty.clone(), + inner: ExpressionInner::Single(ast_single), + span: *from.as_ref(), + }) + } else { + Ok(Self::error(*from.as_ref())) + } } parse::ExpressionInner::Block(statements, expression) => { scope.push_scope(); let ast_statements = statements .iter() - .map(|s| Statement::analyze(s, &ResolvedType::unit(), scope)) - .collect::, RichError>>()?; + .filter_map(|s| { + Statement::analyze(s, &ResolvedType::unit(), scope).report(scope) + }) + .collect::>(); let ast_expression = match expression { - Some(expression) => Expression::analyze(expression, ty, scope) - .map(Arc::new) - .map(Some), + Some(expression) => Ok(Expression::analyze(expression, ty, scope) + .report(scope) + .map(Arc::new)), None if ty.is_unit() => Ok(None), None => Err(Error::ExpressionTypeMismatch( ty.clone(), ResolvedType::unit(), )) .with_span(from), - }?; + } + .report_and_fallback(scope, None); + scope.pop_scope(); Ok(Self { @@ -983,7 +1062,7 @@ impl AbstractSyntaxTree for SingleExpression { .get_variable(identifier) .ok_or(Error::UndefinedVariable(identifier.clone())) .with_span(from)?; - if ty != bound_ty { + if ty != bound_ty && !(ty.is_error() || bound_ty.is_error()) { return Err(Error::ExpressionTypeMismatch(ty.clone(), bound_ty.clone())) .with_span(from); } @@ -1085,17 +1164,17 @@ impl AbstractSyntaxTree for Call { type From = parse::Call; fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result { - fn check_argument_types( + fn check_argument_types>( parse_args: &[parse::Expression], expected_tys: &[ResolvedType], - ) -> Result<(), Error> { - if parse_args.len() == expected_tys.len() { - Ok(()) - } else { - Err(Error::InvalidNumberOfArguments( - expected_tys.len(), - parse_args.len(), - )) + scope: &mut Scope, + span: T, + ) { + if parse_args.len() != expected_tys.len() { + scope.push_error( + Error::InvalidNumberOfArguments(expected_tys.len(), parse_args.len()) + .with_span(span.into()), + ) } } @@ -1103,7 +1182,7 @@ impl AbstractSyntaxTree for Call { observed_ty: &ResolvedType, expected_ty: &ResolvedType, ) -> Result<(), Error> { - if observed_ty == expected_ty { + if observed_ty == expected_ty || (observed_ty.is_error() || expected_ty.is_error()) { Ok(()) } else { Err(Error::ExpressionTypeMismatch( @@ -1117,13 +1196,16 @@ impl AbstractSyntaxTree for Call { parse_args: &[parse::Expression], args_tys: &[ResolvedType], scope: &mut Scope, - ) -> Result, RichError> { + ) -> Arc<[Expression]> { let args = parse_args .iter() .zip(args_tys.iter()) - .map(|(arg_parse, arg_ty)| Expression::analyze(arg_parse, arg_ty, scope)) - .collect::, RichError>>()?; - Ok(args) + .map(|(arg_parse, arg_ty)| { + Expression::analyze(arg_parse, arg_ty, scope) + .report_and_fallback(scope, Expression::error(*arg_parse.as_ref())) + }) + .collect::>(); + args } let name = CallName::analyze(from, ty, scope)?; @@ -1135,63 +1217,63 @@ impl AbstractSyntaxTree for Call { .collect::, AliasName>>() .map_err(Error::UndefinedAlias) .with_span(from)?; - check_argument_types(from.args(), &args_tys).with_span(from)?; + check_argument_types(from.args(), &args_tys, scope, from); let out_ty = crate::jet::target_type(jet) .resolve_builtin() .map_err(Error::UndefinedAlias) .with_span(from)?; check_output_type(&out_ty, ty).with_span(from)?; scope.track_call(from, TrackedCallName::Jet); - analyze_arguments(from.args(), &args_tys, scope)? + analyze_arguments(from.args(), &args_tys, scope) } CallName::UnwrapLeft(right_ty) => { let args_tys = [ResolvedType::either(ty.clone(), right_ty)]; - check_argument_types(from.args(), &args_tys).with_span(from)?; - let args = analyze_arguments(from.args(), &args_tys, scope)?; + check_argument_types(from.args(), &args_tys, scope, from); + let args = analyze_arguments(from.args(), &args_tys, scope); let [arg_ty] = args_tys; scope.track_call(from, TrackedCallName::UnwrapLeft(arg_ty)); args } CallName::UnwrapRight(left_ty) => { let args_tys = [ResolvedType::either(left_ty, ty.clone())]; - check_argument_types(from.args(), &args_tys).with_span(from)?; - let args = analyze_arguments(from.args(), &args_tys, scope)?; + check_argument_types(from.args(), &args_tys, scope, from); + let args = analyze_arguments(from.args(), &args_tys, scope); let [arg_ty] = args_tys; scope.track_call(from, TrackedCallName::UnwrapRight(arg_ty)); args } CallName::IsNone(some_ty) => { let args_tys = [ResolvedType::option(some_ty)]; - check_argument_types(from.args(), &args_tys).with_span(from)?; + check_argument_types(from.args(), &args_tys, scope, from); let out_ty = ResolvedType::boolean(); check_output_type(&out_ty, ty).with_span(from)?; - analyze_arguments(from.args(), &args_tys, scope)? + analyze_arguments(from.args(), &args_tys, scope) } CallName::Unwrap => { let args_tys = [ResolvedType::option(ty.clone())]; - check_argument_types(from.args(), &args_tys).with_span(from)?; + check_argument_types(from.args(), &args_tys, scope, from); scope.track_call(from, TrackedCallName::Unwrap); - analyze_arguments(from.args(), &args_tys, scope)? + analyze_arguments(from.args(), &args_tys, scope) } CallName::Assert => { let args_tys = [ResolvedType::boolean()]; - check_argument_types(from.args(), &args_tys).with_span(from)?; + check_argument_types(from.args(), &args_tys, scope, from); let out_ty = ResolvedType::unit(); check_output_type(&out_ty, ty).with_span(from)?; scope.track_call(from, TrackedCallName::Assert); - analyze_arguments(from.args(), &args_tys, scope)? + analyze_arguments(from.args(), &args_tys, scope) } CallName::Panic => { let args_tys = []; - check_argument_types(from.args(), &args_tys).with_span(from)?; + // panic! allows every output type because it will never return anything scope.track_call(from, TrackedCallName::Panic); - analyze_arguments(from.args(), &args_tys, scope)? + analyze_arguments(from.args(), &args_tys, scope) } CallName::Debug => { let args_tys = [ty.clone()]; - check_argument_types(from.args(), &args_tys).with_span(from)?; - let args = analyze_arguments(from.args(), &args_tys, scope)?; + check_argument_types(from.args(), &args_tys, scope, from); + let args = analyze_arguments(from.args(), &args_tys, scope); let [arg_ty] = args_tys; scope.track_call(from, TrackedCallName::Debug(arg_ty)); args @@ -1202,8 +1284,8 @@ impl AbstractSyntaxTree for Call { } let args_tys = [source]; - check_argument_types(from.args(), &args_tys).with_span(from)?; - analyze_arguments(from.args(), &args_tys, scope)? + check_argument_types(from.args(), &args_tys, scope, from); + analyze_arguments(from.args(), &args_tys, scope) } CallName::Custom(function) => { let args_ty = function @@ -1212,10 +1294,10 @@ impl AbstractSyntaxTree for Call { .map(FunctionParam::ty) .cloned() .collect::>(); - check_argument_types(from.args(), &args_ty).with_span(from)?; + check_argument_types(from.args(), &args_ty, scope, from); let out_ty = function.body().ty(); check_output_type(out_ty, ty).with_span(from)?; - analyze_arguments(from.args(), &args_ty, scope)? + analyze_arguments(from.args(), &args_ty, scope) } CallName::Fold(function, bound) => { // A list fold has the signature: @@ -1232,10 +1314,10 @@ impl AbstractSyntaxTree for Call { .clone(); let args_ty = [list_ty, accumulator_ty]; - check_argument_types(from.args(), &args_ty).with_span(from)?; + check_argument_types(from.args(), &args_ty, scope, from); let out_ty = function.body().ty(); check_output_type(out_ty, ty).with_span(from)?; - analyze_arguments(from.args(), &args_ty, scope)? + analyze_arguments(from.args(), &args_ty, scope) } CallName::ArrayFold(function, size) => { // An array fold has the signature: @@ -1252,10 +1334,10 @@ impl AbstractSyntaxTree for Call { .clone(); let args_ty = [array_ty, accumulator_ty]; - check_argument_types(from.args(), &args_ty).with_span(from)?; + check_argument_types(from.args(), &args_ty, scope, from); let out_ty = function.body().ty(); check_output_type(out_ty, ty).with_span(from)?; - analyze_arguments(from.args(), &args_ty, scope)? + analyze_arguments(from.args(), &args_ty, scope) } CallName::ForWhile(function, _bit_width) => { // A for-while loop has the signature: @@ -1277,10 +1359,10 @@ impl AbstractSyntaxTree for Call { .clone(); let args_ty = [accumulator_ty, context_ty]; - check_argument_types(from.args(), &args_ty).with_span(from)?; + check_argument_types(from.args(), &args_ty, scope, from); let out_ty = function.body().ty(); check_output_type(out_ty, ty).with_span(from)?; - analyze_arguments(from.args(), &args_ty, scope)? + analyze_arguments(from.args(), &args_ty, scope) } }; diff --git a/src/lib.rs b/src/lib.rs index a9e5bc0e..341be39f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,11 +56,11 @@ impl TemplateProgram { let mut error_handler = ErrorCollector::new(Arc::clone(&file)); let parse_program = parse::Program::parse_from_str_with_errors(&file, &mut error_handler); if let Some(program) = parse_program { - let ast_program = ast::Program::analyze(&program).with_file(Arc::clone(&file))?; - Ok(Self { - simfony: ast_program, - file, - }) + let ast_program = ast::Program::analyze(&program, &mut error_handler); + match ast_program { + Some(ast) => Ok(Self { simfony: ast, file }), + None => Err(ErrorCollector::to_string(&error_handler))?, + } } else { Err(ErrorCollector::to_string(&error_handler))? } diff --git a/src/witness.rs b/src/witness.rs index 031b3a71..e5a2cb4c 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -210,7 +210,7 @@ mod tests { use super::*; use crate::parse::ParseFromStr; use crate::value::ValueConstructible; - use crate::{ast, parse, CompiledProgram, SatisfiedProgram}; + use crate::{ast, CompiledProgram, SatisfiedProgram}; #[test] fn witness_reuse() { @@ -218,10 +218,12 @@ mod tests { assert!(jet::eq_32(witness::A, witness::A)); }"#; let program = parse::Program::parse_from_str(s).expect("parsing works"); - match ast::Program::analyze(&program).map_err(Error::from) { - Ok(_) => panic!("Witness reuse was falsely accepted"), - Err(Error::WitnessReused(..)) => {} - Err(error) => panic!("Unexpected error: {error}"), + let mut error_collector = crate::error::ErrorCollector::new(Arc::from(s)); + ast::Program::analyze(&program, &mut error_collector); + match error_collector.get().first().map(|e| e.error()) { + None => panic!("Witness reuse was falsely accepted"), + Some(Error::WitnessReused(..)) => {} + Some(error) => panic!("Unexpected error: {error}"), } }