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:
@@ -1,5 +1,7 @@
|
||||
//! Elm-style diagnostic messages for beautiful error reporting
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::ast::Span;
|
||||
|
||||
/// ANSI color codes for terminal output
|
||||
|
||||
@@ -881,7 +881,21 @@ impl Interpreter {
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
Declaration::Effect(_) | Declaration::Type(_) | Declaration::Trait(_) | Declaration::Impl(_) => {
|
||||
Declaration::Type(type_decl) => {
|
||||
// Register ADT constructors if this is an enum type
|
||||
if let crate::ast::TypeDef::Enum(variants) = &type_decl.definition {
|
||||
for variant in variants {
|
||||
let constructor = Value::Constructor {
|
||||
name: variant.name.name.clone(),
|
||||
fields: Vec::new(),
|
||||
};
|
||||
self.global_env.define(&variant.name.name, constructor);
|
||||
}
|
||||
}
|
||||
Ok(Value::Unit)
|
||||
}
|
||||
|
||||
Declaration::Effect(_) | Declaration::Trait(_) | Declaration::Impl(_) => {
|
||||
// These are compile-time only
|
||||
Ok(Value::Unit)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! Handles loading, parsing, and resolving module imports.
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::ast::{Declaration, ImportDecl, Program, Visibility};
|
||||
use crate::parser::Parser;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Parser for the Lux language
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::diagnostics::{Diagnostic, Severity};
|
||||
use crate::lexer::{LexError, Lexer, Token, TokenKind};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1148,8 +1148,10 @@ pub fn unify(t1: &Type, t2: &Type) -> Result<Substitution, String> {
|
||||
));
|
||||
}
|
||||
|
||||
// For now, effects must match exactly
|
||||
if e1 != e2 {
|
||||
// Function's required effects (e1) must be a subset of available effects (e2)
|
||||
// A pure function (empty effects) can be called anywhere
|
||||
// A function requiring {Logger} can be called in context with {Logger} or {Logger, Console}
|
||||
if !e1.is_subset(&e2) {
|
||||
return Err(format!(
|
||||
"Effect mismatch: expected {{{}}}, got {{{}}}",
|
||||
e1, e2
|
||||
|
||||
Reference in New Issue
Block a user