blob: d51cd638e7d8671a9fac834322f35d32db11cab9 [file] [log] [blame]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::borrow::Cow;
use std::io::Write;
use syn::ext::IdentExt;
use crate::bindgen::cdecl;
use crate::bindgen::config::{Config, Language};
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{GenericArgument, GenericParams, GenericPath, Path};
use crate::bindgen::library::Library;
use crate::bindgen::monomorph::Monomorphs;
use crate::bindgen::utilities::IterHelpers;
use crate::bindgen::writer::{Source, SourceWriter};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PrimitiveType {
Void,
Bool,
Char,
SChar,
UChar,
Char32,
Float,
Double,
VaList,
PtrDiffT,
Integer {
zeroable: bool,
signed: bool,
kind: IntKind,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum IntKind {
Short,
Int,
Long,
LongLong,
SizeT,
Size,
B8,
B16,
B32,
B64,
}
impl PrimitiveType {
pub fn maybe(path: &str) -> Option<PrimitiveType> {
Some(match path {
"c_void" => PrimitiveType::Void,
"c_char" => PrimitiveType::Char,
"c_schar" => PrimitiveType::SChar,
"c_uchar" => PrimitiveType::UChar,
"c_float" => PrimitiveType::Float,
"c_double" => PrimitiveType::Double,
"ptrdiff_t" => PrimitiveType::PtrDiffT,
"VaList" => PrimitiveType::VaList,
"bool" => PrimitiveType::Bool,
"char" => PrimitiveType::Char32,
"f32" => PrimitiveType::Float,
"f64" => PrimitiveType::Double,
_ => {
let (kind, signed) = match path {
"c_short" => (IntKind::Short, true),
"c_int" => (IntKind::Int, true),
"c_long" => (IntKind::Long, true),
"c_longlong" => (IntKind::LongLong, true),
"ssize_t" => (IntKind::SizeT, true),
"c_ushort" => (IntKind::Short, false),
"c_uint" => (IntKind::Int, false),
"c_ulong" => (IntKind::Long, false),
"c_ulonglong" => (IntKind::LongLong, false),
"size_t" => (IntKind::SizeT, false),
"RawFd" => (IntKind::Int, true),
"isize" | "intptr_t" => (IntKind::Size, true),
"usize" | "uintptr_t" => (IntKind::Size, false),
"u8" | "uint8_t" => (IntKind::B8, false),
"u16" | "uint16_t" => (IntKind::B16, false),
"u32" | "uint32_t" => (IntKind::B32, false),
"u64" | "uint64_t" => (IntKind::B64, false),
"i8" | "int8_t" => (IntKind::B8, true),
"i16" | "int16_t" => (IntKind::B16, true),
"i32" | "int32_t" => (IntKind::B32, true),
"i64" | "int64_t" => (IntKind::B64, true),
_ => return None,
};
PrimitiveType::Integer {
zeroable: true,
signed,
kind,
}
}
})
}
pub fn to_repr_rust(&self) -> &'static str {
match *self {
PrimitiveType::Bool => "bool",
PrimitiveType::Void => "c_void",
PrimitiveType::Char => "c_char",
PrimitiveType::SChar => "c_schar",
PrimitiveType::UChar => "c_uchar",
PrimitiveType::Char32 => "char",
PrimitiveType::Integer {
kind,
signed,
zeroable: _,
} => match kind {
IntKind::Short => {
if signed {
"c_short"
} else {
"c_ushort"
}
}
IntKind::Int => {
if signed {
"c_int"
} else {
"c_uint"
}
}
IntKind::Long => {
if signed {
"c_long"
} else {
"c_ulong"
}
}
IntKind::LongLong => {
if signed {
"c_longlong"
} else {
"c_ulonglong"
}
}
IntKind::SizeT => {
if signed {
"ssize_t"
} else {
"size_t"
}
}
IntKind::Size => {
if signed {
"isize"
} else {
"usize"
}
}
IntKind::B8 => {
if signed {
"i8"
} else {
"u8"
}
}
IntKind::B16 => {
if signed {
"i16"
} else {
"u16"
}
}
IntKind::B32 => {
if signed {
"i32"
} else {
"u32"
}
}
IntKind::B64 => {
if signed {
"i64"
} else {
"u64"
}
}
},
PrimitiveType::Float => "f32",
PrimitiveType::Double => "f64",
PrimitiveType::PtrDiffT => "ptrdiff_t",
PrimitiveType::VaList => "va_list",
}
}
pub fn to_repr_c(&self, config: &Config) -> &'static str {
match *self {
PrimitiveType::Void => "void",
PrimitiveType::Bool => "bool",
PrimitiveType::Char => "char",
PrimitiveType::SChar => "signed char",
PrimitiveType::UChar => "unsigned char",
// NOTE: It'd be nice to use a char32_t, but:
//
// * uchar.h is not present on mac (see #423).
//
// * char32_t isn't required to be compatible with Rust's char, as
// the C++ spec only requires it to be the same size as
// uint_least32_t, which is _not_ guaranteed to be 4-bytes.
//
PrimitiveType::Char32 => "uint32_t",
PrimitiveType::Integer {
kind,
signed,
zeroable: _,
} => match kind {
IntKind::Short => {
if signed {
"short"
} else {
"unsigned short"
}
}
IntKind::Int => {
if signed {
"int"
} else {
"unsigned int"
}
}
IntKind::Long => {
if signed {
"long"
} else {
"unsigned long"
}
}
IntKind::LongLong => {
if signed {
"long long"
} else {
"unsigned long long"
}
}
IntKind::SizeT => {
if signed {
"ssize_t"
} else {
"size_t"
}
}
IntKind::Size => {
if config.usize_is_size_t {
if signed {
"ptrdiff_t"
} else {
"size_t"
}
} else if signed {
"intptr_t"
} else {
"uintptr_t"
}
}
IntKind::B8 => {
if signed {
"int8_t"
} else {
"uint8_t"
}
}
IntKind::B16 => {
if signed {
"int16_t"
} else {
"uint16_t"
}
}
IntKind::B32 => {
if signed {
"int32_t"
} else {
"uint32_t"
}
}
IntKind::B64 => {
if signed {
"int64_t"
} else {
"uint64_t"
}
}
},
PrimitiveType::Float => "float",
PrimitiveType::Double => "double",
PrimitiveType::PtrDiffT => "ptrdiff_t",
PrimitiveType::VaList => "va_list",
}
}
fn can_cmp_order(&self) -> bool {
!matches!(*self, PrimitiveType::Bool)
}
fn can_cmp_eq(&self) -> bool {
true
}
}
/// Constant expressions.
///
/// Used for the `U` part of `[T; U]` and const generics. We support a very
/// limited vocabulary here: only identifiers and literals.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ConstExpr {
Name(String),
Value(String),
}
impl ConstExpr {
pub fn as_str(&self) -> &str {
match *self {
ConstExpr::Name(ref string) | ConstExpr::Value(ref string) => string,
}
}
pub fn rename_for_config(&mut self, config: &Config) {
if let ConstExpr::Name(ref mut name) = self {
config.export.rename(name);
}
}
pub fn load(expr: &syn::Expr) -> Result<Self, String> {
match *expr {
syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => {
let val = match *lit {
syn::Lit::Bool(syn::LitBool { value, .. }) => value.to_string(),
syn::Lit::Int(ref len) => len.base10_digits().to_string(),
syn::Lit::Byte(ref byte) => u8::to_string(&byte.value()),
syn::Lit::Char(ref ch) => u32::to_string(&ch.value().into()),
_ => return Err(format!("can't handle const expression {:?}", lit)),
};
Ok(ConstExpr::Value(val))
}
syn::Expr::Path(ref path) => {
let generic_path = GenericPath::load(&path.path)?;
Ok(ConstExpr::Name(generic_path.export_name().to_owned()))
}
_ => Err(format!("can't handle const expression {:?}", expr)),
}
}
pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> ConstExpr {
match *self {
ConstExpr::Name(ref name) => {
let path = Path::new(name);
for &(param, value) in mappings {
if path == *param {
match *value {
GenericArgument::Type(Type::Path(ref path))
if path.is_single_identifier() =>
{
// This happens when the generic argument is a path.
return ConstExpr::Name(path.name().to_string());
}
GenericArgument::Const(ref expr) => {
return expr.clone();
}
_ => {
// unsupported argument type - really should be an error
}
}
}
}
}
ConstExpr::Value(_) => {}
}
self.clone()
}
}
impl Source for ConstExpr {
fn write<F: Write>(&self, _config: &Config, out: &mut SourceWriter<F>) {
write!(out, "{}", self.as_str());
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Type {
Ptr {
ty: Box<Type>,
is_const: bool,
is_nullable: bool,
// FIXME: This is a bit of a hack, this is only to get us to codegen
// `T&` / `const T&`, but we should probably pass that down as an option
// to code generation or something.
is_ref: bool,
},
Path(GenericPath),
Primitive(PrimitiveType),
Array(Box<Type>, ConstExpr),
FuncPtr {
ret: Box<Type>,
args: Vec<(Option<String>, Type)>,
is_nullable: bool,
never_return: bool,
},
}
impl Type {
pub fn const_ref_to(ty: &Self) -> Self {
Type::Ptr {
ty: Box::new(ty.clone()),
is_const: true,
is_nullable: false,
is_ref: true,
}
}
pub fn load_from_output(output: &syn::ReturnType) -> Result<(Type, bool), String> {
let mut never_return = false;
let ty = match output {
syn::ReturnType::Default => Type::Primitive(PrimitiveType::Void),
syn::ReturnType::Type(_, ref ty) => {
if let syn::Type::Never(_) = ty.as_ref() {
never_return = true;
Type::Primitive(PrimitiveType::Void)
} else {
Type::load(ty)?.unwrap_or(Type::Primitive(PrimitiveType::Void))
}
}
};
Ok((ty, never_return))
}
pub fn load(ty: &syn::Type) -> Result<Option<Type>, String> {
let converted = match *ty {
syn::Type::Reference(ref reference) => {
let converted = Type::load(&reference.elem)?;
let converted = match converted {
Some(converted) => converted,
None => Type::Primitive(PrimitiveType::Void),
};
// TODO(emilio): we could make these use is_ref: true.
let is_const = reference.mutability.is_none();
Type::Ptr {
ty: Box::new(converted),
is_const,
is_nullable: false,
is_ref: false,
}
}
syn::Type::Ptr(ref pointer) => {
let converted = Type::load(&pointer.elem)?;
let converted = match converted {
Some(converted) => converted,
None => Type::Primitive(PrimitiveType::Void),
};
let is_const = pointer.mutability.is_none();
Type::Ptr {
ty: Box::new(converted),
is_const,
is_nullable: true,
is_ref: false,
}
}
syn::Type::Path(ref path) => {
let generic_path = GenericPath::load(&path.path)?;
if generic_path.name() == "PhantomData" || generic_path.name() == "PhantomPinned" {
return Ok(None);
}
if let Some(prim) = PrimitiveType::maybe(generic_path.name()) {
if !generic_path.generics().is_empty() {
return Err("Primitive has generics.".to_owned());
}
Type::Primitive(prim)
} else {
Type::Path(generic_path)
}
}
syn::Type::Array(syn::TypeArray {
ref elem, ref len, ..
}) => {
let converted = Type::load(elem)?;
let converted = match converted {
Some(converted) => converted,
None => return Err("Cannot have an array of zero sized types.".to_owned()),
};
let len = ConstExpr::load(len)?;
Type::Array(Box::new(converted), len)
}
syn::Type::BareFn(ref function) => {
let mut wildcard_counter = 0;
let args = function.inputs.iter().try_skip_map(|x| {
Type::load(&x.ty).map(|opt_ty| {
opt_ty.map(|ty| {
(
x.name.as_ref().map(|(ref ident, _)| {
if ident == "_" {
wildcard_counter += 1;
if wildcard_counter == 1 {
"_".to_owned()
} else {
format!("_{}", wildcard_counter - 1)
}
} else {
ident.unraw().to_string()
}
}),
ty,
)
})
})
})?;
let (ret, never_return) = Type::load_from_output(&function.output)?;
Type::FuncPtr {
ret: Box::new(ret),
args,
is_nullable: false,
never_return,
}
}
syn::Type::Tuple(ref tuple) => {
if tuple.elems.is_empty() {
return Ok(None);
}
return Err("Tuples are not supported types.".to_owned());
}
_ => return Err(format!("Unsupported type: {:?}", ty)),
};
Ok(Some(converted))
}
pub fn is_ptr(&self) -> bool {
matches!(*self, Type::Ptr { .. } | Type::FuncPtr { .. })
}
pub fn is_primitive_or_ptr_primitive(&self) -> bool {
match *self {
Type::Primitive(..) => true,
Type::Ptr { ref ty, .. } => matches!(ty.as_ref(), Type::Primitive(..)),
_ => false,
}
}
pub fn make_zeroable(&self) -> Option<Self> {
let (kind, signed) = match *self {
Type::Primitive(PrimitiveType::Integer {
zeroable: false,
kind,
signed,
}) => (kind, signed),
_ => return None,
};
Some(Type::Primitive(PrimitiveType::Integer {
kind,
signed,
zeroable: true,
}))
}
pub fn make_nullable(&self) -> Option<Self> {
match *self {
Type::Ptr {
ref ty,
is_const,
is_ref,
is_nullable: false,
} => Some(Type::Ptr {
ty: ty.clone(),
is_const,
is_ref,
is_nullable: true,
}),
Type::FuncPtr {
ref ret,
ref args,
is_nullable: false,
never_return,
} => Some(Type::FuncPtr {
ret: ret.clone(),
args: args.clone(),
is_nullable: true,
never_return,
}),
_ => None,
}
}
fn nonzero_to_primitive(&self) -> Option<Self> {
let path = match *self {
Type::Path(ref p) => p,
_ => return None,
};
if !path.generics().is_empty() {
return None;
}
let name = path.name();
if !name.starts_with("NonZero") {
return None;
}
let (kind, signed) = match path.name() {
"NonZeroU8" => (IntKind::B8, false),
"NonZeroU16" => (IntKind::B16, false),
"NonZeroU32" => (IntKind::B32, false),
"NonZeroU64" => (IntKind::B64, false),
"NonZeroUSize" => (IntKind::Size, false),
"NonZeroI8" => (IntKind::B8, true),
"NonZeroI16" => (IntKind::B16, true),
"NonZeroI32" => (IntKind::B32, true),
"NonZeroI64" => (IntKind::B64, true),
"NonZeroISize" => (IntKind::Size, true),
_ => return None,
};
Some(Type::Primitive(PrimitiveType::Integer {
zeroable: false,
signed,
kind,
}))
}
fn simplified_type(&self, config: &Config) -> Option<Self> {
let path = match *self {
Type::Path(ref p) => p,
_ => return None,
};
if path.generics().is_empty() {
return self.nonzero_to_primitive();
}
if path.generics().len() != 1 {
return None;
}
let unsimplified_generic = match path.generics()[0] {
GenericArgument::Type(ref ty) => ty,
GenericArgument::Const(_) => return None,
};
let generic = match unsimplified_generic.simplified_type(config) {
Some(generic) => Cow::Owned(generic),
None => Cow::Borrowed(unsimplified_generic),
};
match path.name() {
"Option" => {
if let Some(nullable) = generic.make_nullable() {
return Some(nullable);
}
if let Some(zeroable) = generic.make_zeroable() {
return Some(zeroable);
}
None
}
"NonNull" => Some(Type::Ptr {
ty: Box::new(generic.into_owned()),
is_const: false,
is_nullable: false,
is_ref: false,
}),
"Box" if config.language != Language::Cxx => Some(Type::Ptr {
ty: Box::new(generic.into_owned()),
is_const: false,
is_nullable: false,
is_ref: false,
}),
"Cell" => Some(generic.into_owned()),
"ManuallyDrop" | "MaybeUninit" | "Pin" if config.language != Language::Cxx => {
Some(generic.into_owned())
}
_ => None,
}
}
pub fn simplify_standard_types(&mut self, config: &Config) {
self.visit_types(|ty| ty.simplify_standard_types(config));
if let Some(ty) = self.simplified_type(config) {
*self = ty;
}
}
pub fn replace_self_with(&mut self, self_ty: &Path) {
if let Type::Path(ref mut generic_path) = *self {
generic_path.replace_self_with(self_ty);
}
self.visit_types(|ty| ty.replace_self_with(self_ty))
}
fn visit_types(&mut self, mut visitor: impl FnMut(&mut Type)) {
match *self {
Type::Array(ref mut ty, ..) | Type::Ptr { ref mut ty, .. } => visitor(ty),
Type::Path(ref mut path) => {
for generic in path.generics_mut() {
match *generic {
GenericArgument::Type(ref mut ty) => visitor(ty),
GenericArgument::Const(_) => {}
}
}
}
Type::Primitive(..) => {}
Type::FuncPtr {
ref mut ret,
ref mut args,
..
} => {
visitor(ret);
for arg in args {
visitor(&mut arg.1)
}
}
}
}
pub fn get_root_path(&self) -> Option<Path> {
let mut current = self;
loop {
match *current {
Type::Ptr { ref ty, .. } => current = ty,
Type::Path(ref generic) => {
return Some(generic.path().clone());
}
Type::Primitive(..) => {
return None;
}
Type::Array(..) => {
return None;
}
Type::FuncPtr { .. } => {
return None;
}
};
}
}
pub fn specialize(&self, mappings: &[(&Path, &GenericArgument)]) -> Type {
match *self {
Type::Ptr {
ref ty,
is_const,
is_nullable,
is_ref,
} => Type::Ptr {
ty: Box::new(ty.specialize(mappings)),
is_const,
is_nullable,
is_ref,
},
Type::Path(ref generic_path) => {
for &(param, value) in mappings {
if generic_path.path() == param {
if let GenericArgument::Type(ref ty) = *value {
return ty.clone();
}
}
}
let specialized = GenericPath::new(
generic_path.path().clone(),
generic_path
.generics()
.iter()
.map(|x| x.specialize(mappings))
.collect(),
);
Type::Path(specialized)
}
Type::Primitive(ref primitive) => Type::Primitive(primitive.clone()),
Type::Array(ref ty, ref constant) => Type::Array(
Box::new(ty.specialize(mappings)),
constant.specialize(mappings),
),
Type::FuncPtr {
ref ret,
ref args,
is_nullable,
never_return,
} => Type::FuncPtr {
ret: Box::new(ret.specialize(mappings)),
args: args
.iter()
.cloned()
.map(|(name, ty)| (name, ty.specialize(mappings)))
.collect(),
is_nullable,
never_return,
},
}
}
pub fn add_dependencies_ignoring_generics(
&self,
generic_params: &GenericParams,
library: &Library,
out: &mut Dependencies,
) {
match *self {
Type::Ptr { ref ty, .. } => {
ty.add_dependencies_ignoring_generics(generic_params, library, out);
}
Type::Path(ref generic) => {
for generic_value in generic.generics() {
if let GenericArgument::Type(ref ty) = *generic_value {
ty.add_dependencies_ignoring_generics(generic_params, library, out);
}
}
let path = generic.path();
if !generic_params.iter().any(|param| param.name() == path) {
if let Some(items) = library.get_items(path) {
if !out.items.contains(path) {
out.items.insert(path.clone());
for item in &items {
item.deref().add_dependencies(library, out);
}
for item in items {
out.order.push(item);
}
}
} else {
warn!(
"Can't find {}. This usually means that this type was incompatible or \
not found.",
path
);
}
}
}
Type::Primitive(_) => {}
Type::Array(ref ty, _) => {
ty.add_dependencies_ignoring_generics(generic_params, library, out);
}
Type::FuncPtr {
ref ret, ref args, ..
} => {
ret.add_dependencies_ignoring_generics(generic_params, library, out);
for (_, ref arg) in args {
arg.add_dependencies_ignoring_generics(generic_params, library, out);
}
}
}
}
pub fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
self.add_dependencies_ignoring_generics(&GenericParams::default(), library, out)
}
pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
match *self {
Type::Ptr { ref ty, .. } => {
ty.add_monomorphs(library, out);
}
Type::Path(ref generic) => {
if generic.generics().is_empty() || out.contains(generic) {
return;
}
let path = generic.path();
if let Some(items) = library.get_items(path) {
for item in items {
item.deref()
.instantiate_monomorph(generic.generics(), library, out);
}
}
}
Type::Primitive(_) => {}
Type::Array(ref ty, _) => {
ty.add_monomorphs(library, out);
}
Type::FuncPtr {
ref ret, ref args, ..
} => {
ret.add_monomorphs(library, out);
for (_, ref arg) in args {
arg.add_monomorphs(library, out);
}
}
}
}
pub fn rename_for_config(&mut self, config: &Config, generic_params: &GenericParams) {
match *self {
Type::Ptr { ref mut ty, .. } => {
ty.rename_for_config(config, generic_params);
}
Type::Path(ref mut ty) => {
ty.rename_for_config(config, generic_params);
}
Type::Primitive(_) => {}
Type::Array(ref mut ty, ref mut len) => {
ty.rename_for_config(config, generic_params);
len.rename_for_config(config);
}
Type::FuncPtr {
ref mut ret,
ref mut args,
..
} => {
ret.rename_for_config(config, generic_params);
for (_, arg) in args {
arg.rename_for_config(config, generic_params);
}
}
}
}
pub fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) {
match *self {
Type::Ptr { ref mut ty, .. } => {
ty.resolve_declaration_types(resolver);
}
Type::Path(ref mut generic_path) => {
generic_path.resolve_declaration_types(resolver);
}
Type::Primitive(_) => {}
Type::Array(ref mut ty, _) => {
ty.resolve_declaration_types(resolver);
}
Type::FuncPtr {
ref mut ret,
ref mut args,
..
} => {
ret.resolve_declaration_types(resolver);
for (_, ref mut arg) in args {
arg.resolve_declaration_types(resolver);
}
}
}
}
pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) {
match *self {
Type::Ptr { ref mut ty, .. } => {
ty.mangle_paths(monomorphs);
}
Type::Path(ref mut generic_path) => {
if generic_path.generics().is_empty() {
return;
}
if let Some(mangled_path) = monomorphs.mangle_path(generic_path) {
*generic_path = GenericPath::new(mangled_path.clone(), vec![]);
} else {
warn!(
"Cannot find a mangling for generic path {:?}. This usually means that a \
type referenced by this generic was incompatible or not found.",
generic_path
);
}
}
Type::Primitive(_) => {}
Type::Array(ref mut ty, _) => {
ty.mangle_paths(monomorphs);
}
Type::FuncPtr {
ref mut ret,
ref mut args,
..
} => {
ret.mangle_paths(monomorphs);
for (_, ref mut arg) in args {
arg.mangle_paths(monomorphs);
}
}
}
}
pub fn can_cmp_order(&self) -> bool {
match *self {
// FIXME: Shouldn't this look at ty.can_cmp_order() as well?
Type::Ptr { is_ref, .. } => !is_ref,
Type::Path(..) => true,
Type::Primitive(ref p) => p.can_cmp_order(),
Type::Array(..) => false,
Type::FuncPtr { .. } => false,
}
}
pub fn can_cmp_eq(&self) -> bool {
match *self {
Type::Ptr { ref ty, is_ref, .. } => !is_ref || ty.can_cmp_eq(),
Type::Path(..) => true,
Type::Primitive(ref p) => p.can_cmp_eq(),
Type::Array(..) => false,
Type::FuncPtr { .. } => true,
}
}
}
impl Source for String {
fn write<F: Write>(&self, _config: &Config, out: &mut SourceWriter<F>) {
write!(out, "{}", self);
}
}
impl Source for Type {
fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
cdecl::write_type(out, self, config);
}
}