use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum KtType {
Named {
fqn: String,
args: Vec<KtType>,
nullable: bool,
},
Function {
params: Vec<(String, KtType)>,
ret: Box<KtType>,
nullable: bool,
},
}
impl KtType {
pub const UNIT: &'static str = "Unit";
pub fn cls(fqn: impl Into<String>) -> Self {
KtType::Named {
fqn: fqn.into(),
args: vec![],
nullable: false,
}
}
pub fn generic(fqn: impl Into<String>, args: impl IntoIterator<Item = KtType>) -> Self {
KtType::Named {
fqn: fqn.into(),
args: args.into_iter().collect(),
nullable: false,
}
}
pub fn lambda(params: impl IntoIterator<Item = (String, KtType)>, ret: KtType) -> Self {
KtType::Function {
params: params.into_iter().collect(),
ret: Box::new(ret),
nullable: false,
}
}
pub fn unit() -> Self {
Self::cls("Unit")
}
pub fn int() -> Self {
Self::cls("Int")
}
pub fn long() -> Self {
Self::cls("Long")
}
pub fn boolean() -> Self {
Self::cls("Boolean")
}
pub fn string() -> Self {
Self::cls("String")
}
pub fn byte_array() -> Self {
Self::cls("ByteArray")
}
pub fn any() -> Self {
Self::cls("Any")
}
pub fn var_(name: impl Into<String>) -> Self {
Self::cls(name)
}
pub fn var_r() -> Self {
Self::cls("R")
}
pub fn nullable(mut self) -> Self {
match &mut self {
KtType::Named { nullable, .. } | KtType::Function { nullable, .. } => *nullable = true,
}
self
}
pub fn is_nullable(&self) -> bool {
match self {
KtType::Named { nullable, .. } | KtType::Function { nullable, .. } => *nullable,
}
}
pub fn leaf_name(&self) -> Option<&str> {
match self {
KtType::Named { fqn, args, .. } if args.is_empty() => Some(fqn),
_ => None,
}
}
pub fn simple_name(&self) -> Option<&str> {
match self {
KtType::Named { fqn, .. } => Some(fqn.rsplit('.').next().unwrap_or(fqn)),
KtType::Function { .. } => None,
}
}
pub fn render_receiver(&self, imports: &mut ImportSet) -> String {
let rendered = self.render(imports);
if self.needs_receiver_parens() {
format!("({rendered})")
} else {
rendered
}
}
pub(crate) fn needs_receiver_parens(&self) -> bool {
matches!(
self,
KtType::Function {
nullable: false,
..
}
)
}
pub fn render(&self, imports: &mut ImportSet) -> String {
match self {
KtType::Named {
fqn,
args,
nullable,
} => {
let mut s = imports.short(fqn);
if !args.is_empty() {
s.push('<');
let rendered: Vec<String> = args.iter().map(|a| a.render(imports)).collect();
s.push_str(&rendered.join(", "));
s.push('>');
}
if *nullable {
s.push('?');
}
s
}
KtType::Function {
params,
ret,
nullable,
} => {
let ps: Vec<String> = params
.iter()
.map(|(n, t)| {
if n.is_empty() {
t.render(imports)
} else {
format!("{n}: {}", t.render(imports))
}
})
.collect();
let core = format!("({}) -> {}", ps.join(", "), ret.render(imports));
if *nullable {
format!("({core})?")
} else {
core
}
}
}
}
}
impl std::fmt::Display for KtType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
KtType::Named {
fqn,
args,
nullable,
} => {
f.write_str(fqn)?;
if !args.is_empty() {
write!(f, "<")?;
for (i, a) in args.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{a}")?;
}
write!(f, ">")?;
}
if *nullable {
write!(f, "?")?;
}
Ok(())
}
KtType::Function {
params,
ret,
nullable,
} => {
if *nullable {
write!(f, "(")?;
}
write!(f, "(")?;
for (i, (n, t)) in params.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
if !n.is_empty() {
write!(f, "{n}: ")?;
}
write!(f, "{t}")?;
}
write!(f, ") -> {ret}")?;
if *nullable {
write!(f, ")?")?;
}
Ok(())
}
}
}
}
#[derive(Default, Debug)]
pub struct ImportSet {
package: String,
by_simple: BTreeMap<String, String>,
fn_imports: std::collections::BTreeSet<String>,
}
impl ImportSet {
pub fn new(package: impl Into<String>) -> Self {
Self {
package: package.into(),
by_simple: BTreeMap::new(),
fn_imports: Default::default(),
}
}
pub fn short(&mut self, name: &str) -> String {
let is_fqn_path = name.contains('.')
&& name
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '.');
if !is_fqn_path {
return name.to_string();
}
let Some((_pkg, simple)) = name.rsplit_once('.') else {
return name.to_string();
};
if simple.chars().next().is_some_and(|c| c.is_lowercase()) {
self.fn_imports.insert(name.to_string());
return simple.to_string();
}
match self.by_simple.get(simple) {
Some(owner) if owner == name => simple.to_string(),
Some(_) => name.to_string(), None => {
self.by_simple.insert(simple.to_string(), name.to_string());
simple.to_string()
}
}
}
pub fn register(&mut self, fqn: &str) {
let _ = self.short(fqn);
}
pub fn import_lines(&self) -> Vec<String> {
self.by_simple
.values()
.chain(self.fn_imports.iter())
.filter(|fqn| {
fqn.rsplit_once('.')
.map(|(pkg, _)| pkg != self.package)
.unwrap_or(false)
})
.map(|fqn| format!("import {fqn}"))
.collect::<std::collections::BTreeSet<_>>()
.into_iter()
.collect()
}
}