refactor: interpreter and type system improvements

- Parser and typechecker updates for new features
- Schema evolution refinements
- Type system enhancements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 03:54:23 -05:00
parent 634f665b1b
commit c6d7f5cffb
5 changed files with 400 additions and 64 deletions

View File

@@ -12,6 +12,7 @@ use crate::ast::{
use crate::diagnostics::{find_similar_names, format_did_you_mean, Diagnostic, Severity};
use crate::exhaustiveness::{check_exhaustiveness, missing_patterns_hint};
use crate::modules::ModuleLoader;
use crate::schema::{SchemaRegistry, Compatibility, BreakingChange};
use crate::types::{
self, unify, EffectDef, EffectOpDef, EffectSet, HandlerDef, Property, PropertySet,
TraitBoundDef, TraitDef, TraitImpl, TraitMethodDef, Type, TypeEnv, TypeScheme, VariantDef,
@@ -480,6 +481,7 @@ fn check_termination(func: &FunctionDecl) -> Result<(), String> {
#[derive(Debug, Clone)]
pub struct ParamPropertyConstraint {
pub param_name: String,
pub param_index: usize,
pub required_properties: PropertySet,
}
@@ -502,6 +504,8 @@ pub struct TypeChecker {
latest_versions: HashMap<String, u32>,
/// Migrations: type_name -> source_version -> migration_body
migrations: HashMap<String, HashMap<u32, Expr>>,
/// Schema registry for compatibility checking
schema_registry: SchemaRegistry,
}
impl TypeChecker {
@@ -517,6 +521,7 @@ impl TypeChecker {
versioned_types: HashMap::new(),
latest_versions: HashMap::new(),
migrations: HashMap::new(),
schema_registry: SchemaRegistry::new(),
}
}
@@ -826,6 +831,57 @@ impl TypeChecker {
.or_default()
.insert(migration.from_version.number, migration.body.clone());
}
// Register in schema registry and check compatibility
self.schema_registry.register(&type_name, type_decl);
// Check compatibility with previous version if this isn't v1
if version_num > 1 {
let prev_version = version_num - 1;
// Check if both versions are registered
if self.schema_registry.get_version(&type_name, prev_version).is_some() {
match self.schema_registry.check_compatibility(&type_name, prev_version, version_num) {
Ok(Compatibility::Breaking(changes)) => {
// Check if a migration exists for the breaking changes
let has_migration = self.schema_registry.has_migration(&type_name, prev_version, version_num);
if !has_migration {
// No migration for breaking changes - this is a warning
let change_descriptions: Vec<String> = changes.iter().map(|c| {
match c {
BreakingChange::FieldRemoved { field_name } =>
format!("field '{}' removed", field_name),
BreakingChange::FieldRenamed { old_name, new_name } =>
format!("field '{}' renamed to '{}'", old_name, new_name),
BreakingChange::FieldTypeChanged { field_name, old_type, new_type } =>
format!("field '{}' type changed from {} to {}", field_name, old_type, new_type),
BreakingChange::RequiredFieldAdded { field_name } =>
format!("required field '{}' added", field_name),
}
}).collect();
self.errors.push(TypeError {
message: format!(
"Breaking changes in {} @v{} without migration from @v{}: {}. \
Add 'from @v{} = {{ ... }}' to provide a migration.",
type_name, version_num, prev_version,
change_descriptions.join(", "),
prev_version
),
span: type_decl.name.span,
});
}
}
Ok(Compatibility::AutoMigrate(_)) | Ok(Compatibility::Compatible) => {
// No issues - compatible or auto-migratable
}
Err(_) => {
// Previous version not registered yet - that's fine
}
}
}
}
}
// Register ADT constructors as values with polymorphic types
@@ -1128,7 +1184,13 @@ impl TypeChecker {
}
});
if let Some(param) = param_with_type {
if let Some((param_index, param)) = func.params.iter().enumerate().find(|(_, p)| {
if let TypeExpr::Named(name) = &p.typ {
name.name == type_param.name
} else {
false
}
}) {
// Record the constraint for checking at call sites
let constraints = self
.property_constraints
@@ -1143,6 +1205,7 @@ impl TypeChecker {
props.insert(Property::from(*property));
constraints.push(ParamPropertyConstraint {
param_name: param.name.name.clone(),
param_index,
required_properties: props,
});
}
@@ -1557,39 +1620,24 @@ impl TypeChecker {
// Check property constraints from where clauses
if let Expr::Var(func_id) = func {
if let Some(constraints) = self.property_constraints.get(&func_id.name).cloned() {
// Get parameter names from the function declaration (if available in env)
// We'll match by position since we have the constraints by param name
if let Some(scheme) = self.env.lookup(&func_id.name) {
let func_typ = scheme.instantiate();
if let Type::Function { params: param_types, .. } = &func_typ {
for constraint in &constraints {
// Find which argument position corresponds to this param
// For now, match by position based on stored param names
for (i, arg) in args.iter().enumerate() {
// Get the properties of the argument
let arg_props = self.get_expr_properties(arg);
for constraint in &constraints {
// Check if the argument at the constrained position satisfies the constraint
if constraint.param_index < args.len() {
let arg = &args[constraint.param_index];
let arg_props = self.get_expr_properties(arg);
// Check if this argument corresponds to a constrained param
// We check all constraints and verify the arg satisfies them
if !arg_props.satisfies(&constraint.required_properties) {
// Only report if this argument could be the constrained one
// (simple heuristic: function type argument)
if i < param_types.len() {
if let Type::Function { .. } = &param_types[i] {
self.errors.push(TypeError {
message: format!(
"Argument to '{}' does not satisfy property constraint: \
expected {:?}, but argument has {:?}",
func_id.name,
constraint.required_properties,
arg_props
),
span: arg.span(),
});
}
}
}
}
if !arg_props.satisfies(&constraint.required_properties) {
self.errors.push(TypeError {
message: format!(
"Argument '{}' to '{}' does not satisfy property constraint: \
expected {:?}, but argument has {:?}",
constraint.param_name,
func_id.name,
constraint.required_properties,
arg_props
),
span: arg.span(),
});
}
}
}