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

@@ -25,6 +25,7 @@ pub enum BuiltinFn {
ListLength,
ListGet,
ListRange,
ListForEach,
// String operations
StringSplit,
@@ -35,6 +36,8 @@ pub enum BuiltinFn {
StringLength,
StringChars,
StringLines,
StringParseInt,
StringParseFloat,
// Option operations
OptionMap,
@@ -842,6 +845,10 @@ impl Interpreter {
("all".to_string(), Value::Builtin(BuiltinFn::ListAll)),
("take".to_string(), Value::Builtin(BuiltinFn::ListTake)),
("drop".to_string(), Value::Builtin(BuiltinFn::ListDrop)),
(
"forEach".to_string(),
Value::Builtin(BuiltinFn::ListForEach),
),
]));
env.define("List", list_module);
@@ -888,6 +895,14 @@ impl Interpreter {
"fromChar".to_string(),
Value::Builtin(BuiltinFn::StringFromChar),
),
(
"parseInt".to_string(),
Value::Builtin(BuiltinFn::StringParseInt),
),
(
"parseFloat".to_string(),
Value::Builtin(BuiltinFn::StringParseFloat),
),
]));
env.define("String", string_module);
@@ -1138,6 +1153,20 @@ impl Interpreter {
self.global_env.define(&variant.name.name, constructor);
}
}
// Register migrations for versioned types
for migration in &type_decl.migrations {
let stored = StoredMigration {
body: migration.body.clone(),
env: self.global_env.clone(),
};
self.register_migration(
&type_decl.name.name,
migration.from_version.number,
stored,
);
}
Ok(Value::Unit)
}
@@ -1615,11 +1644,10 @@ impl Interpreter {
loop {
match result {
EvalResult::Value(v) => return Ok(v),
EvalResult::Effect(_) => {
return Err(RuntimeError {
message: "Effect in callback not supported".to_string(),
span: Some(span),
});
EvalResult::Effect(req) => {
// Handle the effect and continue
let handled = self.handle_effect(req)?;
return Ok(handled);
}
EvalResult::TailCall { func, args, span } => {
result = self.eval_call(func, args, span)?;
@@ -1853,6 +1881,36 @@ impl Interpreter {
Ok(EvalResult::Value(Value::List(lines)))
}
BuiltinFn::StringParseInt => {
let s = Self::expect_arg_1::<String>(&args, "String.parseInt", span)?;
let trimmed = s.trim();
match trimmed.parse::<i64>() {
Ok(n) => Ok(EvalResult::Value(Value::Constructor {
name: "Some".to_string(),
fields: vec![Value::Int(n)],
})),
Err(_) => Ok(EvalResult::Value(Value::Constructor {
name: "None".to_string(),
fields: vec![],
})),
}
}
BuiltinFn::StringParseFloat => {
let s = Self::expect_arg_1::<String>(&args, "String.parseFloat", span)?;
let trimmed = s.trim();
match trimmed.parse::<f64>() {
Ok(f) => Ok(EvalResult::Value(Value::Constructor {
name: "Some".to_string(),
fields: vec![Value::Float(f)],
})),
Err(_) => Ok(EvalResult::Value(Value::Constructor {
name: "None".to_string(),
fields: vec![],
})),
}
}
// Option operations
BuiltinFn::OptionMap => {
let (opt, func) = Self::expect_args_2::<Value, Value>(&args, "Option.map", span)?;
@@ -2105,27 +2163,9 @@ impl Interpreter {
Value::Int(n) => *n as u32,
_ => return Err(err("Schema.migrate: second argument must be an Int")),
};
match &args[0] {
Value::Versioned { type_name, version, value } => {
if *version == target {
// Same version, return as-is
Ok(EvalResult::Value(args[0].clone()))
} else if *version < target {
// Upgrade - for now just update version (no migration logic)
Ok(EvalResult::Value(Value::Versioned {
type_name: type_name.clone(),
version: target,
value: value.clone(),
}))
} else {
Err(err(&format!(
"Cannot downgrade from version {} to {}",
version, target
)))
}
}
_ => Err(err("Schema.migrate: first argument must be a Versioned value")),
}
// Use migrate_value which executes registered migrations
let migrated = self.migrate_value(args[0].clone(), target)?;
Ok(EvalResult::Value(migrated))
}
BuiltinFn::GetVersion => {
@@ -2327,6 +2367,18 @@ impl Interpreter {
Ok(EvalResult::Value(Value::List(result)))
}
BuiltinFn::ListForEach => {
// List.forEach(list, fn(item) => { effectful code })
// Unlike map, forEach doesn't collect results - it just runs effects
let (list, func) =
Self::expect_args_2::<Vec<Value>, Value>(&args, "List.forEach", span)?;
for item in list {
// Call the function for each item, ignoring the result
self.eval_call_to_value(func.clone(), vec![item], span)?;
}
Ok(EvalResult::Value(Value::Unit))
}
// Additional String operations
BuiltinFn::StringStartsWith => {
let (s, prefix) = Self::expect_args_2::<String, String>(&args, "String.startsWith", span)?;
@@ -3967,4 +4019,69 @@ mod tests {
_ => panic!("Expected Versioned value"),
}
}
#[test]
fn test_migration_from_type_declaration() {
use crate::parser::Parser;
// Test that migrations defined in type declarations are registered and executed
let source = r#"
type User @v2 {
name: String,
email: String,
from @v1 = { name: old.name, email: "default@example.com" }
}
// Create a v1 user using Schema.versioned
let v1_user = Schema.versioned("User", 1, { name: "Alice" })
// Migrate to v2 - should use the declared migration
let v2_user = Schema.migrate(v1_user, 2)
// Get the migrated value
let version = Schema.getVersion(v2_user)
"#;
let program = Parser::parse_source(source).expect("parse failed");
let mut interp = Interpreter::new();
let result = interp.run(&program);
assert!(result.is_ok(), "Interpreter failed: {:?}", result);
let result_value = result.unwrap();
// The last expression should be the version number
assert_eq!(format!("{}", result_value), "2");
}
#[test]
fn test_migration_chain() {
use crate::parser::Parser;
// Test that migrations chain correctly: v1 -> v2 -> v3
let source = r#"
type Config @v3 {
host: String,
port: Int,
secure: Bool,
from @v2 = { host: old.host, port: old.port, secure: true },
from @v1 = { host: old.host, port: 8080 }
}
// Create v1 config
let v1_config = Schema.versioned("Config", 1, { host: "localhost" })
// Migrate directly to v3 - should go v1 -> v2 -> v3
let v3_config = Schema.migrate(v1_config, 3)
let version = Schema.getVersion(v3_config)
"#;
let program = Parser::parse_source(source).expect("parse failed");
let mut interp = Interpreter::new();
let result = interp.run(&program);
assert!(result.is_ok(), "Interpreter failed: {:?}", result);
assert_eq!(format!("{}", result.unwrap()), "3");
}
}