use std::collections::HashMap;
use arora_types::module::low::{ExportFunction, ExportSymbol, Header, TypeRef};
use arora_types::record::module::frozen::{self, ExportKind, Function, Parameter};
use arora_types::record::ty::{FrozenOption, FrozenTy, PrimitiveKind};
use arora_types::record::Version;
use arora_types::SemanticVersion;
use uuid::Uuid;
fn freeze_type_ref(ty: &TypeRef) -> Option<FrozenTy> {
match ty {
TypeRef::Scalar { id } => PrimitiveKind::from_id(id).map(FrozenTy::from),
TypeRef::Array { id } => PrimitiveKind::from_id(id)?.array_of().map(FrozenTy::from),
TypeRef::Option { id } => {
let element = PrimitiveKind::from_id(id)
.filter(|kind| kind.is_scalar() && *kind != PrimitiveKind::Unit)?;
Some(FrozenTy::FrozenOption(FrozenOption {
element: Box::new(FrozenTy::from(element)),
}))
}
TypeRef::FixedArray { .. } | TypeRef::Map { .. } => None,
}
}
pub(crate) fn guest_function_signature(function: &ExportFunction) -> Option<Function> {
let mut parameters = HashMap::new();
let mut parameter_ordering = Vec::with_capacity(function.parameters.len());
for param in &function.parameters {
parameters.insert(
param.id,
Parameter {
name: param.name.clone(),
ty: freeze_type_ref(¶m.ty)?,
mutable: param.mutable,
},
);
parameter_ordering.push(param.id);
}
Some(Function {
parameters,
parameter_ordering,
return_ty: freeze_type_ref(&function.ret)?,
})
}
pub(crate) fn check_record(header: &Header, record: &frozen::Module) -> Result<(), String> {
for export in &header.exports {
let ExportSymbol::Function(function) = export;
let what = format!("export '{}' ({})", function.name, function.id);
let Some(declared) = record.exports.get(&function.id) else {
return Err(format!("{what} is not in the record"));
};
if declared.name != function.name {
return Err(format!("{what} is named '{}' in the record", declared.name));
}
let ExportKind::Function(declared) = &declared.kind;
let ids: Vec<Uuid> = function.parameters.iter().map(|p| p.id).collect();
if declared.parameter_ordering != ids {
return Err(format!(
"{what} has parameters {ids:?} in the header and {:?} in the record",
declared.parameter_ordering
));
}
for parameter in &function.parameters {
let Some(described) = declared.parameters.get(¶meter.id) else {
return Err(format!(
"{what}: the record orders parameter '{}' ({}) but does not declare it",
parameter.name, parameter.id
));
};
if described.name != parameter.name || described.mutable != parameter.mutable {
return Err(format!(
"{what}: parameter {} is '{}'{} in the header and '{}'{} in the record",
parameter.id,
parameter.name,
if parameter.mutable { " (mutable)" } else { "" },
described.name,
if described.mutable { " (mutable)" } else { "" },
));
}
if !names(&described.ty, ¶meter.ty) {
return Err(format!(
"{what}: parameter '{}' is {:?} in the header and {:?} in the record",
parameter.name, parameter.ty, described.ty
));
}
}
if !names(&declared.return_ty, &function.ret) {
return Err(format!(
"{what} returns {:?} in the header and {:?} in the record",
function.ret, declared.return_ty
));
}
}
let exported = |id: &Uuid| header.exports.iter().any(|export| export.id() == id);
if let Some((id, extra)) = record.exports.iter().find(|(id, _)| !exported(id)) {
return Err(format!(
"the record's export '{}' ({id}) is not in the header",
extra.name
));
}
Ok(())
}
fn names(frozen: &FrozenTy, type_ref: &TypeRef) -> bool {
let version = pinned(frozen).cloned().unwrap_or_else(|| {
Version::from(SemanticVersion {
major: 0,
minor: 0,
patch: 0,
})
});
FrozenTy::from_type_ref(type_ref, &version).as_ref() == Some(frozen)
}
fn pinned(frozen: &FrozenTy) -> Option<&Version> {
match frozen {
FrozenTy::FrozenScalar(scalar) => Some(&scalar.reference.version),
FrozenTy::FrozenArray(array) => Some(&array.reference.version),
FrozenTy::FrozenOption(option) => pinned(&option.element),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use arora_types::ty;
fn option_of(kind: PrimitiveKind) -> FrozenTy {
FrozenTy::FrozenOption(FrozenOption {
element: Box::new(FrozenTy::from(kind)),
})
}
#[test]
fn primitives_and_arrays_of_them_freeze_as_primitives() {
assert_eq!(
freeze_type_ref(&TypeRef::Scalar { id: *ty::U64_ID }),
Some(FrozenTy::from(PrimitiveKind::U64))
);
assert_eq!(
freeze_type_ref(&TypeRef::Array { id: *ty::F32_ID }),
Some(FrozenTy::from(PrimitiveKind::ArrayF32))
);
}
#[test]
fn an_optional_scalar_primitive_freezes_as_an_optional() {
assert_eq!(
freeze_type_ref(&TypeRef::Option { id: *ty::U64_ID }),
Some(option_of(PrimitiveKind::U64))
);
assert_eq!(
freeze_type_ref(&TypeRef::Option { id: *ty::STRING_ID }),
Some(option_of(PrimitiveKind::String))
);
}
#[test]
fn a_record_s_type_names_the_header_s_in_one_form() {
let version = Version::parse("1.2.0").expect("a version");
let reference = |id: Uuid| arora_types::record::FrozenReference {
id,
version: version.clone(),
};
let string = TypeRef::Scalar { id: *ty::STRING_ID };
assert!(names(&FrozenTy::from(PrimitiveKind::String), &string));
assert!(!names(
&FrozenTy::FrozenScalar(arora_types::record::ty::FrozenScalar {
reference: reference(*ty::STRING_ID)
}),
&string
));
assert!(names(
&option_of(PrimitiveKind::String),
&TypeRef::Option { id: *ty::STRING_ID }
));
assert!(names(
&FrozenTy::from(PrimitiveKind::ArrayString),
&TypeRef::Array { id: *ty::STRING_ID }
));
let record = Uuid::from_u128(0x276);
let scalar = FrozenTy::FrozenScalar(arora_types::record::ty::FrozenScalar {
reference: reference(record),
});
assert!(names(&scalar, &TypeRef::Scalar { id: record }));
assert!(!names(&scalar, &TypeRef::Scalar { id: *ty::U8_ID }));
assert!(names(
&FrozenTy::FrozenArray(arora_types::record::ty::FrozenArray {
reference: reference(record)
}),
&TypeRef::Array { id: record }
));
assert!(names(
&FrozenTy::FrozenOption(FrozenOption {
element: Box::new(scalar.clone())
}),
&TypeRef::Option { id: record }
));
assert!(!names(&scalar, &TypeRef::Array { id: record }));
}
#[test]
fn records_optional_unit_or_array_primitive_and_maps_do_not_freeze() {
let record = Uuid::from_u128(0x276);
for type_ref in [
TypeRef::Scalar { id: record },
TypeRef::Array { id: record },
TypeRef::Option { id: record },
TypeRef::Option { id: *ty::UNIT_ID },
TypeRef::Option {
id: *ty::ARRAY_U8_ID,
},
TypeRef::FixedArray {
id: *ty::U8_ID,
len: 4,
},
TypeRef::Map {
key_id: *ty::STRING_ID,
value_id: *ty::U8_ID,
},
] {
assert_eq!(freeze_type_ref(&type_ref), None, "{type_ref:?}");
}
}
}