| # Java-Direct: Iteration Results Log |
| |
| **Current status**: `:compiler:java-direct:test` full suite green, 2839/2839 (100%). No known won't-fix. |
| |
| **Last archived**: `implDocs/archive/ITERATION_RESULTS_2026_07_13.md` (entries through 2026-07-13). |
| |
| --- |
| |
| ## How to write entries |
| |
| This log is read into the agent's context every session, so **entries must stay short**. |
| |
| - **Newest entry on top.** One entry per landed change or per investigated regression. |
| - **Cap each entry at ~15 lines / ~150 words.** If the rationale, a trace, or a |
| measurement table is longer, put it in a dedicated `implDocs/<TOPIC>.md` and link to it |
| from the entry — do not inline it here. |
| - **Use the fixed fields below.** No free-form multi-paragraph narration; if a field needs |
| more than ~2 lines, link out instead. |
| - **No pasted logs, stacktraces, or diffs.** Quote the single line that matters; link the rest. |
| - **Archive when this file passes ~600 lines** (see `AGENT_INSTRUCTIONS.md` → |
| "Docs Maintenance"): `git mv` it to |
| `implDocs/archive/ITERATION_RESULTS_<last-entry-date>.md`, add an archive banner, and |
| reset this file to the template below. |
| |
| ### Entry template |
| |
| ``` |
| ### YYYY-MM-DD — <one-line title> |
| - **Change**: what changed and why (1–3 lines). |
| - **Files**: key files touched (+N/−M LoC if useful). |
| - **Tests**: suites run + counts (e.g. box 1178/1178, phased 1513/1513). |
| - **Result**: green / regression fixed / won't-fix — link to a detail doc if there is one. |
| ``` |
| |
| --- |
| |
| <!-- Add new entries below, newest first. --> |
| |
| ### 2026-07-31 — Unit tests use JUnit asserters; lightweight scan of malformed package names |
| - **Change**: reviewer follow-up on `JavaParsingLightweightScannerTest`. Every raw Kotlin |
| `assert(...)` in the module's unit tests (a no-op without `-ea`) is now a JUnit assertion — |
| `assertEquals`/`assertTrue`/`assertFalse`/`assertNull`/`assertSame` and the contract-carrying |
| `org.junit.jupiter.api.assertNotNull`, which let the following `!!` go. Messages that only |
| restated expected/actual are dropped. New scanner tests: a package name split across a line |
| and a block comment (`package builder // c \n . /* c */ subpackage;`), `package com.123;`, and |
| `class 456 {}` with and without a well-formed sibling. `extractFileInfoLightweight` now joins |
| identifier segments instead of appending identifiers and dots verbatim, so a malformed name |
| degrades to its valid prefix (`com`) rather than the stray-dot `com.`. |
| - **Files**: `util/JavaSourceIndex.kt` (+3/−3); all 10 unit test files under `test/` (~440 |
| assertions rewritten), `JavaParsingLightweightScannerTest.kt` +4 tests. |
| - **Tests**: unit tests 121/121 green (12 classes); box + phased green (2795 executed, 0 |
| failures, 0 errors). |
| - **Result**: green. |
| |
| ### 2026-07-31 — Lazy enum-entry annotations without the fragile mutable list |
| - **Change**: the previous schema let `FirLazyJavaAnnotationMutableList` sit in |
| `FirEnumEntryImpl`'s `MutableOrEmptyList` slot, and depended on nothing but `isEmpty` being |
| called before the list materialised. Instead `FirEnumEntryImpl.annotations` is now a plain |
| `var annotations: List<FirAnnotation>` whose `replace` installs the given list as is, so |
| `FirJavaFacade` can install a `FirLazyJavaAnnotationList` (same class the members use) via |
| `replaceAnnotations` after `buildEnumEntry`. The only cost is `transformAnnotations`, which |
| maps into a new list instead of transforming in place. |
| - **Files**: `fir-jvm/.../FirJavaFacade.kt`, `fir-jvm/.../enhancement/FirJavaAnnotationList.kt` |
| (−29, `FirLazyJavaAnnotationMutableList` deleted); FIR tree generator: new |
| `ListField.isAssignableList` + `useAssignableList("annotations")` on `impl(enumEntry)`; |
| `FirEnumEntryImpl`/`FirEnumEntryBuilder` regenerated. The builder-side `var`-list support in |
| `generators/tree-generator-common` is reverted — those files are identical to master again. |
| - **Tests**: box + phased green (2795 executed, 0 failures, 0 errors); `JavaParsingTest` and |
| `JavaCycleBreakerTest` green; both regression gates green |
| (`PhasedJvmDiagnosticLightTreeTestGenerated.*`, `*CompileKotlinAgainstKotlin*`). |
| - **Result**: green; enum entries and members now share one lazy annotation list class. |
| |
| ### 2026-07-31 — Delete `JavaSupertypeGraph`: the duplicated supertype-reference resolver was test-only |
| - **Change**: answers the reviewer's standing objection to `resolveSupertypeReference` ("we partly |
| repeat type resolution logic here"). `JavaSupertypeGraph` re-derived import lookup, star-import |
| candidates and package/class splits from the AST, and returned an empty list for any dotted |
| supertype — an unspecified under-approximation. Repo-wide, its two entry points |
| (`LeanJavaClassFinder.getDirectSupertypes` / `collectInheritedInnerClasses`) had **no production |
| callers**: the test fake in `JavaParsingTestBase` implemented both as empty, and only |
| `JavaParsingClassFinderTest` invoked the real ones. Production already resolves the first |
| supertype level through the class's own `JavaResolutionContext` |
| (`resolveInheritedInnerClassToClassId`) and deeper levels through `directSupertypeClassIds`. |
| - **Files**: `util/JavaSupertypeGraph.kt` (−270, deleted), `JavaClassFinderOverAstImpl.kt`, |
| `resolution/LeanJavaClassFinder.kt`, `resolution/JavaModelSessionAccess.kt` (KDoc), |
| `implDocs/ARCHITECTURE.md`, `test/JavaParsingTestBase.kt`, `test/JavaParsingClassFinderTest.kt`. |
| - **Test rework**: `JavaParsingClassFinderTest` re-pointed at the real path — the `B<String>.C` case is |
| now a *positive* assertion (the inherited `a.B.C.N` must resolve; it previously only asserted the |
| wrongly-truncated `a.B` was absent), plus a new same-level multi-supertype test covering the |
| single-match and ambiguous-decline arms of `walkSupertypeClassIds`. Two graph-only tests |
| (`testGetDirectSupertypesUsesCache`, `testDiamondInheritanceInnerClasses`) were dropped: they |
| asserted the deleted cache, and the transitive/grandparent case they gestured at is already |
| covered end-to-end by `qualifiedInheritedNestedClassInOwnImplementsClause.kt`. |
| - **Tests**: `JavaParsing*` unit tests green (`JavaParsingClassFinderTest` 12/12); full box + |
| phased green (2795 executed, 0 failures, 0 errors). |
| - **Result**: green; per-representation duplicate removed, one supertype walk remains. |
| |
| ### 2026-07-31 — Box guards for the JLS accessibility check, incl. a genuinely *binary* Java supertype |
| - **Change**: complements the phased guard below with two box tests. Unlike diagnostics tests |
| (which hard-code `DependencyKind.Source`), a codegen `// MODULE: lib` dependency is really |
| compiled: `JavaCompilerFacade.compileJavaFiles` javac-compiles `lib`'s `.java` into `lib`'s output |
| dir, and `JvmEnvironmentConfigurator.registerModuleDependencies` puts that dir on `main`'s |
| classpath — so `main` reads `a.Base` as a **binary** Java class through |
| `FirBackedJavaClassAdapter`, i.e. the exact shape of the IJ-FP `testIntellij_exceptionAnalyzer` |
| regression. So a box test *can* express what a phased test cannot. |
| - **Files**: `testData/codegen/box/javaDirect/packagePrivateInheritedNestedClassNotVisibleAcrossPackages.kt` |
| (source arm, single module) and |
| `testData/codegen/boxJvm/javaDirect/packagePrivateInheritedNestedClassFromBinaryModule.kt` |
| (adapter arm, `lib` → `main`) — both new; no generator change needed. |
| - **Tests**: full `JavaUsingAstPhasedTestGenerated` + `JavaUsingAstBoxTestGenerated` green |
| (2795 executed, 0 failures, 0 errors); shared-data gate green in |
| `Fir{LightTree,Psi}BlackBoxCodegenTestGenerated$Box{,Jvm}$JavaDirect`. |
| - **Result**: green; non-vacuity verified with two probes — forcing |
| `FirBackedJavaClassAdapter.visibility` to `Visibilities.Public` fails *only* the binary-module |
| test, while short-circuiting `isInheritedNestedClassAccessible` to `true` fails *both*. Both |
| probes reverted (`git diff` on `src` empty). Box data needs no golden file, so rule 5/6 concerns |
| about shared diagnostic data do not apply. |
| |
| ### 2026-07-31 — Phased guard for the adapter's JLS accessibility check (via `// MODULE`) |
| - **Change**: the `FirBackedJavaClassAdapter.visibility` arm of `isInheritedNestedClassAccessible` |
| (2026-07-14, IJ-FP `testIntellij_exceptionAnalyzer`) had no small-test guard: the adapter is only |
| consulted for classes outside the module's own Java source index, and a phased test always gets |
| Java as sources. `// MODULE: lib` / `// MODULE: main(lib)` closes that gap — a dependency module's |
| Java class is not in `main`'s `JavaSourceRoot` set (`JavaDirectFacadeBuilder.kt:40`), so |
| `classifierAdapterFor` returns the adapter exactly as it does for binary Java. |
| - **Files**: `testData/diagnostics/tests/jvm/javaDirect/packagePrivateInheritedNestedClassFromOtherModule.kt` (new). |
| - **Tests**: new test green in `JavaUsingAstPhasedTestGenerated` and in both PSI gates |
| (`PhasedJvmDiagnostic{LightTree,Psi}TestGenerated`); full `JavaUsingAstPhasedTestGenerated` |
| re-run green, 0 FAILED. |
| - **Result**: green; non-vacuity verified — forcing `visibility` back to `Visibilities.Public` |
| makes it fail ("Actual data differs"). Note: diagnostics tests hard-code |
| `DependencyKind.Source`, so this is *not* a compiled-jar dependency; a genuinely binary |
| Java dependency still needs `PROVIDE_JAVA_AS_BINARIES` (foreign-annotations roots only) or a |
| box test. |
| |
| ### 2026-07-30 — Binary index remeasured on `KotlinFullPipelineTestsGenerated`; not the M1 0.5% |
| - **Change**: `JavaClassFinderOverBinaryIndex` no longer builds an `FqName` per lookup: the top-level |
| cache is now two-level, keyed by the `classId`'s own `packageFqName` + outermost class `Name` |
| (new `FqName.topLevelName()`), so the string concatenation, `pathSegments()` list, `FqName`/ |
| `FqNameUnsafe` pair and re-hash are gone from the hot path (378994 lookups, ~1.5M objects per |
| corpus). Paired in-run A/B: **631 -> 465 ns per hit lookup (-26%)**, but only ~54 ms per corpus. |
| - **Measured** (413-module FP corpus, JDK 8 launcher, 8 GB heap, M5 Max): the whole binary finder is |
| **9.47 s = 0.99%** of 958 s of compilation, of which 72% is `readBinaryJavaClass` on 69617 class |
| files — work master does through the same reader — 18% `knownClassNamesInPackage` (2.05M name |
| strings, 35306 traversals), 5% top-level index traversals. Allocation/GC with `-Xjava-direct` |
| on vs off is **identical** (216-221 GB per corpus, 112-119 young GCs, 8-9 full GCs), so extra |
| garbage is not the mechanism. The regression does not reproduce here: two A/B batches disagree on |
| the sign (±4%), i.e. developer-machine wall time cannot resolve 0.5%. |
| - **Files**: `JavaClassFinderOverBinaryIndex.kt`, review doc §12 (method, tables, and seven ranked |
| strategies for locating the gap on the mini), this file. All harnesses removed. |
| - **Tests**: java-direct box+phased green; full `KotlinFullPipelineTestsGenerated` (413 modules) |
| green. |
| - **Result**: index exonerated as the carrier of the M1 delta; see §12.5 for next steps. |
| |
| ### 2026-07-30 — `TopLevelClassFiles` shape: measured, holder kept |
| - **Change**: comment-only. Benchmarked the merged cache's value shape (holder object vs two-element |
| array vs `SmartList`-style encoded slot vs the pre-merge two maps) on the real workload from §10 |
| (1469/1615 finders, 19k/12k cached keys, 77k/42k lookups per suite). The **whole** cache costs |
| <1 ms and ~1.4 MB per full suite; the spread between shapes is ~0.15 ms/suite (~0.1 µs per |
| compilation). The array is not faster — it is the slowest single-map variant on a 20× corpus |
| (26.7 ms vs 23.4 ms) and loses the type/names; the encoded slot saves 33% of the allocation but |
| trades the scoped/unscoped distinction for an unchecked encoding. Kept the named holder, added a |
| KDoc pointer to `implDocs/BINARY_SOURCE_DIVIDE_REVIEW_2026_07_22.md` §11. |
| - **Files**: `JavaClassFinderOverBinaryIndex.kt` (KDoc +5), review doc §11, this file. |
| - **Tests**: java-direct box+phased green (no behaviour change). |
| - **Result**: won't-fix by measurement — see §11. |
| |
| ### 2026-07-30 — One top-level-class cache in the binary finder; roll back two no-op shared-file diffs |
| - **Change**: (1) `JavaClassFinderOverBinaryIndex` had two `FqName -> VirtualFile?` maps differing |
| only in `firstOrNull { it in scope }` vs `firstOrNull()`. Measured over both suites (3084 finder |
| instances): 69–75% of scoped keys were also in the all-scope map, **every** shared key had an |
| identical value, and no lookup ever saw >1 candidate or a candidate outside `scope` |
| (`multiCandidate=0`, `noCandidateInScope=0`) — see |
| `implDocs/BINARY_SOURCE_DIVIDE_REVIEW_2026_07_22.md` §10. Replaced by one |
| `FqName -> TopLevelClassFiles(anywhere, inScope)` cache filled in a single index pass, which keeps |
| the two answers distinct while dropping ~12k redundant index lookups per suite (−39%). |
| (2) Reverted `FirJvmConflictsChecker` + the `FirSession.getJavaClassLikeSymbolByClassId` forwarder |
| and `FirDirectJavaActualDeclarationExtractor` to master: master already bypassed the composite |
| provider, so both diffs were pure restyling, and no fixture covers the Kotlin-vs-*binary*-Java |
| redeclaration case the checker's comment described. |
| - **Files**: `JavaClassFinderOverBinaryIndex.kt` (−12/+15); reverted `FirJvmConflictsChecker.kt`, |
| `fir-jvm/JavaSymbolProvider.kt`, `FirDirectJavaActualDeclarationExtractor.kt` (shared-file diff |
| vs master for these three is now zero). |
| - **Tests**: java-direct box+phased green, `PhasedJvmDiagnosticLightTreeTestGenerated` green, |
| `FirLightTreeBlackBoxCodegenTestGenerated*CompileKotlinAgainstKotlin*` green. |
| - **Result**: green. |
| |
| ### 2026-07-29 — Fail-safe finder dispatch: identify the source scope, default to binary |
| - **Change**: `createJavaDirectJavaFacadeBuilder` now takes `javaSourcesScope` instead of |
| `librariesScope` and dispatches `scope === javaSourcesScope -> JavaClassFinderOverAstImpl`, every |
| other scope -> `JavaClassFinderOverBinaryIndex` over *that* scope. Previously the single |
| whitelisted case was the library scope, so any new binary scope (IC output, |
| HMPP-fragment classpath) would silently have been answered by the source AST finder. Memo map is |
| now an `IdentityHashMap` keyed by the scope object (was `System.identityHashCode` + ct.sym flag in |
| a data class — collision-prone with several scopes in play); the `CliVirtualFileFinderFactory` |
| lookup is hoisted out of the lambda. Also routed the HMPP-common library session |
| (`JvmFrontendPipelinePhase.kt:397`) through `javaDirectFacade`: it already asks for |
| `context.librariesScope`, so it reuses the *same* memoized binary finder, and it is a literal |
| no-op when `useJavaDirect` is off. The IC precompiled-binaries provider still constructs the PSI |
| facade deliberately — it has no test coverage (see the 2026-07-29 review notes). |
| - **Files**: `JavaDirectFacadeBuilder.kt` (−13/+16), `JvmFrontendPipelinePhase.kt` (2 lines), |
| `implDocs/ARCHITECTURE.md` §3. |
| - **Tests**: java-direct box+phased 2792/2792, `PhasedJvmDiagnosticLightTreeTestGenerated` |
| 10988/10991 (3 pre-existing skips). |
| - **Result**: green. The green java-direct suite is now itself evidence the source branch is taken: |
| if the scope identity failed to match, the source session would receive the binary finder and |
| every Java-source test would fail. |
| |
| ### 2026-07-29 — Express binary/source sidedness by wiring, not by two lookup vocabularies |
| - **Change**: dropped both parallel APIs added by the divide commit. (1) Removed the source-only |
| probes `isInSourceIndex` / `hasPackageInSources` / `sourceClassNamesInPackage` from |
| `JavaClassFinder` + `FirJavaFacade`; every production finder is already single-sided by |
| construction, so `JavaSymbolProvider` is back on `hasTopLevelClassOf` / `hasPackage` / |
| `knownClassNamesInPackage` (this restores a real AST-index gate on java-direct, where |
| `isInSourceIndex` was a constant `true`, and re-enables the facade caches). (2) Deleted the |
| `JvmBinaryClassFinderInputs` seam: `JvmBinaryClassFinderInputsOverIndex` became |
| `JavaClassFinderOverBinaryIndex : JavaClassFinder`, absorbing `LibraryJavaClassFinder` / |
| `BinaryPackageInfoJavaPackage`, so `JvmClassFileBasedSymbolProvider` has one input |
| (`javaFacade`), no `?:` fallbacks and no flag awareness. The `packagePartProvider` fallback in |
| `hasPackage` (for `@file:JvmPackageName`) is now unconditional. |
| - **Files**: `JavaClassFinder.kt`, `FirJavaFacade.kt`, `JavaSymbolProvider.kt`, |
| `JvmClassFileBasedSymbolProvider.kt`, `FirJvmSessionFactory.kt`, `JvmFrontendPipelinePhase.kt`, |
| `JavaDirectFacadeBuilder.kt`, +`JavaClassFinderOverBinaryIndex.kt`, |
| −`JvmBinaryClassFinderInputs.kt`, −`JvmBinaryClassFinderInputsOverIndex.kt`. |
| - **Tests**: java-direct box+phased 2792/2792, `PhasedJvmDiagnosticLightTreeTestGenerated` |
| 10991/10991, `FirLightTreeBlackBoxCodegenTestGenerated` (full) 10643/10643 incl. |
| `CompileKotlinAgainstKotlin`. |
| - **Result**: green. See the superseded banner in |
| `implDocs/BINARY_SOURCE_DIVIDE_REVIEW_2026_07_22.md`. |
| |
| ### 2026-07-29 — Re-derive the KT-74097 guard rationale after enum-entry annotations went lazy |
| - **Change**: re-traced whether the lazy Java annotation lists retire any cycle breaker. They do |
| not: they removed the only known crashing trigger (`@Deprecated` enum constant, |
| `testIntellij_vcs_git`), demoting `cycleSafeClassLikeSymbol` to genuine defense-in-depth, but the |
| cycle class stays reachable — the `declarations` lazy reads `FirJavaClass.typeParameters`, whose |
| bound enhancement iterates the *class's own* annotations via `extractDefaultQualifiers` (plus a raw |
| outer-class `getClassLikeSymbolByClassId`); the enum-entry `returnTypeRef` is still eager because |
| `SignatureEnhancement` requires a resolved ref; and 3 of 5 guard call sites carry no annotation |
| (const-field values, `@Target`, type-argument substitution). The other breakers |
| (`cycleGuardedSupertypeWalk`, supertype memoization, local `visited` sets) are annotation-agnostic. |
| Docs/comments updated accordingly; also fixed the stale `JavaSupertypeLoopChecker` name (the code |
| is `cycleGuardedSupertypeWalk` / `JavaModelSupertypeWalkGuard`). |
| - **Files**: `resolution/JavaModelSessionAccess.kt`, `test/.../JavaCycleBreakerTest.kt` (comments |
| only), `AGENT_INSTRUCTIONS.md`. |
| - **Tests**: not run — comment/docs-only, verified via `git diff` that no code line changed. |
| - **Result**: green (no code changes). |
| |
| ### 2026-07-29 — Clamp the lightweight scanner's brace/paren balance at zero |
| - **Change**: review of `extractFileInfoLightweight` against its production ancestor |
| `SingleJavaFileRootsIndex.JavaSourceClassIdReader` (cli-base). Unmatched closers drove the |
| balances negative, so `atTypeDeclaration()` (`== 0`) stopped firing and every top-level type |
| after a stray `}`/`)` was lost — for a stray `}` before the file's namesake class the file is |
| dropped from the index entirely (`tryBuildFileEntry` requires the base name). Now clamped, |
| mirroring `Kotlin.flex`'s `if (lBraceCount == 0) popState() else lBraceCount--`; no-op on |
| well-formed input. Also dropped a dangling KDoc, the redundant `if (at(SEMICOLON)) advance()` |
| (the class-scan loop skips it anyway) and mapped an empty package name to `null`. |
| Divergences from the ancestor are intentional and stay: the `break@loop` on a non-`package` |
| token (upstream appends the first class name to the package on a missing `;`), no `isPackageInfo` |
| arm (`JavaPackageIndexer` routes `package-info.java` to `JavaPackageInfoIndexer` first), and |
| `when (lexer.getTokenType())` instead of four `at()` calls. `getTokenType()` itself is a cached |
| field read (`JavaLexer.locateToken`: `if (myTokenType != null) return`), so repeated calls need |
| no hoisting; `getTokenText()` is the allocating one, and it is only reached at balance 0. |
| - **Files**: `util/JavaSourceIndex.kt` (+7/−12); `JavaParsingLightweightScannerTest.kt` (+47, 2 tests). |
| - **Tests**: `JavaParsing*` 16/16; box+phased 2792 executed / 0 FAILED. Both new tests fail |
| without the clamp (`got [Broken]`, `got [Foo]`). |
| - **Result**: green (error-tolerance fix). |
| |
| ### 2026-07-29 — Delete the `JavaSourceFileReader` abstraction; read via `File.readText` |
| - **Change**: after the `VirtualFile`→`File` switch the interface had a single implementation |
| (`DefaultJavaSourceFileReader`), `walkSourceRoots` had no callers (it served the deleted eager |
| `buildIndex`), and no production or test call site ever substituted a reader — the parameter was |
| threaded through four collaborators for nothing. Replaced by one internal |
| `readJavaSourceFileText(File): String?`; the reader parameter is gone from `JavaClassCache`, |
| `JavaPackageIndexer`, `JavaPackageInfoIndexer`, `JavaSupertypeGraph`, |
| `JavaClassFinderOverAstImpl` and `extractFileInfoLightweight`. Content is now read with |
| `File.readText()` instead of `String(readBytes(), UTF_8)`: measured over 1503 repo `.java` files |
| (37 MB, interleaved rounds, 7 samples) the medians are 46 ms vs 48 ms — ~2 ms of pure read time |
| per full corpus, i.e. negligible against lexing/parsing. |
| - **Files**: deleted `util/JavaSourceFileReader.kt` (−57), added `util/javaSourceFileText.kt` (+19); |
| `JavaClassCache.kt`, `JavaClassFinderOverAstImpl.kt`, `JavaPackageIndexer.kt`, |
| `JavaPackageInfoIndexer.kt`, `util/JavaSourceIndex.kt`, `util/JavaSupertypeGraph.kt`; tests |
| `JavaParsingTestBase.kt`, `JavaParsingLightweightScannerTest.kt`; `implDocs/ARCHITECTURE.md`. |
| - **Tests**: `JavaParsing*` green; box+phased 2790 executed / 0 FAILED. |
| - **Result**: green (behaviour-preserving simplification). |
| |
| ### 2026-07-28 — Replace `FirJavaEnumEntry` with `buildEnumEntry` + lazy `MutableList` annotations |
| - **Change**: review follow-up on the 2026-07-21 KT-74097 fix — the hand-written `FirJavaEnumEntry` |
| duplicated `FirEnumEntryImpl` (~180 LoC) just to host a lazy annotation slot. The tree generator |
| gained a per-field opt-in (`LeafBuilder.listFieldsWithVar` + `useVarForListField` DSL in |
| `AbstractBuilderConfigurator`; configured only for `builder(enumEntry)` in the FIR |
| `BuilderConfigurator`), so default builder generation is unchanged and only |
| `FirEnumEntryBuilder.annotations` becomes `var`. The enum-entry arm of |
| `convertJavaFieldToFir` uses plain `buildEnumEntry` with a new |
| `FirLazyJavaAnnotationMutableList` — an `AbstractMutableList` composing a plain |
| `FirLazyJavaAnnotationList` (conversion reused via `toMutableList()` on first mutable access; |
| only the 5 abstract members are overridden; cheap `isEmpty` keeps `toMutableOrEmpty()` from |
| forcing conversion) — and `FirJavaLazyDeprecationsProvider`. `FirJavaEnumEntry` deleted. |
| - **Files**: `generators/tree-generator-common/.../Builder.kt`, `AbstractBuilderPrinter.kt`, |
| `config/AbstractBuilderConfigurator.kt`, `fir/tree/tree-generator/.../BuilderConfigurator.kt`, |
| regenerated `fir/tree/gen/.../builder/FirEnumEntryBuilder.kt` (single-line `val`→`var`), |
| `fir-jvm/.../FirJavaFacade.kt`, `FirJavaAnnotationList.kt`, `javaAnnotationsMapping.kt`; |
| deleted `FirJavaEnumEntry.kt`. |
| - **Tests**: box+phased green (0 FAILED); PSI gate + `CompileKotlinAgainstKotlin` gate green |
| (shared fir-jvm + fir-tree edits). |
| - **Result**: green (behaviour-preserving simplification; laziness retained). |
| |
| ### 2026-07-28 — Comment-style cleanup of the binary/source-divide branch |
| - **Change**: rewrote/deleted LLM-verbose comments added since the divide commit (added |
| comment lines ~290 → ~125): dropped restatements of obvious code, caller inventories, |
| counterfactual "rather than" phrasing, per-`@param` chatter, `Stage`/`§`/`implDocs` |
| references in shared `fir-jvm` sources; kept short why-notes, API contracts, and |
| regression/testData guards. Codified the rules in `AGENT_INSTRUCTIONS.md` → |
| *Source Comment Conventions* ("default is no comment" gate, ~3% density baseline). |
| - **Files**: comment-only edits across ~20 files in `cli-base`, `cli-jvm`, `fir-jvm`, |
| `frontend.common.jvm`, `core/compiler.common.jvm`, `java-direct/{src,test}`; docs: |
| `AGENT_INSTRUCTIONS.md` (conventions rewrite, reference-table refresh, stale status header). |
| - **Tests**: not run — comment-only change, verified via `git diff` that no code line changed. |
| - **Result**: green (no code changes). |
| |
| ### 2026-07-27 — Make Java-source package directory descent case-sensitive |
| - **Change**: `JavaPackageIndexer`'s per-package directory descent used `File(dir, segment).isDirectory` |
| (added 2026-07-21 when the source path moved from `VirtualFile` to `java.io.File`), which is |
| case-insensitive on macOS/Windows. A sibling source dir (`syntax/logger`, `platform/ml/session`) |
| was wrongly accepted as package `Logger`/`Session`, so nested-class imports like |
| `com.intellij.platform.syntax.Logger.Attachment` mis-split into a package prefix and reported |
| `UNRESOLVED_IMPORT`. Descent now matches against the parent's real child names via `File.list()` |
| (case-sensitive), mirroring the binary index / PSI VFS `findChild`. |
| - **Files**: `JavaPackageIndexer.kt` (new `descendDirectoriesCaseSensitive`, used by |
| `findPackageDirectories` + `findPackageDirectoryUnder`). |
| - **Tests**: box+phased green (0 FAILED); `IntelliJFullPipelineTestsGenerated.testIntellij_platform_syntax` |
| and `testIntellij_platform_ml` now pass with java-direct on. |
| - **Result**: regression fixed. |
| |
| ### 2026-07-22 — Gate the binary seam on `useJavaDirect`; delete dead finder; dedup the ASM binary reader |
| - **Change**: applied `implDocs/BINARY_SOURCE_DIVIDE_REVIEW_2026_07_22.md` §4.1/§4.2/§4.7. |
| §4.1: the binary deserializer seam is now gated on `configuration.useJavaDirect` in |
| `prepareJvmSessions` — ON uses `JvmBinaryClassFinderInputsOverIndex`, OFF returns `null` so the |
| deserializer falls back to the PSI `FirJavaFacade` binary reader (both source and binary now share |
| one flag). §4.2: removed dead `CombinedJavaClassFinder.kt` (no references). §4.7: extracted the |
| shared `readBinaryJavaClass` core (caching + inner-class dispatch + `ClassifierResolutionContext`) |
| into `frontend.common.jvm`; both `JvmBinaryClassFinderInputsOverIndex` and the binary branch of |
| `KotlinCliJavaFileManagerImpl` delegate to it. Investigated §4.1's "turn off PSI finder creation": |
| not doable now — `KotlinCliJavaFileManagerImpl` still backs JPMS `module-info` resolution |
| (`ClasspathRootsResolver` → `JavaModuleInfo.read` → `findClass`) regardless of the flag, and its |
| PSI class-loading branch is already inert by default (`usePsiClassFilesReading=false`). |
| - **Files**: `cli-jvm/.../JvmFrontendPipelinePhase.kt`, `cli-base/.../KotlinCliJavaFileManagerImpl.kt`, |
| `frontend.common.jvm/.../classFiles/BinaryJavaClassReader.kt` (new), |
| `JvmBinaryClassFinderInputsOverIndex.kt`; deleted `CombinedJavaClassFinder.kt`. |
| - **Tests**: box+phased green (0 FAILED); PSI (`PhasedJvmDiagnosticLightTreeTestGenerated`) 0 fail, |
| `CompileKotlinAgainstKotlin` 0 fail (shared-pipeline + file-manager edits), `KotlinCliJavaFileManagerTest` 7/7. |
| - **Result**: green (behaviour-preserving refactor + flag-gated seam). |
| |
| ### 2026-07-21 — Thread `java.io.File` through the Java-source indexing path (drop internal `VirtualFile`) |
| - **Change**: the module no longer relies on `com.intellij.openapi.vfs.VirtualFile` for its own |
| source-file representation/reading; source roots are consumed as `java.io.File` (the CLI's |
| `JavaSourceRoot.file` is already a `File`, so the old VFS-resolution step is removed). Per-package |
| directory descent uses `File(dir, segment)`/`listFiles()`; content is read via `readBytes()` |
| decoded as UTF-8 (unchanged charset). Binary-class-finder/CLI wiring stays on `VirtualFile` |
| intentionally (external contract). |
| - **Files**: `JavaDirectFacadeBuilder.kt`, `JavaPackageIndexer.kt`, `JavaPackageInfoIndexer.kt`, |
| `util/JavaSourceFileReader.kt`, `util/JavaSourceIndex.kt`; tests `JavaParsingTestBase.kt`, |
| `JavaParsingClassFinderTest.kt`, `JavaParsingLightweightScannerTest.kt`. |
| - **Tests**: box+phased green (0 FAILED); `JavaParsing*` green. |
| - **Result**: green. Watch-point: a missing source root now drops later in the pipeline |
| (`isDirectory`/`isFile`) rather than at VFS resolution — equivalent end behaviour. |
| |
| ### 2026-07-21 — Defer Java enum-entry annotations via `FirLazyJavaAnnotationList` (KT-74097) |
| - **Change**: the enum-entry arm of `convertJavaFieldToFir` resolved annotations eagerly while |
| materialising `FirJavaClass.declarations`, which could re-enter the same in-flight `ClassId` |
| (self-cycle). New `FirJavaEnumEntry` (mirrors `FirJavaField`) backs `annotations`/ |
| `deprecationsProvider` with `FirLazyJavaAnnotationList`, so no eager resolution happens while |
| `declarations` is built. Removed the now-dead `setAnnotationsFromJava`; the |
| `cycleSafeClassLikeSymbol` guard is now defense-in-depth, not the sole crash preventer. |
| - **Files**: `fir-jvm/.../declarations/FirJavaEnumEntry.kt` (new), `fir-jvm/.../FirJavaFacade.kt`, |
| `fir-jvm/.../javaAnnotationsMapping.kt`, `JavaCycleBreakerTest.kt` (comment). |
| - **Tests**: box+phased green; PSI (`PhasedJvmDiagnosticLightTreeTestGenerated`) + |
| `CompileKotlinAgainstKotlin` gates green (shared fir-jvm edit). |
| - **Result**: green. |
| |
| ### 2026-07-21 — Reuse AST name extraction in `JavaSupertypeGraph` (drop `splitCanonicalFqName`) |
| - **Change**: supertype-reference name splitting no longer re-implements type-resolution via the |
| generic-bracket-aware `splitCanonicalFqName` text scan; it reuses the AST-based |
| `extractReferenceNameParts` (extracted from `JavaClassifierTypeOverAst` into `JavaTypeOverAst`), |
| reading `JAVA_CODE_REFERENCE` identifier segments directly. The generic-argument edge case |
| (`a.B<String>.C`) is preserved. |
| - **Files**: `util/JavaSupertypeGraph.kt`, `model/JavaTypeOverAst.kt`. |
| - **Tests**: box+phased green (0 FAILED); `JavaParsing*` green. |
| - **Result**: green. |
| |
| ### 2026-07-21 — Scan Java-lexer tokens in `JavaSourceIndex`; exclude comment/bad tokens from the light-tree root |
| - **Change**: `extractFileInfoLightweight` scans the Java-lexer token stream |
| (`JavaSyntaxDefinition.createLexer`) instead of regex/comment-stripping to find the package name |
| and top-level type names, using brace/paren balance for nesting; removed |
| `PACKAGE_REGEX`/`DECLARATION_REGEX`, manual comment stripping, and the now-unused reader |
| `openLineReader`. `JavaLightTree` synthetic-root children now also exclude comments (root-only, |
| each declaration keeps its `DOC_COMMENT` for `@deprecated`) and `BAD_CHARACTER`. |
| - **Files**: `util/JavaSourceIndex.kt`, `util/JavaSourceFileReader.kt`, `parse/JavaLightTree.kt`. |
| - **Tests**: `JavaParsing*` (incl. lightweight scanner) green; box+phased green (0 FAILED). |
| - **Result**: green. |
| |
| ### 2026-07-20 — Perf review: memoize recomputed reads in the Java-source model |
| - **Change**: the model layer recomputed pure, AST-derived values on every access. Converted the |
| hot ones to `by lazy(PUBLICATION)` (same precedent as `supertypes`/`typeParameters`): class |
| keyword flags (`isInterface`/`isEnum`/`isRecord`/`isAnnotationType`/`isSealed`), |
| `methods`/`fields`/`constructors`/`recordComponents`/`innerClassNames`/`annotations`; per-member |
| `resolutionContext`/`valueParameters`/`returnType` and field |
| `leadingFieldNode`/`modifierList`/`type`/`initializerNode`/`annotations`; type |
| `rawTypeNameParts`/`typeArguments`. Memoizing the class collections is the key enabler — member |
| wrappers are now stable, so the per-member lazies actually cache. Behaviour-preserving (pure |
| functions of the immutable AST + already-lazy `classifier`). |
| - **Files**: `model/JavaClassOverAst.kt`, `model/JavaMemberOverAst.kt`, `model/JavaTypeOverAst.kt`. |
| - **Tests**: box+phased 2767/2767 (0 FAILED); `JavaParsing*` 105/105. |
| - **Result**: green. Full write-up + reviewed-healthy caches + riskier follow-ups (plain-`HashMap` |
| concurrency in `JvmBinaryClassFinderInputsOverIndex`, annotation `classId` memoization, more |
| per-type/param lazies) in `implDocs/PERFORMANCE_REVIEW_2026_07_20.md`. |
| |
| ### 2026-07-20 — Read package-level default-nullability annotations off binary `package-info.class` |
| - **Change**: the library-session facade's finder (was a no-op `findPackage`) now materialises a |
| binary `<pkg>/package-info.class` and exposes its class-level annotations as the package's |
| `JavaPackage.annotations`. Previously those were dropped, so JSR-305/JSpecify package defaults |
| (`@ParametersAreNonnullByDefault`, `@TypeQualifierDefault`, `@NullMarked`, …) on a **binary** |
| Java package were invisible: a type-variable parameter substituted with an explicitly nullable |
| Kotlin type argument stayed nullable instead of becoming definitely-non-null, producing a |
| spurious `UNSAFE_CALL` in user code (dokka's `Property<File?>.map { it.relativeToOrSelf(..) }`). |
| The finder reuses the same memoised binary index the deserializer reads through; class/package |
| existence still routes through the deserializer, so only annotations are added. |
| - **Files**: `JvmBinaryClassFinderInputsOverIndex.kt` (+`findPackageInfoClass`), |
| `JavaDirectFacadeBuilder.kt` (`NoOpJavaClassFinder` → `LibraryJavaClassFinder` + |
| `BinaryPackageInfoJavaPackage`), `cli-jvm/…/JvmFrontendPipelinePhase.kt` (thread the shared |
| binary-inputs builder into the facade builder); new test |
| `codegen/boxJvm/javaInterop/foreignAnnotationsTests/tests/dnnParameterFromBinaryPackageAnnotation.kt`. |
| - **Tests**: full box+phased suite green (0 FAILED). New reproducer fails without the fix with the |
| exact reported symptom (`UNSAFE_CALL … nullable receiver of type 'String?'`) and passes with it. |
| - **Result**: regression fixed (java-direct now matches PSI on binary package defaults). |
| |
| ### 2026-07-16 — Remove the loose `probeFqnSplits` fallback: commit to the leftmost type like javac |
| - **Change**: `resolveQualifiedNameToClassIdFromParts` no longer retries a failed name as a plain |
| `package.Class` split. Like javac (JLS 6.5.4/6.5.5), once a leftmost type is found the |
| interpretation is committed: a failed member-type descent returns the *nonexistent* nested id of |
| the committed prefix (full resolution), which stays unresolved downstream — red code, exactly as |
| javac reports on a package/type name clash (JLS 6.1). The reentrance-safe flavor returns `null` |
| instead, so supertype-walk seeding is never poisoned by a dangling id. `probeFqnSplits` deleted. |
| - **Tests**: the strict behavior conflicts with the PSI Java model (which loosely resolves the |
| package interpretation), so the two tests pinning the loose behavior moved out of the shared |
| roots: `qualifiedNamePackageClassClash.kt` deleted from the shared roots and recreated with |
| javac-strict expectations in the new java-direct-owned `testData/diagnostics` root (wired into |
| `TestGenerator`); the pre-existing `javac/qualifiedExpression/PackageVsClass2.kt` — verified |
| against real javac to be red code ("cannot find symbol: class b, location: class a") — is |
| skipped via the new `SkipTestsPinningPsiJavaModelDeviationsMetaConfigurator` and mirrored |
| strictly in the same root. Strict diagnostics: `MISSING_DEPENDENCY_CLASS` on the call whose Java |
| signature uses the clash name + `UNRESOLVED_REFERENCE` on members of the unresolved type. |
| - **Files**: `resolution/JavaTypeResolver.kt` (−32/+18), `testFixtures/…/components.kt`, |
| `testFixtures/…/AbstractJavaUsingAstTest.kt`, `testFixtures/…/TestGenerator.kt`, |
| `build.gradle.kts` (own testdata root registered); Scenario D refreshed in `ReadMe.md` and |
| `implDocs/RESOLUTION_SCHEMA.md`. |
| - **Result**: the module is now javac-conformant on qualified-name resolution — the last |
| deliberate JLS deviation (KT-87813's unsound loose fallback) is gone. Full module suite green |
| (box + phased, 0 FAILED; the skipped PSI-pinning test is mirrored strictly). |
| |
| ### 2026-07-16 — Rewrite `resolveQualifiedNameToClassIdFromParts` as a left-to-right JLS 6.5.4 pass |
| - **Change**: replaced the recursive try-every-split loop (outer-prefix enumeration + per-prefix |
| recursion, O(n²) probes) with a single non-recursive left-to-right pass that mirrors javac's |
| PackageOrTypeName classification: first segment as a simple type name in scope (JLS 6.5.4.1), |
| else grow the package prefix until a segment names a top-level type (JLS 6.5.4.2), then a |
| member-type descent probing declared-then-inherited at every segment (JLS 6.5.5.2). The loose |
| `probeFqnSplits` fallback survives, but is now reached only when the JLS pass fails — the sole |
| divergence from javac remains the package/type name clash pinned by |
| `qualifiedNamePackageClassClash.kt` (KT-87813). |
| - **Files**: `resolution/JavaTypeResolver.kt` (−36/+28); Scenario D refreshed in `ReadMe.md` and |
| `implDocs/RESOLUTION_SCHEMA.md`. |
| - **Tests**: box+phased suite green (0 FAILED); `JavaParsing*` unit tests green. |
| - **Result**: simplification landed (behavior-preserving on the whole suite). |
| |
| ### 2026-07-15 — Drop redundant `JavaToKotlinClassMap` disjunct in `resolveFromJavaLang` |
| - **Change**: `resolveFromJavaLang` accepted a name when either `JavaToKotlinClassMap.mapJavaToKotlin` |
| hit **or** `classExists` was true; the map disjunct is dead. It only ever probes `ClassId(java.lang, X)`, |
| and `classExists` resolves those via the symbol provider whose JVM builtins arm answers only `kotlin.*` |
| ids (`StandardClassIds.builtInsPackages`), so a `java.lang.*` lookup never returns a `BuiltIns`-origin |
| symbol filtered by `tryResolve` — it hits the JDK library class instead. Every mapped `java.lang` fqName |
| (Object/String/Number/CharSequence/Comparable/Throwable/Cloneable/Iterable/Enum/wrappers/Deprecated) |
| exists in both full JDK and mockJDK, so `classExists` already covers exactly what the map would. |
| Collapsed to `classExists(classId, fullResolution)`, matching `resolveFromSamePackage`; better PSI |
| parity (no divergence in the no-JDK case). Removed the now-unused `JavaToKotlinClassMap` import. |
| - **Files**: `resolution/JavaTypeResolver.kt` (−5/+1). |
| - **Tests**: box+phased suite green (0 FAILED/0 errors). |
| - **Result**: simplification landed (reviewer question — `classExists` alone suffices). |
| |
| ### 2026-07-15 — Nameless Java method recovered as a constructor (`testNamelessInJava`) |
| - **Change**: `JavaClassOverAst.constructors` required a constructor `METHOD` node to have both no |
| return `TYPE` **and** an `IDENTIFIER`, so a malformed nameless declaration like `void () {}` |
| (its `void` is an error element, not a return type) was dropped; with no explicit constructor a |
| public default constructor was synthesized, so `class K : Nameless()` saw a visible constructor and |
| produced no diagnostic. PSI (same syntax parser) treats any no-return-type method as a |
| (package-private) constructor, suppressing the default one → `INVISIBLE_REFERENCE`. Dropped the |
| `IDENTIFIER` requirement so constructor detection mirrors PSI's `getReturnTypeElement() == null`. |
| - **Files**: `model/JavaClassOverAst.kt` (constructors filter). Test data unchanged (shared golden). |
| - **Tests**: full box+phased suite 2839/2839 (0 FAILED); `JavaParsingTest`/`JavaLightTreeTest` green. |
| - **Result**: regression fixed (new test from master merge; SDK 261 golden). |
| |
| ### 2026-07-14 — Skip inaccessible inherited nested classes (IJ-FP regression: `testIntellij_exceptionAnalyzer`) |
| - **Change**: `walkSupertypeClassIds` accepted the first inherited nested class of a matching simple |
| name regardless of accessibility, so a package-private nested type in a supertype from another |
| package (e.g. `SimpleColoredComponent.TextRenderer`) wrongly shadowed a same-named top-level class, |
| producing a spurious `RETURN_TYPE_MISMATCH`. Now filtered by JLS 6.6/8.2 accessibility: `private` |
| never inherited, package-private only within the declaring package; inaccessible matches expand |
| deeper instead of resolving. Visibility read cycle-safely via new `FirJavaClass.nonEnhancedVisibility` |
| (from `originalStatus`, no lazy `status`) surfaced through `FirBackedJavaClassAdapter.visibility`. |
| - **Files**: `resolution/JavaInheritedClassResolver.kt`, `resolution/FirBackedJavaClassAdapter.kt`, |
| `fir-jvm/.../FirJavaClass.kt` (+`nonEnhancedVisibility`); test |
| `codegen/box/javaDirect/packagePrivateInheritedNestedClassNotVisibleAcrossPackages.kt`. |
| - **Tests**: box 1179/1179, phased 1513/1513 (0 FAILED); `IntelliJFullPipelineTestsGenerated.testIntellij_exceptionAnalyzer` |
| green under java-direct; PSI + CompileKotlinAgainstKotlin gates green (shared `FirJavaClass` edit). |
| - **Result**: regression fixed. |
| |
| ### 2026-07-13 — Memoize `JavaClassOverAst.supertypes` (IJ-FP regression) |
| - **Change**: `supertypes` was a recomputing `get()` that returned fresh `JavaClassifierTypeOverAst` |
| instances on every read; FIR forces it from two lazy slots per class (`FirJavaClass.superTypeRefs` |
| enhancement and `directSupertypeClassIdsCache`), so each supertype's `classifier` (a per-instance |
| lazy hitting the symbol provider) resolved from cold twice. Made it `by lazy(PUBLICATION)` so both |
| reads share instances and each supertype resolves once. List build allocates wrappers only → still |
| resolution-safe. |
| - **Files**: `model/JavaClassOverAst.kt` (supertypes get()→by lazy). |
| - **Tests**: box 1178/1178, phased 1513/1513 (0 FAILED). |
| - **Result**: regression fixed. IntelliJ `testIntellij_platform_ide_impl` warm frontend (isolated bench, |
| 4 iters, same build): java-direct 21.5s wall / 20.8s CPU vs legacy 25.3s / 23.6s — was ~+8% before. |