Refactor Binder to use Diagnostics
The Binder and its helper functions have been updated to accept and propagate a `Diagnostics` struct. This allows for better error reporting during the binding phase, enabling the compiler to continue processing even after encountering errors and collect all issues before halting. The `BoundKind::Error` node and `StaticType::Error` are introduced as "poison" nodes/types. These nodes indicate that an error occurred during compilation, preventing further valid processing of that specific AST fragment but allowing the compiler to continue with other parts of the code. The `Parser` has also been updated to return a `Diagnostics` struct, enabling it to report errors during the initial parsing stage while still attempting to build a partial AST. This adheres to the same error recovery strategy.
This commit is contained in:
@@ -306,6 +306,7 @@ impl<'a> Analyzer<'a> {
|
||||
}
|
||||
|
||||
BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure),
|
||||
BoundKind::Error => (BoundKind::Error, Purity::Impure),
|
||||
};
|
||||
|
||||
crate::ast::nodes::Node {
|
||||
|
||||
+708
-665
File diff suppressed because it is too large
Load Diff
@@ -161,6 +161,9 @@ pub enum BoundKind<T = ()> {
|
||||
bound_expanded: Box<BoundNode<T>>,
|
||||
},
|
||||
|
||||
/// A diagnostic poison node, allowing compilation to continue after an error.
|
||||
Error,
|
||||
|
||||
/// DSL-specific extension slot
|
||||
Extension(Box<dyn BoundExtension<T>>),
|
||||
}
|
||||
@@ -260,14 +263,15 @@ where
|
||||
}
|
||||
(
|
||||
BoundKind::Expansion {
|
||||
original_call: oa,
|
||||
bound_expanded: ba,
|
||||
original_call: ca,
|
||||
bound_expanded: ea,
|
||||
},
|
||||
BoundKind::Expansion {
|
||||
original_call: ob,
|
||||
bound_expanded: bb,
|
||||
original_call: cb,
|
||||
bound_expanded: eb,
|
||||
},
|
||||
) => Rc::ptr_eq(oa, ob) && ba == bb,
|
||||
) => Rc::ptr_eq(ca, cb) && ea == eb,
|
||||
(BoundKind::Error, BoundKind::Error) => true,
|
||||
(BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions
|
||||
_ => false,
|
||||
}
|
||||
@@ -314,6 +318,7 @@ impl<T> BoundKind<T> {
|
||||
BoundKind::Record { values, .. } => format!("RECORD({})", values.len()),
|
||||
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
|
||||
BoundKind::Extension(ext) => ext.display_name(),
|
||||
BoundKind::Error => "ERROR".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,8 @@ impl CapturePass {
|
||||
BoundKind::Nop
|
||||
| BoundKind::Constant(_)
|
||||
| BoundKind::Get { .. }
|
||||
| BoundKind::Extension(_) => {}
|
||||
| BoundKind::Extension(_)
|
||||
| BoundKind::Error => {}
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
@@ -256,6 +256,9 @@ impl Dumper {
|
||||
BoundKind::Extension(ext) => {
|
||||
self.log(&ext.display_name(), node);
|
||||
}
|
||||
BoundKind::Error => {
|
||||
self.log("ERROR_NODE", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+819
-816
File diff suppressed because it is too large
Load Diff
@@ -158,7 +158,7 @@ impl UsageInfo {
|
||||
}
|
||||
self.collect(lambda);
|
||||
}
|
||||
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) => {}
|
||||
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) | BoundKind::Error => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ impl TCO {
|
||||
)),
|
||||
},
|
||||
BoundKind::Extension(_) => BoundKind::Nop,
|
||||
BoundKind::Error => BoundKind::Error,
|
||||
};
|
||||
|
||||
Node {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, GlobalIdx, TypedNode};
|
||||
use crate::ast::diagnostics::Diagnostics;
|
||||
use crate::ast::nodes::Node;
|
||||
use crate::ast::types::StaticType;
|
||||
use std::collections::HashMap;
|
||||
@@ -78,7 +79,7 @@ impl TypeChecker {
|
||||
Self { global_types }
|
||||
}
|
||||
|
||||
pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
|
||||
pub fn check(&self, node: BoundNode, arg_types: &[StaticType], diag: &mut Diagnostics) -> TypedNode {
|
||||
match node.kind {
|
||||
BoundKind::Lambda {
|
||||
params,
|
||||
@@ -104,11 +105,10 @@ impl TypeChecker {
|
||||
StaticType::Tuple(arg_types.to_vec())
|
||||
};
|
||||
|
||||
let params_typed =
|
||||
self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx)?;
|
||||
let params_typed = self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx, diag);
|
||||
|
||||
// 4. Check body with the new types
|
||||
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
|
||||
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx, diag);
|
||||
let ret_ty = body_typed.ty.clone();
|
||||
|
||||
// 5. Construct specialized function type
|
||||
@@ -119,7 +119,7 @@ impl TypeChecker {
|
||||
ret: ret_ty,
|
||||
}));
|
||||
|
||||
Ok(Node {
|
||||
Node {
|
||||
identity: node.identity,
|
||||
kind: BoundKind::Lambda {
|
||||
params: Rc::new(params_typed),
|
||||
@@ -128,7 +128,7 @@ impl TypeChecker {
|
||||
positional_count,
|
||||
},
|
||||
ty: fn_ty,
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Fallback: Wrap in implicit lambda
|
||||
@@ -146,7 +146,7 @@ impl TypeChecker {
|
||||
},
|
||||
ty: (),
|
||||
};
|
||||
self.check(virtual_lambda, &[])
|
||||
self.check(virtual_lambda, &[], diag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,8 @@ impl TypeChecker {
|
||||
node: BoundNode,
|
||||
specialized_ty: &StaticType,
|
||||
ctx: &mut TypeContext,
|
||||
) -> Result<TypedNode, String> {
|
||||
diag: &mut Diagnostics,
|
||||
) -> TypedNode {
|
||||
let (kind, ty) = match node.kind {
|
||||
BoundKind::Define {
|
||||
name,
|
||||
@@ -208,12 +209,15 @@ impl TypeChecker {
|
||||
| StaticType::Vector(_, _)
|
||||
| StaticType::Matrix(_, _)
|
||||
| StaticType::List(_)
|
||||
| StaticType::Record(_) => {}
|
||||
| StaticType::Record(_)
|
||||
| StaticType::Error => {}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Cannot destructure type {} as a tuple/vector",
|
||||
specialized_ty
|
||||
));
|
||||
diag.push_error(format!("Cannot destructure type {} as a tuple/vector", specialized_ty), Some(node.identity.clone()));
|
||||
return Node {
|
||||
identity: node.identity,
|
||||
kind: BoundKind::Error,
|
||||
ty: StaticType::Error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,9 +235,10 @@ impl TypeChecker {
|
||||
.get(i)
|
||||
.map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone())
|
||||
.unwrap_or(StaticType::Any),
|
||||
StaticType::Error => StaticType::Error,
|
||||
_ => StaticType::Any,
|
||||
};
|
||||
let t = self.check_params(el, &sub_ty, ctx)?;
|
||||
let t = self.check_params(el, &sub_ty, ctx, diag);
|
||||
elem_types.push(t.ty.clone());
|
||||
typed_elements.push(t);
|
||||
}
|
||||
@@ -244,17 +249,21 @@ impl TypeChecker {
|
||||
StaticType::Tuple(elem_types),
|
||||
)
|
||||
}
|
||||
_ => return Err("Invalid node in parameter list".to_string()),
|
||||
BoundKind::Error => (BoundKind::Error, StaticType::Error),
|
||||
_ => {
|
||||
diag.push_error("Invalid node in parameter list", Some(node.identity.clone()));
|
||||
(BoundKind::Error, StaticType::Error)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Node {
|
||||
Node {
|
||||
identity: node.identity,
|
||||
kind,
|
||||
ty,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result<TypedNode, String> {
|
||||
fn check_node(&self, node: BoundNode, ctx: &mut TypeContext, diag: &mut Diagnostics) -> TypedNode {
|
||||
let (kind, ty) = match node.kind {
|
||||
BoundKind::Nop => (BoundKind::Nop, StaticType::Void),
|
||||
|
||||
@@ -270,7 +279,7 @@ impl TypeChecker {
|
||||
value,
|
||||
captured_by,
|
||||
} => {
|
||||
let val_typed = self.check_node(*value, ctx)?;
|
||||
let val_typed = self.check_node(*value, ctx, diag);
|
||||
let ty = val_typed.ty.clone();
|
||||
ctx.set_type(addr, ty.clone());
|
||||
|
||||
@@ -300,22 +309,21 @@ impl TypeChecker {
|
||||
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(k), StaticType::FieldAccessor(k)),
|
||||
|
||||
BoundKind::GetField { rec, field } => {
|
||||
let rec_typed = self.check_node(*rec, ctx)?;
|
||||
let rec_typed = self.check_node(*rec, ctx, diag);
|
||||
let field_ty = match &rec_typed.ty {
|
||||
StaticType::Record(layout) => {
|
||||
if let Some(idx) = layout.index_of(field) {
|
||||
layout.fields[idx].1.clone()
|
||||
} else {
|
||||
return Err(format!("Record does not have field :{}", field.name()));
|
||||
diag.push_error(format!("Record does not have field :{}", field.name()), Some(rec_typed.identity.clone()));
|
||||
StaticType::Error
|
||||
}
|
||||
}
|
||||
StaticType::Any => StaticType::Any,
|
||||
StaticType::Error => StaticType::Error,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Cannot access field :{} on non-record type {}",
|
||||
field.name(),
|
||||
rec_typed.ty
|
||||
));
|
||||
diag.push_error(format!("Cannot access field :{} on non-record type {}", field.name(), rec_typed.ty), Some(rec_typed.identity.clone()));
|
||||
StaticType::Error
|
||||
}
|
||||
};
|
||||
(
|
||||
@@ -328,7 +336,7 @@ impl TypeChecker {
|
||||
}
|
||||
|
||||
BoundKind::Set { addr, value } => {
|
||||
let val_typed = self.check_node(*value, ctx)?;
|
||||
let val_typed = self.check_node(*value, ctx, diag);
|
||||
let ty = val_typed.ty.clone();
|
||||
ctx.set_type(addr, ty.clone());
|
||||
|
||||
@@ -342,8 +350,8 @@ impl TypeChecker {
|
||||
}
|
||||
|
||||
BoundKind::Destructure { pattern, value } => {
|
||||
let val_typed = self.check_node(*value, ctx)?;
|
||||
let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx)?;
|
||||
let val_typed = self.check_node(*value, ctx, diag);
|
||||
let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx, diag);
|
||||
let ty = val_typed.ty.clone();
|
||||
(
|
||||
BoundKind::Destructure {
|
||||
@@ -359,14 +367,14 @@ impl TypeChecker {
|
||||
then_br,
|
||||
else_br,
|
||||
} => {
|
||||
let cond_typed = self.check_node(*cond, ctx)?;
|
||||
let then_typed = self.check_node(*then_br, ctx)?;
|
||||
let cond_typed = self.check_node(*cond, ctx, diag);
|
||||
let then_typed = self.check_node(*then_br, ctx, diag);
|
||||
|
||||
let mut else_typed = None;
|
||||
let mut final_ty = then_typed.ty.clone();
|
||||
|
||||
if let Some(e) = else_br {
|
||||
let et = self.check_node(*e, ctx)?;
|
||||
let et = self.check_node(*e, ctx, diag);
|
||||
// Basic type promotion: if types differ, fall back to Any
|
||||
if et.ty != final_ty {
|
||||
final_ty = StaticType::Any;
|
||||
@@ -391,7 +399,7 @@ impl TypeChecker {
|
||||
let mut typed_inputs = Vec::with_capacity(inputs.len());
|
||||
let mut arg_types = Vec::with_capacity(inputs.len());
|
||||
for input in inputs {
|
||||
let typed_input = self.check_node(input, ctx)?;
|
||||
let typed_input = self.check_node(input, ctx, diag);
|
||||
// Unwrap Series(T) to T for the lambda argument
|
||||
let arg_ty = if let StaticType::Series(inner) = &typed_input.ty {
|
||||
*inner.clone()
|
||||
@@ -403,7 +411,7 @@ impl TypeChecker {
|
||||
}
|
||||
|
||||
// Specialized check for the lambda using the input types!
|
||||
let typed_lambda = self.check(*lambda, &arg_types)?;
|
||||
let typed_lambda = self.check(*lambda, &arg_types, diag);
|
||||
|
||||
let ret_ty = if let StaticType::Function(sig) = &typed_lambda.ty {
|
||||
// If the lambda returns an Optional(T), the pipeline filters Void and stores T!
|
||||
@@ -430,7 +438,7 @@ impl TypeChecker {
|
||||
let mut last_ty = StaticType::Void;
|
||||
|
||||
for e in exprs {
|
||||
let t = self.check_node(e, ctx)?;
|
||||
let t = self.check_node(e, ctx, diag);
|
||||
last_ty = t.ty.clone();
|
||||
typed_exprs.push(t);
|
||||
}
|
||||
@@ -455,13 +463,12 @@ impl TypeChecker {
|
||||
TypeContext::new(64, upvalue_types, ctx.global_types, Some(ctx));
|
||||
|
||||
// 3. Check parameters and body
|
||||
let params_typed =
|
||||
self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx)?;
|
||||
let params_typed = self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx, diag);
|
||||
|
||||
// Set current params type for 'again' validation
|
||||
lambda_ctx.current_params_ty = Some(params_typed.ty.clone());
|
||||
|
||||
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
|
||||
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx, diag);
|
||||
let ret_ty = body_typed.ty.clone();
|
||||
|
||||
// 4. Construct function type
|
||||
@@ -482,14 +489,14 @@ impl TypeChecker {
|
||||
}
|
||||
|
||||
BoundKind::Call { callee, args } => {
|
||||
let callee_typed = self.check_node(*callee, ctx)?;
|
||||
let callee_typed = self.check_node(*callee, ctx, diag);
|
||||
|
||||
// Manually check args (Tuple) to prevent Vector/Matrix promotion
|
||||
let args_typed = if let BoundKind::Tuple { elements } = args.kind {
|
||||
let mut typed_elements = Vec::new();
|
||||
let mut elem_types = Vec::new();
|
||||
for e in elements {
|
||||
let t = self.check_node(e, ctx)?;
|
||||
let t = self.check_node(e, ctx, diag);
|
||||
elem_types.push(t.ty.clone());
|
||||
typed_elements.push(t);
|
||||
}
|
||||
@@ -502,16 +509,14 @@ impl TypeChecker {
|
||||
}
|
||||
} else {
|
||||
// Should not happen as parser wraps args in Tuple, but fallback safely
|
||||
self.check_node(*args, ctx)?
|
||||
self.check_node(*args, ctx, diag)
|
||||
};
|
||||
|
||||
let ret_ty = match callee_typed.ty.resolve_call(&args_typed.ty) {
|
||||
Some(ty) => ty,
|
||||
None => {
|
||||
return Err(format!(
|
||||
"Invalid arguments for function call. Expected {}, got {}",
|
||||
callee_typed.ty, args_typed.ty
|
||||
));
|
||||
diag.push_error(format!("Invalid arguments for function call. Expected {}, got {}", callee_typed.ty, args_typed.ty), Some(node.identity.clone()));
|
||||
StaticType::Error
|
||||
}
|
||||
};
|
||||
|
||||
@@ -530,7 +535,7 @@ impl TypeChecker {
|
||||
let mut typed_elements = Vec::new();
|
||||
let mut elem_types = Vec::new();
|
||||
for e in elements {
|
||||
let t = self.check_node(e, ctx)?;
|
||||
let t = self.check_node(e, ctx, diag);
|
||||
elem_types.push(t.ty.clone());
|
||||
typed_elements.push(t);
|
||||
}
|
||||
@@ -542,16 +547,13 @@ impl TypeChecker {
|
||||
ty: StaticType::Tuple(elem_types),
|
||||
}
|
||||
} else {
|
||||
self.check_node(*args, ctx)?
|
||||
self.check_node(*args, ctx, diag)
|
||||
};
|
||||
|
||||
if let Some(expected_ty) = &ctx.current_params_ty
|
||||
&& !expected_ty.is_assignable_from(&args_typed.ty)
|
||||
{
|
||||
return Err(format!(
|
||||
"Type mismatch in 'again' call: expected {}, but got {}",
|
||||
expected_ty, args_typed.ty
|
||||
));
|
||||
diag.push_error(format!("Type mismatch in 'again' call: expected {}, but got {}", expected_ty, args_typed.ty), Some(node.identity.clone()));
|
||||
}
|
||||
|
||||
(
|
||||
@@ -565,7 +567,7 @@ impl TypeChecker {
|
||||
BoundKind::Tuple { elements } => {
|
||||
let mut typed_elements = Vec::new();
|
||||
for e in elements {
|
||||
typed_elements.push(self.check_node(e, ctx)?);
|
||||
typed_elements.push(self.check_node(e, ctx, diag));
|
||||
}
|
||||
|
||||
let ty = if typed_elements.is_empty() {
|
||||
@@ -606,7 +608,7 @@ impl TypeChecker {
|
||||
let mut fields_ty = Vec::with_capacity(values.len());
|
||||
|
||||
for (i, v) in values.into_iter().enumerate() {
|
||||
let vt = self.check_node(v, ctx)?;
|
||||
let vt = self.check_node(v, ctx, diag);
|
||||
fields_ty.push((layout.fields[i].0, vt.ty.clone()));
|
||||
typed_values.push(vt);
|
||||
}
|
||||
@@ -625,7 +627,7 @@ impl TypeChecker {
|
||||
original_call,
|
||||
bound_expanded,
|
||||
} => {
|
||||
let expanded_typed = self.check_node(*bound_expanded, ctx)?;
|
||||
let expanded_typed = self.check_node(*bound_expanded, ctx, diag);
|
||||
let ty = expanded_typed.ty.clone();
|
||||
(
|
||||
BoundKind::Expansion {
|
||||
@@ -637,18 +639,17 @@ impl TypeChecker {
|
||||
}
|
||||
|
||||
BoundKind::Extension(_ext) => {
|
||||
return Err(format!(
|
||||
"TypeChecking for extension '{}' not implemented",
|
||||
_ext.display_name()
|
||||
));
|
||||
diag.push_error(format!("TypeChecking for extension '{}' not implemented", _ext.display_name()), Some(node.identity.clone()));
|
||||
(BoundKind::Error, StaticType::Error)
|
||||
}
|
||||
BoundKind::Error => (BoundKind::Error, StaticType::Error),
|
||||
};
|
||||
|
||||
Ok(Node {
|
||||
Node {
|
||||
identity: node.identity,
|
||||
kind,
|
||||
ty,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,13 +660,13 @@ mod tests {
|
||||
|
||||
fn check_source(source: &str) -> TypedNode {
|
||||
let env = crate::ast::environment::Environment::new();
|
||||
env.compile(source).unwrap()
|
||||
env.compile(source).into_result().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_destructuring_scalar_error() {
|
||||
let env = crate::ast::environment::Environment::new();
|
||||
let result = env.compile("(def [x] 5)");
|
||||
let result = env.compile("(def [x] 5)").into_result();
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
@@ -677,7 +678,7 @@ mod tests {
|
||||
fn test_call_argument_mismatch() {
|
||||
let env = crate::ast::environment::Environment::new();
|
||||
// fn takes 1 arg, called with 0
|
||||
let result = env.compile("(do (def f (fn [x] x)) (f))");
|
||||
let result = env.compile("(do (def f (fn [x] x)) (f))").into_result();
|
||||
assert!(result.is_err());
|
||||
assert!(
|
||||
result
|
||||
@@ -690,7 +691,7 @@ mod tests {
|
||||
fn test_destructuring_vector_args() {
|
||||
let env = crate::ast::environment::Environment::new();
|
||||
// ((fn [[x y]] (+ x y)) [10 20]) -> 30
|
||||
let result = env.compile("((fn [[x y]] (+ x y)) [10 20])");
|
||||
let result = env.compile("((fn [[x y]] (+ x y)) [10 20])").into_result();
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user