Kotlin printer: review of 2026-10-15, idiom gaps and order of work for KotlinConf
还没有人认领这个 Issue。
评估
调研方向
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
- 平均合并
- 1 小时 58 分钟
- 30 天内合并 PR
- 5
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
CodeLaser/maddi 的其他 Issue
-
build/ci good first issue
难度 2/5 1-3 小时 新手友好度 72/100
-
bug front-end:javac
难度 4/5 3-5 天 新手友好度 48/100
-
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 一周以上 新手友好度 8/100
-
enhancement extension front-end:csharp
难度 5/5 一周以上 新手友好度 8/100
相似的 Issue
-
难度 1/5 1 小时以内 新手友好度 90/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
apache/rocketmq-dashboard#6896 ·
维护者通常 3 天内回复
-
难度 2/5 1-3 小时 新手友好度 67/100
-
bug
难度 2/5 1-3 小时 新手友好度 72/100
OpenIntegrationEngine/engine#496 ·
维护者通常 1 天内回复
-
enhancement good first issue
难度 2/5 1-3 小时 新手友好度 76/100
SasanLabs/VulnerableApp#836 ·
维护者通常 1 天内回复