fixup! Java Direct: implement main class finder functionality
diff --git a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaClassOverAst.kt b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaClassOverAst.kt index a277e58..0634504 100644 --- a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaClassOverAst.kt +++ b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaClassOverAst.kt
@@ -303,11 +303,13 @@ } override val constructors: Collection<JavaConstructor> + // A constructor is a METHOD node with no return TYPE (mirrors PSI's + // `getReturnTypeElement() == null`); the name is irrelevant. A malformed nameless + // declaration like `void () {}` — whose `void` is an error element, not a return type — + // is therefore a (package-private) constructor, matching PSI and suppressing the + // synthesized default constructor. get() = tree.getChildrenByType(node, JavaSyntaxElementType.METHOD) - .filter { - tree.findChildByType(it, JavaSyntaxElementType.TYPE) == null && - tree.findChildByType(it, JavaSyntaxTokenType.IDENTIFIER) != null - } + .filter { tree.findChildByType(it, JavaSyntaxElementType.TYPE) == null } .map { JavaConstructorOverAst(it, tree, this) } override val recordComponents: Collection<JavaRecordComponent>
diff --git a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaTypeOverAst.kt b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaTypeOverAst.kt index eaed996..1af76bc 100644 --- a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaTypeOverAst.kt +++ b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/model/JavaTypeOverAst.kt
@@ -115,24 +115,17 @@ // were reordered after the nested-class lookup below. findTypeParameter(parts[0])?.let { return it } // 2. Inner/local class names (shadow INHERITED outer type params) - val localClass = findClassInCurrentScope(parts[0]) - if (localClass != null) return localClass + val localClass = findClassInCurrentScope(parts[0])?.let { return it } // 3. INHERITED type parameters from outer class (low priority — shadowed by inner classes) findInheritedTypeParameter(parts[0])?.let { return it } } - // In-scope navigation, kept as a distinct pass *before* the [resolve] fallback below — - // NOT redundant with it. Resolve the head via [findClassInCurrentScope], then walk each - // remaining part with [declaredOrFullyInherited] (declared members plus fully-inherited - // member types from any supertype representation). This pass is load-bearing because: - // - it does not depend on a `FirSession` symbol provider, whereas [resolve]'s - // class-existence probe does (which also helps with parser-only tests); - // - even with a session present it resolves in-scope references straight from the - // AST/model, avoiding a symbol-provider round-trip per segment. - // A missing segment off an in-scope head is a hard miss (`return null`, JLS 6.5.2): - // once `parts[0]` is a class in scope, the tail must be its member type, so we do not - // fall through to [resolve]'s package/import reinterpretation of the whole reference. - var current: JavaClassifier? = findClassInCurrentScope(Name.identifier(parts[0])) + // In-scope (AST/model) navigation, kept as a distinct pass *before* the [resolve] + // fallback below: + // - it needs no `FirSession` symbol provider, unlike [resolve]'s class-existence probe + // (so it also serves parser-only tests); + // - even with a session it avoids a symbol-provider round-trip per segment. + var current: JavaClassifier? = findClassInCurrentScope(parts[0]) if (current is JavaClass) { for (i in 1 until parts.size) {
diff --git a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaInheritedClassResolver.kt b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaInheritedClassResolver.kt index 934aaeb..cdb1a31 100644 --- a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaInheritedClassResolver.kt +++ b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaInheritedClassResolver.kt
@@ -5,10 +5,11 @@ package org.jetbrains.kotlin.java.direct.resolution +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.java.JavaVisibilities import org.jetbrains.kotlin.load.java.structure.JavaClass import org.jetbrains.kotlin.load.java.structure.impl.splitCanonicalFqName import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name /** @@ -87,7 +88,7 @@ if (!visited.add(ancestorId)) continue val innerClassId = ancestorId.createNestedClassId(Name.identifier(simpleName)) - if (tryResolveInherited(innerClassId)) { + if (tryResolveInherited(innerClassId) && isInheritedNestedClassAccessible(innerClassId)) { if (foundClassId != null && foundClassId != innerClassId) return null foundClassId = innerClassId } else { @@ -102,17 +103,23 @@ return foundClassId } -internal fun fqNameInPackageToClassId(fqName: FqName, packageFqName: FqName): ClassId { - val fqnString = fqName.asString() - val pkgString = packageFqName.asString() - - val className = if (pkgString.isEmpty()) { - fqnString - } else if (fqnString.startsWith(pkgString) && fqnString.length > pkgString.length && fqnString[pkgString.length] == '.') { - fqnString.substring(pkgString.length + 1) - } else { - fqnString +/** + * JLS 6.6 accessibility of an inherited nested class from the file being resolved. An inaccessible + * nested class is not inherited (JLS 8.2), so it is not in scope and must not shadow a same-named + * top-level or same-package class: + * - `private` — never inherited; + * - package-private — inherited only within the declaring package; + * - `public` / `protected` (the reference site is always a subtype here) / Kotlin `internal` + * (public in bytecode) — in scope. + * + * Defaults to accessible when the nested class cannot be materialised. + */ +context(c: JavaResolutionContext) +private fun isInheritedNestedClassAccessible(innerClassId: ClassId): Boolean { + val nestedClass = classifierAdapterFor(innerClassId) ?: return true + return when (nestedClass.visibility) { + Visibilities.Private -> false + JavaVisibilities.PackageVisibility -> innerClassId.packageFqName == c.packageFqName + else -> true } - - return ClassId(packageFqName, FqName(className), isLocal = false) }
diff --git a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt index a1b7b65..937ac04 100644 --- a/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt +++ b/compiler/java-direct/src/org/jetbrains/kotlin/java/direct/resolution/JavaTypeResolver.kt
@@ -6,7 +6,6 @@ package org.jetbrains.kotlin.java.direct.resolution import org.jetbrains.annotations.TestOnly -import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.declarations.FirDeclarationOrigin import org.jetbrains.kotlin.fir.declarations.FirRegularClass @@ -60,13 +59,11 @@ parts: List<String>, fullResolution: Boolean, ): ClassId? { - // Try every split point of `parts` into an outer-class prefix and a nested-class tail, from - // the shortest outer prefix up. Every split is tried (not just the last one, `outerParts = - // parts[0..n-2]`) because the outer/nested boundary is not known up front: for `a.b.C.D` the - // outer class might be `a.b.C` (tail `D`) or `a.b` might be the package with `C.D` a two-part - // nested chain. Each candidate outer prefix is resolved with the same rules (recursively for - // multi-part prefixes), so JLS 6.5.2 is respected — a nested-class interpretation wins as soon - // as the outer prefix resolves to a class in scope. + // Try each split of `parts` into an outer-class prefix and a nested tail, shortest prefix first. + // The boundary isn't known up front (for `a.b.C.D`, the outer class could be `a.b.C` with tail + // `D`, or `a.b` with `C.D` a two-part nested tail), so every split is tried; each prefix resolves + // by the same rules (recursively when multi-part). Per JLS 6.5.2, the first prefix resolving to a + // class in scope wins. require(parts.size > 1) for (i in 1 until parts.size) { val outerParts = parts.subList(0, i) @@ -75,7 +72,7 @@ val outerClassId = if (outerParts.size > 1) { resolveQualifiedNameToClassIdFromParts(outerParts, fullResolution) } else { - resolveSimpleNameToClassIdImpl(outerParts[0], fullResolution = fullResolution) + resolveSimpleNameToClassIdImpl(outerParts[0], fullResolution) } if (outerClassId != null) { @@ -85,17 +82,11 @@ val nestedClassId = ClassId(outerClassId.packageFqName, nestedClassName, isLocal = false) if (classExists(nestedClassId, fullResolution)) return nestedClassId - // Nested class not directly declared — search the outer class's supertypes for an - // inherited inner class. Handles cases like `SimpleFunctionDescriptor.CopyBuilder`, - // where `CopyBuilder` is declared in the `FunctionDescriptor` superinterface but - // referenced via the subtype `SimpleFunctionDescriptor`. - // - // Restricted to a single inherited segment. A tail of size >= 2 (e.g. resolving - // `Outer.CopyBuilder.Deeper`) is reached one recursion level down: the split at the - // next `i` resolves `outerClassId` for the prefix `Outer.CopyBuilder` via the recursive - // `resolveQualifiedNameToClassIdFromParts` call above, and that call's own loop hits its - // `i` where `nestedParts == [CopyBuilder]` (size 1) and performs the inherited lookup - // there. So every "outer class plus one inherited segment" sub-problem is still covered. + // Not directly declared — search the outer class's supertypes for an inherited inner + // class (e.g. `SimpleFunctionDescriptor.CopyBuilder`, where `CopyBuilder` comes from the + // `FunctionDescriptor` superinterface). Restricted to a single tail segment; longer tails + // (`Outer.CopyBuilder.Deeper`) are covered one recursion level down, where the prefix + // `Outer.CopyBuilder` resolves and its own loop reaches a size-1 tail. if (fullResolution && nestedParts.size == 1) { val inherited = findInheritedNestedClass(outerClassId, nestedParts[0]) if (inherited != null) return inherited @@ -234,10 +225,7 @@ context(c: JavaResolutionContext) private fun resolveFromJavaLang(simpleName: String, fullResolution: Boolean): ClassId? { val classId = ClassId(FqName("java.lang"), Name.identifier(simpleName)) - if (JavaToKotlinClassMap.mapJavaToKotlin(classId.asSingleFqName()) != null || classExists(classId, fullResolution)) { - return classId - } - return null + return if (classExists(classId, fullResolution)) classId else null } /** @@ -574,8 +562,19 @@ } context(c: JavaResolutionContext) -private fun fqNameToClassId(fqName: FqName): ClassId = - fqNameInPackageToClassId(fqName, c.packageFqName) +private fun fqNameToClassId(fqName: FqName): ClassId { + val fqnString = fqName.asString() + val pkgString = c.packageFqName.asString() + val className = if (pkgString.isEmpty()) { + fqnString + } else if (fqnString.startsWith(pkgString) && fqnString.length > pkgString.length && fqnString[pkgString.length] == '.') { + fqnString.substring(pkgString.length + 1) + } else { + fqnString + } + return ClassId(c.packageFqName, FqName(className), isLocal = false) +} + /** * Resolves a FqName to a ClassId by trying all package/class splits from longest package to
diff --git a/compiler/java-direct/test/org/jetbrains/kotlin/java/direct/JavaParsingBasicTest.kt b/compiler/java-direct/test/org/jetbrains/kotlin/java/direct/JavaParsingBasicTest.kt index 19b90b0..3f63aea 100644 --- a/compiler/java-direct/test/org/jetbrains/kotlin/java/direct/JavaParsingBasicTest.kt +++ b/compiler/java-direct/test/org/jetbrains/kotlin/java/direct/JavaParsingBasicTest.kt
@@ -65,33 +65,6 @@ } @Test - fun testPublicClassWithMalformedMembers() { - val source = """ - package p; - public class Nameless { - void () {} - int ; - } - """.trimIndent() - val parsed = parseSource(source) - val tree = parsed.tree - val classNode = tree.getChildrenByType(parsed.root, JavaSyntaxElementType.CLASS) - .first { - tree.findChildByType(it, JavaSyntaxTokenType.IDENTIFIER)?.let { id -> tree.getText(id).toString() } == "Nameless" - } - val javaClass = JavaClassOverAst(classNode, tree, parsed.context) - assert(javaClass.visibility.toString() == "public") { - "Expected public visibility for 'public class Nameless', got ${javaClass.visibility}" - } - assert(javaClass.constructors.isEmpty()) { - "Malformed 'void () {}' should not be treated as a constructor" - } - assert(javaClass.hasDefaultConstructor()) { - "Class with no valid constructors should have a default constructor" - } - } - - @Test fun testWildcardAST() { val source = """ import java.util.List;