Add new compiler errors and flags when JVM compiles against JVM IR
From now on, the old JVM backend will report an error by default when compiling against class files produced by the JVM IR backend. This is needed because we're not yet sure that the ABI generated by JVM IR is fully correct and do not want to land in a 2-dimensional compatibility situation where we'll need to consider twice more scenarios when introducing any breaking change in the language. This is generally OK since the JVM IR backend is still going to be experimental in 1.4. However, for purposes of users which _do_ need to compile something with the old backend against JVM IR, we provide two new compiler flags: * -Xallow-jvm-ir-dependencies -- allows to suppress the error when compiling with the old backend against JVM IR. * -Xir-binary-with-stable-api -- allows to mark the generated binaries as stable, when compiling anything with JVM IR, so that dependent modules will compile even with the old backend automatically. In this case, the author usually does not care for the generated ABI, or s/he ensures that it's consistent with the one expected by the old compiler with some external tools. Internally, this is implemented by storing two new flags in kotlin.Metadata: one tells if the class file was compiled with the JVM IR, and another tells if the class file is stable (in case it's compiled with JVM IR). Implementation is similar to the diagnostic reported by the pre-release dependency checker.
This commit is contained in:
@@ -264,6 +264,7 @@ class GenerationState private constructor(
|
||||
val disableOptimization = configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false)
|
||||
|
||||
val metadataVersion = configuration.get(CommonConfigurationKeys.METADATA_VERSION) ?: JvmMetadataVersion.INSTANCE
|
||||
val isIrWithStableAbi = configuration.getBoolean(JVMConfigurationKeys.IS_IR_WITH_STABLE_ABI)
|
||||
|
||||
val globalSerializationBindings = JvmSerializationBindings()
|
||||
lateinit var irBasedMapAsmMethod: (FunctionDescriptor) -> Method
|
||||
|
||||
+15
@@ -93,6 +93,20 @@ class K2JVMCompilerArguments : CommonCompilerArguments() {
|
||||
)
|
||||
var irCheckLocalNames: Boolean by FreezableVar(false)
|
||||
|
||||
@Argument(
|
||||
value = "-Xallow-jvm-ir-dependencies",
|
||||
description = "When not using the IR backend, do not report errors on those classes in dependencies, " +
|
||||
"which were compiled by the IR backend"
|
||||
)
|
||||
var allowJvmIrDependencies: Boolean by FreezableVar(false)
|
||||
|
||||
@Argument(
|
||||
value = "-Xir-binary-with-stable-abi",
|
||||
description = "When using the IR backend, produce binaries which can be read by non-IR backend.\n" +
|
||||
"The author is responsible for verifying that the resulting binaries do indeed have the correct ABI"
|
||||
)
|
||||
var isIrWithStableAbi: Boolean by FreezableVar(false)
|
||||
|
||||
@Argument(value = "-Xmodule-path", valueDescription = "<path>", description = "Paths where to find Java 9+ modules")
|
||||
var javaModulePath: String? by NullableStringFreezableVar(null)
|
||||
|
||||
@@ -327,6 +341,7 @@ class K2JVMCompilerArguments : CommonCompilerArguments() {
|
||||
result[JvmAnalysisFlags.sanitizeParentheses] = sanitizeParentheses
|
||||
result[JvmAnalysisFlags.suppressMissingBuiltinsError] = suppressMissingBuiltinsError
|
||||
result[JvmAnalysisFlags.irCheckLocalNames] = irCheckLocalNames
|
||||
result[AnalysisFlags.reportErrorsOnIrDependencies] = !useIR && !useFir && !allowJvmIrDependencies
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
+12
-10
@@ -146,25 +146,19 @@ class AnalyzerWithCompilerReport(
|
||||
return diagnostic.severity == Severity.ERROR
|
||||
}
|
||||
|
||||
data class ReportDiagnosticsResult(val hasErrors: Boolean, val hasIncompatibleClassErrors: Boolean)
|
||||
|
||||
fun reportDiagnostics(unsortedDiagnostics: Diagnostics, reporter: DiagnosticMessageReporter): ReportDiagnosticsResult {
|
||||
fun reportDiagnostics(unsortedDiagnostics: Diagnostics, reporter: DiagnosticMessageReporter): Boolean {
|
||||
var hasErrors = false
|
||||
var hasIncompatibleClassErrors = false
|
||||
val diagnostics = sortedDiagnostics(unsortedDiagnostics.all())
|
||||
for (diagnostic in diagnostics) {
|
||||
hasErrors = hasErrors or reportDiagnostic(diagnostic, reporter)
|
||||
hasIncompatibleClassErrors = hasIncompatibleClassErrors or
|
||||
(diagnostic.factory == Errors.INCOMPATIBLE_CLASS || diagnostic.factory == Errors.PRE_RELEASE_CLASS)
|
||||
}
|
||||
|
||||
return ReportDiagnosticsResult(hasErrors, hasIncompatibleClassErrors)
|
||||
return hasErrors
|
||||
}
|
||||
|
||||
fun reportDiagnostics(diagnostics: Diagnostics, messageCollector: MessageCollector): Boolean {
|
||||
val (hasErrors, hasIncompatibleClassErrors) = reportDiagnostics(diagnostics, DefaultDiagnosticReporter(messageCollector))
|
||||
val hasErrors = reportDiagnostics(diagnostics, DefaultDiagnosticReporter(messageCollector))
|
||||
|
||||
if (hasIncompatibleClassErrors) {
|
||||
if (diagnostics.any { it.factory == Errors.INCOMPATIBLE_CLASS || it.factory == Errors.PRE_RELEASE_CLASS }) {
|
||||
messageCollector.report(
|
||||
ERROR,
|
||||
"Incompatible classes were found in dependencies. " +
|
||||
@@ -172,6 +166,14 @@ class AnalyzerWithCompilerReport(
|
||||
)
|
||||
}
|
||||
|
||||
if (diagnostics.any { it.factory == Errors.IR_COMPILED_CLASS }) {
|
||||
messageCollector.report(
|
||||
ERROR,
|
||||
"Classes compiled by a new Kotlin compiler backend were found in dependencies. " +
|
||||
"Remove them from the classpath or use '-Xallow-jvm-ir-dependencies' to suppress errors"
|
||||
)
|
||||
}
|
||||
|
||||
return hasErrors
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,7 @@ fun CompilerConfiguration.configureAdvancedJvmOptions(arguments: K2JVMCompilerAr
|
||||
put(JVMConfigurationKeys.PARAMETERS_METADATA, arguments.javaParameters)
|
||||
|
||||
put(JVMConfigurationKeys.IR, arguments.useIR && !arguments.noUseIR)
|
||||
put(JVMConfigurationKeys.IS_IR_WITH_STABLE_ABI, arguments.isIrWithStableAbi)
|
||||
put(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, arguments.noCallAssertions)
|
||||
put(JVMConfigurationKeys.DISABLE_RECEIVER_ASSERTIONS, arguments.noReceiverAssertions)
|
||||
put(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, arguments.noParamAssertions)
|
||||
|
||||
@@ -116,4 +116,7 @@ public class JVMConfigurationKeys {
|
||||
|
||||
public static final CompilerConfigurationKey<List<String>> KLIB_PATHS =
|
||||
CompilerConfigurationKey.create("Paths to .klib libraries");
|
||||
|
||||
public static final CompilerConfigurationKey<Boolean> IS_IR_WITH_STABLE_ABI =
|
||||
CompilerConfigurationKey.create("Is IR with stable ABI");
|
||||
}
|
||||
|
||||
@@ -41,4 +41,7 @@ object AnalysisFlags {
|
||||
|
||||
@JvmStatic
|
||||
val ideMode by AnalysisFlag.Delegates.Boolean
|
||||
|
||||
@JvmStatic
|
||||
val reportErrorsOnIrDependencies by AnalysisFlag.Delegates.Boolean
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ public interface Errors {
|
||||
DiagnosticFactory1<PsiElement, String> MISSING_IMPORTED_SCRIPT_PSI = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> PRE_RELEASE_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> IR_COMPILED_CLASS = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory2<PsiElement, String, IncompatibleVersionErrorData<?>> INCOMPATIBLE_CLASS = DiagnosticFactory2.create(ERROR);
|
||||
|
||||
//Elements with "INVISIBLE_REFERENCE" error are marked as unresolved, unlike elements with "INVISIBLE_MEMBER" error
|
||||
|
||||
+1
@@ -384,6 +384,7 @@ public class DefaultErrorMessages {
|
||||
MAP.put(MISSING_IMPORTED_SCRIPT_PSI, "Imported script file ''{0}'' is not loaded. Check your script imports", TO_STRING);
|
||||
MAP.put(MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS, "Cannot access script provided property class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
|
||||
MAP.put(PRE_RELEASE_CLASS, "{0} is compiled by a pre-release version of Kotlin and cannot be loaded by this version of the compiler", TO_STRING);
|
||||
MAP.put(IR_COMPILED_CLASS, "{0} is compiled by a new Kotlin compiler backend and cannot be loaded by the old compiler", TO_STRING);
|
||||
MAP.put(INCOMPATIBLE_CLASS,
|
||||
"{0} was compiled with an incompatible version of Kotlin. {1}",
|
||||
TO_STRING,
|
||||
|
||||
+2
@@ -17,6 +17,8 @@ class CompilerDeserializationConfiguration(languageVersionSettings: LanguageVers
|
||||
override val reportErrorsOnPreReleaseDependencies =
|
||||
!skipMetadataVersionCheck && !languageVersionSettings.isPreRelease() && !KotlinCompilerVersion.isPreRelease()
|
||||
|
||||
override val reportErrorsOnIrDependencies = languageVersionSettings.getFlag(AnalysisFlags.reportErrorsOnIrDependencies)
|
||||
|
||||
override val typeAliasesAllowed = languageVersionSettings.supportsFeature(LanguageFeature.TypeAliases)
|
||||
|
||||
override val isJvmPackageNameSupported = languageVersionSettings.supportsFeature(LanguageFeature.JvmPackageName)
|
||||
|
||||
+3
@@ -58,6 +58,9 @@ object MissingDependencyClassChecker : CallChecker {
|
||||
if (source.isPreReleaseInvisible) {
|
||||
return PRE_RELEASE_CLASS.on(reportOn, source.presentableString)
|
||||
}
|
||||
if (source.isInvisibleIrDependency) {
|
||||
return IR_COMPILED_CLASS.on(reportOn, source.presentableString)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
+10
-4
@@ -216,10 +216,16 @@ open class ClassCodegen protected constructor(
|
||||
state.bindingTrace.record(CodegenBinding.DELEGATED_PROPERTIES_WITH_METADATA, type, localDelegatedProperties.map { it.descriptor })
|
||||
}
|
||||
|
||||
// TODO: if `-Xmultifile-parts-inherit` is enabled, write the corresponding flag for parts and facades to [Metadata.extraInt].
|
||||
var extraFlags = JvmAnnotationNames.METADATA_JVM_IR_FLAG
|
||||
if (state.isIrWithStableAbi) {
|
||||
extraFlags += JvmAnnotationNames.METADATA_JVM_IR_STABLE_ABI_FLAG
|
||||
}
|
||||
|
||||
when (val metadata = irClass.metadata) {
|
||||
is MetadataSource.Class -> {
|
||||
val classProto = serializer!!.classProto(metadata.descriptor).build()
|
||||
writeKotlinMetadata(visitor, state, KotlinClassHeader.Kind.CLASS, 0) {
|
||||
writeKotlinMetadata(visitor, state, KotlinClassHeader.Kind.CLASS, extraFlags) {
|
||||
AsmUtil.writeAnnotationData(it, serializer, classProto)
|
||||
}
|
||||
|
||||
@@ -235,7 +241,7 @@ open class ClassCodegen protected constructor(
|
||||
|
||||
val facadeClassName = context.multifileFacadeForPart[irClass.attributeOwnerId]
|
||||
val kind = if (facadeClassName != null) KotlinClassHeader.Kind.MULTIFILE_CLASS_PART else KotlinClassHeader.Kind.FILE_FACADE
|
||||
writeKotlinMetadata(visitor, state, kind, 0) { av ->
|
||||
writeKotlinMetadata(visitor, state, kind, extraFlags) { av ->
|
||||
AsmUtil.writeAnnotationData(av, serializer, packageProto.build())
|
||||
|
||||
if (facadeClassName != null) {
|
||||
@@ -250,7 +256,7 @@ open class ClassCodegen protected constructor(
|
||||
is MetadataSource.Function -> {
|
||||
val fakeDescriptor = createFreeFakeLambdaDescriptor(metadata.descriptor)
|
||||
val functionProto = serializer!!.functionProto(fakeDescriptor)?.build()
|
||||
writeKotlinMetadata(visitor, state, KotlinClassHeader.Kind.SYNTHETIC_CLASS, 0) {
|
||||
writeKotlinMetadata(visitor, state, KotlinClassHeader.Kind.SYNTHETIC_CLASS, extraFlags) {
|
||||
if (functionProto != null) {
|
||||
AsmUtil.writeAnnotationData(it, serializer, functionProto)
|
||||
}
|
||||
@@ -264,7 +270,7 @@ open class ClassCodegen protected constructor(
|
||||
if (fileClass != null) typeMapper.mapClass(fileClass).internalName else null
|
||||
}
|
||||
MultifileClassCodegenImpl.writeMetadata(
|
||||
visitor, state, 0 /* TODO */, partInternalNames, type, irClass.fqNameWhenAvailable!!.parent()
|
||||
visitor, state, extraFlags, partInternalNames, type, irClass.fqNameWhenAvailable!!.parent()
|
||||
)
|
||||
} else {
|
||||
writeSyntheticClassMetadata(visitor, state)
|
||||
|
||||
+3
@@ -2,6 +2,7 @@ Usage: kotlinc-jvm <options> <source files>
|
||||
where advanced options include:
|
||||
-Xadd-modules=<module[,]> Root modules to resolve in addition to the initial modules,
|
||||
or all modules on the module path if <module> is ALL-MODULE-PATH
|
||||
-Xallow-jvm-ir-dependencies When not using the IR backend, do not report errors on those classes in dependencies, which were compiled by the IR backend
|
||||
-Xallow-no-source-files Allow no source files
|
||||
-Xassertions={always-enable|always-disable|jvm|legacy}
|
||||
Assert calls behaviour
|
||||
@@ -22,6 +23,8 @@ where advanced options include:
|
||||
-Xfriend-paths=<path> Paths to output directories for friend modules (whose internals should be visible)
|
||||
-Xmultifile-parts-inherit Compile multifile classes as a hierarchy of parts and facade
|
||||
-Xir-check-local-names Check that names of local classes and anonymous objects are the same in the IR backend as in the old backend
|
||||
-Xir-binary-with-stable-abi When using the IR backend, produce binaries which can be read by non-IR backend.
|
||||
The author is responsible for verifying that the resulting binaries do indeed have the correct ABI
|
||||
-Xmodule-path=<path> Paths where to find Java 9+ modules
|
||||
-Xjava-package-prefix Package prefix for Java files
|
||||
-Xjava-source-roots=<path> Paths to directories with Java source files
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package lib
|
||||
|
||||
class Box(val value: String)
|
||||
|
||||
inline fun <T> get(block: () -> T): T = block()
|
||||
+1
@@ -0,0 +1 @@
|
||||
OK
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import lib.*
|
||||
|
||||
fun main() {
|
||||
get { Box("OK").value }
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package lib
|
||||
|
||||
class Box(val value: String)
|
||||
|
||||
inline fun <T> get(block: () -> T): T = block()
|
||||
+1
@@ -0,0 +1 @@
|
||||
OK
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import lib.*
|
||||
|
||||
fun main() {
|
||||
get { Box("OK").value }
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package lib
|
||||
|
||||
class Box(val value: String)
|
||||
|
||||
inline fun <T> get(block: () -> T): T = block()
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
error: classes compiled by a new Kotlin compiler backend were found in dependencies. Remove them from the classpath or use '-Xallow-jvm-ir-dependencies' to suppress errors
|
||||
compiler/testData/compileKotlinAgainstCustomBinaries/oldAgainstJvmIr/source.kt:4:5: error: class 'lib.AKt' is compiled by a new Kotlin compiler backend and cannot be loaded by the old compiler
|
||||
get { Box("OK").value }
|
||||
^
|
||||
compiler/testData/compileKotlinAgainstCustomBinaries/oldAgainstJvmIr/source.kt:4:11: error: class 'lib.Box' is compiled by a new Kotlin compiler backend and cannot be loaded by the old compiler
|
||||
get { Box("OK").value }
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import lib.*
|
||||
|
||||
fun main() {
|
||||
get { Box("OK").value }
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package lib
|
||||
|
||||
class Box(val value: String)
|
||||
|
||||
inline fun <T> get(block: () -> T): T = block()
|
||||
+1
@@ -0,0 +1 @@
|
||||
OK
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import lib.*
|
||||
|
||||
fun main() {
|
||||
get { Box("OK").value }
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
package lib
|
||||
|
||||
class Box(val value: String)
|
||||
|
||||
inline fun <T> get(block: () -> T): T = block()
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
OK
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
import lib.*
|
||||
|
||||
fun main() {
|
||||
get { Box("OK").value }
|
||||
}
|
||||
+25
@@ -629,6 +629,31 @@ class CompileKotlinAgainstCustomBinariesTest : AbstractKotlinCompilerIntegration
|
||||
classLoader.loadClass("SourceKt").getDeclaredMethod("main").invoke(null)
|
||||
}
|
||||
|
||||
fun testJvmIrAgainstJvmIr() {
|
||||
val library = compileLibrary("library", additionalOptions = listOf("-Xuse-ir"))
|
||||
compileKotlin("source.kt", tmpdir, listOf(library), additionalOptions = listOf("-Xuse-ir"))
|
||||
}
|
||||
|
||||
fun testJvmIrAgainstOld() {
|
||||
val library = compileLibrary("library")
|
||||
compileKotlin("source.kt", tmpdir, listOf(library), additionalOptions = listOf("-Xuse-ir"))
|
||||
}
|
||||
|
||||
fun testOldAgainstJvmIr() {
|
||||
val library = compileLibrary("library", additionalOptions = listOf("-Xuse-ir"))
|
||||
compileKotlin("source.kt", tmpdir, listOf(library))
|
||||
}
|
||||
|
||||
fun testOldAgainstJvmIrWithStableAbi() {
|
||||
val library = compileLibrary("library", additionalOptions = listOf("-Xuse-ir", "-Xir-binary-with-stable-abi"))
|
||||
compileKotlin("source.kt", tmpdir, listOf(library))
|
||||
}
|
||||
|
||||
fun testOldAgainstJvmIrWithAllowIrDependencies() {
|
||||
val library = compileLibrary("library", additionalOptions = listOf("-Xuse-ir"))
|
||||
compileKotlin("source.kt", tmpdir, listOf(library), additionalOptions = listOf("-Xallow-jvm-ir-dependencies"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
// compiler before 1.1.4 version did not include suspension marks into bytecode.
|
||||
private fun stripSuspensionMarksToImitateLegacyCompiler(bytes: ByteArray): Pair<ByteArray, Int> {
|
||||
|
||||
Reference in New Issue
Block a user