JVM: remove StubClassBuilder and its usage

After legacy light classes were removed, the only remaining usage was in
KotlinLightClassBuilderFactory where we ran JVM backend in the
LIGHT_CLASSES mode, and that was only used from diagnostic tests to
report JVM backend diagnostics.

We don't actually need to build stubs here, so we can just use ASM's
class writer, which means that StubClassBuilder and all its dependencies
can be removed.
This commit is contained in:
Alexander Udalov
2024-03-09 00:14:37 +01:00
committed by Space Team
parent e5ad035039
commit 2f056fe155
9 changed files with 17 additions and 378 deletions
@@ -5,19 +5,13 @@
package org.jetbrains.kotlin.asJava.builder;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.impl.compiled.ClsClassImpl;
import com.intellij.psi.impl.compiled.ClsEnumConstantImpl;
import com.intellij.psi.impl.compiled.ClsFieldImpl;
import com.intellij.psi.impl.java.stubs.*;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ClsWrapperStubPsiFactory extends StubPsiFactory {
public static final Key<LightElementOrigin> ORIGIN = Key.create("ORIGIN");
public static final ClsWrapperStubPsiFactory INSTANCE = new ClsWrapperStubPsiFactory();
private final StubPsiFactory delegate = new ClsStubPsiFactory();
@@ -26,16 +20,7 @@ public class ClsWrapperStubPsiFactory extends StubPsiFactory {
@Override
public PsiClass createClass(@NotNull PsiClassStub stub) {
PsiElement origin = getOriginalElement(stub);
return new ClsClassImpl(stub) {
@NotNull
@Override
public PsiElement getNavigationElement() {
if (origin != null) return origin;
return super.getNavigationElement();
}
@Nullable
@Override
public PsiClass getSourceMirrorClass() {
@@ -44,12 +29,6 @@ public class ClsWrapperStubPsiFactory extends StubPsiFactory {
};
}
@Nullable
public static PsiElement getOriginalElement(@NotNull StubElement stub) {
LightElementOrigin origin = ((StubBase) stub).getUserData(ORIGIN);
return origin != null ? origin.getOriginalElement() : null;
}
@Override
public PsiAnnotation createAnnotation(PsiAnnotationStub stub) {
return delegate.createAnnotation(stub);
@@ -67,26 +46,7 @@ public class ClsWrapperStubPsiFactory extends StubPsiFactory {
@Override
public PsiField createField(PsiFieldStub stub) {
PsiElement origin = getOriginalElement(stub);
if (origin == null) return delegate.createField(stub);
if (stub.isEnumConstant()) {
return new ClsEnumConstantImpl(stub) {
@NotNull
@Override
public PsiElement getNavigationElement() {
return origin;
}
};
}
else {
return new ClsFieldImpl(stub) {
@NotNull
@Override
public PsiElement getNavigationElement() {
return origin;
}
};
}
return delegate.createField(stub);
}
@Override
@@ -10,23 +10,9 @@ import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind
interface LightElementOrigin {
val originalElement: PsiElement?
val originKind: JvmDeclarationOriginKind?
object None : LightElementOrigin {
override val originalElement: PsiElement?
get() = null
override val originKind: JvmDeclarationOriginKind?
get() = null
override fun toString() = "NONE"
}
}
interface LightMemberOrigin : LightElementOrigin {
override val originalElement: KtDeclaration?
override val originKind: JvmDeclarationOriginKind
interface LightMemberOrigin {
val originalElement: KtDeclaration?
val originKind: JvmDeclarationOriginKind
val parametersForJvmOverloads: List<KtParameter?>? get() = null
val auxiliaryOriginalElement: KtDeclaration? get() = null
@@ -59,7 +45,3 @@ data class LightMemberOriginForDeclaration(
return LightMemberOriginForDeclaration(originalElement.copy() as KtDeclaration, originKind, parametersForJvmOverloads)
}
}
data class DefaultLightElementOrigin(override val originalElement: PsiElement?) : LightElementOrigin {
override val originKind: JvmDeclarationOriginKind? get() = null
}
@@ -5,38 +5,22 @@
package org.jetbrains.kotlin.cli.jvm.compiler.builder
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.stubs.StubElement
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.codegen.AbstractClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.ClassWriter
class KotlinLightClassBuilderFactory(private val javaFileStub: PsiJavaFileStub) : ClassBuilderFactory {
private val stubStack = Stack<StubElement<PsiElement>>().apply {
@Suppress("UNCHECKED_CAST")
push(javaFileStub as StubElement<PsiElement>)
}
object KotlinLightClassBuilderFactory : ClassBuilderFactory {
override fun getClassBuilderMode(): ClassBuilderMode = ClassBuilderMode.LIGHT_CLASSES
override fun newClassBuilder(origin: JvmDeclarationOrigin) =
StubClassBuilder(stubStack, javaFileStub)
override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder =
object : AbstractClassBuilder() {
override fun getVisitor(): ClassVisitor = ClassWriter(0)
}
override fun asText(builder: ClassBuilder) = throw UnsupportedOperationException("asText is not implemented")
override fun asBytes(builder: ClassBuilder) = throw UnsupportedOperationException("asBytes is not implemented")
override fun close() {}
fun result(): PsiJavaFileStub {
val pop = stubStack.pop()
if (pop !== javaFileStub) {
LOG.error("Unbalanced stack operations: $pop")
}
return javaFileStub
}
}
private val LOG = Logger.getInstance(KotlinLightClassBuilderFactory::class.java)
@@ -9,10 +9,6 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
@@ -22,7 +18,7 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
data class CodeGenerationResult(val stub: PsiJavaFileStub, val bindingContext: BindingContext, val diagnostics: Diagnostics)
data class CodeGenerationResult(val bindingContext: BindingContext, val diagnostics: Diagnostics)
fun extraJvmDiagnosticsFromBackend(
packageFqName: FqName,
@@ -34,10 +30,9 @@ fun extraJvmDiagnosticsFromBackend(
val project = files.first().project
try {
val classBuilderFactory = KotlinLightClassBuilderFactory(createJavaFileStub(packageFqName, files))
val state = GenerationState.Builder(
project,
classBuilderFactory,
KotlinLightClassBuilderFactory,
context.module,
context.bindingContext,
context.languageVersionSettings?.let {
@@ -53,8 +48,7 @@ fun extraJvmDiagnosticsFromBackend(
generate(state, files)
val javaFileStub = classBuilderFactory.result()
return CodeGenerationResult(javaFileStub, context.bindingContext, state.collectedExtraJvmDiagnostics)
return CodeGenerationResult(context.bindingContext, state.collectedExtraJvmDiagnostics)
} catch (e: ProcessCanceledException) {
throw e
} catch (e: RuntimeException) {
@@ -63,21 +57,6 @@ fun extraJvmDiagnosticsFromBackend(
}
}
private fun createJavaFileStub(packageFqName: FqName, files: Collection<KtFile>): PsiJavaFileStub {
val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), /* compiled = */true)
javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
val fakeFile = object : ClsFileImpl(files.first().viewProvider) {
override fun getStub() = javaFileStub
override fun getPackageName() = packageFqName.asString()
override fun isPhysical() = false
override fun getText(): String = files.singleOrNull()?.text ?: super.getText()
}
javaFileStub.psi = fakeFile
return javaFileStub
}
private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) {
val path = virtualFile?.path ?: "<null>"
LOG.error(
@@ -1,203 +0,0 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.jvm.compiler.builder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.compiled.InnerClassSourceStrategy;
import com.intellij.psi.impl.compiled.StubBuildingVisitor;
import com.intellij.psi.impl.java.stubs.PsiClassStub;
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory;
import org.jetbrains.kotlin.asJava.builder.LightElementOrigin;
import org.jetbrains.kotlin.asJava.builder.LightElementOriginKt;
import org.jetbrains.kotlin.codegen.AbstractClassBuilder;
import org.jetbrains.kotlin.fileClasses.OldPackageFacadeClassUtils;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.org.objectweb.asm.ClassVisitor;
import org.jetbrains.org.objectweb.asm.FieldVisitor;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import java.util.List;
public class StubClassBuilder extends AbstractClassBuilder {
private static final InnerClassSourceStrategy<Object> EMPTY_STRATEGY = new InnerClassSourceStrategy<Object>() {
@Override
public Object findInnerClass(String s, Object o) {
return null;
}
@Override
public void accept(Object innerClass, StubBuildingVisitor<Object> visitor) {
throw new UnsupportedOperationException("Shall not be called!");
}
};
private final StubElement parent;
private final PsiJavaFileStub fileStub;
private StubBuildingVisitor v;
private final Stack<StubElement> parentStack;
private boolean isPackageClass = false;
public StubClassBuilder(@NotNull Stack<StubElement> parentStack, @NotNull PsiJavaFileStub fileStub) {
this.parentStack = parentStack;
this.parent = parentStack.peek();
this.fileStub = fileStub;
}
@NotNull
@Override
public ClassVisitor getVisitor() {
assert v != null : "Called before class is defined";
return v;
}
@Override
public void defineClass(
PsiElement origin,
int version,
int access,
@NotNull String name,
@Nullable String signature,
@NotNull String superName,
@NotNull String[] interfaces
) {
assert v == null : "defineClass() called twice?";
//noinspection ConstantConditions
v = new StubBuildingVisitor<>(null, EMPTY_STRATEGY, parent, access, calculateShortName(name));
super.defineClass(origin, version, access, name, signature, superName, interfaces);
if (origin instanceof KtFile) {
FqName packageName = ((KtFile) origin).getPackageFqName();
String packageClassName = OldPackageFacadeClassUtils.getPackageClassName(packageName);
if (name.equals(packageClassName) || name.endsWith("/" + packageClassName)) {
isPackageClass = true;
}
}
if (!isPackageClass) {
parentStack.push(v.getResult());
}
((StubBase) v.getResult()).putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightClassOrigin(origin));
}
@Nullable
private String calculateShortName(@NotNull String internalName) {
if (parent instanceof PsiJavaFileStub) {
assert parent == fileStub;
String packagePrefix = getPackageInternalNamePrefix();
assert internalName.startsWith(packagePrefix) : internalName + " : " + packagePrefix;
return internalName.substring(packagePrefix.length());
}
if (parent instanceof PsiClassStub<?>) {
String parentPrefix = getClassInternalNamePrefix((PsiClassStub) parent);
if (parentPrefix == null) return null;
assert internalName.startsWith(parentPrefix) : internalName + " : " + parentPrefix;
return internalName.substring(parentPrefix.length());
}
return null;
}
@Nullable
private String getClassInternalNamePrefix(@NotNull PsiClassStub classStub) {
String packageName = fileStub.getPackageName();
String classStubQualifiedName = classStub.getQualifiedName();
if (classStubQualifiedName == null) return null;
if (packageName.isEmpty()) {
return classStubQualifiedName.replace('.', '$') + "$";
}
else {
return packageName.replace('.', '/') + "/" + classStubQualifiedName.substring(packageName.length() + 1).replace('.', '$') + "$";
}
}
@NotNull
private String getPackageInternalNamePrefix() {
String packageName = fileStub.getPackageName();
if (packageName.isEmpty()) {
return "";
}
else {
return packageName.replace('.', '/') + "/";
}
}
@NotNull
@Override
public MethodVisitor newMethod(
@NotNull JvmDeclarationOrigin origin,
int access,
@NotNull String name,
@NotNull String desc,
@Nullable String signature,
@Nullable String[] exceptions
) {
MethodVisitor internalVisitor = super.newMethod(origin, access, name, desc, signature, exceptions);
if (internalVisitor != EMPTY_METHOD_VISITOR) {
// If stub for method generated
markLastChild(origin);
}
return internalVisitor;
}
@NotNull
@Override
public FieldVisitor newField(
@NotNull JvmDeclarationOrigin origin,
int access,
@NotNull String name,
@NotNull String desc,
@Nullable String signature,
@Nullable Object value
) {
FieldVisitor internalVisitor = super.newField(origin, access, name, desc, signature, value);
if (internalVisitor != EMPTY_FIELD_VISITOR) {
// If stub for field generated
markLastChild(origin);
}
return internalVisitor;
}
private void markLastChild(@NotNull JvmDeclarationOrigin origin) {
List children = v.getResult().getChildrenStubs();
StubBase last = (StubBase) children.get(children.size() - 1);
LightElementOrigin oldOrigin = last.getUserData(ClsWrapperStubPsiFactory.ORIGIN);
if (oldOrigin != null) {
PsiElement originalElement = oldOrigin.getOriginalElement();
throw new IllegalStateException("Rewriting origin element: " +
(originalElement != null ? originalElement.getText() : null) + " for stub " + last.toString());
}
last.putUserData(ClsWrapperStubPsiFactory.ORIGIN, LightElementOriginKt.toLightMemberOrigin(origin));
}
@Override
public void done(boolean generateSmapCopyToAnnotation) {
if (!isPackageClass) {
StubElement pop = parentStack.pop();
assert pop == v.getResult() : "parentStack: got " + pop + ", expected " + v.getResult();
}
super.done(generateSmapCopyToAnnotation);
}
}
@@ -48,7 +48,7 @@ object CliExtraDiagnosticsProvider {
.ifEmpty { return Diagnostics.EMPTY }
val context = (LightClassGenerationSupport.getInstance(project) as CliLightClassGenerationSupport).context
val (_, _, diagnostics) = extraJvmDiagnosticsFromBackend(
val (_, diagnostics) = extraJvmDiagnosticsFromBackend(
facadeFqName.parent(),
facadeCollection,
ClassFilterForFacade,
@@ -33,7 +33,7 @@ internal class LightClassDataProviderForClassOrObject(
//force resolve companion for light class generation
cliSupport.traceHolder.bindingContext.get(BindingContext.CLASS, classOrObject)?.companionObjectDescriptor
val (_, bindingContext, diagnostics) = extraJvmDiagnosticsFromBackend(
val (bindingContext, diagnostics) = extraJvmDiagnosticsFromBackend(
packageFqName,
listOf(file),
ClassFilterForClassOrObject(classOrObject),
@@ -1,42 +0,0 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.fileClasses;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.name.FqName;
public final class OldPackageFacadeClassUtils {
private static final String PACKAGE_CLASS_NAME_SUFFIX = "Package";
private static final String DEFAULT_PACKAGE_CLASS_NAME = "_Default" + PACKAGE_CLASS_NAME_SUFFIX;
private OldPackageFacadeClassUtils() {
}
// ex. <root> -> _DefaultPackage, a -> APackage, a.b -> BPackage
@NotNull
public static String getPackageClassName(@NotNull FqName packageFQN) {
if (packageFQN.isRoot()) {
return DEFAULT_PACKAGE_CLASS_NAME;
}
return capitalizeNonEmptyString(packageFQN.shortName().asString()) + PACKAGE_CLASS_NAME_SUFFIX;
}
@NotNull
private static String capitalizeNonEmptyString(@NotNull String s) {
return Character.isUpperCase(s.charAt(0)) ? s : Character.toUpperCase(s.charAt(0)) + s.substring(1);
}
}
@@ -1,21 +0,0 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.asJava.builder
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
fun JvmDeclarationOrigin.toLightMemberOrigin(): LightElementOrigin = when (val originalElement = element) {
is KtAnnotationEntry -> DefaultLightElementOrigin(originalElement)
is KtDeclaration -> LightMemberOriginForDeclaration(originalElement, originKind, parametersForJvmOverload)
else -> LightElementOrigin.None
}
fun PsiElement?.toLightClassOrigin(): LightElementOrigin {
return if (this != null) DefaultLightElementOrigin(this) else LightElementOrigin.None
}