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:
@@ -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 { .. } = ¶m_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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user