JVM IR: restore ability to suppress codegen-reported diagnostics
In the old backend, BindingContextSuppressCache is used (which is now created explicitly in GenerationState), which looks up `@Suppress` annotations on elements right before reporting the diagnostic. In JVM IR, we clear the binding context after psi2ir, so this approach doesn't work. This change provides another implementation of KotlinSuppressCache which eagerly precomputes all suppressions on all annotated elements in all source files at the point of creation of GenerationState (when the binding context is still full). #KT-43047 Fixed
This commit is contained in:
committed by
Alexander Udalov
parent
61548a0a36
commit
106ee1d1ff
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.resolve.*
|
||||
import org.jetbrains.kotlin.resolve.deprecation.CoroutineCompatibilitySupport
|
||||
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.PrecomputedSuppressCache
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind.*
|
||||
@@ -178,7 +179,11 @@ class GenerationState private constructor(
|
||||
}
|
||||
|
||||
val extraJvmDiagnosticsTrace: BindingTrace =
|
||||
DelegatingBindingTrace(originalFrontendBindingContext, "For extra diagnostics in ${this::class.java}", false)
|
||||
DelegatingBindingTrace(
|
||||
originalFrontendBindingContext, "For extra diagnostics in ${this::class.java}", false,
|
||||
customSuppressCache = if (isIrBackend) PrecomputedSuppressCache(originalFrontendBindingContext, files) else null,
|
||||
)
|
||||
|
||||
private val interceptedBuilderFactory: ClassBuilderFactory
|
||||
private var used = false
|
||||
|
||||
|
||||
Generated
+5
@@ -15807,6 +15807,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/ir/simple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suppressConflictingSignatureErrors.kt")
|
||||
public void testSuppressConflictingSignatureErrors() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/ir/suppressConflictingSignatureErrors.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/ir/closureConversion")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||
import org.jetbrains.kotlin.psi.KtExpression;
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.BindingContextSuppressCache;
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.MutableDiagnosticsWithSuppression;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
@@ -93,12 +94,12 @@ public class BindingTraceContext implements BindingTrace {
|
||||
this(TRACK_REWRITES && !allowSliceRewrite ? new TrackingSlicedMap(TRACK_WITH_STACK_TRACES) : new SlicedMapImpl(allowSliceRewrite), filter);
|
||||
}
|
||||
|
||||
|
||||
private BindingTraceContext(@NotNull MutableSlicedMap map, BindingTraceFilter filter) {
|
||||
this.map = map;
|
||||
this.mutableDiagnostics = !filter.getIgnoreDiagnostics()
|
||||
? new MutableDiagnosticsWithSuppression(bindingContext, Diagnostics.Companion.getEMPTY())
|
||||
: null;
|
||||
this.mutableDiagnostics =
|
||||
filter.getIgnoreDiagnostics()
|
||||
? null
|
||||
: new MutableDiagnosticsWithSuppression(new BindingContextSuppressCache(bindingContext), Diagnostics.Companion.getEMPTY());
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
|
||||
@@ -20,7 +20,9 @@ import com.google.common.collect.ImmutableMap
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.BindingContextSuppressCache
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.MutableDiagnosticsWithSuppression
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.expressions.typeInfoFactory.createTypeInfo
|
||||
@@ -31,7 +33,8 @@ open class DelegatingBindingTrace(
|
||||
private val name: String,
|
||||
withParentDiagnostics: Boolean = true,
|
||||
private val filter: BindingTraceFilter = BindingTraceFilter.ACCEPT_ALL,
|
||||
allowSliceRewrite: Boolean = false
|
||||
allowSliceRewrite: Boolean = false,
|
||||
customSuppressCache: KotlinSuppressCache? = null,
|
||||
) : BindingTrace {
|
||||
|
||||
protected val map = if (BindingTraceContext.TRACK_REWRITES && !allowSliceRewrite)
|
||||
@@ -39,8 +42,6 @@ open class DelegatingBindingTrace(
|
||||
else
|
||||
SlicedMapImpl(allowSliceRewrite)
|
||||
|
||||
protected val mutableDiagnostics: MutableDiagnosticsWithSuppression?
|
||||
|
||||
private inner class MyBindingContext : BindingContext {
|
||||
override fun getDiagnostics(): Diagnostics = mutableDiagnostics ?: Diagnostics.EMPTY
|
||||
|
||||
@@ -68,13 +69,12 @@ open class DelegatingBindingTrace(
|
||||
|
||||
private val bindingContext = MyBindingContext()
|
||||
|
||||
init {
|
||||
this.mutableDiagnostics = when {
|
||||
filter.ignoreDiagnostics -> null
|
||||
withParentDiagnostics -> MutableDiagnosticsWithSuppression(bindingContext, parentContext.diagnostics)
|
||||
else -> MutableDiagnosticsWithSuppression(bindingContext)
|
||||
}
|
||||
}
|
||||
protected val mutableDiagnostics: MutableDiagnosticsWithSuppression? =
|
||||
if (filter.ignoreDiagnostics) null
|
||||
else MutableDiagnosticsWithSuppression(
|
||||
customSuppressCache ?: BindingContextSuppressCache(bindingContext),
|
||||
if (withParentDiagnostics) parentContext.diagnostics else Diagnostics.EMPTY
|
||||
)
|
||||
|
||||
constructor(
|
||||
parentContext: BindingContext,
|
||||
|
||||
@@ -39,7 +39,7 @@ public class TemporaryBindingTrace extends DelegatingBindingTrace {
|
||||
protected final BindingTrace trace;
|
||||
|
||||
protected TemporaryBindingTrace(@NotNull BindingTrace trace, String debugName, BindingTraceFilter filter) {
|
||||
super(trace.getBindingContext(), debugName, true, filter, false);
|
||||
super(trace.getBindingContext(), debugName, true, filter, false, null);
|
||||
this.trace = trace;
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -23,20 +23,19 @@ import kotlin.collections.CollectionsKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class DiagnosticsWithSuppression implements Diagnostics {
|
||||
private final KotlinSuppressCache kotlinSuppressCache;
|
||||
private final KotlinSuppressCache suppressCache;
|
||||
private final Collection<Diagnostic> diagnostics;
|
||||
private final DiagnosticsElementsCache elementsCache;
|
||||
|
||||
public DiagnosticsWithSuppression(@NotNull BindingContext context, @NotNull Collection<Diagnostic> diagnostics) {
|
||||
public DiagnosticsWithSuppression(@NotNull KotlinSuppressCache suppressCache, @NotNull Collection<Diagnostic> diagnostics) {
|
||||
this.diagnostics = diagnostics;
|
||||
this.kotlinSuppressCache = new BindingContextSuppressCache(context);
|
||||
this.elementsCache = new DiagnosticsElementsCache(this, kotlinSuppressCache.getFilter());
|
||||
this.suppressCache = suppressCache;
|
||||
this.elementsCache = new DiagnosticsElementsCache(this, suppressCache.getFilter());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -48,13 +47,13 @@ public class DiagnosticsWithSuppression implements Diagnostics {
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<Diagnostic> iterator() {
|
||||
return new FilteringIterator<>(diagnostics.iterator(), kotlinSuppressCache.getFilter()::invoke);
|
||||
return new FilteringIterator<>(diagnostics.iterator(), suppressCache.getFilter()::invoke);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<Diagnostic> all() {
|
||||
return CollectionsKt.filter(diagnostics, kotlinSuppressCache.getFilter());
|
||||
return CollectionsKt.filter(diagnostics, suppressCache.getFilter());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+9
-11
@@ -16,26 +16,24 @@
|
||||
|
||||
package org.jetbrains.kotlin.resolve.diagnostics
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import java.util.ArrayList
|
||||
import com.intellij.openapi.util.CompositeModificationTracker
|
||||
import com.intellij.util.CachedValueImpl
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.util.CachedValueImpl
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
|
||||
class MutableDiagnosticsWithSuppression @JvmOverloads constructor(
|
||||
private val bindingContext: BindingContext,
|
||||
private val delegateDiagnostics: Diagnostics = Diagnostics.EMPTY
|
||||
class MutableDiagnosticsWithSuppression(
|
||||
private val suppressCache: KotlinSuppressCache,
|
||||
private val delegateDiagnostics: Diagnostics,
|
||||
) : Diagnostics {
|
||||
private val diagnosticList = ArrayList<Diagnostic>()
|
||||
|
||||
//NOTE: CachedValuesManager is not used because it requires Project passed to this object
|
||||
private val cache = CachedValueImpl(CachedValueProvider {
|
||||
private val cache = CachedValueImpl {
|
||||
val allDiagnostics = delegateDiagnostics.noSuppression().all() + diagnosticList
|
||||
CachedValueProvider.Result(DiagnosticsWithSuppression(bindingContext, allDiagnostics), modificationTracker)
|
||||
})
|
||||
CachedValueProvider.Result(DiagnosticsWithSuppression(suppressCache, allDiagnostics), modificationTracker)
|
||||
}
|
||||
|
||||
private fun readonlyView(): DiagnosticsWithSuppression = cache.value!!
|
||||
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.resolve.diagnostics
|
||||
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtAnnotated
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
|
||||
/**
|
||||
* A [KotlinSuppressCache] implementation that computes all suppressions at the moment of instantiation.
|
||||
* This is useful in the IR backend, where we clear the main binding context after psi2ir to avoid taking extra memory.
|
||||
* To make suppression of errors reported from backend possible though, we need to precompute all resolved `@Suppress` annotations,
|
||||
* and store this information outside of the binding context, which is going to be cleared.
|
||||
*
|
||||
* @param context the binding context where the data should be loaded from. Note that it shouldn't be stored as a property because the
|
||||
* primary use case of this class is when that binding context is cleared and thus is useless after a certain point.
|
||||
*/
|
||||
class PrecomputedSuppressCache(context: BindingContext, files: List<KtFile>) : KotlinSuppressCache() {
|
||||
val storage: Map<KtAnnotated, List<AnnotationDescriptor>> =
|
||||
mutableMapOf<KtAnnotated, List<AnnotationDescriptor>>().also { storage ->
|
||||
val visitor = PrecomputingVisitor(storage, BindingContextSuppressCache(context))
|
||||
for (file in files) {
|
||||
file.accept(visitor, null)
|
||||
}
|
||||
}
|
||||
|
||||
private class PrecomputingVisitor(
|
||||
val storage: MutableMap<KtAnnotated, List<AnnotationDescriptor>>,
|
||||
val computer: KotlinSuppressCache,
|
||||
) : KtTreeVisitorVoid() {
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
super.visitKtElement(element)
|
||||
if (element is KtAnnotated) {
|
||||
computeAnnotations(element)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitKtFile(file: KtFile) {
|
||||
super.visitKtFile(file)
|
||||
computeAnnotations(file)
|
||||
}
|
||||
|
||||
private fun computeAnnotations(element: KtAnnotated) {
|
||||
val suppressions = computer.getSuppressionAnnotations(element).filter { it.fqName == StandardNames.FqNames.suppress }
|
||||
if (suppressions.isNotEmpty()) {
|
||||
storage[element] = suppressions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSuppressionAnnotations(annotated: KtAnnotated): List<AnnotationDescriptor> =
|
||||
storage[annotated].orEmpty()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// This test checks that suppressing conflicting signature / accidental override errors works.
|
||||
// This is used in libraries for binary-compatible migration, as well as in some cases in multiplatform projects.
|
||||
// TARGET_BACKEND: JVM
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// FILE: box.kt
|
||||
|
||||
open class B {
|
||||
open val s: String
|
||||
get() = "Fail"
|
||||
}
|
||||
|
||||
class C : B() {
|
||||
@Suppress("ACCIDENTAL_OVERRIDE")
|
||||
fun getS(): String = "O"
|
||||
}
|
||||
|
||||
fun box(): String = C().getS() + D().getS()
|
||||
|
||||
// FILE: another.kt
|
||||
|
||||
@file:Suppress("ACCIDENTAL_OVERRIDE")
|
||||
|
||||
class D : B() {
|
||||
fun getS(): String = "K"
|
||||
}
|
||||
+5
@@ -17207,6 +17207,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/ir/simple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suppressConflictingSignatureErrors.kt")
|
||||
public void testSuppressConflictingSignatureErrors() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/ir/suppressConflictingSignatureErrors.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/ir/closureConversion")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
+5
@@ -17207,6 +17207,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
runTest("compiler/testData/codegen/box/ir/simple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suppressConflictingSignatureErrors.kt")
|
||||
public void testSuppressConflictingSignatureErrors() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/ir/suppressConflictingSignatureErrors.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/ir/closureConversion")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
+5
@@ -15807,6 +15807,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/ir/simple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suppressConflictingSignatureErrors.kt")
|
||||
public void testSuppressConflictingSignatureErrors() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/ir/suppressConflictingSignatureErrors.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/ir/closureConversion")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user