use shape_ast::ast::{TypeAnnotation, types::ImplBlock, types::TraitMember};
use shape_ast::error::Result;
use shape_value::value::{
ThunkSignature, VTable, VTableEntry, VTableEntryFlags, WrapTarget,
};
use smallvec::SmallVec;
use std::collections::HashMap;
use std::sync::Arc;
use super::BytecodeCompiler;
impl BytecodeCompiler {
pub(super) fn build_and_register_vtable(
&mut self,
trait_basename: &str,
type_name: &str,
impl_block: &ImplBlock,
) -> Result<()> {
let (canonical_trait, _) = self.resolve_trait_name(trait_basename);
let trait_def = match self.trait_defs.get(&canonical_trait) {
Some(t) => t.clone(),
None => {
return Ok(());
}
};
let mut trait_method_returns: HashMap<String, TypeAnnotation> =
HashMap::new();
let mut trait_method_self_args: HashMap<String, SmallVec<[u8; 4]>> =
HashMap::new();
let mut trait_method_generic_count: HashMap<String, u8> =
HashMap::new();
for member in &trait_def.members {
let (mname, return_type, params, type_params) = match member {
TraitMember::Required(
shape_ast::ast::types::TraitMemberSignature::Method {
name,
params,
return_type,
..
},
) => (name.clone(), Some(return_type.clone()), Some(params.clone()), None),
TraitMember::Default(method) => (
method.name.clone(),
method.return_type.clone(),
None, method.type_params.clone(),
),
_ => continue,
};
if let Some(rt) = return_type {
trait_method_returns.insert(mname.clone(), rt);
}
if let Some(ps) = params {
let mut self_positions: SmallVec<[u8; 4]> = SmallVec::new();
for (i, p) in ps.iter().enumerate() {
if type_annotation_references_self(&p.type_annotation) {
let receiver_excluded_idx = i.saturating_sub(1);
self_positions.push(receiver_excluded_idx as u8);
}
}
if !self_positions.is_empty() {
trait_method_self_args
.insert(mname.clone(), self_positions);
}
}
if let Some(tp) = type_params {
let n = tp.len();
if n > 0 {
trait_method_generic_count
.insert(mname.clone(), n.min(u8::MAX as usize) as u8);
}
}
}
let mut methods: HashMap<String, VTableEntry> = HashMap::new();
for method in &impl_block.methods {
let impl_name = impl_block.impl_name.as_deref();
let compiled_fn_name = if let Some(name) = impl_name {
format!(
"{}::{}::{}::{}",
trait_basename, type_name, name, method.name
)
} else {
format!("{}::{}", type_name, method.name)
};
let func_idx = match self.find_function(&compiled_fn_name) {
Some(idx) => idx as u16,
None => continue,
};
let declared_return = trait_method_returns
.get(&method.name)
.cloned()
.or_else(|| method.return_type.clone());
let mut wrap_targets: SmallVec<[WrapTarget; 2]> = SmallVec::new();
if let Some(rt) = declared_return.as_ref() {
let mut path: SmallVec<[u8; 4]> = SmallVec::new();
collect_self_wrap_targets(rt, &mut path, &mut wrap_targets);
}
let self_arg_positions = trait_method_self_args
.get(&method.name)
.cloned()
.unwrap_or_default();
let type_param_count = trait_method_generic_count
.get(&method.name)
.copied()
.unwrap_or(0);
let sig = ThunkSignature::build(
0,
0,
method.name.clone(),
wrap_targets,
self_arg_positions,
type_param_count,
);
let entry = sig.to_vtable_entry(func_idx);
methods.insert(method.name.clone(), entry);
}
let vtable = VTable {
trait_names: vec![trait_basename.to_string()],
concrete_type_id: 0, methods,
};
let key = format!("{}::{}", trait_basename, type_name);
self.program
.trait_vtables
.insert(key, Arc::new(vtable));
Ok(())
}
}
pub(super) fn is_top_level_self(ann: &TypeAnnotation) -> bool {
match ann {
TypeAnnotation::Basic(name) => name == "Self",
TypeAnnotation::Reference(path) => path.as_str() == "Self",
_ => false,
}
}
pub(super) fn has_nested_self(ann: &TypeAnnotation) -> bool {
fn walk(ann: &TypeAnnotation, inside_generic: bool) -> bool {
match ann {
TypeAnnotation::Basic(name) => {
inside_generic && (name == "Self")
}
TypeAnnotation::Reference(path) => {
inside_generic && (path.as_str() == "Self")
}
TypeAnnotation::Generic { args, .. } => {
args.iter().any(|a| walk(a, true))
}
TypeAnnotation::Tuple(items) => {
items.iter().any(|a| walk(a, true))
}
TypeAnnotation::Function { params, returns } => {
params.iter().any(|p| walk(&p.type_annotation, true))
|| walk(returns, true)
}
TypeAnnotation::Array(inner) => walk(inner, true),
_ => false,
}
}
walk(ann, false)
}
pub(super) fn type_annotation_references_self(ann: &TypeAnnotation) -> bool {
is_top_level_self(ann) || has_nested_self(ann)
}
pub(crate) fn trait_name_from_annotation(ann: &TypeAnnotation) -> Option<&str> {
match ann {
TypeAnnotation::Dyn(traits) if !traits.is_empty() => {
Some(traits[0].as_str())
}
_ => None,
}
}
pub(super) fn collect_self_wrap_targets(
ann: &TypeAnnotation,
path: &mut SmallVec<[u8; 4]>,
out: &mut SmallVec<[WrapTarget; 2]>,
) {
if is_top_level_self(ann) {
out.push(WrapTarget {
path: path.clone(),
wrap_as_trait_id: 0,
});
return;
}
match ann {
TypeAnnotation::Generic { args, .. } => {
for (i, a) in args.iter().enumerate() {
path.push(i as u8);
collect_self_wrap_targets(a, path, out);
path.pop();
}
}
TypeAnnotation::Tuple(items) => {
for (i, a) in items.iter().enumerate() {
path.push(i as u8);
collect_self_wrap_targets(a, path, out);
path.pop();
}
}
TypeAnnotation::Array(inner) => {
path.push(0);
collect_self_wrap_targets(inner, path, out);
path.pop();
}
_ => {}
}
}
#[cfg(test)]
mod wrap_target_tests {
use super::*;
use shape_ast::ast::TypeAnnotation;
fn t_self() -> TypeAnnotation {
TypeAnnotation::Basic("Self".to_string())
}
fn t_concrete(name: &str) -> TypeAnnotation {
TypeAnnotation::Basic(name.to_string())
}
fn t_generic(name: &str, args: Vec<TypeAnnotation>) -> TypeAnnotation {
TypeAnnotation::Generic {
name: shape_ast::ast::TypePath::simple(name),
args,
}
}
fn run(ann: &TypeAnnotation) -> Vec<Vec<u8>> {
let mut path = SmallVec::new();
let mut out = SmallVec::new();
collect_self_wrap_targets(ann, &mut path, &mut out);
out.iter().map(|w| w.path.to_vec()).collect()
}
#[test]
fn top_level_self_yields_empty_path() {
let paths = run(&t_self());
assert_eq!(paths, vec![vec![] as Vec<u8>]);
}
#[test]
fn concrete_type_yields_no_targets() {
let paths = run(&t_concrete("int"));
assert_eq!(paths, Vec::<Vec<u8>>::new());
}
#[test]
fn result_of_self_yields_path_zero() {
let paths = run(&t_generic(
"Result",
vec![t_self(), t_concrete("Error")],
));
assert_eq!(paths, vec![vec![0u8]]);
}
#[test]
fn option_of_self_yields_path_zero() {
let paths = run(&t_generic("Option", vec![t_self()]));
assert_eq!(paths, vec![vec![0u8]]);
}
#[test]
fn hashmap_k_self_yields_path_one() {
let paths = run(&t_generic("HashMap", vec![t_concrete("string"), t_self()]));
assert_eq!(paths, vec![vec![1u8]]);
}
#[test]
fn tuple_self_self_yields_two_paths() {
let paths = run(&TypeAnnotation::Tuple(vec![t_self(), t_self()]));
assert_eq!(paths, vec![vec![0u8], vec![1u8]]);
}
#[test]
fn nested_option_result_self_yields_path_zero_zero() {
let paths = run(&t_generic(
"Option",
vec![t_generic("Result", vec![t_self(), t_concrete("E")])],
));
assert_eq!(paths, vec![vec![0u8, 0u8]]);
}
#[test]
fn array_of_self_yields_path_zero() {
let paths = run(&TypeAnnotation::Array(Box::new(t_self())));
assert_eq!(paths, vec![vec![0u8]]);
}
}