1use super::{
7 code::KtCode,
8 model::*,
9 slot::KtPropertyValue,
10 types::{ImportSet, KtType},
11};
12
13pub const KOTLIN_BANNER: &str = "// Auto-generated by kotlin-codegen — do not edit by hand.";
21
22pub(crate) const MAX_SIGNATURE_WIDTH: usize = 100;
27
28impl KtFile {
29 pub fn render(&self) -> String {
32 let mut imports = ImportSet::new(&self.package);
33 for fqn in &self.extra_imports {
37 imports.register(fqn);
38 }
39 let mut raw_imports = Vec::new();
40 for d in &self.decls {
41 collect_decl_imports(d, &mut raw_imports);
42 }
43 for fqn in raw_imports {
44 imports.register(&fqn);
45 }
46 let mut body = String::new();
47 for (i, d) in self.decls.iter().enumerate() {
48 if i > 0 {
49 body.push('\n');
50 }
51 render_decl(d, 0, &mut imports, &mut body);
52 }
53
54 let mut out = String::new();
55 let banner_text = self.banner.as_deref().unwrap_or(KOTLIN_BANNER);
56 if !banner_text.is_empty() {
57 out.push_str(banner_text);
58 out.push('\n');
59 }
60 if !self.package.is_empty() {
61 out.push_str(&format!("package {}\n", self.package));
62 }
63 let import_lines = imports.import_lines();
64 if !import_lines.is_empty() {
65 out.push('\n');
66 for l in &import_lines {
67 out.push_str(l);
68 out.push('\n');
69 }
70 }
71 if !out.is_empty() {
72 out.push('\n');
73 }
74 out.push_str(&body);
75 if !out.ends_with('\n') {
76 out.push('\n');
77 }
78 out
79 }
80}
81
82fn render_code_inline(c: &KtCode) -> String {
84 let mut s = String::new();
85 c.render(0, &mut s);
86 s.trim_end().to_string()
87}
88
89fn collect_decl_imports(d: &KtDecl, sink: &mut Vec<String>) {
91 match d {
92 KtDecl::Class(c) => {
93 for p in c.ctor_params() {
94 if let Some(default) = &p.default {
95 default.collect_imports(sink);
96 }
97 }
98 for (_, args) in c.supertypes.iter() {
99 if let Some(args) = args {
100 args.collect_imports(sink);
101 }
102 }
103 for e in c.kind.entries() {
104 if let Some(args) = &e.args {
105 args.collect_imports(sink);
106 }
107 }
108 for m in &c.members {
109 collect_decl_imports(m, sink);
110 }
111 if let Some(comp) = &c.companion {
112 collect_companion_imports(comp, sink);
113 }
114 }
115 KtDecl::Fun(f) => collect_fun_imports(f, sink),
116 KtDecl::FunInterface(i) => collect_fun_sig_imports(&i.method, sink),
117 KtDecl::Property(p) => {
118 p.value.collect_imports(sink);
119 if let Some(a) = &p.accessors {
120 a.collect_imports(sink);
121 }
122 }
123 KtDecl::TypeAlias { .. } => {}
124 KtDecl::Raw { code, .. } => code.collect_imports(sink),
125 }
126}
127
128fn collect_companion_imports(c: &KtCompanion, sink: &mut Vec<String>) {
129 for (_, args) in c.supertypes.iter() {
130 if let Some(args) = args {
131 args.collect_imports(sink);
132 }
133 }
134 for m in &c.members {
135 collect_decl_imports(m, sink);
136 }
137}
138
139fn collect_fun_imports(f: &KtFun, sink: &mut Vec<String>) {
140 match &f.body {
141 KtBody::Expr(c) | KtBody::Block(c) => c.collect_imports(sink),
142 KtBody::None | KtBody::External => {}
143 }
144 collect_param_imports(&f.params, sink);
145}
146
147fn collect_fun_sig_imports(f: &KtFunSig, sink: &mut Vec<String>) {
149 collect_param_imports(&f.params, sink);
150}
151
152fn collect_param_imports(params: &[KtParam], sink: &mut Vec<String>) {
153 for p in params {
154 if let Some(default) = &p.default {
155 default.collect_imports(sink);
156 }
157 }
158}
159
160fn indent(level: usize, out: &mut String) {
161 for _ in 0..level {
162 out.push_str(" ");
163 }
164}
165
166fn render_kdoc(doc: &str, level: usize, out: &mut String) {
168 let lines: Vec<&str> = doc.lines().collect();
169 if lines.len() == 1 {
170 indent(level, out);
171 out.push_str(&format!("/** {} */\n", lines[0]));
172 return;
173 }
174 indent(level, out);
175 out.push_str("/**\n");
176 for l in &lines {
177 indent(level, out);
178 if l.is_empty() {
179 out.push_str(" *\n");
180 } else {
181 out.push_str(&format!(" * {l}\n"));
182 }
183 }
184 indent(level, out);
185 out.push_str(" */\n");
186}
187
188fn render_decl(d: &KtDecl, level: usize, imports: &mut ImportSet, out: &mut String) {
189 match d {
190 KtDecl::Class(c) => render_class(c, level, imports, out),
191 KtDecl::Fun(f) => render_fun(f, level, imports, out),
192 KtDecl::FunInterface(i) => render_fun_interface(i, level, imports, out),
193 KtDecl::Property(p) => render_property(p, level, imports, out),
194 KtDecl::TypeAlias { vis, name, target } => {
195 indent(level, out);
196 out.push_str(&format!(
197 "{}typealias {name} = {}\n",
198 vis.prefix(),
199 target.render(imports)
200 ));
201 }
202 KtDecl::Raw { code, .. } => code.render(level, out),
203 }
204}
205
206fn render_ctor_param(p: &KtCtorParam, imports: &mut ImportSet) -> String {
207 let mut s = String::new();
208 for a in &p.annotations {
209 s.push_str(&format!("@{a} "));
210 }
211 s.push_str(p.vis.prefix());
212 if p.overrides {
213 s.push_str("override ");
214 }
215 match p.prop {
216 Some(false) => s.push_str("val "),
217 Some(true) => s.push_str("var "),
218 None => {}
219 }
220 s.push_str(&format!("{}: {}", p.name, p.ty.render(imports)));
221 if let Some(d) = &p.default {
222 s.push_str(&format!(" = {}", render_code_inline(d)));
223 }
224 s
225}
226
227fn render_supertypes(supertypes: &KtSupertypes, imports: &mut ImportSet, out: &mut String) {
229 if supertypes.is_empty() {
230 return;
231 }
232 let sts: Vec<String> = supertypes
233 .iter()
234 .map(|(ty, args)| {
235 let t = ty.render(imports);
236 match args {
237 Some(a) => format!("{t}({})", render_code_inline(a)),
238 None => t,
239 }
240 })
241 .collect();
242 out.push_str(&format!(" : {}", sts.join(", ")));
243}
244
245fn render_companion(c: &KtCompanion, level: usize, imports: &mut ImportSet, out: &mut String) {
248 if let Some(doc) = &c.kdoc {
249 render_kdoc(doc, level, out);
250 }
251 for a in &c.annotations {
252 indent(level, out);
253 out.push_str(&format!("@{a}\n"));
254 }
255 indent(level, out);
256 out.push_str(c.vis.prefix());
257 out.push_str("companion object");
258 if let Some(name) = &c.name {
259 out.push(' ');
260 out.push_str(name);
261 }
262 render_supertypes(&c.supertypes, imports, out);
263 if c.members.is_empty() {
264 out.push('\n');
265 return;
266 }
267 out.push_str(" {\n");
268 for (i, m) in c.members.iter().enumerate() {
269 if i > 0 {
270 out.push('\n');
271 }
272 render_decl(m, level + 1, imports, out);
273 }
274 indent(level, out);
275 out.push_str("}\n");
276}
277
278fn render_class(c: &KtClass, level: usize, imports: &mut ImportSet, out: &mut String) {
279 if let Some(doc) = &c.kdoc {
280 render_kdoc(doc, level, out);
281 }
282 let mut annotations = c.annotations.clone();
283 if matches!(c.kind, KtClassKind::Value { .. }) && !annotations.iter().any(|a| a == "JvmInline")
284 {
285 annotations.insert(0, "JvmInline".to_string());
286 }
287 for a in &annotations {
288 indent(level, out);
289 out.push_str(&format!("@{a}\n"));
290 }
291
292 indent(level, out);
293 out.push_str(c.vis.prefix());
294 out.push_str(c.kind.keyword());
295 out.push(' ');
296 out.push_str(&c.name);
297 let ctor_params = c.ctor_params();
298 if !ctor_params.is_empty() {
299 let ps: Vec<String> = ctor_params
300 .iter()
301 .map(|p| render_ctor_param(p, imports))
302 .collect();
303 out.push_str(&format!("({})", ps.join(", ")));
304 }
305 render_supertypes(&c.supertypes, imports, out);
306
307 let entries = c.kind.entries();
308 let has_body = !entries.is_empty() || !c.members.is_empty() || c.companion.is_some();
309 if !has_body {
310 out.push('\n');
311 return;
312 }
313 out.push_str(" {\n");
314
315 if !entries.is_empty() {
316 for (i, e) in entries.iter().enumerate() {
317 indent(level + 1, out);
318 out.push_str(&e.name);
319 if let Some(args) = &e.args {
320 out.push_str(&format!("({})", render_code_inline(args)));
321 }
322 out.push_str(if i + 1 == entries.len() { ";\n" } else { ",\n" });
323 }
324 if !c.members.is_empty() || c.companion.is_some() {
325 out.push('\n');
326 }
327 }
328
329 let mut first = true;
330 for m in &c.members {
331 if !first {
332 out.push('\n');
333 }
334 first = false;
335 render_decl(m, level + 1, imports, out);
336 }
337 if let Some(comp) = &c.companion {
338 if !first {
339 out.push('\n');
340 }
341 render_companion(comp, level + 1, imports, out);
342 }
343
344 indent(level, out);
345 out.push_str("}\n");
346}
347
348fn render_signature_param(p: &KtParam, imports: &mut ImportSet, level: usize) -> String {
355 let name_prefix = format!("{}: ", p.name);
356 let default = p
357 .default
358 .as_ref()
359 .map(|d| format!(" = {}", render_code_inline(d)))
360 .unwrap_or_default();
361 let type_col = level * 4 + name_prefix.len();
363 format!(
364 "{name_prefix}{}{default}",
365 render_type_wrapped(&p.ty, imports, level, type_col)
366 )
367}
368
369fn render_type_wrapped(
377 ty: &KtType,
378 imports: &mut ImportSet,
379 level: usize,
380 start_col: usize,
381) -> String {
382 let single = ty.render(imports);
383 if start_col + single.len() <= MAX_SIGNATURE_WIDTH {
384 return single;
385 }
386 let KtType::Function {
387 params,
388 ret,
389 nullable,
390 } = ty
391 else {
392 return single;
393 };
394 if params.is_empty() {
395 return single;
396 }
397 let mut s = String::from(if *nullable { "((" } else { "(" });
398 s.push('\n');
399 for (name, pty) in params {
400 indent(level + 1, &mut s);
401 let prefix = if name.is_empty() {
402 String::new()
403 } else {
404 format!("{name}: ")
405 };
406 s.push_str(&prefix);
407 let col = (level + 1) * 4 + prefix.len();
408 s.push_str(&render_type_wrapped(pty, imports, level + 1, col));
409 s.push_str(",\n");
410 }
411 indent(level, &mut s);
412 let closer = ") -> ";
413 s.push_str(closer);
414 s.push_str(&render_type_wrapped(
415 ret,
416 imports,
417 level,
418 level * 4 + closer.len(),
419 ));
420 if *nullable {
421 s.push_str(")?");
422 }
423 s
424}
425
426struct SigView<'a> {
430 kdoc: Option<&'a String>,
431 annotations: &'a [String],
432 vis: KtVis,
433 modifiers: &'a [String],
434 external: bool,
436 generics: &'a [String],
437 receiver: Option<&'a KtType>,
439 name: &'a str,
440 params: &'a [KtParam],
441 ret: Option<&'a KtType>,
442}
443
444impl<'a> From<&'a KtFun> for SigView<'a> {
445 fn from(f: &'a KtFun) -> Self {
446 SigView {
447 kdoc: f.kdoc.as_ref(),
448 annotations: &f.annotations,
449 vis: f.vis,
450 modifiers: &f.modifiers,
451 external: matches!(f.body, KtBody::External),
452 generics: &f.generics,
453 receiver: f.receiver.as_ref(),
454 name: &f.name,
455 params: &f.params,
456 ret: f.ret.as_ref(),
457 }
458 }
459}
460
461impl<'a> From<&'a KtFunSig> for SigView<'a> {
462 fn from(f: &'a KtFunSig) -> Self {
463 SigView {
464 kdoc: f.kdoc.as_ref(),
465 annotations: &f.annotations,
466 vis: f.vis,
467 modifiers: &[],
468 external: false,
469 generics: &f.generics,
470 receiver: f.receiver.as_ref(),
471 name: &f.name,
472 params: &f.params,
473 ret: f.ret.as_ref(),
474 }
475 }
476}
477
478fn render_fun_signature(f: &SigView<'_>, level: usize, imports: &mut ImportSet, out: &mut String) {
481 if let Some(doc) = f.kdoc {
482 render_kdoc(doc, level, out);
483 }
484 for a in f.annotations {
485 indent(level, out);
486 out.push_str(&format!("@{a}\n"));
487 }
488 indent(level, out);
489 out.push_str(f.vis.prefix());
490 if f.external {
491 out.push_str("external ");
492 }
493 for m in f.modifiers {
494 out.push_str(m);
495 out.push(' ');
496 }
497 out.push_str("fun ");
498 if !f.generics.is_empty() {
499 out.push_str(&format!("<{}> ", f.generics.join(", ")));
500 }
501 if let Some(recv) = f.receiver {
502 out.push_str(&recv.render_receiver(imports));
505 out.push('.');
506 }
507 out.push_str(f.name);
508 let ps: Vec<String> = f
509 .params
510 .iter()
511 .map(|p| {
512 let mut s = format!("{}: {}", p.name, p.ty.render(imports));
513 if let Some(d) = &p.default {
514 s.push_str(&format!(" = {}", render_code_inline(d)));
515 }
516 s
517 })
518 .collect();
519 let ret_suffix = match f.ret {
522 Some(rt) => {
523 let rendered = rt.render(imports);
524 if rendered != KtType::UNIT {
525 format!(": {rendered}")
526 } else {
527 String::new()
528 }
529 }
530 None => String::new(),
531 };
532 let header_col = out.rsplit('\n').next().map_or(out.len(), str::len);
535 let single_line = format!("({}){ret_suffix}", ps.join(", "));
536 if !ps.is_empty() && header_col + single_line.len() > MAX_SIGNATURE_WIDTH {
537 out.push_str("(\n");
542 for p in f.params {
543 indent(level + 1, out);
544 out.push_str(&render_signature_param(p, imports, level + 1));
545 out.push_str(",\n");
546 }
547 indent(level, out);
548 out.push(')');
549 out.push_str(&ret_suffix);
550 } else {
551 out.push_str(&single_line);
552 }
553}
554
555fn render_fun_sig(f: &KtFunSig, level: usize, imports: &mut ImportSet, out: &mut String) {
557 render_fun_signature(&f.into(), level, imports, out);
558 out.push('\n');
559}
560
561fn render_fun(f: &KtFun, level: usize, imports: &mut ImportSet, out: &mut String) {
562 render_fun_signature(&f.into(), level, imports, out);
563 match &f.body {
564 KtBody::None | KtBody::External => out.push('\n'),
565 KtBody::Expr(c) => {
566 let rendered = render_code_inline(c);
567 if rendered.lines().count() <= 1 {
568 out.push_str(&format!(" = {rendered}\n"));
569 } else {
570 out.push_str(" =\n");
571 for line in rendered.lines() {
572 indent(level + 1, out);
573 out.push_str(line);
574 out.push('\n');
575 }
576 }
577 }
578 KtBody::Block(c) => {
579 out.push_str(" {\n");
580 c.render(level + 1, out);
581 indent(level, out);
582 out.push_str("}\n");
583 }
584 }
585}
586
587fn render_fun_interface(
588 i: &KtFunInterface,
589 level: usize,
590 imports: &mut ImportSet,
591 out: &mut String,
592) {
593 if let Some(doc) = &i.kdoc {
594 render_kdoc(doc, level, out);
595 }
596 indent(level, out);
597 out.push_str(i.vis.prefix());
598 out.push_str("fun interface ");
599 out.push_str(&i.name);
600 if !i.type_params.is_empty() {
601 out.push_str(&format!("<{}>", i.type_params.join(", ")));
602 }
603 out.push_str(" {\n");
604 render_fun_sig(&i.method, level + 1, imports, out);
605 indent(level, out);
606 out.push_str("}\n");
607}
608
609fn render_property(p: &KtProperty, level: usize, imports: &mut ImportSet, out: &mut String) {
610 if let Some(doc) = &p.kdoc {
611 render_kdoc(doc, level, out);
612 }
613 indent(level, out);
614 for a in &p.annotations {
615 out.push_str(&format!("@{a} "));
616 }
617 out.push_str(p.vis.prefix());
618 for m in &p.modifiers {
619 out.push_str(m);
620 out.push(' ');
621 }
622 out.push_str(if p.mutable { "var " } else { "val " });
623 out.push_str(&p.name);
624 if let Some(ty) = &p.ty {
625 out.push_str(&format!(": {}", ty.render(imports)));
626 }
627 match &p.value {
628 KtPropertyValue::None => {}
629 KtPropertyValue::Delegate(d) => {
630 out.push_str(&format!(" by {}", render_code_inline(d)));
631 }
632 KtPropertyValue::Initializer(i) => {
633 out.push_str(&format!(" = {}", render_code_inline(i)));
634 }
635 }
636 out.push('\n');
637 if let Some(acc) = &p.accessors {
638 acc.render(level + 1, out);
639 }
640}
641
642#[cfg(test)]
643pub(crate) fn render_one(d: &KtDecl, package: &str) -> String {
644 KtFile::new(package).decl(d.clone()).render()
645}