feat: implement Schema module for versioned types

Add Schema module with functions for creating and migrating versioned
values. This provides the runtime foundation for schema evolution.

Schema module functions:
- Schema.versioned(typeName, version, value) - create versioned value
- Schema.migrate(value, targetVersion) - migrate to new version
- Schema.getVersion(value) - get version number

Changes:
- Add Versioned, Migrate, GetVersion builtins to interpreter
- Add Schema module to global environment
- Add Schema module type to type environment
- Add 4 tests for schema operations
- Add examples/versioning.lux demonstrating usage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 09:59:52 -05:00
parent 9511957076
commit 4eebaebb27
4 changed files with 187 additions and 0 deletions

View File

@@ -53,6 +53,11 @@ pub enum BuiltinFn {
Print,
ToString,
TypeOf,
// Schema Evolution
Versioned, // Create versioned value: versioned("TypeName", 1, value)
Migrate, // Migrate to version: migrate(versionedValue, targetVersion)
GetVersion, // Get version number: getVersion(versionedValue)
}
/// Runtime value
@@ -770,6 +775,20 @@ impl Interpreter {
env.define("print", Value::Builtin(BuiltinFn::Print));
env.define("toString", Value::Builtin(BuiltinFn::ToString));
env.define("typeOf", Value::Builtin(BuiltinFn::TypeOf));
// Schema Evolution module
let schema_module = Value::Record(HashMap::from([
(
"versioned".to_string(),
Value::Builtin(BuiltinFn::Versioned),
),
("migrate".to_string(), Value::Builtin(BuiltinFn::Migrate)),
(
"getVersion".to_string(),
Value::Builtin(BuiltinFn::GetVersion),
),
]));
env.define("Schema", schema_module);
}
/// Execute a program
@@ -1860,6 +1879,72 @@ impl Interpreter {
args[0].type_name().to_string(),
)))
}
// Schema Evolution
BuiltinFn::Versioned => {
// versioned(typeName: String, version: Int, value: Any) -> Versioned
if args.len() != 3 {
return Err(err("Schema.versioned requires 3 arguments: typeName, version, value"));
}
let type_name = match &args[0] {
Value::String(s) => s.clone(),
_ => return Err(err("Schema.versioned: first argument must be a String")),
};
let version = match &args[1] {
Value::Int(n) => *n as u32,
_ => return Err(err("Schema.versioned: second argument must be an Int")),
};
Ok(EvalResult::Value(Value::Versioned {
type_name,
version,
value: Box::new(args[2].clone()),
}))
}
BuiltinFn::Migrate => {
// migrate(value: Versioned, targetVersion: Int) -> Versioned
if args.len() != 2 {
return Err(err("Schema.migrate requires 2 arguments: value, targetVersion"));
}
let target = match &args[1] {
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")),
}
}
BuiltinFn::GetVersion => {
// getVersion(value: Versioned) -> Int
if args.len() != 1 {
return Err(err("Schema.getVersion requires 1 argument"));
}
match &args[0] {
Value::Versioned { version, .. } => {
Ok(EvalResult::Value(Value::Int(*version as i64)))
}
_ => Err(err("Schema.getVersion: argument must be a Versioned value")),
}
}
}
}