Skip to content

A2: a companion object becomes its own type - #8

Merged
milyin merged 1 commit into
docs/validation-umbrellafrom
step/a2-companion-type
Aug 6, 2026
Merged

A2: a companion object becomes its own type#8
milyin merged 1 commit into
docs/validation-umbrellafrom
step/a2-companion-type

Conversation

@milyin

@milyin milyin commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Second step of #6. Stacked on #7.

KtClassKind::Companion was documented as "only valid as KtClass::companion"
and nothing enforced it. A companion object could be built as a top-level
declaration, and the renderer would emit companion object { … } at file level,
where it means nothing.

KtCompanion is now its own type with no impl Into<KtDecl>, so it cannot
reach a declaration position at all. That is proved by a compile_fail doctest
rather than by a runtime check:

/// ```compile_fail
/// use kotlin_codegen::{KtCompanion, KtFile};
/// let _ = KtFile::new("io.p").decl(KtCompanion::new());
/// ```

Being a distinct type, it also has no KtClassKind to be handed the wrong
variant and no primary constructor to misuse — a companion object cannot have
one, and now there is nowhere to put one.

Incidental cleanups

  • name: Option<String> replaces the empty-string-means-anonymous sentinel
    the renderer used to special-case. Naming a companion (companion object Factory) is now explicit instead of implied by a non-empty string.
  • KtClass::companion drops its Box — the indirection now comes from the
    Vec<KtDecl> inside KtCompanion.
  • Supertype rendering is shared between class and companion instead of
    duplicated.

Verification

All 41 pre-existing tests pass untouched — output is byte-identical. Two
tests added for the named and anonymous forms, plus the compile_fail doctest.
cargo clippy --all-targets clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

KtCompanion::named currently permits empty names, which can render invalid Kotlin (companion object ) and should be rejected or normalized.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR is the second step of the validation umbrella work, restructuring the declaration model so a Kotlin companion object cannot be constructed in an invalid top-level declaration position by making it a dedicated KtCompanion type.

Changes:

  • Introduces KtCompanion as its own model type (no Into<KtDecl>), and updates KtClass to hold Option<KtCompanion> instead of a boxed KtClass.
  • Updates the renderer to render companions via a dedicated render_companion path and shares superclass/interface rendering via render_supertypes.
  • Updates and extends tests to cover named vs anonymous companion rendering.
File summaries
File Description
src/model.rs Removes KtClassKind::Companion, adds KtCompanion, and updates KtClass companion storage/API accordingly.
src/render.rs Adds companion-specific import collection/rendering and factors supertype rendering into a shared helper.
src/tests.rs Migrates companion construction to KtCompanion and adds coverage for named/anonymous rendering.
src/lib.rs Exports KtCompanion from the crate prelude.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread src/model.rs
Comment on lines +392 to +398
/// A named `companion object Factory { … }`.
pub fn named(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
..Self::default()
}
}
@milyin
milyin changed the base branch from step/a1-class-kind-payload to docs/validation-umbrella August 6, 2026 11:38
`KtClassKind::Companion` was documented as "only valid as
`KtClass::companion`" and nothing enforced it, so a companion object
could be built as a top-level declaration and rendered into a file where
`companion object { … }` is meaningless.

Give it its own type. `KtCompanion` has no `impl Into<KtDecl>`, so it
cannot reach a declaration position at all — proved by a `compile_fail`
doctest rather than by a runtime check. Being a distinct type it also
has no primary constructor to misuse, and no `KtClassKind` to be given
the wrong one.

Two incidental cleanups fall out:

  * `name: Option<String>` replaces the empty-string-means-anonymous
    sentinel the renderer used to special-case.
  * `KtClass::companion` drops its `Box`: the indirection now comes from
    the `Vec<KtDecl>` inside `KtCompanion`.

Supertype rendering is shared between class and companion rather than
duplicated. Rendering is otherwise unchanged: all 41 pre-existing tests
pass untouched.
@milyin
milyin force-pushed the step/a2-companion-type branch from 191a2c1 to 2aee77c Compare August 6, 2026 11:40
@milyin
milyin merged commit 2e5305a into docs/validation-umbrella Aug 6, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants