fix: make all example programs work correctly
- Add string concatenation support to + operator in typechecker - Register ADT constructors in both type environment and interpreter - Bind handlers as values so they can be referenced in run...with - Fix effect checking to use subset instead of exact match - Add built-in effects (Console, Fail, State) to run block contexts - Suppress dead code warnings in diagnostics, modules, parser Update all example programs with: - Expected output documented in comments - Proper run...with statements to execute code Add new example programs: - behavioral.lux: pure, idempotent, deterministic, commutative functions - pipelines.lux: pipe operator demonstrations - statemachine.lux: ADT-based state machines - tailcall.lux: tail call optimization examples - traits.lux: type classes and pattern matching Add documentation: - docs/IMPLEMENTATION_PLAN.md: feature roadmap and status - docs/PERFORMANCE_AND_TRADEOFFS.md: performance analysis Add benchmarks for performance testing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -245,13 +245,47 @@ impl TypeChecker {
|
||||
}
|
||||
Declaration::Type(type_decl) => {
|
||||
let type_def = self.type_def(type_decl);
|
||||
self.env.types.insert(type_decl.name.name.clone(), type_def);
|
||||
self.env.types.insert(type_decl.name.name.clone(), type_def.clone());
|
||||
|
||||
// Register ADT constructors as values in the type environment
|
||||
if let ast::TypeDef::Enum(variants) = &type_decl.definition {
|
||||
let type_name = Type::Named(type_decl.name.name.clone());
|
||||
for variant in variants {
|
||||
let constructor_type = match &variant.fields {
|
||||
VariantFields::Unit => {
|
||||
// Unit variant is just the type itself
|
||||
type_name.clone()
|
||||
}
|
||||
VariantFields::Tuple(field_types) => {
|
||||
// Tuple variant is a function from fields to the type
|
||||
let param_types: Vec<Type> = field_types
|
||||
.iter()
|
||||
.map(|t| self.resolve_type(t))
|
||||
.collect();
|
||||
Type::function(param_types, type_name.clone())
|
||||
}
|
||||
VariantFields::Record(fields) => {
|
||||
// Record variant is a function from record to the type
|
||||
let field_types: Vec<(String, Type)> = fields
|
||||
.iter()
|
||||
.map(|f| (f.name.name.clone(), self.resolve_type(&f.typ)))
|
||||
.collect();
|
||||
Type::function(vec![Type::Record(field_types)], type_name.clone())
|
||||
}
|
||||
};
|
||||
self.env.bind(&variant.name.name, TypeScheme::mono(constructor_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
Declaration::Handler(handler) => {
|
||||
let handler_def = self.handler_def(handler);
|
||||
self.env
|
||||
.handlers
|
||||
.insert(handler.name.name.clone(), handler_def);
|
||||
// Also bind the handler as a value so it can be referenced in run...with expressions
|
||||
// Handler type is the effect it handles (as an opaque type for now)
|
||||
let handler_type = Type::Named(format!("Handler<{}>", handler.effect.name));
|
||||
self.env.bind(&handler.name.name, TypeScheme::mono(handler_type));
|
||||
}
|
||||
Declaration::Let(let_decl) => {
|
||||
// Will be typed in second pass
|
||||
@@ -625,7 +659,30 @@ impl TypeChecker {
|
||||
let right_type = self.infer_expr(right);
|
||||
|
||||
match op {
|
||||
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
|
||||
BinaryOp::Add => {
|
||||
// Add supports both numeric types and string concatenation
|
||||
if let Err(e) = unify(&left_type, &right_type) {
|
||||
self.errors.push(TypeError {
|
||||
message: format!("Operands of '{}' must have same type: {}", op, e),
|
||||
span,
|
||||
});
|
||||
}
|
||||
match &left_type {
|
||||
Type::Int | Type::Float | Type::String | Type::Var(_) => left_type,
|
||||
_ => {
|
||||
self.errors.push(TypeError {
|
||||
message: format!(
|
||||
"Operator '{}' requires numeric or string operands, got {}",
|
||||
op, left_type
|
||||
),
|
||||
span,
|
||||
});
|
||||
Type::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
|
||||
// Arithmetic: both operands must be same numeric type
|
||||
if let Err(e) = unify(&left_type, &right_type) {
|
||||
self.errors.push(TypeError {
|
||||
@@ -741,7 +798,13 @@ impl TypeChecker {
|
||||
let arg_types: Vec<Type> = args.iter().map(|a| self.infer_expr(a)).collect();
|
||||
|
||||
let result_type = Type::var();
|
||||
let expected_fn = Type::function(arg_types.clone(), result_type.clone());
|
||||
// Include current effects in the expected function type
|
||||
// This allows calling functions that require effects when those effects are available
|
||||
let expected_fn = Type::function_with_effects(
|
||||
arg_types.clone(),
|
||||
result_type.clone(),
|
||||
self.current_effects.clone(),
|
||||
);
|
||||
|
||||
match unify(&func_type, &expected_fn) {
|
||||
Ok(subst) => result_type.apply(&subst),
|
||||
@@ -1302,8 +1365,12 @@ impl TypeChecker {
|
||||
let handled_effects: EffectSet =
|
||||
EffectSet::from_iter(handlers.iter().map(|(e, _)| e.name.clone()));
|
||||
|
||||
// Extend current effects with handled ones
|
||||
let combined = self.current_effects.union(&handled_effects);
|
||||
// Built-in effects are always available in run blocks (they have runtime implementations)
|
||||
let builtin_effects: EffectSet =
|
||||
EffectSet::from_iter(["Console", "Fail", "State"].iter().map(|s| s.to_string()));
|
||||
|
||||
// Extend current effects with handled ones and built-in effects
|
||||
let combined = self.current_effects.union(&handled_effects).union(&builtin_effects);
|
||||
let old_effects = std::mem::replace(&mut self.current_effects, combined);
|
||||
|
||||
// Type check the expression
|
||||
|
||||
Reference in New Issue
Block a user