Skip to main content

kotlin_codegen/
render.rs

1//! Rendering: model → formatted Kotlin source. Two passes per file —
2//! declarations render into a body buffer while registering imports in an
3//! [`ImportSet`], then banner / `package` / sorted imports / body are
4//! assembled.
5
6use super::{
7    code::KtCode,
8    model::*,
9    slot::KtPropertyValue,
10    types::{ImportSet, KtType},
11};
12
13/// The default first line of every generated file — the "do not edit" marker
14/// a reader needs to know the file is machine-written.
15///
16/// A file renders this line unless [`KtFile::banner`] sets one of its own, in
17/// which case that text is used verbatim — and `""` suppresses the line
18/// entirely. Public so a consumer prepending its own header, or recognising a
19/// file as machine-written, has something to match against.
20pub const KOTLIN_BANNER: &str = "// Auto-generated by kotlin-codegen — do not edit by hand.";
21
22/// When a function's single-line signature (from the indentation through the
23/// closing parenthesis and return type) would exceed this many columns, the
24/// parameter list is broken onto one parameter per line. Keeps generated
25/// signatures readable instead of emitting one very long line.
26pub(crate) const MAX_SIGNATURE_WIDTH: usize = 100;
27
28impl KtFile {
29    /// Render the complete file: banner, package, sorted imports, then
30    /// declarations in insertion order separated by blank lines.
31    pub fn render(&self) -> String {
32        let mut imports = ImportSet::new(&self.package);
33        // Raw-text imports (file-level extras + Code-carried) register FIRST:
34        // raw text already uses short names, so its imports must own them —
35        // a colliding model-rendered type then falls back to its FQN.
36        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
82/// Render a `KtCode` value to a trimmed single-line string.
83fn 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
89/// Gather the imports referenced by raw `KtCode` values within a declaration.
90fn 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
147/// A signature has no body, so only parameter defaults can carry imports.
148fn 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
166/// KDoc: `/** … */` with ` * ` continuations for multi-line docs.
167fn 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
227/// `: Superclass(args), Interface, …` — the superclass, if any, first.
228fn 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
245/// A `companion object`: like a class body, but with no primary constructor
246/// and an optional name (absent renders the anonymous form).
247fn 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
348/// Render one parameter for the multiline signature layout, given the indent
349/// `level` of the parameter line itself. The type renders width-aware (see
350/// [`render_type_wrapped`]). A default value always renders inline and is
351/// deliberately excluded from the width decision: breaking the *type* cannot
352/// shorten a long default expression, so counting it would only force a
353/// pointless wrap.
354fn 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    // Column where the type begins on the parameter line.
362    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
369/// Width-aware type rendering: single-line when it fits within
370/// [`MAX_SIGNATURE_WIDTH`] starting at column `start_col`. A function type
371/// that doesn't fit breaks its parameters one-per-line at `level + 1` with
372/// the `) -> Ret` closer back at `level` (a nullable one keeps its `(…)?`
373/// wrapper around the broken form); each parameter type and the return type
374/// recurse at their own columns, so arbitrarily nested function types keep
375/// breaking. Non-function types render single-line regardless of width.
376fn 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
426/// The parts of a declaration that render as a function signature. Lets
427/// [`KtFun`] and [`KtFunSig`] share one layout implementation — including the
428/// width-driven parameter breaking — without either owning the other.
429struct SigView<'a> {
430    kdoc: Option<&'a String>,
431    annotations: &'a [String],
432    vis: KtVis,
433    modifiers: &'a [String],
434    /// `external` renders ahead of the other modifiers; it lives on the body.
435    external: bool,
436    generics: &'a [String],
437    /// Extension receiver, rendered as `Recv.` before the name.
438    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
478/// Everything up to but not including the body: kdoc, annotations, visibility,
479/// modifiers, `fun <generics> name(params): Ret`.
480fn 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        // Receiver position, not ordinary type position: a function type needs
503        // parentheses here (see `KtType::render_receiver`).
504        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    // Render the return-type suffix up front so the width decision accounts for
520    // the whole signature.
521    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    // Column at which the parameter list opens: length of the current (last)
533    // line already accumulated in `out` (indentation + `fun name`).
534    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        // One parameter per line, indented one level deeper, with a trailing
538        // comma and the closing paren back at the function's indent level. A
539        // parameter whose type is itself a wide function type breaks its own
540        // parameters one-per-line too (see `render_signature_param`).
541        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
555/// An abstract member: a signature and nothing else.
556fn 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}