feat: implement type classes / traits
Add support for type classes (traits) with full parsing, type checking, and
validation. The implementation includes:
- Trait declarations: trait Show { fn show(x: T): String }
- Trait implementations: impl Show for Int { fn show(x: Int) = ... }
- Super traits: trait Ord: Eq { ... }
- Trait constraints in where clauses: where T: Show + Eq
- Type parameters on traits: trait Functor<F> { ... }
- Default method implementations
- Validation of required method implementations
This provides a foundation for ad-hoc polymorphism and enables
more expressive type-safe abstractions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,16 +3,16 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
use crate::ast::{
|
||||
self, BinaryOp, Declaration, EffectDecl, Expr, FunctionDecl, HandlerDecl, Ident, ImportDecl,
|
||||
LetDecl, Literal, LiteralKind, MatchArm, Parameter, Pattern, Program, Span, Statement,
|
||||
TypeDecl, TypeExpr, UnaryOp, VariantFields,
|
||||
self, BinaryOp, Declaration, EffectDecl, Expr, FunctionDecl, HandlerDecl, Ident, ImplDecl,
|
||||
ImportDecl, LetDecl, Literal, LiteralKind, MatchArm, Parameter, Pattern, Program, Span,
|
||||
Statement, TraitDecl, TypeDecl, TypeExpr, UnaryOp, VariantFields,
|
||||
};
|
||||
use crate::diagnostics::{Diagnostic, Severity};
|
||||
use crate::exhaustiveness::{check_exhaustiveness, missing_patterns_hint};
|
||||
use crate::modules::ModuleLoader;
|
||||
use crate::types::{
|
||||
self, unify, EffectDef, EffectOpDef, EffectSet, HandlerDef, PropertySet, Type, TypeEnv,
|
||||
TypeScheme, VariantDef, VariantFieldsDef,
|
||||
self, unify, EffectDef, EffectOpDef, EffectSet, HandlerDef, PropertySet, TraitBoundDef,
|
||||
TraitDef, TraitImpl, TraitMethodDef, Type, TypeEnv, TypeScheme, VariantDef, VariantFieldsDef,
|
||||
};
|
||||
|
||||
/// Type checking error
|
||||
@@ -256,6 +256,15 @@ impl TypeChecker {
|
||||
};
|
||||
self.env.bind(&let_decl.name.name, TypeScheme::mono(typ));
|
||||
}
|
||||
Declaration::Trait(trait_decl) => {
|
||||
let trait_def = self.trait_def(trait_decl);
|
||||
self.env.traits.insert(trait_decl.name.name.clone(), trait_def);
|
||||
}
|
||||
Declaration::Impl(impl_decl) => {
|
||||
// Will be checked in second pass
|
||||
let trait_impl = self.collect_impl(impl_decl);
|
||||
self.env.trait_impls.push(trait_impl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +280,10 @@ impl TypeChecker {
|
||||
Declaration::Handler(handler) => {
|
||||
self.check_handler(handler);
|
||||
}
|
||||
// Effects and types don't need checking beyond collection
|
||||
Declaration::Impl(impl_decl) => {
|
||||
self.check_impl(impl_decl);
|
||||
}
|
||||
// Effects, types, and traits don't need checking beyond collection
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -354,6 +366,29 @@ impl TypeChecker {
|
||||
// For now, we just type-check the predicate expression
|
||||
// (would need 'result' in scope, which we don't have yet)
|
||||
}
|
||||
ast::WhereClause::TraitConstraint(constraint) => {
|
||||
// Validate that the type parameter exists
|
||||
if !func.type_params.iter().any(|p| p.name == constraint.type_param.name)
|
||||
&& !func.params.iter().any(|p| p.name.name == constraint.type_param.name)
|
||||
{
|
||||
self.errors.push(TypeError {
|
||||
message: format!(
|
||||
"Unknown type parameter '{}' in where clause",
|
||||
constraint.type_param.name
|
||||
),
|
||||
span: constraint.span,
|
||||
});
|
||||
}
|
||||
// Validate that each trait in the bounds exists
|
||||
for bound in &constraint.bounds {
|
||||
if !self.env.traits.contains_key(&bound.trait_name.name) {
|
||||
self.errors.push(TypeError {
|
||||
message: format!("Unknown trait: {}", bound.trait_name.name),
|
||||
span: bound.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1304,6 +1339,165 @@ impl TypeChecker {
|
||||
}
|
||||
}
|
||||
|
||||
fn trait_def(&self, trait_decl: &TraitDecl) -> TraitDef {
|
||||
let methods = trait_decl
|
||||
.methods
|
||||
.iter()
|
||||
.map(|m| TraitMethodDef {
|
||||
name: m.name.name.clone(),
|
||||
type_params: m.type_params.iter().map(|p| p.name.clone()).collect(),
|
||||
params: m
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| (p.name.name.clone(), self.resolve_type(&p.typ)))
|
||||
.collect(),
|
||||
return_type: self.resolve_type(&m.return_type),
|
||||
has_default: m.default_impl.is_some(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let super_traits = trait_decl
|
||||
.super_traits
|
||||
.iter()
|
||||
.map(|b| TraitBoundDef {
|
||||
trait_name: b.trait_name.name.clone(),
|
||||
type_args: b.type_args.iter().map(|t| self.resolve_type(t)).collect(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
TraitDef {
|
||||
name: trait_decl.name.name.clone(),
|
||||
type_params: trait_decl.type_params.iter().map(|p| p.name.clone()).collect(),
|
||||
super_traits,
|
||||
methods,
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_impl(&self, impl_decl: &ImplDecl) -> TraitImpl {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let methods: HashMap<String, Type> = impl_decl
|
||||
.methods
|
||||
.iter()
|
||||
.map(|m| {
|
||||
let return_type = m
|
||||
.return_type
|
||||
.as_ref()
|
||||
.map(|t| self.resolve_type(t))
|
||||
.unwrap_or_else(Type::var);
|
||||
let param_types: Vec<Type> = m
|
||||
.params
|
||||
.iter()
|
||||
.map(|p| self.resolve_type(&p.typ))
|
||||
.collect();
|
||||
let func_type = Type::function(param_types, return_type);
|
||||
(m.name.name.clone(), func_type)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let constraints = impl_decl
|
||||
.constraints
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let bounds = c
|
||||
.bounds
|
||||
.iter()
|
||||
.map(|b| TraitBoundDef {
|
||||
trait_name: b.trait_name.name.clone(),
|
||||
type_args: b.type_args.iter().map(|t| self.resolve_type(t)).collect(),
|
||||
})
|
||||
.collect();
|
||||
(c.type_param.name.clone(), bounds)
|
||||
})
|
||||
.collect();
|
||||
|
||||
TraitImpl {
|
||||
trait_name: impl_decl.trait_name.name.clone(),
|
||||
trait_args: impl_decl
|
||||
.trait_args
|
||||
.iter()
|
||||
.map(|t| self.resolve_type(t))
|
||||
.collect(),
|
||||
target_type: self.resolve_type(&impl_decl.target_type),
|
||||
type_params: impl_decl.type_params.iter().map(|p| p.name.clone()).collect(),
|
||||
constraints,
|
||||
methods,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_impl(&mut self, impl_decl: &ImplDecl) {
|
||||
// Verify the trait exists
|
||||
let trait_name = &impl_decl.trait_name.name;
|
||||
let trait_def = match self.env.traits.get(trait_name) {
|
||||
Some(def) => def.clone(),
|
||||
None => {
|
||||
self.errors.push(TypeError {
|
||||
message: format!("Unknown trait: {}", trait_name),
|
||||
span: impl_decl.span,
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Verify all required methods are implemented
|
||||
for method_def in &trait_def.methods {
|
||||
if !method_def.has_default {
|
||||
let implemented = impl_decl.methods.iter().any(|m| m.name.name == method_def.name);
|
||||
if !implemented {
|
||||
self.errors.push(TypeError {
|
||||
message: format!(
|
||||
"Missing implementation for required method '{}' of trait '{}'",
|
||||
method_def.name, trait_name
|
||||
),
|
||||
span: impl_decl.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Type check each implemented method
|
||||
for impl_method in &impl_decl.methods {
|
||||
// Find the method signature in the trait
|
||||
let method_def = trait_def.methods.iter().find(|m| m.name == impl_method.name.name);
|
||||
if method_def.is_none() {
|
||||
self.errors.push(TypeError {
|
||||
message: format!(
|
||||
"Method '{}' is not defined in trait '{}'",
|
||||
impl_method.name.name, trait_name
|
||||
),
|
||||
span: impl_method.span,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set up local environment with parameters
|
||||
let mut local_env = self.env.clone();
|
||||
for param in &impl_method.params {
|
||||
let param_type = self.resolve_type(¶m.typ);
|
||||
local_env.bind(¶m.name.name, TypeScheme::mono(param_type));
|
||||
}
|
||||
|
||||
// Type check the body
|
||||
let old_env = std::mem::replace(&mut self.env, local_env);
|
||||
let body_type = self.infer_expr(&impl_method.body);
|
||||
self.env = old_env;
|
||||
|
||||
// Check return type matches if specified
|
||||
if let Some(ref return_type_expr) = impl_method.return_type {
|
||||
let return_type = self.resolve_type(return_type_expr);
|
||||
if let Err(e) = unify(&body_type, &return_type) {
|
||||
self.errors.push(TypeError {
|
||||
message: format!(
|
||||
"Method '{}' body has type {}, but declared return type is {}: {}",
|
||||
impl_method.name.name, body_type, return_type, e
|
||||
),
|
||||
span: impl_method.span,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_type(&self, type_expr: &TypeExpr) -> Type {
|
||||
match type_expr {
|
||||
TypeExpr::Named(ident) => match ident.name.as_str() {
|
||||
|
||||
Reference in New Issue
Block a user