Revert "[light classes] drop old light classes and backend: iteration #6"

This reverts commit 8aeda37d2e.
This commit is contained in:
Dmitry Gridin
2022-06-28 13:52:08 +02:00
parent d1cde22254
commit 8db53c0ca7
15 changed files with 135 additions and 48 deletions
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.asJava
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.asJava.builder.LightClassBuilderResult
import org.jetbrains.kotlin.asJava.builder.LightClassConstructionContext
import org.jetbrains.kotlin.asJava.classes.*
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -19,6 +21,8 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
typealias LightClassBuilder = (LightClassConstructionContext) -> LightClassBuilderResult
abstract class LightClassGenerationSupport {
abstract fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor?
@@ -0,0 +1,51 @@
/*
* 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.asJava.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.ClassBuilder
import org.jetbrains.kotlin.codegen.ClassBuilderFactory
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
class KotlinLightClassBuilderFactory(private val javaFileStub: PsiJavaFileStub) : ClassBuilderFactory {
private val stubStack = Stack<StubElement<PsiElement>>().apply {
@Suppress("UNCHECKED_CAST")
push(javaFileStub as StubElement<PsiElement>)
}
override fun getClassBuilderMode(): ClassBuilderMode = ClassBuilderMode.LIGHT_CLASSES
override fun newClassBuilder(origin: JvmDeclarationOrigin) = StubClassBuilder(stubStack, javaFileStub)
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)
@@ -0,0 +1,102 @@
/*
* 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.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.registry.Registry
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.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
data class LightClassBuilderResult(val stub: PsiJavaFileStub, val bindingContext: BindingContext, val diagnostics: Diagnostics)
fun buildLightClass(
packageFqName: FqName,
files: Collection<KtFile>,
generateClassFilter: GenerationState.GenerateClassFilter,
context: LightClassConstructionContext,
generate: (state: GenerationState, files: Collection<KtFile>) -> Unit
): LightClassBuilderResult {
val project = files.first().project
try {
if (Registry.`is`("kotlin.ultra.light.classes.error.on.old.backend", false)) {
error("Access to backend detected")
}
val classBuilderFactory = KotlinLightClassBuilderFactory(createJavaFileStub(packageFqName, files))
val state = GenerationState.Builder(
project,
classBuilderFactory,
context.module,
context.bindingContext,
context.languageVersionSettings?.let {
CompilerConfiguration().apply {
languageVersionSettings = it
put(JVMConfigurationKeys.JVM_TARGET, context.jvmTarget)
isReadOnly = true
}
} ?: CompilerConfiguration.EMPTY
).generateDeclaredClassFilter(generateClassFilter).wantsDiagnostics(false).build()
state.beforeCompile()
state.oldBEInitTrace(files)
generate(state, files)
val javaFileStub = classBuilderFactory.result()
stubComputationTrackerInstance(project)?.onStubComputed(javaFileStub, context)
return LightClassBuilderResult(javaFileStub, context.bindingContext, state.collectedExtraJvmDiagnostics)
} catch (e: ProcessCanceledException) {
throw e
} catch (e: RuntimeException) {
logErrorWithOSInfo(e, packageFqName, files.firstOrNull()?.virtualFile)
throw e
}
}
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 {
return 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(
"Could not generate LightClass for $fqName declared in $path\n" +
"System: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION} Java Runtime: ${SystemInfo.JAVA_RUNTIME_VERSION}",
cause,
)
}
private val LOG = Logger.getInstance(LightClassBuilderResult::class.java)
@@ -0,0 +1,29 @@
/*
* 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.asJava.builder
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.resolve.BindingContext
open class LightClassConstructionContext(
val bindingContext: BindingContext,
val module: ModuleDescriptor,
val languageVersionSettings: LanguageVersionSettings?,
val jvmTarget: JvmTarget,
)
@@ -0,0 +1,26 @@
/*
* 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 org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
interface LightClassDataHolder {
val extraDiagnostics: Diagnostics
interface ForClass : LightClassDataHolder
interface ForFacade : LightClassDataHolder
interface ForScript : ForClass
}
object InvalidLightClassDataHolder : LightClassDataHolder.ForClass {
override val extraDiagnostics: Diagnostics get() = shouldNotBeCalled()
private fun shouldNotBeCalled(): Nothing = throw UnsupportedOperationException("Should not be called")
}
class LightClassDataHolderImpl(
override val extraDiagnostics: Diagnostics
) : LightClassDataHolder.ForClass, LightClassDataHolder.ForFacade, LightClassDataHolder.ForScript
@@ -0,0 +1,13 @@
/*
* 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.impl.java.stubs.PsiJavaFileStub
interface StubComputationTracker {
fun onStubComputed(javaFileStub: PsiJavaFileStub, context: LightClassConstructionContext)
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2017 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.asJava.builder
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import com.intellij.psi.PsiMember
import com.intellij.psi.StubBasedPsiElement
data class MemberIndex(private val index: Int) {
companion object {
@JvmField
val KEY = Key.create<MemberIndex>("MEMBER_INDEX")
}
}
val PsiMember.memberIndex: MemberIndex?
get() = ((this as? StubBasedPsiElement<*>)?.stub as? UserDataHolder)?.getUserData(MemberIndex.KEY)
@@ -0,0 +1,213 @@
/*
* 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.asJava.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.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;
private int memberIndex = 0;
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));
last.putUserData(MemberIndex.KEY, new MemberIndex(memberIndex++));
}
@Override
public void done() {
if (!isPackageClass) {
StubElement pop = parentStack.pop();
assert pop == v.getResult() : "parentStack: got " + pop + ", expected " + v.getResult();
}
super.done();
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2019 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.openapi.project.Project
fun stubComputationTrackerInstance(project: Project): StubComputationTracker? {
return project.getComponent(StubComputationTracker::class.java)
}
@@ -14,6 +14,7 @@ import com.intellij.psi.javadoc.PsiDocComment
import com.intellij.psi.util.MethodSignature
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod
import org.jetbrains.kotlin.asJava.builder.LightMemberOriginForDeclaration
import org.jetbrains.kotlin.asJava.builder.MemberIndex
import org.jetbrains.kotlin.asJava.elements.KtLightAbstractAnnotation
import org.jetbrains.kotlin.asJava.elements.KtLightMethodImpl
import org.jetbrains.kotlin.asJava.elements.KtUltraLightModifierList
@@ -121,7 +122,10 @@ internal abstract class KtUltraLightMethod(
abstract val checkNeedToErasureParametersTypes: Boolean
override val psiTypeForNullabilityAnnotation: PsiType? get() = returnType
override val memberIndex: MemberIndex? = null
override val psiTypeForNullabilityAnnotation: PsiType?
get() = returnType
// These two overrides are necessary because ones from KtLightMethodImpl suppose that clsDelegate.returnTypeElement is valid
// While here we only set return type for LightMethodBuilder (see org.jetbrains.kotlin.asJava.classes.KtUltraLightClass.asJavaMethod)
@@ -13,6 +13,7 @@ import com.intellij.psi.util.MethodSignatureBackedByPsiMethod
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.*
import org.jetbrains.kotlin.asJava.builder.LightMemberOriginForDeclaration
import org.jetbrains.kotlin.asJava.builder.MemberIndex
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.cannotModify
import org.jetbrains.kotlin.asJava.classes.lazyPub
@@ -97,6 +98,8 @@ abstract class KtLightMethodImpl protected constructor(
return typeParameters.all { processor.execute(it, state) }
}
protected abstract val memberIndex: MemberIndex?
/* comparing origin and member index should be enough to determine equality:
for compiled elements origin contains delegate
for source elements index is unique to each member
@@ -104,10 +107,13 @@ abstract class KtLightMethodImpl protected constructor(
override fun equals(other: Any?): Boolean = other === this ||
other is KtLightMethodImpl &&
other.javaClass == javaClass &&
other.memberIndex == memberIndex &&
other.containingClass == containingClass &&
other.lightMemberOrigin == lightMemberOrigin
override fun hashCode(): Int = name.hashCode().times(31).plus(containingClass.hashCode())
override fun hashCode(): Int = name.hashCode()
.times(31).plus(containingClass.hashCode())
.times(31).plus(memberIndex.hashCode())
abstract override fun getDefaultValue(): PsiAnnotationMemberValue?