use pretty::BoxDoc;
use crate::import::ImportRef;
use crate::lang::CodeLang;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypePresentation<'a> {
GenericWrap {
name: &'a str,
},
Prefix {
prefix: &'a str,
},
Postfix {
suffix: &'a str,
},
Surround {
prefix: &'a str,
suffix: &'a str,
},
Delimited {
open: &'a str,
sep: &'a str,
close: &'a str,
},
Infix {
sep: &'a str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum GenericApplicationStyle {
Delimited,
PrefixJuxtaposition,
PostfixJuxtaposition,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FunctionPresentation<'a> {
pub keyword: &'a str,
pub params_open: &'a str,
pub params_sep: &'a str,
pub params_close: &'a str,
pub arrow: &'a str,
pub return_first: bool,
pub curried: bool,
pub wrapper_open: &'a str,
pub wrapper_close: &'a str,
}
impl Default for FunctionPresentation<'_> {
fn default() -> Self {
Self {
keyword: "",
params_open: "(",
params_sep: ", ",
params_close: ")",
arrow: " => ",
return_first: false,
curried: false,
wrapper_open: "",
wrapper_close: "",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssociatedTypeStyle<'a> {
QualifiedPath {
open: &'a str,
as_kw: &'a str,
close_sep: &'a str,
simple_sep: &'a str,
},
DotAccess,
IndexAccess {
open: &'a str,
close: &'a str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BoundsPresentation<'a> {
pub keyword: &'a str,
pub separator: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WildcardPresentation<'a> {
pub unbounded: &'a str,
pub upper_keyword: &'a str,
pub lower_keyword: &'a str,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum TypeName {
Importable {
module: String,
name: String,
is_type_only: bool,
alias: Option<String>,
},
Primitive(String),
Array(Box<TypeName>),
ReadonlyArray(Box<TypeName>),
Generic {
base: Box<TypeName>,
params: Vec<TypeName>,
},
Union(Vec<TypeName>),
Intersection(Vec<TypeName>),
Pointer(Box<TypeName>),
Slice(Box<TypeName>),
Map {
key: Box<TypeName>,
value: Box<TypeName>,
},
Optional(Box<TypeName>),
Tuple(Vec<TypeName>),
Reference {
inner: Box<TypeName>,
mutable: bool,
#[serde(default)]
lifetime: Option<String>,
},
AssociatedType {
base: Box<TypeName>,
qualifier: Option<Box<TypeName>>,
member: String,
},
ImplTrait {
bounds: Vec<TypeName>,
},
DynTrait {
bounds: Vec<TypeName>,
},
Wildcard {
upper_bound: Option<Box<TypeName>>,
lower_bound: Option<Box<TypeName>>,
},
Function {
params: Vec<TypeName>,
return_type: Box<TypeName>,
},
Raw(String),
}
fn render_presentation(
pres: &TypePresentation<'_>,
inner_docs: Vec<BoxDoc<'static, ()>>,
gs: &crate::lang::config::GenericSyntaxConfig<'_>,
) -> BoxDoc<'static, ()> {
match pres {
TypePresentation::GenericWrap { name } => {
let sep = BoxDoc::text(",").append(BoxDoc::softline());
let params = BoxDoc::intersperse(inner_docs, sep);
BoxDoc::text(name.to_string())
.append(BoxDoc::text(gs.open.to_string()))
.append(params.nest(2).group())
.append(BoxDoc::text(gs.close.to_string()))
}
TypePresentation::Prefix { prefix } => {
debug_assert_eq!(inner_docs.len(), 1);
let inner = inner_docs.into_iter().next().unwrap_or_else(BoxDoc::nil);
BoxDoc::text(prefix.to_string()).append(inner)
}
TypePresentation::Postfix { suffix } => {
debug_assert_eq!(inner_docs.len(), 1);
let inner = inner_docs.into_iter().next().unwrap_or_else(BoxDoc::nil);
inner.append(BoxDoc::text(suffix.to_string()))
}
TypePresentation::Surround { prefix, suffix } => {
debug_assert_eq!(inner_docs.len(), 1);
let inner = inner_docs.into_iter().next().unwrap_or_else(BoxDoc::nil);
BoxDoc::text(prefix.to_string())
.append(inner)
.append(BoxDoc::text(suffix.to_string()))
}
TypePresentation::Delimited { open, sep, close } => {
let separator = BoxDoc::text(sep.to_string());
let body = BoxDoc::intersperse(inner_docs, separator);
BoxDoc::text(open.to_string())
.append(body.nest(2).group())
.append(BoxDoc::text(close.to_string()))
}
TypePresentation::Infix { sep } => {
let sep_trimmed = sep.trim_start();
let separator = BoxDoc::softline().append(BoxDoc::text(sep_trimmed.to_string()));
BoxDoc::intersperse(inner_docs, separator).group()
}
}
}
fn render_function_presentation(
pres: &FunctionPresentation<'_>,
param_docs: Vec<BoxDoc<'static, ()>>,
return_doc: BoxDoc<'static, ()>,
) -> BoxDoc<'static, ()> {
if pres.curried {
let mut all = param_docs;
all.push(return_doc);
let sep = BoxDoc::text(pres.arrow.to_string());
return BoxDoc::intersperse(all, sep);
}
let sep = BoxDoc::text(pres.params_sep.to_string());
let params_doc = BoxDoc::intersperse(param_docs, sep);
let params_block = BoxDoc::text(pres.params_open.to_string())
.append(params_doc.nest(2).group())
.append(BoxDoc::text(pres.params_close.to_string()));
let keyword_doc = if pres.keyword.is_empty() {
BoxDoc::nil()
} else {
BoxDoc::text(pres.keyword.to_string())
};
let signature = if pres.return_first {
return_doc.append(keyword_doc).append(params_block)
} else {
keyword_doc
.append(params_block)
.append(BoxDoc::text(pres.arrow.to_string()))
.append(return_doc)
};
if pres.wrapper_open.is_empty() {
signature
} else {
BoxDoc::text(pres.wrapper_open.to_string())
.append(signature)
.append(BoxDoc::text(pres.wrapper_close.to_string()))
}
}
fn is_compound_type(t: &TypeName) -> bool {
matches!(
t,
TypeName::Generic { .. }
| TypeName::Union(_)
| TypeName::Intersection(_)
| TypeName::Function { .. }
| TypeName::Tuple(_)
)
}
impl TypeName {
pub fn importable(module: &str, name: &str) -> Self {
TypeName::Importable {
module: module.to_string(),
name: name.to_string(),
is_type_only: false,
alias: None,
}
}
pub fn importable_type(module: &str, name: &str) -> Self {
TypeName::Importable {
module: module.to_string(),
name: name.to_string(),
is_type_only: true,
alias: None,
}
}
pub fn primitive(name: &str) -> Self {
TypeName::Primitive(name.to_string())
}
pub fn is_empty(&self) -> bool {
matches!(self, TypeName::Primitive(s) | TypeName::Raw(s) if s.is_empty())
}
pub fn with_alias(mut self, alias: &str) -> Self {
if let TypeName::Importable {
alias: ref mut a, ..
} = self
{
*a = Some(alias.to_string());
}
self
}
pub fn array(inner: TypeName) -> Self {
TypeName::Array(Box::new(inner))
}
pub fn readonly_array(inner: TypeName) -> Self {
TypeName::ReadonlyArray(Box::new(inner))
}
pub fn generic(base: TypeName, params: Vec<TypeName>) -> Self {
TypeName::Generic {
base: Box::new(base),
params,
}
}
pub fn union(members: Vec<TypeName>) -> Self {
TypeName::Union(members)
}
pub fn intersection(members: Vec<TypeName>) -> Self {
TypeName::Intersection(members)
}
pub fn pointer(inner: TypeName) -> Self {
TypeName::Pointer(Box::new(inner))
}
pub fn slice(inner: TypeName) -> Self {
TypeName::Slice(Box::new(inner))
}
pub fn map(key: TypeName, value: TypeName) -> Self {
TypeName::Map {
key: Box::new(key),
value: Box::new(value),
}
}
pub fn optional(inner: TypeName) -> Self {
TypeName::Optional(Box::new(inner))
}
pub fn tuple(elements: Vec<TypeName>) -> Self {
TypeName::Tuple(elements)
}
pub fn unit() -> Self {
TypeName::Tuple(Vec::new())
}
pub fn reference(inner: TypeName) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: false,
lifetime: None,
}
}
pub fn reference_mut(inner: TypeName) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: true,
lifetime: None,
}
}
pub fn reference_with_lifetime(inner: TypeName, lifetime: &str) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: false,
lifetime: Some(lifetime.to_string()),
}
}
pub fn reference_mut_with_lifetime(inner: TypeName, lifetime: &str) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: true,
lifetime: Some(lifetime.to_string()),
}
}
pub fn function(params: Vec<TypeName>, return_type: TypeName) -> Self {
TypeName::Function {
params,
return_type: Box::new(return_type),
}
}
pub fn raw(s: &str) -> Self {
TypeName::Raw(s.to_string())
}
pub fn associated_type(base: TypeName, qualifier: Option<TypeName>, member: &str) -> Self {
TypeName::AssociatedType {
base: Box::new(base),
qualifier: qualifier.map(Box::new),
member: member.to_string(),
}
}
pub fn member_type(base: TypeName, member: &str) -> Self {
TypeName::AssociatedType {
base: Box::new(base),
qualifier: None,
member: member.to_string(),
}
}
pub fn impl_trait(bounds: Vec<TypeName>) -> Self {
TypeName::ImplTrait { bounds }
}
pub fn dyn_trait(bounds: Vec<TypeName>) -> Self {
TypeName::DynTrait { bounds }
}
pub fn wildcard() -> Self {
TypeName::Wildcard {
upper_bound: None,
lower_bound: None,
}
}
pub fn wildcard_extends(bound: TypeName) -> Self {
TypeName::Wildcard {
upper_bound: Some(Box::new(bound)),
lower_bound: None,
}
}
pub fn wildcard_super(bound: TypeName) -> Self {
TypeName::Wildcard {
upper_bound: None,
lower_bound: Some(Box::new(bound)),
}
}
pub fn simple_name(&self) -> Option<&str> {
match self {
TypeName::Importable { name, .. } => Some(name),
TypeName::Primitive(name) => Some(name),
TypeName::Generic { base, .. } => base.simple_name(),
TypeName::Raw(s) => Some(s),
_ => None,
}
}
pub fn collect_imports(&self, out: &mut Vec<ImportRef>) {
match self {
TypeName::Importable {
module,
name,
is_type_only,
alias,
} => {
out.push(ImportRef {
module: module.clone(),
name: name.clone(),
is_type_only: *is_type_only,
alias: alias.clone(),
});
}
TypeName::Array(inner)
| TypeName::ReadonlyArray(inner)
| TypeName::Pointer(inner)
| TypeName::Slice(inner)
| TypeName::Optional(inner) => {
inner.collect_imports(out);
}
TypeName::Reference { inner, .. } => {
inner.collect_imports(out);
}
TypeName::Generic { base, params } => {
base.collect_imports(out);
for p in params {
p.collect_imports(out);
}
}
TypeName::Union(members)
| TypeName::Intersection(members)
| TypeName::Tuple(members) => {
for m in members {
m.collect_imports(out);
}
}
TypeName::Map { key, value } => {
key.collect_imports(out);
value.collect_imports(out);
}
TypeName::Function {
params,
return_type,
} => {
for p in params {
p.collect_imports(out);
}
return_type.collect_imports(out);
}
TypeName::AssociatedType {
base, qualifier, ..
} => {
base.collect_imports(out);
if let Some(q) = qualifier {
q.collect_imports(out);
}
}
TypeName::ImplTrait { bounds } | TypeName::DynTrait { bounds } => {
for b in bounds {
b.collect_imports(out);
}
}
TypeName::Wildcard {
upper_bound,
lower_bound,
} => {
if let Some(ub) = upper_bound {
ub.collect_imports(out);
}
if let Some(lb) = lower_bound {
lb.collect_imports(out);
}
}
TypeName::Primitive(_) | TypeName::Raw(_) => {}
}
}
pub fn to_doc<F>(&self, resolve: &F) -> BoxDoc<'static, ()>
where
F: Fn(&str, &str) -> String,
{
match self {
TypeName::Importable { module, name, .. } => {
let display = resolve(module, name);
BoxDoc::text(display)
}
TypeName::Primitive(name) => BoxDoc::text(name.clone()),
TypeName::Raw(s) => BoxDoc::text(s.clone()),
TypeName::Array(inner) => {
inner.to_doc(resolve).append(BoxDoc::text("[]"))
}
TypeName::ReadonlyArray(inner) => {
BoxDoc::text("readonly ")
.append(inner.to_doc(resolve))
.append(BoxDoc::text("[]"))
}
TypeName::Generic { base, params } => {
let base_doc = base.to_doc(resolve);
let params_docs: Vec<_> = params.iter().map(|p| p.to_doc(resolve)).collect();
let sep = BoxDoc::text(",").append(BoxDoc::softline());
let params_doc = BoxDoc::intersperse(params_docs, sep);
base_doc
.append(BoxDoc::text("<"))
.append(params_doc.nest(2).group())
.append(BoxDoc::text(">"))
}
TypeName::Union(members) => {
let docs: Vec<_> = members.iter().map(|m| m.to_doc(resolve)).collect();
let sep = BoxDoc::softline().append(BoxDoc::text("| "));
BoxDoc::intersperse(docs, sep).group()
}
TypeName::Intersection(members) => {
let docs: Vec<_> = members.iter().map(|m| m.to_doc(resolve)).collect();
let sep = BoxDoc::softline().append(BoxDoc::text("& "));
BoxDoc::intersperse(docs, sep).group()
}
TypeName::Pointer(inner) => BoxDoc::text("*").append(inner.to_doc(resolve)),
TypeName::Slice(inner) => BoxDoc::text("[]").append(inner.to_doc(resolve)),
TypeName::Map { key, value } => BoxDoc::text("map[")
.append(key.to_doc(resolve))
.append(BoxDoc::text("]"))
.append(value.to_doc(resolve)),
TypeName::Optional(inner) => {
let inner_doc = inner.to_doc(resolve);
inner_doc
.append(BoxDoc::softline())
.append(BoxDoc::text("| null"))
.group()
}
TypeName::Tuple(elements) => {
let docs: Vec<_> = elements.iter().map(|e| e.to_doc(resolve)).collect();
if docs.is_empty() {
return BoxDoc::text("()");
}
let sep = BoxDoc::text(",").append(BoxDoc::softline());
BoxDoc::text("(")
.append(BoxDoc::intersperse(docs, sep).nest(2).group())
.append(BoxDoc::text(")"))
}
TypeName::Reference {
inner,
mutable,
lifetime,
} => {
let mut prefix = String::from("&");
if let Some(lt) = lifetime {
prefix.push_str(lt);
prefix.push(' ');
}
if *mutable {
prefix.push_str("mut ");
}
BoxDoc::text(prefix).append(inner.to_doc(resolve))
}
TypeName::Function {
params,
return_type,
} => {
let params_docs: Vec<_> = params.iter().map(|p| p.to_doc(resolve)).collect();
let sep = BoxDoc::text(",").append(BoxDoc::softline());
let params_doc = BoxDoc::intersperse(params_docs, sep);
BoxDoc::text("(")
.append(params_doc.nest(2).group())
.append(BoxDoc::text(") => "))
.append(return_type.to_doc(resolve))
}
TypeName::AssociatedType {
base,
qualifier,
member,
} => {
if let Some(qual) = qualifier {
BoxDoc::text("<")
.append(base.to_doc(resolve))
.append(BoxDoc::text(" as "))
.append(qual.to_doc(resolve))
.append(BoxDoc::text(">::"))
.append(BoxDoc::text(member.clone()))
} else {
base.to_doc(resolve)
.append(BoxDoc::text("::"))
.append(BoxDoc::text(member.clone()))
}
}
TypeName::ImplTrait { bounds } => {
let docs: Vec<_> = bounds.iter().map(|b| b.to_doc(resolve)).collect();
let sep = BoxDoc::text(" + ");
BoxDoc::text("impl ").append(BoxDoc::intersperse(docs, sep))
}
TypeName::DynTrait { bounds } => {
let docs: Vec<_> = bounds.iter().map(|b| b.to_doc(resolve)).collect();
let sep = BoxDoc::text(" + ");
BoxDoc::text("dyn ").append(BoxDoc::intersperse(docs, sep))
}
TypeName::Wildcard {
upper_bound,
lower_bound,
} => {
debug_assert!(
upper_bound.is_none() || lower_bound.is_none(),
"Wildcard cannot have both upper and lower bounds"
);
if let Some(ub) = upper_bound {
BoxDoc::text("? extends ").append(ub.to_doc(resolve))
} else if let Some(lb) = lower_bound {
BoxDoc::text("? super ").append(lb.to_doc(resolve))
} else {
BoxDoc::text("?")
}
}
}
}
pub fn render<F>(
&self,
width: usize,
resolve: &F,
) -> Result<String, crate::error::SigilStitchError>
where
F: Fn(&str, &str) -> String,
{
let doc = self.to_doc(resolve);
let mut buf = Vec::new();
doc.render(width, &mut buf)
.map_err(|e| crate::error::SigilStitchError::Render {
context: "TypeName::render".to_string(),
message: e.to_string(),
})?;
String::from_utf8(buf).map_err(|e| crate::error::SigilStitchError::Render {
context: "TypeName::render UTF-8 conversion".to_string(),
message: e.to_string(),
})
}
pub fn to_doc_with_lang<F>(&self, resolve: &F, lang: &dyn CodeLang) -> BoxDoc<'static, ()>
where
F: Fn(&str, &str) -> String,
{
let tp = lang.type_presentation();
let gs = lang.generic_syntax();
match self {
TypeName::Generic { base, params } => {
let base_doc = base.to_doc_with_lang(resolve, lang);
let params_docs: Vec<_> = params
.iter()
.map(|p| p.to_doc_with_lang(resolve, lang))
.collect();
match gs.application_style {
GenericApplicationStyle::Delimited => {
let sep = BoxDoc::text(",").append(BoxDoc::softline());
let params_doc = BoxDoc::intersperse(params_docs, sep);
base_doc
.append(BoxDoc::text(gs.open.to_string()))
.append(params_doc.nest(2).group())
.append(BoxDoc::text(gs.close.to_string()))
}
GenericApplicationStyle::PrefixJuxtaposition => {
let mut doc = base_doc;
for (i, param_doc) in params_docs.into_iter().enumerate() {
doc = doc.append(BoxDoc::text(" "));
if is_compound_type(¶ms[i]) {
doc = doc
.append(BoxDoc::text("("))
.append(param_doc)
.append(BoxDoc::text(")"));
} else {
doc = doc.append(param_doc);
}
}
doc
}
GenericApplicationStyle::PostfixJuxtaposition => {
if params_docs.len() == 1 {
params_docs
.into_iter()
.next()
.unwrap()
.append(BoxDoc::text(" "))
.append(base_doc)
} else {
let sep = BoxDoc::text(",").append(BoxDoc::softline());
let params_doc = BoxDoc::intersperse(params_docs, sep);
BoxDoc::text("(")
.append(params_doc.nest(2).group())
.append(BoxDoc::text(") "))
.append(base_doc)
}
}
}
}
TypeName::Array(inner) => {
let inner_doc = inner.to_doc_with_lang(resolve, lang);
render_presentation(&tp.array, vec![inner_doc], &gs)
}
TypeName::ReadonlyArray(inner) => {
let inner_doc = inner.to_doc_with_lang(resolve, lang);
if let Some(pres) = tp.readonly_array {
render_presentation(&pres, vec![inner_doc], &gs)
} else {
let array_doc = render_presentation(&tp.array, vec![inner_doc], &gs);
BoxDoc::text("readonly ").append(array_doc)
}
}
TypeName::Union(members) => {
let docs: Vec<_> = members
.iter()
.map(|m| m.to_doc_with_lang(resolve, lang))
.collect();
render_presentation(&tp.union, docs, &gs)
}
TypeName::Intersection(members) => {
let docs: Vec<_> = members
.iter()
.map(|m| m.to_doc_with_lang(resolve, lang))
.collect();
render_presentation(&tp.intersection, docs, &gs)
}
TypeName::Pointer(inner) => {
let inner_doc = inner.to_doc_with_lang(resolve, lang);
render_presentation(&tp.pointer, vec![inner_doc], &gs)
}
TypeName::Slice(inner) => {
let inner_doc = inner.to_doc_with_lang(resolve, lang);
render_presentation(&tp.slice, vec![inner_doc], &gs)
}
TypeName::Map { key, value } => {
let key_doc = key.to_doc_with_lang(resolve, lang);
let value_doc = value.to_doc_with_lang(resolve, lang);
render_presentation(&tp.map, vec![key_doc, value_doc], &gs)
}
TypeName::Optional(inner) => {
let inner_doc = inner.to_doc_with_lang(resolve, lang);
match &tp.optional {
TypePresentation::Infix { .. } => {
let null_doc = BoxDoc::text(tp.optional_absent_literal.to_string());
render_presentation(&tp.optional, vec![inner_doc, null_doc], &gs)
}
_ => render_presentation(&tp.optional, vec![inner_doc], &gs),
}
}
TypeName::Tuple(elements) => {
let docs: Vec<_> = elements
.iter()
.map(|e| e.to_doc_with_lang(resolve, lang))
.collect();
render_presentation(&tp.tuple, docs, &gs)
}
TypeName::Reference {
inner,
mutable,
lifetime,
} => {
let inner_doc = inner.to_doc_with_lang(resolve, lang);
if let Some(lt) = lifetime {
let mut prefix = String::from("&");
prefix.push_str(lt);
prefix.push(' ');
if *mutable {
prefix.push_str("mut ");
}
BoxDoc::text(prefix).append(inner_doc)
} else {
let pres = if *mutable {
tp.reference_mut
} else {
tp.reference
};
render_presentation(&pres, vec![inner_doc], &gs)
}
}
TypeName::Function {
params,
return_type,
} => {
let param_docs: Vec<_> = params
.iter()
.map(|p| p.to_doc_with_lang(resolve, lang))
.collect();
let return_doc = return_type.to_doc_with_lang(resolve, lang);
render_function_presentation(&tp.function, param_docs, return_doc)
}
TypeName::AssociatedType {
base,
qualifier,
member,
} => {
let base_doc = base.to_doc_with_lang(resolve, lang);
match tp.associated_type {
AssociatedTypeStyle::QualifiedPath {
open,
as_kw,
close_sep,
simple_sep,
} => {
if let Some(qual) = qualifier {
let qual_doc = qual.to_doc_with_lang(resolve, lang);
BoxDoc::text(open.to_string())
.append(base_doc)
.append(BoxDoc::text(as_kw.to_string()))
.append(qual_doc)
.append(BoxDoc::text(close_sep.to_string()))
.append(BoxDoc::text(member.clone()))
} else {
base_doc
.append(BoxDoc::text(simple_sep.to_string()))
.append(BoxDoc::text(member.clone()))
}
}
AssociatedTypeStyle::DotAccess => base_doc
.append(BoxDoc::text("."))
.append(BoxDoc::text(member.clone())),
AssociatedTypeStyle::IndexAccess { open, close } => base_doc
.append(BoxDoc::text(open.to_string()))
.append(BoxDoc::text(member.clone()))
.append(BoxDoc::text(close.to_string())),
}
}
TypeName::ImplTrait { bounds } => {
let docs: Vec<_> = bounds
.iter()
.map(|b| b.to_doc_with_lang(resolve, lang))
.collect();
let sep = BoxDoc::text(tp.impl_trait.separator.to_string());
BoxDoc::text(tp.impl_trait.keyword.to_string())
.append(BoxDoc::intersperse(docs, sep))
}
TypeName::DynTrait { bounds } => {
let docs: Vec<_> = bounds
.iter()
.map(|b| b.to_doc_with_lang(resolve, lang))
.collect();
let sep = BoxDoc::text(tp.dyn_trait.separator.to_string());
BoxDoc::text(tp.dyn_trait.keyword.to_string())
.append(BoxDoc::intersperse(docs, sep))
}
TypeName::Wildcard {
upper_bound,
lower_bound,
} => {
debug_assert!(
upper_bound.is_none() || lower_bound.is_none(),
"Wildcard cannot have both upper and lower bounds"
);
if let Some(ub) = upper_bound {
let ub_doc = ub.to_doc_with_lang(resolve, lang);
BoxDoc::text(tp.wildcard.upper_keyword.to_string()).append(ub_doc)
} else if let Some(lb) = lower_bound {
let lb_doc = lb.to_doc_with_lang(resolve, lang);
BoxDoc::text(tp.wildcard.lower_keyword.to_string()).append(lb_doc)
} else {
BoxDoc::text(tp.wildcard.unbounded.to_string())
}
}
_ => self.to_doc(resolve),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lang::typescript::TypeScript;
fn identity_resolve(module: &str, name: &str) -> String {
let _ = module;
name.to_string()
}
macro_rules! test_lang {
($name:ident, $base:ty { $($override:tt)* }) => {
#[derive(Debug, Clone)]
struct $name(pub $base);
impl $name {
fn new() -> Self { Self(<$base>::new()) }
}
impl crate::lang::CodeLang for $name {
fn file_extension(&self) -> &str { self.0.file_extension() }
fn reserved_words(&self) -> &[&str] { self.0.reserved_words() }
fn render_imports(&self, imports: &crate::import::ImportGroup) -> String { self.0.render_imports(imports) }
fn render_string_literal(&self, s: &str) -> String { self.0.render_string_literal(s) }
fn render_doc_comment(&self, lines: &[&str]) -> String { self.0.render_doc_comment(lines) }
fn line_comment_prefix(&self) -> &str { self.0.line_comment_prefix() }
fn render_visibility(&self, vis: crate::spec::modifiers::Visibility, ctx: crate::spec::modifiers::DeclarationContext) -> &str { self.0.render_visibility(vis, ctx) }
fn function_keyword(&self, ctx: crate::spec::modifiers::DeclarationContext) -> &str { self.0.function_keyword(ctx) }
fn type_keyword(&self, kind: crate::spec::modifiers::TypeKind) -> &str { self.0.type_keyword(kind) }
fn methods_inside_type_body(&self, kind: crate::spec::modifiers::TypeKind) -> bool { self.0.methods_inside_type_body(kind) }
fn type_presentation(&self) -> crate::lang::config::TypePresentationConfig<'_> { self.0.type_presentation() }
fn block_syntax(&self) -> crate::lang::config::BlockSyntaxConfig<'_> { self.0.block_syntax() }
fn function_syntax(&self) -> crate::lang::config::FunctionSyntaxConfig<'_> { self.0.function_syntax() }
fn type_decl_syntax(&self) -> crate::lang::config::TypeDeclSyntaxConfig<'_> { self.0.type_decl_syntax() }
fn enum_and_annotation(&self) -> crate::lang::config::EnumAndAnnotationConfig<'_> { self.0.enum_and_annotation() }
$($override)*
}
};
}
test_lang!(PrefixLang, TypeScript {
fn generic_syntax(&self) -> crate::lang::config::GenericSyntaxConfig<'_> {
crate::lang::config::GenericSyntaxConfig {
application_style: GenericApplicationStyle::PrefixJuxtaposition,
..self.0.generic_syntax()
}
}
});
test_lang!(PostfixLang, TypeScript {
fn generic_syntax(&self) -> crate::lang::config::GenericSyntaxConfig<'_> {
crate::lang::config::GenericSyntaxConfig {
application_style: GenericApplicationStyle::PostfixJuxtaposition,
..self.0.generic_syntax()
}
}
});
#[test]
fn test_primitive() {
let t = TypeName::primitive("number");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "number");
}
#[test]
fn test_importable() {
let t = TypeName::importable("./models", "User");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "User");
}
#[test]
fn test_importable_with_alias() {
let t = TypeName::importable("./other", "User");
let resolve = |module: &str, name: &str| {
if module == "./other" && name == "User" {
"OtherUser".to_string()
} else {
name.to_string()
}
};
assert_eq!(t.render(80, &resolve).unwrap(), "OtherUser");
}
#[test]
fn test_array() {
let t = TypeName::array(TypeName::primitive("string"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "string[]");
}
#[test]
fn test_generic() {
let t = TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::importable("./models", "User")],
);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "Promise<User>");
}
#[test]
fn test_generic_multiline() {
let t = TypeName::generic(
TypeName::primitive("Map"),
vec![
TypeName::primitive("VeryLongKeyTypeName"),
TypeName::primitive("VeryLongValueTypeName"),
],
);
let output = t.render(20, &identity_resolve).unwrap();
assert!(output.contains('\n'));
assert!(output.contains("VeryLongKeyTypeName"));
assert!(output.contains("VeryLongValueTypeName"));
}
#[test]
fn test_union() {
let t = TypeName::union(vec![
TypeName::primitive("string"),
TypeName::primitive("number"),
TypeName::primitive("boolean"),
]);
assert_eq!(
t.render(80, &identity_resolve).unwrap(),
"string | number | boolean"
);
}
#[test]
fn test_union_multiline() {
let t = TypeName::union(vec![
TypeName::primitive("VeryLongTypeName1"),
TypeName::primitive("VeryLongTypeName2"),
TypeName::primitive("VeryLongTypeName3"),
]);
let output = t.render(30, &identity_resolve).unwrap();
assert!(output.contains('\n'));
}
#[test]
fn test_pointer() {
let t = TypeName::pointer(TypeName::primitive("User"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "*User");
}
#[test]
fn test_slice() {
let t = TypeName::slice(TypeName::primitive("User"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "[]User");
}
#[test]
fn test_map() {
let t = TypeName::map(TypeName::primitive("string"), TypeName::primitive("User"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "map[string]User");
}
#[test]
fn test_optional() {
let t = TypeName::optional(TypeName::primitive("string"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "string | null");
}
#[test]
fn test_function_type() {
let t = TypeName::function(
vec![TypeName::primitive("string"), TypeName::primitive("number")],
TypeName::primitive("boolean"),
);
assert_eq!(
t.render(80, &identity_resolve).unwrap(),
"(string, number) => boolean"
);
}
#[test]
fn test_deeply_nested() {
let inner = TypeName::generic(
TypeName::primitive("Array"),
vec![TypeName::importable("./models", "User")],
);
let outer = TypeName::generic(TypeName::primitive("Promise"), vec![inner]);
assert_eq!(
outer.render(80, &identity_resolve).unwrap(),
"Promise<Array<User>>"
);
}
#[test]
fn test_collect_imports() {
let t = TypeName::generic(
TypeName::importable("./base", "Base"),
vec![
TypeName::importable("./models", "User"),
TypeName::array(TypeName::importable("./models", "Tag")),
],
);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 3);
assert_eq!(imports[0].name, "Base");
assert_eq!(imports[1].name, "User");
assert_eq!(imports[2].name, "Tag");
}
#[test]
fn test_raw_no_imports() {
let t = TypeName::raw("any");
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert!(imports.is_empty());
assert_eq!(t.render(80, &identity_resolve).unwrap(), "any");
}
#[test]
fn test_with_alias_on_importable() {
let t = TypeName::importable("./models", "User").with_alias("MyUser");
if let TypeName::Importable { alias, .. } = &t {
assert_eq!(alias.as_deref(), Some("MyUser"));
} else {
panic!("Expected Importable variant");
}
}
#[test]
fn test_with_alias_propagates_to_import_ref() {
let t = TypeName::importable("./models", "User").with_alias("MyUser");
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].name, "User");
assert_eq!(imports[0].alias.as_deref(), Some("MyUser"));
}
#[test]
fn test_with_alias_noop_on_primitive() {
let t = TypeName::primitive("number").with_alias("MyNumber");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "number");
}
#[test]
fn test_with_alias_renders_alias_name() {
let t = TypeName::importable("./models", "User").with_alias("MyUser");
let resolve = |_module: &str, _name: &str| "MyUser".to_string();
assert_eq!(t.render(80, &resolve).unwrap(), "MyUser");
}
#[test]
fn test_tuple() {
let t = TypeName::tuple(vec![
TypeName::primitive("string"),
TypeName::primitive("number"),
]);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "(string, number)");
}
#[test]
fn test_unit_tuple() {
let t = TypeName::unit();
assert_eq!(t.render(80, &identity_resolve).unwrap(), "()");
}
#[test]
fn test_tuple_collect_imports() {
let t = TypeName::tuple(vec![
TypeName::importable("./models", "User"),
TypeName::importable("./models", "Tag"),
]);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 2);
assert_eq!(imports[0].name, "User");
assert_eq!(imports[1].name, "Tag");
}
#[test]
fn test_tuple_with_lang_ts() {
let lang = TypeScript::new();
let t = TypeName::tuple(vec![
TypeName::primitive("string"),
TypeName::primitive("number"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "[string, number]");
}
#[test]
fn test_tuple_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::tuple(vec![
TypeName::primitive("String"),
TypeName::primitive("i32"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "(String, i32)");
}
#[test]
fn test_tuple_with_lang_python() {
use crate::lang::python::Python;
let lang = Python::new();
let t = TypeName::tuple(vec![TypeName::primitive("str"), TypeName::primitive("int")]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "tuple[str, int]");
}
#[test]
fn test_tuple_with_lang_cpp() {
use crate::lang::cpp_lang::CppLang;
let lang = CppLang::new();
let t = TypeName::tuple(vec![
TypeName::primitive("int"),
TypeName::primitive("std::string"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(
String::from_utf8(buf).unwrap(),
"std::tuple<int, std::string>"
);
}
#[test]
fn test_unit_tuple_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::unit();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "()");
}
#[test]
fn test_reference() {
let t = TypeName::reference(TypeName::primitive("str"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "&str");
}
#[test]
fn test_reference_mut() {
let t = TypeName::reference_mut(TypeName::primitive("Vec<i32>"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "&mut Vec<i32>");
}
#[test]
fn test_reference_collect_imports() {
let t = TypeName::reference(TypeName::importable("./models", "User"));
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].name, "User");
}
#[test]
fn test_reference_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference(TypeName::primitive("String"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&String");
}
#[test]
fn test_reference_mut_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference_mut(TypeName::primitive("String"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&mut String");
}
#[test]
fn test_reference_with_lang_cpp() {
use crate::lang::cpp_lang::CppLang;
let lang = CppLang::new();
let t = TypeName::reference(TypeName::primitive("std::string"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "const std::string&");
}
#[test]
fn test_reference_mut_with_lang_cpp() {
use crate::lang::cpp_lang::CppLang;
let lang = CppLang::new();
let t = TypeName::reference_mut(TypeName::primitive("std::string"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "std::string&");
}
#[test]
fn test_reference_with_lang_ts() {
let lang = TypeScript::new();
let t = TypeName::reference(TypeName::primitive("string"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "string");
}
#[test]
fn test_reference_mut_with_lang_go() {
use crate::lang::go_lang::GoLang;
let lang = GoLang::new();
let t = TypeName::reference_mut(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "*int");
}
#[test]
fn test_reference_with_lang_go() {
use crate::lang::go_lang::GoLang;
let lang = GoLang::new();
let t = TypeName::reference(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "int");
}
#[test]
fn test_reference_with_lang_c() {
use crate::lang::c_lang::CLang;
let lang = CLang::new();
let t = TypeName::reference(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "const int*");
}
#[test]
fn test_reference_mut_with_lang_c() {
use crate::lang::c_lang::CLang;
let lang = CLang::new();
let t = TypeName::reference_mut(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "int*");
}
#[test]
fn test_generic_prefix_juxtaposition() {
let lang = PrefixLang::new();
let t = TypeName::generic(
TypeName::primitive("Maybe"),
vec![TypeName::primitive("Int")],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Maybe Int");
}
#[test]
fn test_generic_prefix_juxtaposition_compound_param() {
let lang = PrefixLang::new();
let inner = TypeName::generic(
TypeName::primitive("Maybe"),
vec![TypeName::primitive("Int")],
);
let t = TypeName::generic(
TypeName::primitive("Either"),
vec![TypeName::primitive("String"), inner],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Either String (Maybe Int)");
}
#[test]
fn test_generic_postfix_juxtaposition_single() {
let lang = PostfixLang::new();
let t = TypeName::generic(
TypeName::primitive("option"),
vec![TypeName::primitive("int")],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "int option");
}
#[test]
fn test_generic_postfix_juxtaposition_multi() {
let lang = PostfixLang::new();
let t = TypeName::generic(
TypeName::primitive("result"),
vec![TypeName::primitive("int"), TypeName::primitive("string")],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "(int, string) result");
}
#[test]
fn test_is_compound_type() {
assert!(is_compound_type(&TypeName::generic(
TypeName::primitive("A"),
vec![TypeName::primitive("B")],
)));
assert!(is_compound_type(&TypeName::union(vec![
TypeName::primitive("A"),
TypeName::primitive("B"),
])));
assert!(is_compound_type(&TypeName::intersection(vec![
TypeName::primitive("A"),
TypeName::primitive("B"),
])));
assert!(is_compound_type(&TypeName::function(
vec![TypeName::primitive("A")],
TypeName::primitive("B"),
)));
assert!(is_compound_type(&TypeName::tuple(vec![
TypeName::primitive("A"),
TypeName::primitive("B"),
])));
assert!(!is_compound_type(&TypeName::primitive("Int")));
assert!(!is_compound_type(&TypeName::array(TypeName::primitive(
"Int"
),)));
}
#[test]
fn test_associated_type_rust_qualified() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::associated_type(
TypeName::primitive("T"),
Some(TypeName::primitive("Iterator")),
"Item",
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "<T as Iterator>::Item");
}
#[test]
fn test_associated_type_rust_simple() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::member_type(TypeName::primitive("Self"), "Output");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Self::Output");
}
#[test]
fn test_associated_type_ts_index_access() {
let lang = TypeScript::new();
let t = TypeName::associated_type(
TypeName::primitive("T"),
Some(TypeName::primitive("Qual")),
"key",
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "T[\"key\"]");
}
#[test]
fn test_associated_type_java_dot() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::member_type(TypeName::primitive("Map"), "Entry");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Map.Entry");
}
#[test]
fn test_associated_type_collect_imports() {
let t = TypeName::associated_type(
TypeName::importable("./models", "User"),
Some(TypeName::importable("./traits", "Serializable")),
"Output",
);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 2);
}
#[test]
fn test_impl_trait_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::impl_trait(vec![
TypeName::primitive("Display"),
TypeName::primitive("Debug"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "impl Display + Debug");
}
#[test]
fn test_dyn_trait_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::dyn_trait(vec![TypeName::primitive("Error")]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "dyn Error");
}
#[test]
fn test_impl_trait_ts_intersection() {
let lang = TypeScript::new();
let t = TypeName::impl_trait(vec![
TypeName::primitive("Serializable"),
TypeName::primitive("Loggable"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Serializable & Loggable");
}
#[test]
fn test_wildcard_java_unbounded() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "?");
}
#[test]
fn test_wildcard_java_extends() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::wildcard_extends(TypeName::primitive("Comparable"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "? extends Comparable");
}
#[test]
fn test_wildcard_java_super() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::wildcard_super(TypeName::primitive("Number"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "? super Number");
}
#[test]
fn test_wildcard_kotlin() {
use crate::lang::kotlin::Kotlin;
let lang = Kotlin::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "*");
}
#[test]
fn test_wildcard_kotlin_out() {
use crate::lang::kotlin::Kotlin;
let lang = Kotlin::new();
let t = TypeName::wildcard_extends(TypeName::primitive("Number"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "out Number");
}
#[test]
fn test_wildcard_kotlin_in() {
use crate::lang::kotlin::Kotlin;
let lang = Kotlin::new();
let t = TypeName::wildcard_super(TypeName::primitive("Number"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "in Number");
}
#[test]
fn test_wildcard_go() {
use crate::lang::go_lang::GoLang;
let lang = GoLang::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "any");
}
#[test]
fn test_wildcard_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "_");
}
#[test]
fn test_impl_trait_collect_imports() {
let t = TypeName::impl_trait(vec![
TypeName::importable("./traits", "Serializable"),
TypeName::primitive("Debug"),
]);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
}
#[test]
fn test_dyn_trait_collect_imports() {
let t = TypeName::dyn_trait(vec![TypeName::importable("./errors", "AppError")]);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
}
#[test]
fn test_wildcard_collect_imports() {
let t = TypeName::wildcard_extends(TypeName::importable("./models", "User"));
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
}
#[test]
fn test_associated_type_default_rendering() {
let t = TypeName::associated_type(
TypeName::primitive("T"),
Some(TypeName::primitive("Iter")),
"Item",
);
assert_eq!(
t.render(80, &identity_resolve).unwrap(),
"<T as Iter>::Item"
);
}
#[test]
fn test_member_type_default_rendering() {
let t = TypeName::member_type(TypeName::primitive("Self"), "Output");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "Self::Output");
}
#[test]
fn test_impl_trait_default_rendering() {
let t = TypeName::impl_trait(vec![TypeName::primitive("Display")]);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "impl Display");
}
#[test]
fn test_dyn_trait_default_rendering() {
let t = TypeName::dyn_trait(vec![
TypeName::primitive("Error"),
TypeName::primitive("Send"),
]);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "dyn Error + Send");
}
#[test]
fn test_wildcard_default_rendering() {
assert_eq!(
TypeName::wildcard().render(80, &identity_resolve).unwrap(),
"?"
);
assert_eq!(
TypeName::wildcard_extends(TypeName::primitive("T"))
.render(80, &identity_resolve)
.unwrap(),
"? extends T"
);
assert_eq!(
TypeName::wildcard_super(TypeName::primitive("T"))
.render(80, &identity_resolve)
.unwrap(),
"? super T"
);
}
#[test]
fn test_reference_with_lifetime_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference_with_lifetime(TypeName::primitive("str"), "'a");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&'a str");
}
#[test]
fn test_reference_mut_with_lifetime_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference_mut_with_lifetime(TypeName::primitive("String"), "'a");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&'a mut String");
}
#[test]
fn test_reference_with_lifetime_default_rendering() {
let t = TypeName::reference_with_lifetime(TypeName::primitive("str"), "'a");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "&'a str");
}
#[test]
fn test_reference_without_lifetime_unchanged() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference(TypeName::primitive("String"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&String");
}
}