diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java index 8894592e28b..b8e8394ba91 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java @@ -50,6 +50,7 @@ public class ClassFileFactory implements OutputFileCollection { private final GenerationState state; private final ClassBuilderFactory builderFactory; private final Map package2codegen = new HashMap(); + private final Map multifileClass2codegen = new HashMap(); private final Map generators = new LinkedHashMap(); private boolean isDone = false; @@ -189,7 +190,7 @@ public class ClassFileFactory implements OutputFileCollection { } @NotNull - public PackageCodegen forFacade(@NotNull FqName facadeFqName, @NotNull Collection files) { + public PackageCodegen forFacadeLightClass(@NotNull FqName facadeFqName, @NotNull Collection files) { assert !isDone : "Already done!"; PackageCodegen codegen = package2codegen.get(facadeFqName); if (codegen == null) { @@ -200,6 +201,17 @@ public class ClassFileFactory implements OutputFileCollection { return codegen; } + @NotNull + public MultifileClassCodegen forMultifileClass(@NotNull FqName facadeFqName, @NotNull Collection files) { + assert !isDone : "Already done!"; + MultifileClassCodegen codegen = multifileClass2codegen.get(facadeFqName); + if (codegen == null) { + codegen = new MultifileClassCodegen(state, files, facadeFqName); + multifileClass2codegen.put(facadeFqName, codegen); + } + return codegen; + } + @NotNull private static List toIoFilesIgnoringNonPhysical(@NotNull Collection psiFiles) { List result = Lists.newArrayList(); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/KotlinCodegenFacade.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/KotlinCodegenFacade.java index e00477ae138..142b8940ba6 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/KotlinCodegenFacade.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/KotlinCodegenFacade.java @@ -20,6 +20,8 @@ import com.google.common.collect.Sets; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.codegen.state.GenerationState; +import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo; +import org.jetbrains.kotlin.load.kotlin.PackageClassUtils; import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.psi.JetFile; @@ -62,15 +64,31 @@ public class KotlinCodegenFacade { ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); MultiMap packageFqNameToFiles = new MultiMap(); + MultiMap multifileClassFqNameToFiles = new MultiMap(); + for (JetFile file : state.getFiles()) { if (file == null) throw new IllegalArgumentException("A null file given for compilation"); - packageFqNameToFiles.putValue(file.getPackageFqName(), file); + JvmFileClassInfo fileClassInfo = state.getFileClassesProvider().getFileClassInfo(file); + if (fileClassInfo.getIsMultifileClass()) { + multifileClassFqNameToFiles.putValue(fileClassInfo.getFacadeClassFqName(), file); + } + else if (state.getPackageFacadesAsMultifileClasses()) { + multifileClassFqNameToFiles.putValue(PackageClassUtils.getPackageClassFqName(file.getPackageFqName()), file); + } + else { + packageFqNameToFiles.putValue(file.getPackageFqName(), file); + } } Set packagesWithObsoleteParts = new HashSet(state.getPackagesWithObsoleteParts()); - for (FqName fqName : Sets.union(packagesWithObsoleteParts, packageFqNameToFiles.keySet())) { + for (FqName packageFqName : Sets.union(packagesWithObsoleteParts, packageFqNameToFiles.keySet())) { ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); - generatePackage(state, fqName, packageFqNameToFiles.get(fqName), errorHandler); + generatePackage(state, packageFqName, packageFqNameToFiles.get(packageFqName), errorHandler); + } + + for (FqName multifileClassFqName : multifileClassFqNameToFiles.keySet()) { + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); + generateMultifileClass(state, multifileClassFqName, multifileClassFqNameToFiles.get(multifileClassFqName), errorHandler); } ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); @@ -79,13 +97,23 @@ public class KotlinCodegenFacade { public static void generatePackage( @NotNull GenerationState state, - @NotNull FqName fqName, + @NotNull FqName packageFqName, @NotNull Collection jetFiles, @NotNull CompilationErrorHandler errorHandler ) { - PackageCodegen codegen = state.getFactory().forPackage(fqName, jetFiles); + PackageCodegen codegen = state.getFactory().forPackage(packageFqName, jetFiles); codegen.generate(errorHandler); } + private static void generateMultifileClass( + @NotNull GenerationState state, + @NotNull FqName multifileClassFqName, + @NotNull Collection files, + @NotNull CompilationErrorHandler handler + ) { + MultifileClassCodegen codegen = state.getFactory().forMultifileClass(multifileClassFqName, files); + codegen.generate(handler); + } + private KotlinCodegenFacade() {} } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassCodegen.kt new file mode 100644 index 00000000000..a31131e8a17 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassCodegen.kt @@ -0,0 +1,272 @@ +/* + * Copyright 2010-2015 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.codegen + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.progress.ProcessCanceledException +import com.intellij.util.ArrayUtil +import com.intellij.util.SmartList +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.codegen.context.CodegenContext +import org.jetbrains.kotlin.codegen.context.FieldOwnerContext +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.config.IncrementalCompilation +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticUtils +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.load.java.JvmAnnotationNames +import org.jetbrains.kotlin.load.kotlin.incremental.IncrementalPackageFragmentProvider +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.MemberComparator +import org.jetbrains.kotlin.resolve.jvm.diagnostics.MultifileClass +import org.jetbrains.kotlin.resolve.jvm.diagnostics.MultifileClassPart +import org.jetbrains.kotlin.serialization.DescriptorSerializer +import org.jetbrains.kotlin.serialization.PackageData +import org.jetbrains.kotlin.serialization.SerializationUtil +import org.jetbrains.kotlin.serialization.deserialization.NameResolver +import org.jetbrains.kotlin.serialization.jvm.BitEncoding +import org.jetbrains.org.objectweb.asm.Opcodes +import java.util.* + + +public class MultifileClassCodegen( + private val state: GenerationState, + private val files: Collection, + private val facadeFqName: FqName +) { + private val facadeClassType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(facadeFqName) + + private val packageFragment = getOnlyPackageFragment(facadeFqName.parent(), files, state.bindingContext) + + private val compiledPackageFragment = getCompiledPackageFragment(facadeFqName.parent(), state) + + // TODO incremental compilation support + // TODO previouslyCompiledCallables + // We can do this (probably without 'compiledPackageFragment') after modifications to part codegen. + + private val classBuilder = ClassBuilderOnDemand { + val filesWithTopLevelCallables = files.filter { hasTopLevelCallables(it) } + val sourceFile = filesWithTopLevelCallables.firstOrNull() + val actualPackageFragment = packageFragment ?: + compiledPackageFragment ?: + throw AssertionError("No package fragment for multifile facade $facadeFqName; files: $files") + val declarationOrigin = MultifileClass(actualPackageFragment, facadeFqName) + val classBuilder = state.factory.newVisitor(declarationOrigin, facadeClassType, filesWithTopLevelCallables) + + classBuilder.defineClass(sourceFile, Opcodes.V1_6, FACADE_CLASS_ATTRIBUTES, + facadeClassType.internalName, + null, "java/lang/Object", ArrayUtil.EMPTY_STRING_ARRAY) + sourceFile?.let { classBuilder.visitSource(it.name, null) } + classBuilder + } + + private fun hasTopLevelCallables(file: JetFile) = + file.declarations.any { it is JetNamedFunction || it is JetProperty } + + public fun generate(errorHandler: CompilationErrorHandler) { + val bindings = ArrayList(files.size() + 1) + val generateCallableMemberTasks = HashMap Unit>() + val partFqNames = arrayListOf() + + for (file in files) { + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + try { + val partClassBuilder = generatePart(file, generateCallableMemberTasks, partFqNames) + if (partClassBuilder != null) { + bindings.add(partClassBuilder.serializationBindings) + } + } + catch (e: ProcessCanceledException) { + throw e + } + catch (e: Throwable) { + val vFile = file.virtualFile + errorHandler.reportException(e, if (vFile == null) "no file" else vFile.url) + DiagnosticUtils.throwIfRunningOnServer(e) + if (ApplicationManager.getApplication().isInternal) { + //noinspection CallToPrintStackTrace + e.printStackTrace() + } + } + } + +// generateDelegationsToPreviouslyCompiled(generateCallableMemberTasks) + + if (!generateCallableMemberTasks.isEmpty()) { + generateMultifileFacadeClass(generateCallableMemberTasks, bindings, partFqNames) + } + } + + private fun generateMultifileFacadeClass( + tasks: Map Unit>, + bindings: MutableList, + partFqNames: List + ) { + MemberCodegen.generateModuleNameField(state, classBuilder) + + for (member in tasks.keySet().sortedWith(MemberComparator.INSTANCE)) { + tasks[member]!!() + } + + bindings.add(classBuilder.serializationBindings) + writeKotlinMultifileFacadeAnnotationIfNeeded(JvmSerializationBindings.union(bindings), partFqNames) + } + + public fun generateClassOrObject(classOrObject: JetClassOrObject) { + val file = classOrObject.getContainingJetFile() + val packagePartType = state.fileClassesProvider.getFileClassType(file) + val context = CodegenContext.STATIC.intoPackagePart(packageFragment!!, packagePartType) + MemberCodegen.genClassOrObject(context, classOrObject, state, null) + } + + private fun generatePart( + file: JetFile, + generateCallableMemberTasks: MutableMap Unit>, + partFqNames: MutableList + ): ClassBuilder? { + val packageFragment = this.packageFragment ?: + throw AssertionError("File part $file of $facadeFqName: no package fragment") + + var generatePart = false + val partClassInfo = state.fileClassesProvider.getFileClassInfo(file) + val partType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(partClassInfo.fileClassFqName) + val partContext = CodegenContext.STATIC.intoMultifileClassPart(packageFragment, facadeClassType, partType) + + for (declaration in file.declarations) { + if (declaration is JetProperty || declaration is JetNamedFunction) { + generatePart = true + } + else if (declaration is JetClassOrObject) { + if (state.generateDeclaredClassFilter.shouldGenerateClass(declaration)) { + generateClassOrObject(declaration) + } + } + else if (declaration is JetScript) { + + // SCRIPT: generate script code, should be separate execution branch + if (state.generateDeclaredClassFilter.shouldGenerateScript(declaration)) { + ScriptCodegen.createScriptCodegen(declaration, state, partContext).generate() + } + } + } + + + if (!generatePart || !state.generateDeclaredClassFilter.shouldGeneratePackagePart(file)) return null + + partFqNames.add(partClassInfo.fileClassFqName) + +// val name = partType.internalName +// packageParts.parts.add(name.substring(name.lastIndexOf('/') + 1)) + + val builder = state.factory.newVisitor(MultifileClassPart(file, packageFragment, facadeFqName), partType, file) + + MultifileClassPartCodegen(builder, file, partType, facadeFqName, partContext, state).generate() + + val facadeContext = CodegenContext.STATIC.intoMultifileClass(packageFragment, facadeClassType, partType) + val memberCodegen = createCodegenForPartOfMultifileFacade(facadeContext) + for (declaration in file.declarations) { + if (declaration is JetNamedFunction || declaration is JetProperty) { + val descriptor = state.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) + assert(descriptor is CallableMemberDescriptor) { "Expected callable member, was " + descriptor + " for " + declaration.text } + generateCallableMemberTasks.put(descriptor as CallableMemberDescriptor, + { memberCodegen.genFunctionOrProperty(declaration) }) + } + } + + return builder + } + + private fun writeKotlinMultifileFacadeAnnotationIfNeeded(bindings: JvmSerializationBindings, partFqNames: List) { + if (state.classBuilderMode != ClassBuilderMode.FULL) return + if (files.any { it.isScript }) return + + val serializer = DescriptorSerializer.createTopLevel(JvmSerializerExtension(bindings, state.typeMapper)) + + val packageFragments = arrayListOf() + ContainerUtil.addIfNotNull(packageFragments, packageFragment) + ContainerUtil.addIfNotNull(packageFragments, compiledPackageFragment) + val facadeProto = serializer.packageProto(packageFragments, /* skip= */ { true }).build() + + val strings = serializer.stringTable + val nameResolver = NameResolver(strings.serializeSimpleNames(), strings.serializeQualifiedNames()) + val facadeData = PackageData(nameResolver, facadeProto) + + val av = classBuilder.newAnnotation(AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_MULTIFILE_CLASS), true) + av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION) + + val shortNames = partFqNames.map { it.shortName().asString() }.sorted() + val filePartClassNamesArray = av.visitArray(JvmAnnotationNames.FILE_PART_CLASS_NAMES_FIELD_NAME) + for (shortName in shortNames) { + filePartClassNamesArray.visit(null, shortName) + } + filePartClassNamesArray.visitEnd() + + val dataArray = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME) + for (string in BitEncoding.encodeBytes(SerializationUtil.serializePackageData(facadeData))) { + dataArray.visit(null, string) + } + dataArray.visitEnd() + + av.visitEnd() + } + + private fun createCodegenForPartOfMultifileFacade(facadeContext: FieldOwnerContext): MemberCodegen = + object : MemberCodegen(state, null, facadeContext, null, classBuilder) { + override fun generateDeclaration() = throw UnsupportedOperationException() + override fun generateBody() = throw UnsupportedOperationException() + override fun generateKotlinAnnotation() = throw UnsupportedOperationException() + } + + public fun done() { + classBuilder.done() + } + + companion object { + private val FACADE_CLASS_ATTRIBUTES = Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL + + private fun getOnlyPackageFragment(packageFqName: FqName, files: Collection, bindingContext: BindingContext): PackageFragmentDescriptor? { + val fragments = SmartList() + for (file in files) { + val fragment = bindingContext.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, file) + assert(fragment != null) { "package fragment is null for " + file + "\n" + file.text } + + assert(packageFqName == fragment!!.fqName) { "expected package fq name: " + packageFqName + ", actual: " + fragment.fqName } + + if (!fragments.contains(fragment)) { + fragments.add(fragment) + } + } + if (fragments.size() > 1) { + throw IllegalStateException("More than one package fragment, files: $files | fragments: $fragments") + } + return fragments.firstOrNull() + } + + private fun getCompiledPackageFragment(packageFqName: FqName, state: GenerationState): PackageFragmentDescriptor? = + if (!IncrementalCompilation.ENABLED) null + else state.module.getPackage(packageFqName).fragments.firstOrNull { fragment -> + fragment is IncrementalPackageFragmentProvider.IncrementalPackageFragment && + fragment.moduleId == state.moduleId + } + } + +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt new file mode 100644 index 00000000000..0e5d9db4bfb --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2015 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.codegen + +import com.intellij.util.ArrayUtil +import org.jetbrains.kotlin.codegen.context.FieldOwnerContext +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.load.java.JvmAnnotationNames +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.psi.JetNamedFunction +import org.jetbrains.kotlin.psi.JetProperty +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.serialization.DescriptorSerializer +import org.jetbrains.kotlin.serialization.PackageData +import org.jetbrains.kotlin.serialization.SerializationUtil +import org.jetbrains.kotlin.serialization.deserialization.NameResolver +import org.jetbrains.kotlin.serialization.jvm.BitEncoding +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import java.util.* + + +public class MultifileClassPartCodegen( + v: ClassBuilder, + file: JetFile, + private val filePartType: Type, + private val multifileClassFqName: FqName, + partContext: FieldOwnerContext<*>, + state: GenerationState +) : MemberCodegen(state, null, partContext, file, v){ + override fun generateDeclaration() { + v.defineClass(element, Opcodes.V1_6, + Opcodes.ACC_SYNTHETIC, + filePartType.internalName, + null, + "java/lang/Object", + ArrayUtil.EMPTY_STRING_ARRAY) + v.visitSource(element.name, null) + + generatePropertyMetadataArrayFieldIfNeeded(filePartType) + } + + override fun generateBody() { + for (declaration in element.declarations) { + if (declaration is JetNamedFunction || declaration is JetProperty) { + genFunctionOrProperty(declaration); + } + } + + if (state.classBuilderMode == ClassBuilderMode.FULL) { + generateInitializers({ createOrGetClInitCodegen(); }); + } + } + + override fun generateKotlinAnnotation() { + if (state.classBuilderMode != ClassBuilderMode.FULL) { + return + } + + val members = ArrayList() + for (declaration in element.declarations) { + when (declaration) { + is JetNamedFunction -> { + val functionDescriptor = bindingContext.get(BindingContext.FUNCTION, declaration) + members.add(functionDescriptor ?: throw AssertionError("Function ${declaration.name} is not bound in ${element.name}")) + } + is JetProperty -> { + val property = bindingContext.get(BindingContext.VARIABLE, declaration) + members.add(property ?: throw AssertionError("Property ${declaration.name} is not bound in ${element.name}")) + } + } + } + + val bindings = v.serializationBindings + + val serializer = DescriptorSerializer.createTopLevel(JvmSerializerExtension(bindings, state.typeMapper)) + val packageProto = serializer.packagePartProto(members).build() + + if (packageProto.memberCount == 0) return + + val strings = serializer.stringTable + val nameResolver = NameResolver(strings.serializeSimpleNames(), strings.serializeQualifiedNames()) + val data = PackageData(nameResolver, packageProto) + + val av = v.newAnnotation(AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_MULTIFILE_CLASS_PART), true) + av.visit(JvmAnnotationNames.ABI_VERSION_FIELD_NAME, JvmAbi.VERSION) + av.visit(JvmAnnotationNames.MULTIFILE_CLASS_NAME_FIELD_NAME, multifileClassFqName.shortName().asString()) + val dataArray = av.visitArray(JvmAnnotationNames.DATA_FIELD_NAME) + for (string in BitEncoding.encodeBytes(SerializationUtil.serializePackageData(data))) { + dataArray.visit(null, string) + } + dataArray.visitEnd() + av.visitEnd() + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegen.java index 70f2d73b948..488d053f2a3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegen.java @@ -39,6 +39,7 @@ import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor; import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor; import org.jetbrains.kotlin.diagnostics.DiagnosticUtils; +import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo; import org.jetbrains.kotlin.load.java.JvmAbi; import org.jetbrains.kotlin.load.java.JvmAnnotationNames; import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils; @@ -335,7 +336,6 @@ public class PackageCodegen { } } - if (!generatePackagePart || !state.getGenerateDeclaredClassFilter().shouldGeneratePackagePart(file)) return null; String name = packagePartType.getInternalName(); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/CodegenContext.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/CodegenContext.java index ed827d3fe16..e90bf2eeb93 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/CodegenContext.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/CodegenContext.java @@ -189,6 +189,24 @@ public abstract class CodegenContext { return new PackageFacadeContext(descriptor, this, delegateTo); } + @NotNull + public FieldOwnerContext intoMultifileClassPart( + @NotNull PackageFragmentDescriptor descriptor, + @NotNull Type multifileClassType, + @NotNull Type filePartType + ) { + return new MultifileClassPartContext(descriptor, this, multifileClassType, filePartType); + } + + @NotNull + public FieldOwnerContext intoMultifileClass( + @NotNull PackageFragmentDescriptor descriptor, + @NotNull Type multifileClassType, + @NotNull Type filePartType + ) { + return new MultifileClassContext(descriptor, this, multifileClassType, filePartType); + } + @NotNull public ClassContext intoClass(ClassDescriptor descriptor, OwnerKind kind, GenerationState state) { return new ClassContext(state.getTypeMapper(), descriptor, kind, this, null); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassContext.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassContext.java new file mode 100644 index 00000000000..f6974d9652b --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassContext.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2015 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.codegen.context; + +import org.jetbrains.kotlin.codegen.OwnerKind; +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor; +import org.jetbrains.org.objectweb.asm.Type; + +public class MultifileClassContext extends FieldOwnerContext { + private final Type multifileClassType; + private final Type filePartType; + + public MultifileClassContext( + PackageFragmentDescriptor descriptor, + CodegenContext parent, + Type multifileClassType, + Type filePartType + ) { + super(descriptor, OwnerKind.PACKAGE, parent, null, null, null); + this.multifileClassType = multifileClassType; + this.filePartType = filePartType; + } + + public Type getMultifileClassType() { + return multifileClassType; + } + + public Type getFilePartType() { + return filePartType; + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassPartContext.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassPartContext.java new file mode 100644 index 00000000000..7ce7bf13e77 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassPartContext.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2015 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.codegen.context; + +import org.jetbrains.kotlin.codegen.OwnerKind; +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor; +import org.jetbrains.org.objectweb.asm.Type; + +public class MultifileClassPartContext extends FieldOwnerContext { + private final Type multifileClassType; + private final Type filePartType; + + public MultifileClassPartContext( + PackageFragmentDescriptor descriptor, + CodegenContext parent, + Type multifileClassType, + Type filePartType + ) { + super(descriptor, OwnerKind.PACKAGE, parent, null, null, null); + this.multifileClassType = multifileClassType; + this.filePartType = filePartType; + } + + public Type getMultifileClassType() { + return multifileClassType; + } + + public Type getFilePartType() { + return filePartType; + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 14e9a80e17d..17f179b00e2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -52,6 +52,7 @@ public class GenerationState jvmOverloads constructor( public val generateDeclaredClassFilter: GenerationState.GenerateClassFilter = GenerationState.GenerateClassFilter.GENERATE_ALL, disableInline: Boolean = false, disableOptimization: Boolean = false, + public val packageFacadesAsMultifileClasses: Boolean = false, public val diagnostics: DiagnosticSink = DiagnosticSink.DO_NOTHING, public val packagesWithObsoleteParts: Collection = emptySet(), // for PackageCodegen in incremental compilation mode diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java index 853b2435ec8..ee3f4042992 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java @@ -69,6 +69,9 @@ public class K2JVMCompilerArguments extends CommonCompilerArguments { @Argument(value = "Xno-optimize", description = "Disable optimizations") public boolean noOptimize; + @Argument(value = "Xmultifile-package-facades", description = "Compile package facade classes as multifile classes") + public boolean packageFacadesAsMultifileClasses; + @Argument(value = "Xreport-perf", description = "Report detailed performance statistics") public boolean reportPerf; diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index d03e84d42c0..75bac00c286 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -272,6 +272,7 @@ public open class K2JVMCompiler : CLICompiler() { configuration.put(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, arguments.noParamAssertions) configuration.put(JVMConfigurationKeys.DISABLE_INLINE, arguments.noInline) configuration.put(JVMConfigurationKeys.DISABLE_OPTIMIZATION, arguments.noOptimize) + configuration.put(JVMConfigurationKeys.PACKAGE_FACADES_AS_MULTIFILE_CLASSES, arguments.packageFacadesAsMultifileClasses); } private fun getClasspath(paths: KotlinPaths, arguments: K2JVMCompilerArguments): List { diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java index 0171e7cbfd4..19689a3c54f 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.java @@ -392,6 +392,7 @@ public class KotlinToJVMBytecodeCompiler { GenerationState.GenerateClassFilter.GENERATE_ALL, configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false), configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false), + configuration.get(JVMConfigurationKeys.PACKAGE_FACADES_AS_MULTIFILE_CLASSES, false), diagnosticHolder, packagesWithObsoleteParts, targetId, diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JVMConfigurationKeys.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JVMConfigurationKeys.java index 9ebdce170b7..90c48395c17 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JVMConfigurationKeys.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/config/JVMConfigurationKeys.java @@ -42,6 +42,8 @@ public class JVMConfigurationKeys { CompilerConfigurationKey.create("disable inline"); public static final CompilerConfigurationKey DISABLE_OPTIMIZATION = CompilerConfigurationKey.create("disable optimization"); + public static final CompilerConfigurationKey PACKAGE_FACADES_AS_MULTIFILE_CLASSES = + CompilerConfigurationKey.create("compile package facades as multifile classes"); public static final CompilerConfigurationKey INCREMENTAL_COMPILATION_COMPONENTS = CompilerConfigurationKey.create("incremental cache provider"); diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassInfo.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassInfo.kt index bc77fb4b6c6..014dee25985 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassInfo.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassInfo.kt @@ -22,30 +22,23 @@ import org.jetbrains.kotlin.psi.JetFile public interface JvmFileClassInfo { public val fileClassFqName: FqName public val facadeClassFqName: FqName - public val kind: Kind - - public enum class Kind { - FILE_CLASS, - MULTIFILE_CLASS_PART - ; - } + public val isWithJvmName: Boolean + public val isMultifileClass: Boolean } -public class JvmFileFacadeInfo( - override val fileClassFqName: FqName +public class JvmSimpleFileClassInfo( + override val fileClassFqName: FqName, + override val isWithJvmName: Boolean ) : JvmFileClassInfo { - override val facadeClassFqName: FqName - get() = fileClassFqName - override val kind: JvmFileClassInfo.Kind - get() = JvmFileClassInfo.Kind.FILE_CLASS + override val facadeClassFqName: FqName get() = fileClassFqName + override val isMultifileClass: Boolean get() = false } -public class JvmMultifileFacadePartInfo( +public class JvmMultifileClassPartInfo( override val fileClassFqName: FqName, override val facadeClassFqName: FqName ) : JvmFileClassInfo { - override val kind: JvmFileClassInfo.Kind - get() = JvmFileClassInfo.Kind.MULTIFILE_CLASS_PART - + override val isWithJvmName: Boolean get() = true + override val isMultifileClass: Boolean get() = true } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt index 2dd383bbbd1..0e09c735f6f 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt @@ -30,7 +30,8 @@ public object JvmFileClassUtil { public val JVM_NAME: FqName = FqName("kotlin.jvm.JvmName") public val JVM_NAME_SHORT: String = JVM_NAME.shortName().asString() - // TODO @JvmMultifileClass + public val JVM_MULTIFILE_CLASS: FqName = FqName("kotlin.jvm.JvmMultifileClass") + public val JVM_MULTIFILE_CLASS_SHORT = JVM_MULTIFILE_CLASS.shortName().asString() public @jvmStatic fun getFileClassInfo(file: JetFile, jvmFileClassAnnotations: ParsedJmvFileClassAnnotations?): JvmFileClassInfo = if (jvmFileClassAnnotations != null) @@ -40,13 +41,13 @@ public object JvmFileClassUtil { public @jvmStatic fun getFileClassInfoForAnnotation(file: JetFile, jvmFileClassAnnotations: ParsedJmvFileClassAnnotations): JvmFileClassInfo = if (jvmFileClassAnnotations.multipleFiles) - JvmMultifileFacadePartInfo(getHiddenPartFqName(file, jvmFileClassAnnotations), - getFacadeFqName(file, jvmFileClassAnnotations)) + JvmMultifileClassPartInfo(getHiddenPartFqName(file, jvmFileClassAnnotations), + getFacadeFqName(file, jvmFileClassAnnotations)) else - JvmFileFacadeInfo(getFacadeFqName(file, jvmFileClassAnnotations)) + JvmSimpleFileClassInfo(getFacadeFqName(file, jvmFileClassAnnotations), true) public @jvmStatic fun getDefaultFileClassInfo(file: JetFile): JvmFileClassInfo = - JvmFileFacadeInfo(PackagePartClassUtils.getPackagePartFqName(file.packageFqName, file.name)) + JvmSimpleFileClassInfo(PackagePartClassUtils.getPackagePartFqName(file.packageFqName, file.name), false) public @jvmStatic fun getFacadeFqName(file: JetFile, jvmFileClassAnnotations: ParsedJmvFileClassAnnotations): FqName = file.packageFqName.child(Name.identifier(jvmFileClassAnnotations.name)) @@ -59,31 +60,35 @@ public object JvmFileClassUtil { public @jvmStatic fun parseJvmFileClass(annotations: Annotations): ParsedJmvFileClassAnnotations? { val jvmName = annotations.findAnnotation(JVM_NAME) - // TODO @JvmMultifileClass - return if (jvmName != null) parseJvmFileClass(jvmName) else null + val jvmMultifileClass = annotations.findAnnotation(JVM_MULTIFILE_CLASS) + return jvmName?.let { + parseJvmFileClass(it, jvmMultifileClass) + } } - public @jvmStatic fun parseJvmFileClass(jvmName: AnnotationDescriptor): ParsedJmvFileClassAnnotations { + public @jvmStatic fun parseJvmFileClass(jvmName: AnnotationDescriptor, jvmMultifileClass: AnnotationDescriptor?): ParsedJmvFileClassAnnotations { val name = jvmName.allValueArguments.values().firstOrNull()?.let { (it as? StringValue)?.value } - return ParsedJmvFileClassAnnotations(name!!, false) + val isMultifileClassPart = jvmMultifileClass != null + return ParsedJmvFileClassAnnotations(name!!, isMultifileClassPart) } public @jvmStatic fun getFileClassInfoNoResolve(file: JetFile): JvmFileClassInfo = getFileClassInfo(file, parseJvmNameOnFileNoResolve(file)) public @jvmStatic fun parseJvmNameOnFileNoResolve(file: JetFile): ParsedJmvFileClassAnnotations? = - findJvmNameOnFileNoResolve(file)?.let { parseJvmNameOnFileNoResolve(it) } - - public @jvmStatic fun findJvmNameOnFileNoResolve(file: JetFile): JetAnnotationEntry? = - file.fileAnnotationList?.annotationEntries?.firstOrNull { - it.calleeExpression?.constructorReferenceExpression?.getReferencedName() == JVM_NAME_SHORT + findAnnotationEntryOnFileNoResolve(file, JVM_NAME_SHORT)?.let { annotationEntry -> + val nameExpr = annotationEntry.valueArguments.firstOrNull()?.getArgumentExpression() ?: return null + val name = getLiteralStringFromRestrictedConstExpression(nameExpr) + val isMultifileClassPart = findAnnotationEntryOnFileNoResolve(file, JVM_MULTIFILE_CLASS_SHORT) != null + name?.let { + ParsedJmvFileClassAnnotations(it, isMultifileClassPart) + } } - public @jvmStatic fun parseJvmNameOnFileNoResolve(annotationEntry: JetAnnotationEntry): ParsedJmvFileClassAnnotations? { - val nameExpr = annotationEntry.valueArguments.firstOrNull()?.getArgumentExpression() ?: return null - val name = getLiteralStringFromRestrictedConstExpression(nameExpr) - return name?.let { ParsedJmvFileClassAnnotations(it, false) } - } + public @jvmStatic fun findAnnotationEntryOnFileNoResolve(file: JetFile, shortName: String): JetAnnotationEntry? = + file.fileAnnotationList?.annotationEntries?.firstOrNull { + it.calleeExpression?.constructorReferenceExpression?.getReferencedName() == shortName + } private @jvmStatic fun getLiteralStringFromRestrictedConstExpression(argumentExpression: JetExpression?): String? { val stringTemplate = argumentExpression as? JetStringTemplateExpression ?: return null diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt index 1d3c200a07b..2765c759839 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/JvmDeclarationOrigin.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.psi.JetClassOrObject import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind.* import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils public enum class MemberKind { FIELD, METHOD } @@ -39,13 +40,16 @@ public enum class JvmDeclarationOriginKind { DELEGATION_TO_TRAIT_IMPL, DELEGATION, BRIDGE, + MULTIFILE_CLASS, + MULTIFILE_CLASS_PART, SYNTHETIC // this means that there's no proper descriptor for this jvm declaration } public class JvmDeclarationOrigin( public val originKind: JvmDeclarationOriginKind, public val element: PsiElement?, - public val descriptor: DeclarationDescriptor? + public val descriptor: DeclarationDescriptor?, + public val multifileClassFqName: FqName? = null ) { companion object { public val NO_ORIGIN: JvmDeclarationOrigin = JvmDeclarationOrigin(OTHER, null, null) @@ -67,6 +71,11 @@ public fun Bridge(descriptor: DeclarationDescriptor, element: PsiElement? = Desc public fun PackageFacade(descriptor: PackageFragmentDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(PACKAGE_FACADE, null, descriptor) public fun PackagePart(file: JetFile, descriptor: PackageFragmentDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(PACKAGE_PART, file, descriptor) +public fun MultifileClass(descriptor: PackageFragmentDescriptor, multifileClassFqName: FqName): JvmDeclarationOrigin = + JvmDeclarationOrigin(MULTIFILE_CLASS, null, descriptor, multifileClassFqName) +public fun MultifileClassPart(file: JetFile, descriptor: PackageFragmentDescriptor, multifileClassFqName: FqName): JvmDeclarationOrigin = + JvmDeclarationOrigin(MULTIFILE_CLASS_PART, file, descriptor, multifileClassFqName) + public fun TraitImpl(element: JetClassOrObject, descriptor: ClassDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(TRAIT_IMPL, element, descriptor) public fun DelegationToTraitImpl(element: PsiElement?, descriptor: FunctionDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(DELEGATION_TO_TRAIT_IMPL, element, descriptor) diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java index 16369b97b4e..b12d0acf0b4 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java @@ -214,7 +214,7 @@ public class KotlinJavaFileStubProvider files) { - PackageCodegen codegen = state.getFactory().forFacade(facadeFqName, files); + PackageCodegen codegen = state.getFactory().forFacadeLightClass(facadeFqName, files); codegen.generate(CompilationErrorHandler.THROW_EXCEPTION); state.getFactory().asList(); } @@ -415,6 +415,7 @@ public class KotlinJavaFileStubProvider?, public val classKind: KotlinClass.Kind?, - public val syntheticClassKind: KotlinSyntheticClass.Kind? + public val syntheticClassKind: KotlinSyntheticClass.Kind?, + public val filePartClassNames: Array?, + public val multifileClassName: String? ) { public val isCompatibleAbiVersion: Boolean get() = AbiVersionUtil.isAbiVersionCompatible(version) @@ -33,6 +35,8 @@ public class KotlinClassHeader( CLASS, PACKAGE_FACADE, FILE_FACADE, + MULTIFILE_CLASS, + MULTIFILE_CLASS_PART, SYNTHETIC_CLASS } @@ -46,4 +50,6 @@ public class KotlinClassHeader( public fun KotlinClassHeader.isCompatibleClassKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.CLASS public fun KotlinClassHeader.isCompatiblePackageFacadeKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.PACKAGE_FACADE public fun KotlinClassHeader.isCompatibleFileFacadeKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.FILE_FACADE +public fun KotlinClassHeader.isCompatibleMultifileClassKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.MULTIFILE_CLASS +public fun KotlinClassHeader.isCompatibleMultifileClassPartKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART public fun KotlinClassHeader.isCompatibleSyntheticClassKind(): Boolean = isCompatibleAbiVersion && kind == KotlinClassHeader.Kind.SYNTHETIC_CLASS diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java index b67a61cda86..453965b1bf7 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/kotlin/header/ReadKotlinClassHeaderAnnotationVisitor.java @@ -37,11 +37,12 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor private static final Map HEADER_KINDS = new HashMap(); private static final Map OLD_DEPRECATED_ANNOTATIONS_KINDS = new HashMap(); - private int version = AbiVersionUtil.INVALID_VERSION; static { HEADER_KINDS.put(KotlinClass.CLASS_NAME, CLASS); HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_PACKAGE), PACKAGE_FACADE); HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_FILE_FACADE), FILE_FACADE); + HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_MULTIFILE_CLASS), MULTIFILE_CLASS); + HEADER_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(KOTLIN_MULTIFILE_CLASS_PART), MULTIFILE_CLASS_PART); HEADER_KINDS.put(KotlinSyntheticClass.CLASS_NAME, SYNTHETIC_CLASS); initOldAnnotations(); @@ -58,6 +59,9 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor OLD_DEPRECATED_ANNOTATIONS_KINDS.put(JvmClassName.byFqNameWithoutInnerClasses(OLD_KOTLIN_TRAIT_IMPL), SYNTHETIC_CLASS); } + private int version = AbiVersionUtil.INVALID_VERSION; + private String multifileClassName = null; + private String[] filePartClassNames = null; private String[] annotationData = null; private KotlinClassHeader.Kind headerKind = null; private KotlinClass.Kind classKind = null; @@ -75,16 +79,24 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor } if (!AbiVersionUtil.isAbiVersionCompatible(version)) { - return new KotlinClassHeader(headerKind, version, null, classKind, syntheticClassKind); + return new KotlinClassHeader(headerKind, version, null, classKind, syntheticClassKind, null, null); } - if ((headerKind == CLASS || headerKind == PACKAGE_FACADE || headerKind == FILE_FACADE) && annotationData == null) { + if (shouldHaveData() && annotationData == null) { // This means that the annotation is found and its ABI version is compatible, but there's no "data" string array in it. // We tell the outside world that there's really no annotation at all return null; } - return new KotlinClassHeader(headerKind, version, annotationData, classKind, syntheticClassKind); + return new KotlinClassHeader(headerKind, version, annotationData, classKind, syntheticClassKind, filePartClassNames, multifileClassName); + } + + private boolean shouldHaveData() { + return headerKind == CLASS || + headerKind == PACKAGE_FACADE || + headerKind == FILE_FACADE || + headerKind == MULTIFILE_CLASS || + headerKind == MULTIFILE_CLASS_PART; } @Nullable @@ -136,15 +148,38 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor @Override public void visit(@Nullable Name name, @Nullable Object value) { - if (name != null && name.asString().equals(ABI_VERSION_FIELD_NAME)) { - version = value instanceof Integer ? (Integer) value : AbiVersionUtil.INVALID_VERSION; + if (name != null) { + if (name.asString().equals(ABI_VERSION_FIELD_NAME)) { + version = value instanceof Integer ? (Integer) value : AbiVersionUtil.INVALID_VERSION; + } + else if (name.asString().equals(MULTIFILE_CLASS_NAME_FIELD_NAME)) { + multifileClassName = value instanceof String ? (String) value : null; + } } } @Override @Nullable public AnnotationArrayArgumentVisitor visitArray(@NotNull Name name) { - return name.asString().equals(DATA_FIELD_NAME) ? stringArrayVisitor() : null; + if (name.asString().equals(DATA_FIELD_NAME)) { + return dataArrayVisitor(); + } + else if (name.asString().equals(FILE_PART_CLASS_NAMES_FIELD_NAME)) { + return filePartClassNamesVisitor(); + } + else { + return null; + } + } + + @NotNull + private AnnotationArrayArgumentVisitor filePartClassNamesVisitor() { + return new CollectStringArrayAnnotationVisitor() { + @Override + protected void visitEnd(String[] data) { + filePartClassNames = data; + } + }; } @Override @@ -158,24 +193,11 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor } @NotNull - private AnnotationArrayArgumentVisitor stringArrayVisitor() { - final List strings = new ArrayList(1); - return new AnnotationArrayArgumentVisitor() { + private AnnotationArrayArgumentVisitor dataArrayVisitor() { + return new CollectStringArrayAnnotationVisitor() { @Override - public void visit(@Nullable Object value) { - if (value instanceof String) { - strings.add((String) value); - } - } - - @Override - public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) { - } - - @Override - public void visitEnd() { - //noinspection SSBasedInspection - annotationData = strings.toArray(new String[strings.size()]); + protected void visitEnd(String[] data) { + annotationData = data; } }; } @@ -183,6 +205,33 @@ public class ReadKotlinClassHeaderAnnotationVisitor implements AnnotationVisitor @Override public void visitEnd() { } + + private abstract class CollectStringArrayAnnotationVisitor implements AnnotationArrayArgumentVisitor { + private final List strings; + + public CollectStringArrayAnnotationVisitor() { + this.strings = new ArrayList(); + } + + @Override + public void visit(@Nullable Object value) { + if (value instanceof String) { + strings.add((String) value); + } + } + + @Override + public void visitEnum(@NotNull ClassId enumClassId, @NotNull Name enumEntryName) { + } + + @Override + public void visitEnd() { + //noinspection SSBasedInspection + visitEnd(strings.toArray(new String[strings.size()])); + } + + protected abstract void visitEnd(String[] data); + } } private class ClassHeaderReader extends HeaderAnnotationArgumentVisitor { diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/KotlinMultifileClass.java b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinMultifileClass.java new file mode 100644 index 00000000000..095b2d0a3d0 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinMultifileClass.java @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2015 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 kotlin.jvm.internal; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface KotlinMultifileClass { + int abiVersion(); + + String[] filePartClassNames(); + String[] data(); +} diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/KotlinMultifileClassPart.java b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinMultifileClassPart.java new file mode 100644 index 00000000000..1f1dd57fb26 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/jvm/internal/KotlinMultifileClassPart.java @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2015 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 kotlin.jvm.internal; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface KotlinMultifileClassPart { + int abiVersion(); + + String multifileClassName(); + String[] data(); +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt index 4aaf898f1fd..711dd891cbd 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/DecompiledUtils.kt @@ -70,7 +70,8 @@ public fun isKotlinInternalCompiledFile(file: VirtualFile): Boolean { } val header = KotlinBinaryClassCache.getKotlinBinaryClass(file)?.getClassHeader() ?: return false return (header.kind == KotlinClassHeader.Kind.SYNTHETIC_CLASS && header.syntheticClassKind != KotlinSyntheticClass.Kind.PACKAGE_PART) || - (header.kind == KotlinClassHeader.Kind.CLASS && header.classKind != null && header.classKind != KotlinClass.Kind.CLASS) + (header.kind == KotlinClassHeader.Kind.CLASS && header.classKind != null && header.classKind != KotlinClass.Kind.CLASS) || + (header.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART) } public fun isKotlinJavaScriptInternalCompiledFile(file: VirtualFile): Boolean = diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubBuilderContext.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubBuilderContext.kt index f58e198b8b3..dde15313dbd 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubBuilderContext.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubBuilderContext.kt @@ -83,7 +83,7 @@ private fun ClsStubBuilderContext.child(typeParameterList: List { + val packageData = JvmProtoBufUtil.readPackageDataFrom(annotationData) + val context = components.createContext(packageData.nameResolver, packageFqName) + val partHeaders = readMultifileClassPartHeaders(file, header, packageFqName) + partHeaders?.let { + createMultifileClassStub(it, classId.asSingleFqName(), context) + } + } else -> throw IllegalStateException("Should have processed " + file.getPath() + " with header $header") } } + private fun readMultifileClassPartHeaders(file: VirtualFile, header: KotlinClassHeader, packageFqName: FqName): List? { + val result = arrayListOf() + val partsFinder = DirectoryBasedClassFinder(file.parent!!, packageFqName) + val partNames = header.filePartClassNames + if (partNames != null) { + for (partName in partNames) { + val partClass = partsFinder.findKotlinClass(ClassId.topLevel(packageFqName.child(Name.identifier(partName)))) + if (partClass == null) { + LOG.error("Missing part class: $packageFqName.$partName") + return null + } + result.add(partClass.classHeader) + } + } + return result + } + private fun createStubBuilderComponents(file: VirtualFile, packageFqName: FqName): ClsStubBuilderComponents { val classFinder = DirectoryBasedClassFinder(file.getParent()!!, packageFqName) val classDataFinder = DirectoryBasedDataFinder(classFinder, LOG) @@ -93,6 +117,6 @@ public open class KotlinClsStubBuilder : ClsStubBuilder() { } companion object { - val LOG = Logger.getInstance(javaClass()) + val LOG = Logger.getInstance(KotlinClsStubBuilder::class.java) } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/clsStubBuilding.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/clsStubBuilding.kt index bfd3e269b32..df9751bdb25 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/clsStubBuilding.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/clsStubBuilding.kt @@ -20,9 +20,9 @@ import com.intellij.psi.PsiElement import com.intellij.psi.stubs.StubElement import com.intellij.util.io.StringRef import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget import org.jetbrains.kotlin.lexer.JetModifierKeywordToken import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.serialization.ProtoBuf import org.jetbrains.kotlin.serialization.ProtoBuf.Callable.CallableKind import org.jetbrains.kotlin.serialization.deserialization.AnnotatedCallableKind import org.jetbrains.kotlin.serialization.deserialization.ProtoContainer +import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil fun createTopLevelClassStub(classId: ClassId, classProto: ProtoBuf.Class, context: ClsStubBuilderContext): KotlinFileStubImpl { val fileStub = createFileStub(classId.getPackageFqName()) @@ -71,6 +72,25 @@ fun createFileFacadeStub( return fileStub } +fun createMultifileClassStub( + partHeaders: List, + facadeFqName: FqName, + c: ClsStubBuilderContext +): KotlinFileStubImpl { + val packageFqName = facadeFqName.parent() + val fileStub = KotlinFileStubImpl.forFileFacadeStub(facadeFqName, packageFqName.isRoot) + setupFileStub(fileStub, packageFqName) + val multifileClassContainer = ProtoContainer(null, facadeFqName.parent()) + for (partHeader in partHeaders) { + val partData = JvmProtoBufUtil.readPackageDataFrom(partHeader.annotationData!!) + val partContext = c.child(partData.nameResolver) + for (partMember in partData.packageProto.memberList) { + createCallableStub(fileStub, partMember, partContext, multifileClassContainer) + } + } + return fileStub +} + fun createIncompatibleAbiVersionFileStub() = createFileStub(FqName.ROOT) fun createFileStub(packageFqName: FqName): KotlinFileStubImpl { diff --git a/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java index 37f89992084..8bf6b583980 100644 --- a/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java +++ b/idea/src/org/jetbrains/kotlin/idea/internal/KotlinBytecodeToolWindow.java @@ -239,6 +239,7 @@ public class KotlinBytecodeToolWindow extends JPanel implements Disposable { generateClassFilter, !enableInline, !enableOptimization, + /*packageFacadesAsMultifileClasses=*/false, sink); KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION); } diff --git a/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt b/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt index d88ac2af45d..76f51207250 100644 --- a/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt +++ b/libraries/stdlib/src/kotlin/jvm/JvmPlatformAnnotations.kt @@ -50,6 +50,14 @@ public annotation class JvmStatic @MustBeDocumented public annotation class JvmName(public val name: String) +/** + * Instructs the Kotlin compiler to generate a multifile class with this file as one o + */ +target(AnnotationTarget.FILE) +@Retention(AnnotationRetention.RUNTIME) +@MustBeDocumented +public annotation class JvmMultifileClass + /** * Instructs the Kotlin compiler to generate a public backing field for this property. */