use anyhow::{bail, Result};
use std::fmt::{self, Display};
use wasmtime_environ::{
EngineOrModuleTypeIndex, EntityType, Global, Memory, ModuleTypes, Table, TypeTrace,
WasmFuncType, WasmHeapType, WasmRefType, WasmValType,
};
use wasmtime_runtime::VMSharedTypeIndex;
use crate::{type_registry::RegisteredType, Engine};
pub(crate) mod matching;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum Mutability {
Const,
Var,
}
#[derive(Clone, Hash)]
pub enum ValType {
I32,
I64,
F32,
F64,
V128,
Ref(RefType),
}
impl fmt::Debug for ValType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl Display for ValType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ValType::I32 => write!(f, "i32"),
ValType::I64 => write!(f, "i64"),
ValType::F32 => write!(f, "f32"),
ValType::F64 => write!(f, "f64"),
ValType::V128 => write!(f, "v128"),
ValType::Ref(r) => Display::fmt(r, f),
}
}
}
impl From<RefType> for ValType {
#[inline]
fn from(r: RefType) -> Self {
ValType::Ref(r)
}
}
impl ValType {
pub const EXTERNREF: Self = ValType::Ref(RefType::EXTERNREF);
pub const FUNCREF: Self = ValType::Ref(RefType::FUNCREF);
pub const NULLFUNCREF: Self = ValType::Ref(RefType::NULLFUNCREF);
pub const ANYREF: Self = ValType::Ref(RefType::ANYREF);
pub const I31REF: Self = ValType::Ref(RefType::I31REF);
pub const NULLREF: Self = ValType::Ref(RefType::NULLREF);
#[inline]
pub fn is_num(&self) -> bool {
match self {
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 => true,
_ => false,
}
}
#[inline]
pub fn is_i32(&self) -> bool {
matches!(self, ValType::I32)
}
#[inline]
pub fn is_i64(&self) -> bool {
matches!(self, ValType::I64)
}
#[inline]
pub fn is_f32(&self) -> bool {
matches!(self, ValType::F32)
}
#[inline]
pub fn is_f64(&self) -> bool {
matches!(self, ValType::F64)
}
#[inline]
pub fn is_v128(&self) -> bool {
matches!(self, ValType::V128)
}
#[inline]
pub fn is_ref(&self) -> bool {
matches!(self, ValType::Ref(_))
}
#[inline]
pub fn is_funcref(&self) -> bool {
matches!(
self,
ValType::Ref(RefType {
is_nullable: true,
heap_type: HeapType::Func
})
)
}
#[inline]
pub fn is_externref(&self) -> bool {
matches!(
self,
ValType::Ref(RefType {
is_nullable: true,
heap_type: HeapType::Extern
})
)
}
#[inline]
pub fn is_anyref(&self) -> bool {
matches!(
self,
ValType::Ref(RefType {
is_nullable: true,
heap_type: HeapType::Any
})
)
}
#[inline]
pub fn as_ref(&self) -> Option<&RefType> {
match self {
ValType::Ref(r) => Some(r),
_ => None,
}
}
#[inline]
pub fn unwrap_ref(&self) -> &RefType {
self.as_ref()
.expect("ValType::unwrap_ref on a non-reference type")
}
pub fn matches(&self, other: &ValType) -> bool {
match (self, other) {
(Self::I32, Self::I32) => true,
(Self::I64, Self::I64) => true,
(Self::F32, Self::F32) => true,
(Self::F64, Self::F64) => true,
(Self::V128, Self::V128) => true,
(Self::Ref(a), Self::Ref(b)) => a.matches(b),
(Self::I32, _)
| (Self::I64, _)
| (Self::F32, _)
| (Self::F64, _)
| (Self::V128, _)
| (Self::Ref(_), _) => false,
}
}
pub fn eq(a: &Self, b: &Self) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn ensure_matches(&self, engine: &Engine, other: &ValType) -> Result<()> {
if !self.comes_from_same_engine(engine) || !other.comes_from_same_engine(engine) {
bail!("type used with wrong engine");
}
if self.matches(other) {
Ok(())
} else {
bail!("type mismatch: expected {other}, found {self}")
}
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
match self {
Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true,
Self::Ref(r) => r.comes_from_same_engine(engine),
}
}
pub(crate) fn to_wasm_type(&self) -> WasmValType {
match self {
Self::I32 => WasmValType::I32,
Self::I64 => WasmValType::I64,
Self::F32 => WasmValType::F32,
Self::F64 => WasmValType::F64,
Self::V128 => WasmValType::V128,
Self::Ref(r) => WasmValType::Ref(r.to_wasm_type()),
}
}
#[inline]
pub(crate) fn from_wasm_type(engine: &Engine, ty: &WasmValType) -> Self {
match ty {
WasmValType::I32 => Self::I32,
WasmValType::I64 => Self::I64,
WasmValType::F32 => Self::F32,
WasmValType::F64 => Self::F64,
WasmValType::V128 => Self::V128,
WasmValType::Ref(r) => Self::Ref(RefType::from_wasm_type(engine, r)),
}
}
}
#[derive(Clone, Hash)]
pub struct RefType {
is_nullable: bool,
heap_type: HeapType,
}
impl fmt::Debug for RefType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(self, f)
}
}
impl fmt::Display for RefType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(ref ")?;
if self.is_nullable() {
write!(f, "null ")?;
}
write!(f, "{})", self.heap_type())
}
}
impl RefType {
pub const EXTERNREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::Extern,
};
pub const FUNCREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::Func,
};
pub const NULLFUNCREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::NoFunc,
};
pub const ANYREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::Any,
};
pub const I31REF: Self = RefType {
is_nullable: true,
heap_type: HeapType::I31,
};
pub const NULLREF: Self = RefType {
is_nullable: true,
heap_type: HeapType::None,
};
pub fn new(is_nullable: bool, heap_type: HeapType) -> RefType {
RefType {
is_nullable,
heap_type,
}
}
pub fn is_nullable(&self) -> bool {
self.is_nullable
}
pub fn heap_type(&self) -> &HeapType {
&self.heap_type
}
pub fn matches(&self, other: &RefType) -> bool {
if self.is_nullable() && !other.is_nullable() {
return false;
}
self.heap_type().matches(other.heap_type())
}
pub fn eq(a: &RefType, b: &RefType) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn ensure_matches(&self, engine: &Engine, other: &RefType) -> Result<()> {
if !self.comes_from_same_engine(engine) || !other.comes_from_same_engine(engine) {
bail!("type used with wrong engine");
}
if self.matches(other) {
Ok(())
} else {
bail!("type mismatch: expected {other}, found {self}")
}
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
self.heap_type().comes_from_same_engine(engine)
}
pub(crate) fn to_wasm_type(&self) -> WasmRefType {
WasmRefType {
nullable: self.is_nullable(),
heap_type: self.heap_type().to_wasm_type(),
}
}
pub(crate) fn from_wasm_type(engine: &Engine, ty: &WasmRefType) -> RefType {
RefType {
is_nullable: ty.nullable,
heap_type: HeapType::from_wasm_type(engine, &ty.heap_type),
}
}
pub(crate) fn is_gc_heap_type(&self) -> bool {
self.heap_type().is_gc_heap_type()
}
}
#[derive(Debug, Clone, Hash)]
pub enum HeapType {
Extern,
Func,
Concrete(FuncType),
NoFunc,
Any,
I31,
None,
}
impl Display for HeapType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeapType::Extern => write!(f, "extern"),
HeapType::Func => write!(f, "func"),
HeapType::NoFunc => write!(f, "nofunc"),
HeapType::Any => write!(f, "any"),
HeapType::I31 => write!(f, "i31"),
HeapType::None => write!(f, "none"),
HeapType::Concrete(ty) => write!(f, "(concrete {:?})", ty.type_index()),
}
}
}
impl From<FuncType> for HeapType {
#[inline]
fn from(f: FuncType) -> Self {
HeapType::Concrete(f)
}
}
impl HeapType {
pub fn is_extern(&self) -> bool {
matches!(self, HeapType::Extern)
}
pub fn is_func(&self) -> bool {
matches!(self, HeapType::Func)
}
pub fn is_no_func(&self) -> bool {
matches!(self, HeapType::NoFunc)
}
pub fn is_any(&self) -> bool {
matches!(self, HeapType::Any)
}
pub fn is_i31(&self) -> bool {
matches!(self, HeapType::I31)
}
pub fn is_none(&self) -> bool {
matches!(self, HeapType::None)
}
pub fn is_abstract(&self) -> bool {
!self.is_concrete()
}
pub fn is_concrete(&self) -> bool {
matches!(self, HeapType::Concrete(_))
}
pub fn as_concrete(&self) -> Option<&FuncType> {
match self {
HeapType::Concrete(f) => Some(f),
_ => None,
}
}
pub fn unwrap_concrete(&self) -> &FuncType {
self.as_concrete()
.expect("HeapType::unwrap_concrete on non-concrete heap type")
}
pub fn top(&self, engine: &Engine) -> HeapType {
let _ = engine;
match self {
HeapType::Func | HeapType::Concrete(_) | HeapType::NoFunc => HeapType::Func,
HeapType::Extern => HeapType::Extern,
HeapType::Any | HeapType::I31 | HeapType::None => HeapType::Any,
}
}
pub fn matches(&self, other: &HeapType) -> bool {
match (self, other) {
(HeapType::Extern, HeapType::Extern) => true,
(HeapType::Extern, _) => false,
(HeapType::NoFunc, HeapType::NoFunc | HeapType::Concrete(_) | HeapType::Func) => true,
(HeapType::NoFunc, _) => false,
(HeapType::Concrete(_), HeapType::Func) => true,
(HeapType::Concrete(a), HeapType::Concrete(b)) => a.matches(b),
(HeapType::Concrete(_), _) => false,
(HeapType::Func, HeapType::Func) => true,
(HeapType::Func, _) => false,
(HeapType::None, HeapType::None | HeapType::I31 | HeapType::Any) => true,
(HeapType::None, _) => false,
(HeapType::I31, HeapType::I31 | HeapType::Any) => true,
(HeapType::I31, _) => false,
(HeapType::Any, HeapType::Any) => true,
(HeapType::Any, _) => false,
}
}
pub fn eq(a: &HeapType, b: &HeapType) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn ensure_matches(&self, engine: &Engine, other: &HeapType) -> Result<()> {
if !self.comes_from_same_engine(engine) || !other.comes_from_same_engine(engine) {
bail!("type used with wrong engine");
}
if self.matches(other) {
Ok(())
} else {
bail!("type mismatch: expected {other}, found {self}");
}
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
match self {
HeapType::Extern
| HeapType::Func
| HeapType::NoFunc
| HeapType::Any
| HeapType::I31
| HeapType::None => true,
HeapType::Concrete(ty) => ty.comes_from_same_engine(engine),
}
}
pub(crate) fn to_wasm_type(&self) -> WasmHeapType {
match self {
HeapType::Extern => WasmHeapType::Extern,
HeapType::Func => WasmHeapType::Func,
HeapType::NoFunc => WasmHeapType::NoFunc,
HeapType::Any => WasmHeapType::Any,
HeapType::I31 => WasmHeapType::I31,
HeapType::None => WasmHeapType::None,
HeapType::Concrete(f) => {
WasmHeapType::Concrete(EngineOrModuleTypeIndex::Engine(f.type_index().bits()))
}
}
}
pub(crate) fn from_wasm_type(engine: &Engine, ty: &WasmHeapType) -> HeapType {
match ty {
WasmHeapType::Extern => HeapType::Extern,
WasmHeapType::Func => HeapType::Func,
WasmHeapType::NoFunc => HeapType::NoFunc,
WasmHeapType::Any => HeapType::Any,
WasmHeapType::I31 => HeapType::I31,
WasmHeapType::None => HeapType::None,
WasmHeapType::Concrete(EngineOrModuleTypeIndex::Engine(idx)) => {
let idx = VMSharedTypeIndex::new(*idx);
HeapType::Concrete(FuncType::from_shared_type_index(engine, idx))
}
WasmHeapType::Concrete(EngineOrModuleTypeIndex::Module(_)) => {
panic!("HeapType::from_wasm_type on non-canonical heap type")
}
}
}
pub(crate) fn is_gc_heap_type(&self) -> bool {
match self {
HeapType::Extern | HeapType::Any => true,
Self::Concrete(_) => false,
HeapType::I31 => false,
HeapType::None => false,
HeapType::Func | HeapType::NoFunc => false,
}
}
}
#[derive(Debug, Clone)]
pub enum ExternType {
Func(FuncType),
Global(GlobalType),
Table(TableType),
Memory(MemoryType),
}
macro_rules! extern_type_accessors {
($(($variant:ident($ty:ty) $get:ident $unwrap:ident))*) => ($(
/// Attempt to return the underlying type of this external type,
/// returning `None` if it is a different type.
pub fn $get(&self) -> Option<&$ty> {
if let ExternType::$variant(e) = self {
Some(e)
} else {
None
}
}
pub fn $unwrap(&self) -> &$ty {
self.$get().expect(concat!("expected ", stringify!($ty)))
}
)*)
}
impl ExternType {
extern_type_accessors! {
(Func(FuncType) func unwrap_func)
(Global(GlobalType) global unwrap_global)
(Table(TableType) table unwrap_table)
(Memory(MemoryType) memory unwrap_memory)
}
pub(crate) fn from_wasmtime(
engine: &Engine,
types: &ModuleTypes,
ty: &EntityType,
) -> ExternType {
match ty {
EntityType::Function(idx) => match idx {
EngineOrModuleTypeIndex::Engine(e) => {
FuncType::from_shared_type_index(engine, VMSharedTypeIndex::new(*e)).into()
}
EngineOrModuleTypeIndex::Module(m) => {
FuncType::from_wasm_func_type(engine, types[*m].clone()).into()
}
},
EntityType::Global(ty) => GlobalType::from_wasmtime_global(engine, ty).into(),
EntityType::Memory(ty) => MemoryType::from_wasmtime_memory(ty).into(),
EntityType::Table(ty) => TableType::from_wasmtime_table(engine, ty).into(),
EntityType::Tag(_) => unimplemented!("wasm tag support"),
}
}
}
impl From<FuncType> for ExternType {
fn from(ty: FuncType) -> ExternType {
ExternType::Func(ty)
}
}
impl From<GlobalType> for ExternType {
fn from(ty: GlobalType) -> ExternType {
ExternType::Global(ty)
}
}
impl From<MemoryType> for ExternType {
fn from(ty: MemoryType) -> ExternType {
ExternType::Memory(ty)
}
}
impl From<TableType> for ExternType {
fn from(ty: TableType) -> ExternType {
ExternType::Table(ty)
}
}
#[derive(Debug, Clone, Hash)]
pub struct FuncType {
registered_type: RegisteredType,
}
impl Display for FuncType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(type (func")?;
if self.params().len() > 0 {
write!(f, " (param")?;
for p in self.params() {
write!(f, " {p}")?;
}
write!(f, ")")?;
}
if self.results().len() > 0 {
write!(f, " (result")?;
for r in self.results() {
write!(f, " {r}")?;
}
write!(f, ")")?;
}
write!(f, "))")
}
}
impl FuncType {
pub fn new(
engine: &Engine,
params: impl IntoIterator<Item = ValType>,
results: impl IntoIterator<Item = ValType>,
) -> FuncType {
let mut registrations = vec![];
let mut to_wasm_type = |ty: ValType| {
if let Some(r) = ty.as_ref() {
if let Some(c) = r.heap_type().as_concrete() {
registrations.push(c.registered_type.clone());
}
}
ty.to_wasm_type()
};
Self::from_wasm_func_type(
engine,
WasmFuncType::new(
params.into_iter().map(&mut to_wasm_type).collect(),
results.into_iter().map(&mut to_wasm_type).collect(),
),
)
}
pub fn engine(&self) -> &Engine {
self.registered_type.engine()
}
pub fn param(&self, i: usize) -> Option<ValType> {
let engine = self.engine();
self.registered_type
.params()
.get(i)
.map(|ty| ValType::from_wasm_type(engine, ty))
}
#[inline]
pub fn params(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
let engine = self.engine();
self.registered_type
.params()
.iter()
.map(|ty| ValType::from_wasm_type(engine, ty))
}
pub fn result(&self, i: usize) -> Option<ValType> {
let engine = self.engine();
self.registered_type
.returns()
.get(i)
.map(|ty| ValType::from_wasm_type(engine, ty))
}
#[inline]
pub fn results(&self) -> impl ExactSizeIterator<Item = ValType> + '_ {
let engine = self.engine();
self.registered_type
.returns()
.iter()
.map(|ty| ValType::from_wasm_type(engine, ty))
}
pub fn matches(&self, other: &FuncType) -> bool {
assert!(self.comes_from_same_engine(other.engine()));
if self.type_index() == other.type_index() {
return true;
}
self.params().len() == other.params().len()
&& self.results().len() == other.results().len()
&& self
.params()
.zip(other.params())
.all(|(a, b)| b.matches(&a))
&& self
.results()
.zip(other.results())
.all(|(a, b)| a.matches(&b))
}
pub fn eq(a: &FuncType, b: &FuncType) -> bool {
assert!(a.comes_from_same_engine(b.engine()));
a.type_index() == b.type_index()
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
Engine::same(self.registered_type.engine(), engine)
}
pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
self.registered_type.index()
}
pub(crate) fn as_wasm_func_type(&self) -> &WasmFuncType {
&self.registered_type
}
pub(crate) fn into_registered_type(self) -> RegisteredType {
self.registered_type
}
pub(crate) fn from_wasm_func_type(engine: &Engine, ty: WasmFuncType) -> FuncType {
let ty = RegisteredType::new(engine, ty);
Self {
registered_type: ty,
}
}
pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> FuncType {
let ty = RegisteredType::root(engine, index).expect(
"VMSharedTypeIndex is not registered in the Engine! Wrong \
engine? Didn't root the index somewhere?",
);
Self {
registered_type: ty,
}
}
}
#[derive(Debug, Clone, Hash)]
pub struct GlobalType {
content: ValType,
mutability: Mutability,
}
impl GlobalType {
pub fn new(content: ValType, mutability: Mutability) -> GlobalType {
GlobalType {
content,
mutability,
}
}
pub fn content(&self) -> &ValType {
&self.content
}
pub fn mutability(&self) -> Mutability {
self.mutability
}
pub(crate) fn to_wasm_type(&self) -> Global {
let wasm_ty = self.content().to_wasm_type();
let mutability = matches!(self.mutability(), Mutability::Var);
Global {
wasm_ty,
mutability,
}
}
pub(crate) fn from_wasmtime_global(engine: &Engine, global: &Global) -> GlobalType {
let ty = ValType::from_wasm_type(engine, &global.wasm_ty);
let mutability = if global.mutability {
Mutability::Var
} else {
Mutability::Const
};
GlobalType::new(ty, mutability)
}
}
#[derive(Debug, Clone, Hash)]
pub struct TableType {
element: RefType,
ty: Table,
}
impl TableType {
pub fn new(element: RefType, min: u32, max: Option<u32>) -> TableType {
let wasm_ty = element.to_wasm_type();
if cfg!(debug_assertions) {
wasm_ty
.trace(&mut |idx| match idx {
EngineOrModuleTypeIndex::Engine(_) => Ok(()),
EngineOrModuleTypeIndex::Module(module_idx) => Err(format!(
"found module-level canonicalized type index: {module_idx:?}"
)),
})
.expect("element type should be engine-level canonicalized");
}
TableType {
element,
ty: Table {
wasm_ty,
minimum: min,
maximum: max,
},
}
}
pub fn element(&self) -> &RefType {
&self.element
}
pub fn minimum(&self) -> u32 {
self.ty.minimum
}
pub fn maximum(&self) -> Option<u32> {
self.ty.maximum
}
pub(crate) fn from_wasmtime_table(engine: &Engine, table: &Table) -> TableType {
let element = RefType::from_wasm_type(engine, &table.wasm_ty);
TableType {
element,
ty: table.clone(),
}
}
pub(crate) fn wasmtime_table(&self) -> &Table {
&self.ty
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct MemoryType {
ty: Memory,
}
impl MemoryType {
pub fn new(minimum: u32, maximum: Option<u32>) -> MemoryType {
MemoryType {
ty: Memory {
memory64: false,
shared: false,
minimum: minimum.into(),
maximum: maximum.map(|i| i.into()),
},
}
}
pub fn new64(minimum: u64, maximum: Option<u64>) -> MemoryType {
MemoryType {
ty: Memory {
memory64: true,
shared: false,
minimum,
maximum,
},
}
}
pub fn shared(minimum: u32, maximum: u32) -> MemoryType {
MemoryType {
ty: Memory {
memory64: false,
shared: true,
minimum: minimum.into(),
maximum: Some(maximum.into()),
},
}
}
pub fn is_64(&self) -> bool {
self.ty.memory64
}
pub fn is_shared(&self) -> bool {
self.ty.shared
}
pub fn minimum(&self) -> u64 {
self.ty.minimum
}
pub fn maximum(&self) -> Option<u64> {
self.ty.maximum
}
pub(crate) fn from_wasmtime_memory(memory: &Memory) -> MemoryType {
MemoryType { ty: memory.clone() }
}
pub(crate) fn wasmtime_memory(&self) -> &Memory {
&self.ty
}
}
#[derive(Clone)]
pub struct ImportType<'module> {
module: &'module str,
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
}
impl<'module> ImportType<'module> {
pub(crate) fn new(
module: &'module str,
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
) -> ImportType<'module> {
ImportType {
module,
name,
ty,
types,
engine,
}
}
pub fn module(&self) -> &'module str {
self.module
}
pub fn name(&self) -> &'module str {
self.name
}
pub fn ty(&self) -> ExternType {
ExternType::from_wasmtime(self.engine, self.types, &self.ty)
}
}
impl<'module> fmt::Debug for ImportType<'module> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ImportType")
.field("module", &self.module())
.field("name", &self.name())
.field("ty", &self.ty())
.finish()
}
}
#[derive(Clone)]
pub struct ExportType<'module> {
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
}
impl<'module> ExportType<'module> {
pub(crate) fn new(
name: &'module str,
ty: EntityType,
types: &'module ModuleTypes,
engine: &'module Engine,
) -> ExportType<'module> {
ExportType {
name,
ty,
types,
engine,
}
}
pub fn name(&self) -> &'module str {
self.name
}
pub fn ty(&self) -> ExternType {
ExternType::from_wasmtime(self.engine, self.types, &self.ty)
}
}
impl<'module> fmt::Debug for ExportType<'module> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ExportType")
.field("name", &self.name().to_owned())
.field("ty", &self.ty())
.finish()
}
}