//! Compile here; execute only in the designated remote validation environment. use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CallType, CodeLayout, ContextNativeFunction, EndorCode, Instruction, NativeCallable, NativeContext, NativeResult, ObjectRef, Opcode, SourceSignature, TypeConstructor, Value, Vm, VmError, }; use std::collections::BTreeMap; fn source( vm: &mut Vm, globals: ObjectRef, owner: &str, parameters: &[&str], returns: &str, result: Value, ) -> ObjectRef { let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), Instruction::new(Opcode::StoreGlobal, 0), Instruction::new(Opcode::LoadConst, 1), Instruction::simple(Opcode::ReturnValue), ], vec![Value::Number(42.into()), result], CodeLayout { parameters: (0..parameters.len()).map(|i| format!("p{i}")).collect(), names: vec!["ran".into()], ..CodeLayout::default() }, ) .unwrap() .with_source_signature(SourceSignature { parameters: parameters.iter().map(|s| (*s).into()).collect(), return_type: returns.into(), method_owner: Some(owner.into()), }) .unwrap(); let code = vm.new_code(code).unwrap(); let function = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); vm.release(code).unwrap(); function } fn class( vm: &mut Vm, name: &str, base: ObjectRef, function: ObjectRef, ) -> Result { vm.new_class( name, base, vec![], BTreeMap::from([("check".into(), function)]), ) } #[test] fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); let base_fn = source( &mut vm, globals, "Base", &["Base", "Number"], "Object", Value::Nil, ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let good_fn = source( &mut vm, globals, "Good", &["Good", "Object"], "Number", Value::Number(7.into()), ); let good = class(&mut vm, "Good", base, good_fn).unwrap(); let narrow = source( &mut vm, globals, "Narrow", &["Narrow", "String"], "Number", Value::Nil, ); assert!(matches!( class(&mut vm, "Narrow", base, narrow), Err(VmError::OverrideViolation { reason: "override narrows a parameter type", .. }) )); let numeric = source( &mut vm, globals, "Numeric", &["Numeric", "Object"], "Number", Value::Number(0.into()), ); let numeric_type = class(&mut vm, "Numeric", roots.object, numeric).unwrap(); let broad = source( &mut vm, globals, "Broad", &["Broad", "Object"], "Object", Value::Nil, ); assert!(matches!( class(&mut vm, "Broad", numeric_type, broad), Err(VmError::OverrideViolation { reason: "override widens the return type", .. }) )); for object in [ good, good_fn, narrow, broad, numeric_type, numeric, base, base_fn, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); let base_fn = source( &mut vm, globals, "Base", &["Base", "Number"], "Object", Value::Nil, ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let child_fn = source( &mut vm, globals, "Child", &["Child", "Number"], "Number", Value::Number(0.into()), ); let child = class(&mut vm, "Child", base, child_fn).unwrap(); let replacement = source( &mut vm, globals, "Base", &["Base", "Object"], "Object", Value::Nil, ); let version = vm.change_version(base).unwrap(); assert!(matches!( vm.define_method(base, "check", replacement), Err(VmError::OverrideViolation { .. }) )); assert_eq!(vm.change_version(base).unwrap(), version); assert_eq!( vm.object_store().lookup_method(base, "check").unwrap(), Some(base_fn) ); assert_eq!( vm.object_store().lookup_method(child, "check").unwrap(), Some(child_fn) ); for object in [ replacement, child, child_fn, base, base_fn, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let parent = vm .new_type("Result", roots.object, TypeConstructor::Record) .unwrap(); let result_type = vm .new_type("ResultChild", parent, TypeConstructor::Record) .unwrap(); let other = vm .new_type("Other", roots.object, TypeConstructor::Record) .unwrap(); let result = vm.new_object().unwrap(); vm.attach_type(result, result_type).unwrap(); let base_globals = vm.new_dict().unwrap(); let globals = vm.new_dict().unwrap(); vm.dict_set( base_globals, Value::Text("Result".into()), Value::Object(parent), ) .unwrap(); vm.dict_set( globals, Value::Text("Actual".into()), Value::Object(result_type), ) .unwrap(); vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) .unwrap(); let base_fn = source( &mut vm, base_globals, "Base", &["Base"], "Result", Value::Nil, ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let child_fn = source( &mut vm, globals, "Child", &["Child"], "Actual", Value::Object(result), ); let child = class(&mut vm, "Child", base, child_fn).unwrap(); let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other)) .unwrap(); assert!(matches!( vm.call(child_fn, &[Value::Object(receiver)]), Err(VmError::OverrideViolation { .. }) )); assert_eq!( vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), Some(Value::Number(0.into())) ); vm.dict_set( globals, Value::Text("Actual".into()), Value::Object(result_type), ) .unwrap(); let value = vm.call(child_fn, &[Value::Object(receiver)]).unwrap(); assert_eq!(value, Value::Object(result)); vm.release_value(value).unwrap(); for object in [ receiver, child, child_fn, base, base_fn, globals, base_globals, result, other, result_type, parent, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] struct Native(CallSignature); impl NativeCallable for Native { fn signature(&self) -> Option { Some(self.0.clone()) } fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result { Ok(NativeResult::Return(Value::Number(0.into()))) } } #[test] fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let base = vm .new_type("Base", roots.object, TypeConstructor::Record) .unwrap(); let mut signature = CallSignature::exact( vec![T::Nominal(base), T::Any], T::Number, CallEffect::ReadOnly, ); signature.parameters[1] = CallType::Union(vec![T::Number, T::String]); let base_fn = vm .allocate(ContextNativeFunction::new( "check", Native(signature), vec![], )) .unwrap(); vm.define_method(base, "check", base_fn).unwrap(); let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap(); let effect_fn = vm .allocate(ContextNativeFunction::new( "effect", Native(CallSignature::exact( vec![T::Nominal(child), T::Any], T::Number, CallEffect::Mutating, )), vec![], )) .unwrap(); assert!(matches!( vm.define_method(child, "check", effect_fn), Err(VmError::OverrideViolation { reason: "override expands native effects", .. }) )); let narrow_fn = vm .allocate(ContextNativeFunction::new( "narrow", Native(CallSignature::exact( vec![T::Nominal(child), T::Number], T::Number, CallEffect::ReadOnly, )), vec![], )) .unwrap(); assert!(matches!( vm.define_method(child, "check", narrow_fn), Err(VmError::OverrideViolation { reason: "override narrows a parameter type", .. }) )); for object in [ narrow_fn, effect_fn, child, base_fn, base, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn unknown_forward_names_defer_until_call_without_entering_the_body() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) .unwrap(); let base_fn = source( &mut vm, globals, "Base", &["Base", "Number"], "Number", Value::Number(0.into()), ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let child_fn = source( &mut vm, globals, "Child", &["Child", "Later"], "Number", Value::Number(0.into()), ); let child = class(&mut vm, "Child", base, child_fn).unwrap(); let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); assert!(matches!( vm.call( child_fn, &[Value::Object(receiver), Value::Number(1.into())] ), Err(VmError::UnknownAnnotation(_)) )); let later = vm .new_type("Later", roots.object, TypeConstructor::Record) .unwrap(); vm.dict_set(globals, Value::Text("Later".into()), Value::Object(later)) .unwrap(); assert!(matches!( vm.call( child_fn, &[Value::Object(receiver), Value::Number(1.into())] ), Err(VmError::OverrideViolation { .. }) )); assert_eq!( vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), Some(Value::Number(0.into())) ); for object in [ receiver, child, child_fn, base, base_fn, later, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); }