Kotlin printer: review of 2026-10-15, idiom gaps and order of work for KotlinConf
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 10/100
調査の方向性
This is a review and planning ticket, not a single change: it asks for a prioritised list of printer improvements, to be split into their own issues. Start with the Kotlin printer's expression code, including KotlinNullability.receiverWithDot and the SAFE_CALL path, and with KotlinMethodPrinter:56. Done means the listed items exist as separate issues or checklist entries, each with its own acceptance criteria.
索引モデルが issue の本文から書いたものです。
説明
A review of the Java → Kotlin translation (2026-10-15), against two goals: the KotlinConf claim (correct output, more idiomatic than J2K, the gain attributable to the analyses) and the longer one of a translator whose output a Kotlin developer would accept as hand-written. Sources: the ratchets, the scoreboard numbers on maddi-mod#35/#36, the printer source at f012f16e9, and the output of the #125 measurement run (build/j2k/langchain4j-preconditions-off, fernflower-preconditions-off).
Verdict. On track for the first goal; not yet for the second. The output is correct, compact, Java-shaped Kotlin: the structure is idiomatic, the expressions are not.
What is strong
- Correctness: langchain4j-core 344/346 compiling, 1,106 tests (J2K 148 / 326); fernflower 199/199, 264 (J2K 37 / 39); guava translates in full where J2K does not complete.
- The ablation attributes it to nullability: langchain4j 301 → 343 compiling from nullability alone.
- Structural idioms are done:
when,object :,enum class,inner,fun interface,use,synchronized, labels,IntArray, expression bodies,const val,@JvmStaticcompanions, smart casts afterisand!!,valfor once-assigned locals, records →data class.
What the output looks like
ToolExecutionResultMessage.kt (langchain4j, fresh run):
private val contents: MutableList<Content> // built by Utils.copy, never mutated → List
this.contents = java.util.List.of(TextContent.from(builder.text)) // → listOf(...)
if (contents.size == 1 && contents.get(0) is TextContent) {return (contents.get(0) as TextContent).text() }
return (Objects.equals(this.id, that.id) && ...) // → this.id == that.id
override fun toString(): String = "ToolExecutionResultMessage{id='" + id + '\'' + ... // → a template
open class Builder { internal var id: String? = null ... } // `open` on everything
MergeHelper.kt (fernflower), the densest !! region:
initExprents[1]!!.fillBytecodeRange(initExprents[1]!!.getRight()!!.bytecode)
stat.getIncExprent()!!.fillBytecodeRange(initExprents[1]!!.getRight()!!.bytecode)
val arr: ArrayExprent?
arr = firstDoExprent.getRight() as ArrayExprent?
Every !! there is correct (short-circuit || defeats the smart cast), but a Kotlin author hoists initExprents[1]?.right into a val once. That shape is where fernflower's 2,946 !! come from: repeated calls to nullable getters, which no smart-cast pruning removes.
Translated tests: memory!!.add(...) on a fixture set in @BeforeEach (→ lateinit), UserMessage.from(("1" as String?)) (redundant cast and parentheses), java.util.List.of(m1, m2).
Idiom scorecard (printer source today)
| Done | Partial | Absent |
|---|---|---|
val/var, when from switch, object :, enums, inner, use, synchronized, labels, IntArray, lambdas/SAM, ternary → if, smart casts, lateinit, isNullOrEmpty, records → data class |
read-only collections (73 of 659 langchain4j declarations), properties (JDK getters only; a Java getX() stays a call), ?./?: (only under NullCheck.SAFE_CALL, off by default, or from Kotlin-parsed source), ranges (no indices/withIndex), index syntax (charAt only), also/run (generated wrappers only), variance (use-site only; multiple bounds dropped) |
string templates, ?./?: derived from Java null checks, let/apply, trailing lambdas and it, listOf/emptyMap, == for equals, streams → collection ops, Optional, buildString, object singletons / top-level functions, default arguments, @Throws, when from if-chains, equals/hashCode/toString → data class |
Counted on the fresh langchain4j output (29.6k lines with tests): ?. 0, ?: 0, .let 0, apply 0, string templates 8 against 194 concatenations, .stream() 61, Optional 101, fun getX()/setX()/isX() 63. fernflower: !! 2,946, ?. 0, ?: 0.
Risks
!!is the headline idiom number, and fernflower is 3× J2K (2,946 vs 997). J2K's output does not compile, which is the answer, but the table will be read.- The modification half of the claim is thin.
val/varis identical in all four ablation arms; read-only collections reach 11% of declarations. #25's world-wide parameters and locals are the remaining work; otherwise the claim should say "nullability" and present read-only types as the secondary effect. - Zero
?./?:is the first thing a Kotlin audience notices. The printer has theSAFE_CALLpath (KotlinNullability.receiverWithDot) and an elvis path for Kotlin-parsed source; deriving them from Java'sx != null ? x.m() : nullandif (v != null) v else dis local, pattern-based work. - Architecture. Straight from the Java AST to text, rewrites scattered through a 2,136-line expression printer, no intermediate Kotlin model. Fast for structural idioms; each expression-level rewrite (templates, collection ops, hoisting) becomes a special case in that file.
- Docs and tests lag.
kotlin-printing.mdhas no dated log and still quotes 84 compiling files; javadocs inKotlinTypeName,KotlinFieldPrinter,KotlinMethodPrintercall implemented features "refinements";KotlinMethodPrinter:56(static initializer asinit) may be a real gap. 92@Testmethods for 7.3k lines; the ratchets measure compile-and-pass, not idiom. The idiom numbers live only in issue comments. - Noise that costs nothing:
openon every class and method (the closed world knows what is subclassed), one-line{x }blocks, splitval arr: T?/arr = …,java.util.List.of,(x as T?).
Proposed order, for 11-25
?./?:from Java null-check shapes;==forObjects.equals/equals.- Hoist repeated nullable getter calls into a
val(finishKotlinValCopies): the fernflower!!lever. - maddi-mod#25 across the world for parameters and locals, so the modification analysis moves a number. If it cannot land, narrow the claim's wording now.
- One cleanup pass: drop
openwhere nothing in the world subclasses;listOf/emptyMap/list[i]; templates for concatenations;lateinitfor@BeforeEachfixtures; brace-block formatting. - Commit the scoreboard as a results file with SHAs; give
kotlin-printing.mda dated log, asnullability.mdhas.
Parked until after the proposal: streams → collection ops, Optional, data classes from equals/hashCode/toString, getX() → properties for Java-parsed code, default arguments, object singletons.
Each item here should become its own issue or a checklist entry when picked up; this ticket is the review.
🤖 Generated with Claude Code
- 主要言語
- Java
- スター
- 1
- フォーク
- 1
- 平均マージ
- 2時間 22分
- マージ済み PR(30日)
- 4
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
CodeLaser/maddi のほかの issue
-
build/ci good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
-
C# printer: an inner class of a generic class needs the outer type's arguments (Outer<A>.Inner)オープンfront-end:csharp
難易度 4/5 3〜5日 初心者へのやさしさ 30/100
-
Java → C# translation: an idiomatic C# printer driven by the modification and nullability analysesオープンenhancement extension front-end:csharp
難易度 5/5 1週間以上 初心者へのやさしさ 8/100
-
enhancement extension front-end:csharp
難易度 5/5 1週間以上 初心者へのやさしさ 8/100
-
難易度 5/5 1週間以上 初心者へのやさしさ 28/100
CodeLaser/maddi の issue をすべて見る
似ている issue
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
scylladb/scylla-cdc-source-connector#350 ·
メンテナーはふだん 1 日以内に返信
-
agent:working documentation enhancement go:build lane:needs-decision
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
Sanne/incus-spawn#1435 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
area:shuffle bug priority:high requires-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
apache/datafusion-comet#7062 ·
メンテナーはふだん 1 日以内に返信
-
wait for review
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
enhancement good first issue up-for-grabs
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
AnikethTS/ChangeRadar#22 ·
メンテナーはふだん 1 日以内に返信