use crate::component::matching::InstanceType;
use crate::component::values::{self, Val};
use crate::{ExternType, FuncType};
use anyhow::{anyhow, Result};
use std::fmt;
use std::mem;
use std::ops::Deref;
use std::sync::Arc;
use wasmtime_environ::component::{
CanonicalAbiInfo, ComponentTypes, InterfaceType, ResourceIndex, TypeComponentIndex,
TypeComponentInstanceIndex, TypeDef, TypeEnumIndex, TypeFlagsIndex, TypeFuncIndex,
TypeListIndex, TypeModuleIndex, TypeOptionIndex, TypeRecordIndex, TypeResourceTableIndex,
TypeResultIndex, TypeTupleIndex, TypeVariantIndex,
};
use wasmtime_environ::PrimaryMap;
pub use crate::component::resources::ResourceType;
#[derive(Clone)]
struct Handle<T> {
index: T,
types: Arc<ComponentTypes>,
resources: Arc<PrimaryMap<ResourceIndex, ResourceType>>,
}
impl<T> Handle<T> {
fn new(index: T, ty: &InstanceType<'_>) -> Handle<T> {
Handle {
index,
types: ty.types.clone(),
resources: ty.resources.clone(),
}
}
fn instance(&self) -> InstanceType<'_> {
InstanceType {
types: &self.types,
resources: &self.resources,
}
}
fn equivalent<'a>(
&'a self,
other: &'a Self,
type_check: fn(&TypeChecker<'a>, T, T) -> bool,
) -> bool
where
T: PartialEq + Copy,
{
(self.index == other.index
&& Arc::ptr_eq(&self.types, &other.types)
&& Arc::ptr_eq(&self.resources, &other.resources))
|| type_check(
&TypeChecker {
a_types: &self.types,
b_types: &other.types,
a_resource: &self.resources,
b_resource: &other.resources,
},
self.index,
other.index,
)
}
}
impl<T: fmt::Debug> fmt::Debug for Handle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Handle")
.field("index", &self.index)
.finish()
}
}
struct TypeChecker<'a> {
a_types: &'a ComponentTypes,
a_resource: &'a PrimaryMap<ResourceIndex, ResourceType>,
b_types: &'a ComponentTypes,
b_resource: &'a PrimaryMap<ResourceIndex, ResourceType>,
}
impl TypeChecker<'_> {
fn interface_types_equal(&self, a: InterfaceType, b: InterfaceType) -> bool {
match (a, b) {
(InterfaceType::Own(o1), InterfaceType::Own(o2)) => self.resources_equal(o1, o2),
(InterfaceType::Own(_), _) => false,
(InterfaceType::Borrow(b1), InterfaceType::Borrow(b2)) => self.resources_equal(b1, b2),
(InterfaceType::Borrow(_), _) => false,
(InterfaceType::List(l1), InterfaceType::List(l2)) => self.lists_equal(l1, l2),
(InterfaceType::List(_), _) => false,
(InterfaceType::Record(r1), InterfaceType::Record(r2)) => self.records_equal(r1, r2),
(InterfaceType::Record(_), _) => false,
(InterfaceType::Variant(v1), InterfaceType::Variant(v2)) => self.variants_equal(v1, v2),
(InterfaceType::Variant(_), _) => false,
(InterfaceType::Result(r1), InterfaceType::Result(r2)) => self.results_equal(r1, r2),
(InterfaceType::Result(_), _) => false,
(InterfaceType::Option(o1), InterfaceType::Option(o2)) => self.options_equal(o1, o2),
(InterfaceType::Option(_), _) => false,
(InterfaceType::Enum(e1), InterfaceType::Enum(e2)) => self.enums_equal(e1, e2),
(InterfaceType::Enum(_), _) => false,
(InterfaceType::Tuple(t1), InterfaceType::Tuple(t2)) => self.tuples_equal(t1, t2),
(InterfaceType::Tuple(_), _) => false,
(InterfaceType::Flags(f1), InterfaceType::Flags(f2)) => self.flags_equal(f1, f2),
(InterfaceType::Flags(_), _) => false,
(InterfaceType::Bool, InterfaceType::Bool) => true,
(InterfaceType::Bool, _) => false,
(InterfaceType::U8, InterfaceType::U8) => true,
(InterfaceType::U8, _) => false,
(InterfaceType::U16, InterfaceType::U16) => true,
(InterfaceType::U16, _) => false,
(InterfaceType::U32, InterfaceType::U32) => true,
(InterfaceType::U32, _) => false,
(InterfaceType::U64, InterfaceType::U64) => true,
(InterfaceType::U64, _) => false,
(InterfaceType::S8, InterfaceType::S8) => true,
(InterfaceType::S8, _) => false,
(InterfaceType::S16, InterfaceType::S16) => true,
(InterfaceType::S16, _) => false,
(InterfaceType::S32, InterfaceType::S32) => true,
(InterfaceType::S32, _) => false,
(InterfaceType::S64, InterfaceType::S64) => true,
(InterfaceType::S64, _) => false,
(InterfaceType::Float32, InterfaceType::Float32) => true,
(InterfaceType::Float32, _) => false,
(InterfaceType::Float64, InterfaceType::Float64) => true,
(InterfaceType::Float64, _) => false,
(InterfaceType::String, InterfaceType::String) => true,
(InterfaceType::String, _) => false,
(InterfaceType::Char, InterfaceType::Char) => true,
(InterfaceType::Char, _) => false,
}
}
fn lists_equal(&self, l1: TypeListIndex, l2: TypeListIndex) -> bool {
let a = &self.a_types[l1];
let b = &self.b_types[l2];
self.interface_types_equal(a.element, b.element)
}
fn resources_equal(&self, o1: TypeResourceTableIndex, o2: TypeResourceTableIndex) -> bool {
let a = &self.a_types[o1];
let b = &self.b_types[o2];
self.a_resource[a.ty] == self.b_resource[b.ty]
}
fn records_equal(&self, r1: TypeRecordIndex, r2: TypeRecordIndex) -> bool {
let a = &self.a_types[r1];
let b = &self.b_types[r2];
if a.fields.len() != b.fields.len() {
return false;
}
a.fields
.iter()
.zip(b.fields.iter())
.all(|(a_field, b_field)| {
a_field.name == b_field.name && self.interface_types_equal(a_field.ty, b_field.ty)
})
}
fn variants_equal(&self, v1: TypeVariantIndex, v2: TypeVariantIndex) -> bool {
let a = &self.a_types[v1];
let b = &self.b_types[v2];
if a.cases.len() != b.cases.len() {
return false;
}
a.cases.iter().zip(b.cases.iter()).all(|(a_case, b_case)| {
if a_case.name != b_case.name {
return false;
}
match (a_case.ty, b_case.ty) {
(Some(a_case_ty), Some(b_case_ty)) => {
self.interface_types_equal(a_case_ty, b_case_ty)
}
(None, None) => true,
_ => false,
}
})
}
fn results_equal(&self, r1: TypeResultIndex, r2: TypeResultIndex) -> bool {
let a = &self.a_types[r1];
let b = &self.b_types[r2];
let oks = match (a.ok, b.ok) {
(Some(ok1), Some(ok2)) => self.interface_types_equal(ok1, ok2),
(None, None) => true,
_ => false,
};
if !oks {
return false;
}
match (a.err, b.err) {
(Some(err1), Some(err2)) => self.interface_types_equal(err1, err2),
(None, None) => true,
_ => false,
}
}
fn options_equal(&self, o1: TypeOptionIndex, o2: TypeOptionIndex) -> bool {
let a = &self.a_types[o1];
let b = &self.b_types[o2];
self.interface_types_equal(a.ty, b.ty)
}
fn enums_equal(&self, e1: TypeEnumIndex, e2: TypeEnumIndex) -> bool {
let a = &self.a_types[e1];
let b = &self.b_types[e2];
a.names == b.names
}
fn tuples_equal(&self, t1: TypeTupleIndex, t2: TypeTupleIndex) -> bool {
let a = &self.a_types[t1];
let b = &self.b_types[t2];
if a.types.len() != b.types.len() {
return false;
}
a.types
.iter()
.zip(b.types.iter())
.all(|(&a, &b)| self.interface_types_equal(a, b))
}
fn flags_equal(&self, f1: TypeFlagsIndex, f2: TypeFlagsIndex) -> bool {
let a = &self.a_types[f1];
let b = &self.b_types[f2];
a.names == b.names
}
}
#[derive(Clone, Debug)]
pub struct List(Handle<TypeListIndex>);
impl PartialEq for List {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::lists_equal)
}
}
impl Eq for List {}
impl List {
pub fn new_val(&self, values: Box<[Val]>) -> Result<Val> {
Ok(Val::List(values::List::new(self, values)?))
}
pub(crate) fn from(index: TypeListIndex, ty: &InstanceType<'_>) -> Self {
List(Handle::new(index, ty))
}
pub fn ty(&self) -> Type {
Type::from(&self.0.types[self.0.index].element, &self.0.instance())
}
}
#[derive(Debug)]
pub struct Field<'a> {
pub name: &'a str,
pub ty: Type,
}
#[derive(Clone, Debug)]
pub struct Record(Handle<TypeRecordIndex>);
impl Record {
pub fn new_val<'a>(&self, values: impl IntoIterator<Item = (&'a str, Val)>) -> Result<Val> {
Ok(Val::Record(values::Record::new(self, values)?))
}
pub(crate) fn from(index: TypeRecordIndex, ty: &InstanceType<'_>) -> Self {
Record(Handle::new(index, ty))
}
pub fn fields(&self) -> impl ExactSizeIterator<Item = Field<'_>> {
self.0.types[self.0.index].fields.iter().map(|field| Field {
name: &field.name,
ty: Type::from(&field.ty, &self.0.instance()),
})
}
}
impl PartialEq for Record {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::records_equal)
}
}
impl Eq for Record {}
#[derive(Clone, Debug)]
pub struct Tuple(Handle<TypeTupleIndex>);
impl Tuple {
pub fn new_val(&self, values: Box<[Val]>) -> Result<Val> {
Ok(Val::Tuple(values::Tuple::new(self, values)?))
}
pub(crate) fn from(index: TypeTupleIndex, ty: &InstanceType<'_>) -> Self {
Tuple(Handle::new(index, ty))
}
pub fn types(&self) -> impl ExactSizeIterator<Item = Type> + '_ {
self.0.types[self.0.index]
.types
.iter()
.map(|ty| Type::from(ty, &self.0.instance()))
}
}
impl PartialEq for Tuple {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::tuples_equal)
}
}
impl Eq for Tuple {}
pub struct Case<'a> {
pub name: &'a str,
pub ty: Option<Type>,
}
#[derive(Clone, Debug)]
pub struct Variant(Handle<TypeVariantIndex>);
impl Variant {
pub fn new_val(&self, name: &str, value: Option<Val>) -> Result<Val> {
Ok(Val::Variant(values::Variant::new(self, name, value)?))
}
pub(crate) fn from(index: TypeVariantIndex, ty: &InstanceType<'_>) -> Self {
Variant(Handle::new(index, ty))
}
pub fn cases(&self) -> impl ExactSizeIterator<Item = Case> {
self.0.types[self.0.index].cases.iter().map(|case| Case {
name: &case.name,
ty: case
.ty
.as_ref()
.map(|ty| Type::from(ty, &self.0.instance())),
})
}
}
impl PartialEq for Variant {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::variants_equal)
}
}
impl Eq for Variant {}
#[derive(Clone, Debug)]
pub struct Enum(Handle<TypeEnumIndex>);
impl Enum {
pub fn new_val(&self, name: &str) -> Result<Val> {
Ok(Val::Enum(values::Enum::new(self, name)?))
}
pub(crate) fn from(index: TypeEnumIndex, ty: &InstanceType<'_>) -> Self {
Enum(Handle::new(index, ty))
}
pub fn names(&self) -> impl ExactSizeIterator<Item = &str> {
self.0.types[self.0.index]
.names
.iter()
.map(|name| name.deref())
}
}
impl PartialEq for Enum {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::enums_equal)
}
}
impl Eq for Enum {}
#[derive(Clone, Debug)]
pub struct OptionType(Handle<TypeOptionIndex>);
impl OptionType {
pub fn new_val(&self, value: Option<Val>) -> Result<Val> {
Ok(Val::Option(values::OptionVal::new(self, value)?))
}
pub(crate) fn from(index: TypeOptionIndex, ty: &InstanceType<'_>) -> Self {
OptionType(Handle::new(index, ty))
}
pub fn ty(&self) -> Type {
Type::from(&self.0.types[self.0.index].ty, &self.0.instance())
}
}
impl PartialEq for OptionType {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::options_equal)
}
}
impl Eq for OptionType {}
#[derive(Clone, Debug)]
pub struct ResultType(Handle<TypeResultIndex>);
impl ResultType {
pub fn new_val(&self, value: Result<Option<Val>, Option<Val>>) -> Result<Val> {
Ok(Val::Result(values::ResultVal::new(self, value)?))
}
pub(crate) fn from(index: TypeResultIndex, ty: &InstanceType<'_>) -> Self {
ResultType(Handle::new(index, ty))
}
pub fn ok(&self) -> Option<Type> {
Some(Type::from(
self.0.types[self.0.index].ok.as_ref()?,
&self.0.instance(),
))
}
pub fn err(&self) -> Option<Type> {
Some(Type::from(
self.0.types[self.0.index].err.as_ref()?,
&self.0.instance(),
))
}
}
impl PartialEq for ResultType {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::results_equal)
}
}
impl Eq for ResultType {}
#[derive(Clone, Debug)]
pub struct Flags(Handle<TypeFlagsIndex>);
impl Flags {
pub fn new_val(&self, names: &[&str]) -> Result<Val> {
Ok(Val::Flags(values::Flags::new(self, names)?))
}
pub(crate) fn from(index: TypeFlagsIndex, ty: &InstanceType<'_>) -> Self {
Flags(Handle::new(index, ty))
}
pub fn names(&self) -> impl ExactSizeIterator<Item = &str> {
self.0.types[self.0.index]
.names
.iter()
.map(|name| name.deref())
}
pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
&self.0.types[self.0.index].abi
}
}
impl PartialEq for Flags {
fn eq(&self, other: &Self) -> bool {
self.0.equivalent(&other.0, TypeChecker::flags_equal)
}
}
impl Eq for Flags {}
#[derive(Clone, PartialEq, Eq, Debug)]
#[allow(missing_docs)]
pub enum Type {
Bool,
S8,
U8,
S16,
U16,
S32,
U32,
S64,
U64,
Float32,
Float64,
Char,
String,
List(List),
Record(Record),
Tuple(Tuple),
Variant(Variant),
Enum(Enum),
Option(OptionType),
Result(ResultType),
Flags(Flags),
Own(ResourceType),
Borrow(ResourceType),
}
impl Type {
pub fn unwrap_list(&self) -> &List {
if let Type::List(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a list", self.desc())
}
}
pub fn unwrap_record(&self) -> &Record {
if let Type::Record(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a record", self.desc())
}
}
pub fn unwrap_tuple(&self) -> &Tuple {
if let Type::Tuple(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a tuple", self.desc())
}
}
pub fn unwrap_variant(&self) -> &Variant {
if let Type::Variant(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a variant", self.desc())
}
}
pub fn unwrap_enum(&self) -> &Enum {
if let Type::Enum(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a enum", self.desc())
}
}
pub fn unwrap_option(&self) -> &OptionType {
if let Type::Option(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a option", self.desc())
}
}
pub fn unwrap_result(&self) -> &ResultType {
if let Type::Result(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a result", self.desc())
}
}
pub fn unwrap_flags(&self) -> &Flags {
if let Type::Flags(handle) = self {
&handle
} else {
panic!("attempted to unwrap a {} as a flags", self.desc())
}
}
pub fn unwrap_own(&self) -> &ResourceType {
match self {
Type::Own(ty) => ty,
_ => panic!("attempted to unwrap a {} as a own", self.desc()),
}
}
pub fn unwrap_borrow(&self) -> &ResourceType {
match self {
Type::Borrow(ty) => ty,
_ => panic!("attempted to unwrap a {} as a own", self.desc()),
}
}
pub(crate) fn is_supertype_of(&self, value: &Val) -> Result<()> {
let other = &value.ty();
if self == other
|| matches!((self, other), (Self::Borrow(s), Self::Own(other)) if s == other)
{
Ok(())
} else if mem::discriminant(self) != mem::discriminant(other) {
Err(anyhow!(
"type mismatch: expected {}, got {}",
self.desc(),
other.desc()
))
} else {
Err(anyhow!(
"type mismatch for {}, possibly due to mixing distinct composite types",
self.desc()
))
}
}
pub(crate) fn from(ty: &InterfaceType, instance: &InstanceType<'_>) -> Self {
match ty {
InterfaceType::Bool => Type::Bool,
InterfaceType::S8 => Type::S8,
InterfaceType::U8 => Type::U8,
InterfaceType::S16 => Type::S16,
InterfaceType::U16 => Type::U16,
InterfaceType::S32 => Type::S32,
InterfaceType::U32 => Type::U32,
InterfaceType::S64 => Type::S64,
InterfaceType::U64 => Type::U64,
InterfaceType::Float32 => Type::Float32,
InterfaceType::Float64 => Type::Float64,
InterfaceType::Char => Type::Char,
InterfaceType::String => Type::String,
InterfaceType::List(index) => Type::List(List::from(*index, instance)),
InterfaceType::Record(index) => Type::Record(Record::from(*index, instance)),
InterfaceType::Tuple(index) => Type::Tuple(Tuple::from(*index, instance)),
InterfaceType::Variant(index) => Type::Variant(Variant::from(*index, instance)),
InterfaceType::Enum(index) => Type::Enum(Enum::from(*index, instance)),
InterfaceType::Option(index) => Type::Option(OptionType::from(*index, instance)),
InterfaceType::Result(index) => Type::Result(ResultType::from(*index, instance)),
InterfaceType::Flags(index) => Type::Flags(Flags::from(*index, instance)),
InterfaceType::Own(index) => Type::Own(instance.resource_type(*index)),
InterfaceType::Borrow(index) => Type::Borrow(instance.resource_type(*index)),
}
}
fn desc(&self) -> &'static str {
match self {
Type::Bool => "bool",
Type::S8 => "s8",
Type::U8 => "u8",
Type::S16 => "s16",
Type::U16 => "u16",
Type::S32 => "s32",
Type::U32 => "u32",
Type::S64 => "s64",
Type::U64 => "u64",
Type::Float32 => "float32",
Type::Float64 => "float64",
Type::Char => "char",
Type::String => "string",
Type::List(_) => "list",
Type::Record(_) => "record",
Type::Tuple(_) => "tuple",
Type::Variant(_) => "variant",
Type::Enum(_) => "enum",
Type::Option(_) => "option",
Type::Result(_) => "result",
Type::Flags(_) => "flags",
Type::Own(_) => "own",
Type::Borrow(_) => "borrow",
}
}
}
#[derive(Clone, Debug)]
pub struct ComponentFunc(Handle<TypeFuncIndex>);
impl ComponentFunc {
pub(crate) fn from(index: TypeFuncIndex, ty: &InstanceType<'_>) -> Self {
Self(Handle::new(index, ty))
}
pub fn params(&self) -> impl ExactSizeIterator<Item = Type> + '_ {
let params = self.0.types[self.0.index].params;
self.0.types[params]
.types
.iter()
.map(|ty| Type::from(ty, &self.0.instance()))
}
pub fn results(&self) -> impl ExactSizeIterator<Item = Type> + '_ {
let results = self.0.types[self.0.index].results;
self.0.types[results]
.types
.iter()
.map(|ty| Type::from(ty, &self.0.instance()))
}
}
#[derive(Clone, Debug)]
pub struct Module(Handle<TypeModuleIndex>);
impl Module {
pub(crate) fn from(index: TypeModuleIndex, ty: &InstanceType<'_>) -> Self {
Self(Handle::new(index, ty))
}
pub fn imports(&self) -> impl ExactSizeIterator<Item = ((&str, &str), ExternType)> {
self.0.types[self.0.index]
.imports
.iter()
.map(|((namespace, name), ty)| {
(
(namespace.as_str(), name.as_str()),
ExternType::from_wasmtime(self.0.types.module_types(), ty),
)
})
}
pub fn exports(&self) -> impl ExactSizeIterator<Item = (&str, ExternType)> {
self.0.types[self.0.index].exports.iter().map(|(name, ty)| {
(
name.as_str(),
ExternType::from_wasmtime(self.0.types.module_types(), ty),
)
})
}
}
#[derive(Clone, Debug)]
pub struct Component(Handle<TypeComponentIndex>);
impl Component {
pub(crate) fn from(index: TypeComponentIndex, ty: &InstanceType<'_>) -> Self {
Self(Handle::new(index, ty))
}
pub fn get_import(&self, name: &str) -> Option<ComponentItem> {
self.0.types[self.0.index]
.imports
.get(name)
.map(|ty| ComponentItem::from(ty, &self.0.instance()))
}
pub fn imports(&self) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> {
self.0.types[self.0.index]
.imports
.iter()
.map(|(name, ty)| (name.as_str(), ComponentItem::from(ty, &self.0.instance())))
}
pub fn get_export(&self, name: &str) -> Option<ComponentItem> {
self.0.types[self.0.index]
.exports
.get(name)
.map(|ty| ComponentItem::from(ty, &self.0.instance()))
}
pub fn exports(&self) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> {
self.0.types[self.0.index]
.exports
.iter()
.map(|(name, ty)| (name.as_str(), ComponentItem::from(ty, &self.0.instance())))
}
}
#[derive(Clone, Debug)]
pub struct ComponentInstance(Handle<TypeComponentInstanceIndex>);
impl ComponentInstance {
pub(crate) fn from(index: TypeComponentInstanceIndex, ty: &InstanceType<'_>) -> Self {
Self(Handle::new(index, ty))
}
pub fn get_export(&self, name: &str) -> Option<ComponentItem> {
self.0.types[self.0.index]
.exports
.get(name)
.map(|ty| ComponentItem::from(ty, &self.0.instance()))
}
pub fn exports(&self) -> impl ExactSizeIterator<Item = (&str, ComponentItem)> {
self.0.types[self.0.index]
.exports
.iter()
.map(|(name, ty)| (name.as_str(), ComponentItem::from(ty, &self.0.instance())))
}
}
#[derive(Clone, Debug)]
pub enum ComponentItem {
ComponentFunc(ComponentFunc),
CoreFunc(FuncType),
Module(Module),
Component(Component),
ComponentInstance(ComponentInstance),
Type(Type),
Resource(ResourceType),
}
impl ComponentItem {
pub(crate) fn from(def: &TypeDef, ty: &InstanceType<'_>) -> Self {
match def {
TypeDef::Component(idx) => Self::Component(Component::from(*idx, ty)),
TypeDef::ComponentInstance(idx) => {
Self::ComponentInstance(ComponentInstance::from(*idx, ty))
}
TypeDef::ComponentFunc(idx) => Self::ComponentFunc(ComponentFunc::from(*idx, ty)),
TypeDef::Interface(iface_ty) => Self::Type(Type::from(iface_ty, ty)),
TypeDef::Module(idx) => Self::Module(Module::from(*idx, ty)),
TypeDef::CoreFunc(idx) => {
Self::CoreFunc(FuncType::from_wasm_func_type(ty.types[*idx].clone()))
}
TypeDef::Resource(idx) => Self::Resource(ty.resources[ty.types[*idx].ty]),
}
}
}