293 lines
10 KiB
Rust
293 lines
10 KiB
Rust
//! Method substitution rules. This is a child of types so validation can inspect
|
|
//! method tables without exporting mutable definition internals.
|
|
use super::{AnnotationType, EndorTypeObject};
|
|
use crate::{
|
|
CallArity, CallEffect, CallSignature, CallType, EndorCodeObject, EndorFunctionObject,
|
|
ObjectRef, ObjectStore, VmError,
|
|
};
|
|
|
|
struct Contract {
|
|
arity: CallArity,
|
|
parameters: Vec<CallType>,
|
|
variadic: Option<CallType>,
|
|
returns: CallType,
|
|
effect: Option<CallEffect>,
|
|
}
|
|
impl Contract {
|
|
fn parameter(&self, index: usize) -> Option<&CallType> {
|
|
self.parameters.get(index).or(self.variadic.as_ref())
|
|
}
|
|
}
|
|
impl From<CallSignature> for Contract {
|
|
fn from(value: CallSignature) -> Self {
|
|
Self {
|
|
arity: value.arity,
|
|
parameters: value.parameters,
|
|
variadic: value.variadic,
|
|
returns: value.returns,
|
|
effect: Some(value.effect),
|
|
}
|
|
}
|
|
}
|
|
#[derive(Clone, Copy)]
|
|
struct Patch<'a> {
|
|
owner: ObjectRef,
|
|
name: &'a str,
|
|
function: ObjectRef,
|
|
}
|
|
|
|
impl CallType {
|
|
/// Whether every value admitted by provided is admitted by self.
|
|
/// Nominal inheritance and explicit capabilities participate; no structural
|
|
/// or callable-signature inference is implied by this relation.
|
|
pub fn accepts_type(&self, store: &ObjectStore, provided: &Self) -> Result<bool, VmError> {
|
|
self.validate(store)?;
|
|
provided.validate(store)?;
|
|
for actual in provided.alternatives() {
|
|
let mut accepted = false;
|
|
for expected in self.alternatives() {
|
|
accepted = match (*expected, *actual) {
|
|
(AnnotationType::Any, _) => true,
|
|
(AnnotationType::Nominal(base), AnnotationType::Nominal(child)) => {
|
|
store.is_subtype(child, base)?
|
|
}
|
|
(
|
|
AnnotationType::Capability(capability),
|
|
AnnotationType::Nominal(definition),
|
|
) => store.implements_trait(definition, capability)?,
|
|
(expected, actual) => expected == actual,
|
|
};
|
|
if accepted {
|
|
break;
|
|
}
|
|
}
|
|
if !accepted {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
Ok(true)
|
|
}
|
|
}
|
|
|
|
impl ObjectStore {
|
|
fn method_contract(
|
|
&self,
|
|
function: ObjectRef,
|
|
owner: ObjectRef,
|
|
) -> Result<Option<Contract>, VmError> {
|
|
if let Some(native) = self.native_signature(function)? {
|
|
return Ok(Some(native.into()));
|
|
}
|
|
let Some(data) = self
|
|
.object(function)?
|
|
.payload()
|
|
.as_any()
|
|
.downcast_ref::<EndorFunctionObject>()
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
let code = self.payload::<EndorCodeObject>(data.code(), "code")?.code();
|
|
let Some(signature) = code.source_signature() else {
|
|
return Ok(None);
|
|
};
|
|
let resolve = |name: &str| {
|
|
if signature.method_owner.as_deref() == Some(name) {
|
|
let mut current = Some(owner);
|
|
while let Some(definition) = current {
|
|
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
|
|
if data.name == name
|
|
&& (definition == owner
|
|
|| data
|
|
.methods
|
|
.values()
|
|
.any(|candidate| *candidate == function))
|
|
{
|
|
return Ok(AnnotationType::Nominal(definition));
|
|
}
|
|
current = data.base;
|
|
}
|
|
}
|
|
self.annotation_type(function, name, None)
|
|
};
|
|
let parameters = signature
|
|
.parameters
|
|
.iter()
|
|
.map(|name| resolve(name).map(CallType::from))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
Ok(Some(Contract {
|
|
arity: self
|
|
.call_arity(function)?
|
|
.ok_or(VmError::InvalidArgument("source method lacks arity"))?,
|
|
parameters,
|
|
variadic: None,
|
|
returns: resolve(&signature.return_type)?.into(),
|
|
effect: None,
|
|
}))
|
|
}
|
|
fn own_method(
|
|
&self,
|
|
owner: ObjectRef,
|
|
name: &str,
|
|
patch: Option<Patch<'_>>,
|
|
) -> Result<Option<ObjectRef>, VmError> {
|
|
if let Some(patch) = patch
|
|
&& patch.owner == owner
|
|
&& patch.name == name
|
|
{
|
|
return Ok(Some(patch.function));
|
|
}
|
|
Ok(self
|
|
.payload::<EndorTypeObject>(owner, "Type")?
|
|
.methods
|
|
.get(name)
|
|
.copied())
|
|
}
|
|
fn inherited_method(
|
|
&self,
|
|
mut owner: Option<ObjectRef>,
|
|
name: &str,
|
|
patch: Option<Patch<'_>>,
|
|
) -> Result<Option<(ObjectRef, ObjectRef)>, VmError> {
|
|
while let Some(definition) = owner {
|
|
if let Some(function) = self.own_method(definition, name, patch)? {
|
|
return Ok(Some((definition, function)));
|
|
}
|
|
owner = self.payload::<EndorTypeObject>(definition, "Type")?.base;
|
|
}
|
|
Ok(None)
|
|
}
|
|
fn check_override(
|
|
&self,
|
|
owner: ObjectRef,
|
|
name: &str,
|
|
function: ObjectRef,
|
|
patch: Option<Patch<'_>>,
|
|
defer_unknown: bool,
|
|
) -> Result<(), VmError> {
|
|
let definition = self.payload::<EndorTypeObject>(owner, "Type")?;
|
|
let Some((base_owner, base_function)) =
|
|
self.inherited_method(definition.base, name, patch)?
|
|
else {
|
|
return Ok(());
|
|
};
|
|
let checked = (|| {
|
|
let Some(base) = self.method_contract(base_function, base_owner)? else {
|
|
return Ok(());
|
|
};
|
|
let invalid = |reason| VmError::OverrideViolation {
|
|
definition: definition.name.clone(),
|
|
method: name.into(),
|
|
reason,
|
|
};
|
|
let derived = self
|
|
.method_contract(function, owner)?
|
|
.ok_or_else(|| invalid("override lacks type metadata"))?;
|
|
if derived.arity.required > base.arity.required
|
|
|| derived.arity.maximum < base.arity.maximum
|
|
{
|
|
return Err(invalid("override narrows accepted argument counts"));
|
|
}
|
|
let receiver = CallType::from(AnnotationType::Nominal(owner));
|
|
if !derived
|
|
.parameter(0)
|
|
.ok_or_else(|| invalid("override lacks receiver"))?
|
|
.accepts_type(self, &receiver)?
|
|
{
|
|
return Err(invalid("override receiver rejects its declaring type"));
|
|
}
|
|
let fixed = base
|
|
.parameters
|
|
.len()
|
|
.max(derived.parameters.len())
|
|
.min(base.arity.maximum);
|
|
for index in 1..fixed {
|
|
let expected = base
|
|
.parameter(index)
|
|
.ok_or_else(|| invalid("incomplete base parameter metadata"))?;
|
|
let actual = derived
|
|
.parameter(index)
|
|
.ok_or_else(|| invalid("incomplete override parameter metadata"))?;
|
|
if !actual.accepts_type(self, expected)? {
|
|
return Err(invalid("override narrows a parameter type"));
|
|
}
|
|
}
|
|
if base.arity.maximum > fixed {
|
|
let expected = base
|
|
.parameter(fixed)
|
|
.ok_or_else(|| invalid("incomplete base variadic metadata"))?;
|
|
let actual = derived
|
|
.parameter(fixed)
|
|
.ok_or_else(|| invalid("incomplete override variadic metadata"))?;
|
|
if !actual.accepts_type(self, expected)? {
|
|
return Err(invalid("override narrows a variadic parameter type"));
|
|
}
|
|
}
|
|
if !base.returns.accepts_type(self, &derived.returns)? {
|
|
return Err(invalid("override widens the return type"));
|
|
}
|
|
if let (Some(expected), Some(actual)) = (base.effect, derived.effect) {
|
|
let compatible = match expected {
|
|
CallEffect::ReadOnly => actual == CallEffect::ReadOnly,
|
|
CallEffect::Mutating => actual != CallEffect::External,
|
|
CallEffect::External => true,
|
|
};
|
|
if !compatible {
|
|
return Err(invalid("override expands native effects"));
|
|
}
|
|
}
|
|
Ok(())
|
|
})();
|
|
match checked {
|
|
Err(VmError::UnknownAnnotation(_)) if defer_unknown => Ok(()),
|
|
result => result,
|
|
}
|
|
}
|
|
pub(super) fn validate_class_overrides(&self, definition: ObjectRef) -> Result<(), VmError> {
|
|
if self.field_type_checks {
|
|
for (name, function) in &self.payload::<EndorTypeObject>(definition, "Type")?.methods {
|
|
self.check_override(definition, name, *function, None, true)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
pub(super) fn validate_override_update(
|
|
&self,
|
|
owner: ObjectRef,
|
|
name: &str,
|
|
function: ObjectRef,
|
|
) -> Result<(), VmError> {
|
|
if self.field_type_checks {
|
|
let patch = Some(Patch {
|
|
owner,
|
|
name,
|
|
function,
|
|
});
|
|
for descendant in self.descendant_types(owner)? {
|
|
if let Some(function) = self.own_method(descendant, name, patch)? {
|
|
self.check_override(descendant, name, function, patch, true)?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
/// Revalidate dynamic annotation names before a source method body executes.
|
|
/// Ordinary functions whose first argument is an object are not methods.
|
|
pub fn check_method_override_call(
|
|
&self,
|
|
function: ObjectRef,
|
|
receiver: ObjectRef,
|
|
) -> Result<(), VmError> {
|
|
let mut owner = self.type_of(receiver)?;
|
|
while let Some(definition) = owner {
|
|
let data = self.payload::<EndorTypeObject>(definition, "Type")?;
|
|
for (name, candidate) in &data.methods {
|
|
if *candidate == function {
|
|
self.check_override(definition, name, function, None, false)?;
|
|
}
|
|
}
|
|
owner = data.base;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|