From db58ebc4b20a5a5078debfd9703d4afafcab2cbd Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Wed, 16 Mar 2016 12:06:18 +0300 Subject: [PATCH] KT-11410: Class hierarchy for parts/facade of multi-file class. Preserve static initialization semantics for parts by introducing a special "clinit trigger" class. Insert "static initialization trigger" call to every method of a part class, remove this call on inline. Always mangle names for private functions in multifile class parts to avoid resolution clashes on inheritance. NB in codegen tests initializers for all non-const vals are wrapped in 'run { ... }', so that the initializer is not a constant expression, and some static initialization code should be generated. --- .../kotlin/codegen/FunctionCodegen.java | 3 + .../kotlin/codegen/MemberCodegen.java | 38 ++- .../kotlin/codegen/MultifileClassCodegen.kt | 268 +++++++++++------- .../codegen/MultifileClassPartCodegen.kt | 121 +++++++- .../kotlin/codegen/PropertyCodegen.java | 4 + .../codegen/context/CodegenContext.java | 5 +- .../context/MultifileClassPartContext.java | 8 + .../kotlin/codegen/inline/InlineCodegen.java | 20 +- .../codegen/state/KotlinTypeMapper.java | 9 +- .../kotlin/fileClasses/JvmFileClassUtil.kt | 14 +- .../jvm/diagnostics/JvmDeclarationOrigin.kt | 5 +- .../multifileClassPartsInitialization.kt | 8 + .../optimized/callableRefToFun.kt | 13 + .../callableRefToInternalValInline.kt | 16 ++ .../optimized/callableRefToPrivateVal.kt | 17 ++ .../optimized/callableRefToVal.kt | 13 + .../box/multifileClasses/optimized/calls.kt | 29 ++ .../optimized/deferredStaticInitialization.kt | 39 +++ .../optimized/delegatedVal.kt | 13 + .../optimized/initializePrivateVal.kt | 15 + .../optimized/initializePublicVal.kt | 13 + .../optimized/overlappingFuns.kt | 27 ++ .../optimized/overlappingVals.kt | 25 ++ .../valAccessFromInlineFunCalledFromJava.kt | 27 ++ .../valAccessFromInlinedToDifferentPackage.kt | 19 ++ .../optimized/valWithAccessor.kt | 17 ++ .../inlineFromOptimizedMultifileClass.kt | 22 ++ .../bytecodeListing/InlineOnlyMultifile.txt | 2 +- .../bytecodeListing/emptyMultifileFacade.txt | 4 +- .../optimizedMultifileClassFacadeMethods.kt | 24 ++ .../codegen/BlackBoxCodegenTestGenerated.java | 93 ++++++ .../BlackBoxInlineCodegenTestGenerated.java | 6 + .../codegen/BytecodeTextTestGenerated.java | 15 + ...otlinAgainstInlineKotlinTestGenerated.java | 6 + 34 files changed, 823 insertions(+), 135 deletions(-) create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/callableRefToFun.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/callableRefToInternalValInline.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/callableRefToPrivateVal.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/callableRefToVal.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/calls.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/deferredStaticInitialization.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/delegatedVal.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/initializePrivateVal.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/initializePublicVal.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/overlappingFuns.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/overlappingVals.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlineFunCalledFromJava.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlinedToDifferentPackage.kt create mode 100644 compiler/testData/codegen/box/multifileClasses/optimized/valWithAccessor.kt create mode 100644 compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt create mode 100644 compiler/testData/codegen/bytecodeText/multifileClasses/optimizedMultifileClassFacadeMethods.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index 3c5d57c691b..753b242a924 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -374,6 +374,9 @@ public class FunctionCodegen { if (!KotlinTypeMapper.isAccessor(functionDescriptor)) { genNotNullAssertionsForParameters(new InstructionAdapter(mv), parentCodegen.state, functionDescriptor, frameMap); } + + parentCodegen.beforeMethodBody(mv); + methodEnd = new Label(); context.setMethodEndLabel(methodEnd); strategy.generateBody(mv, frameMap, signature, context, parentCodegen); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java index 6750893a7fa..fdf634d017c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/MemberCodegen.java @@ -336,23 +336,32 @@ public abstract class MemberCodegen", "()V", null, null); - SimpleFunctionDescriptorImpl clInit = - SimpleFunctionDescriptorImpl.create(descriptor, Annotations.Companion.getEMPTY(), Name.special(""), SYNTHESIZED, - KotlinSourceElementKt.toSourceElement(element)); - clInit.initialize(null, null, Collections.emptyList(), - Collections.emptyList(), - DescriptorUtilsKt.getModule(descriptor).getBuiltIns().getUnitType(), - null, Visibilities.PRIVATE); - - this.clInit = new ExpressionCodegen(mv, new FrameMap(), Type.VOID_TYPE, context.intoFunction(clInit), state, this); + DeclarationDescriptor contextDescriptor = context.getContextDescriptor(); + SimpleFunctionDescriptorImpl clInitDescriptor = createClInitFunctionDescriptor(contextDescriptor); + MethodVisitor mv = createClInitMethodVisitor(contextDescriptor); + clInit = new ExpressionCodegen(mv, new FrameMap(), Type.VOID_TYPE, context.intoFunction(clInitDescriptor), state, this); } return clInit; } + @NotNull + protected MethodVisitor createClInitMethodVisitor(@NotNull DeclarationDescriptor contextDescriptor) { + return v.newMethod(JvmDeclarationOriginKt.OtherOrigin(contextDescriptor), ACC_STATIC, "", "()V", null, null); + } + + @NotNull + protected SimpleFunctionDescriptorImpl createClInitFunctionDescriptor(@NotNull DeclarationDescriptor descriptor) { + SimpleFunctionDescriptorImpl clInit = SimpleFunctionDescriptorImpl.create(descriptor, Annotations.Companion.getEMPTY(), + Name.special(""), SYNTHESIZED, KotlinSourceElementKt.toSourceElement(element)); + clInit.initialize(null, null, Collections.emptyList(), + Collections.emptyList(), + DescriptorUtilsKt.getModule(descriptor).getBuiltIns().getUnitType(), + null, Visibilities.PRIVATE); + return clInit; + } + protected void generateInitializers(@NotNull Function0 createCodegen) { NotNullLazyValue codegen = LockBasedStorageManager.NO_LOCKS.createLazyValue(createCodegen); for (KtDeclaration declaration : ((KtDeclarationContainer) element).getDeclarations()) { @@ -370,6 +379,9 @@ public abstract class MemberCodegen, + private val files: Collection, private val facadeFqName: FqName, private val packagePartRegistry: PackagePartRegistry ) { @@ -71,55 +71,105 @@ class MultifileClassCodegen( getDeserializedCallables(compiledPackageFragment) private fun getDeserializedCallables(compiledPackageFragment: PackageFragmentDescriptor) = - compiledPackageFragment.getMemberScope().getContributedDescriptors(DescriptorKindFilter.CALLABLES, MemberScope.ALL_NAME_FILTER).filterIsInstance() + compiledPackageFragment.getMemberScope() + .getContributedDescriptors(DescriptorKindFilter.CALLABLES, MemberScope.ALL_NAME_FILTER) + .filterIsInstance() + + private fun KtFile.getFileClassFqName() = + state.fileClassesProvider.getFileClassInfo(this).fileClassFqName + + private val shouldGeneratePartHierarchy = + state.inheritMultifileParts // TODO support incremental compilation + + private val partInternalNamesSorted = run { + val partInternalNamesSet = hashSetOf() + for (file in files) { + if (file.hasDeclarationsForPartClass()) { + partInternalNamesSet.add(file.getFileClassFqName().toInternalName()) + } + } + compiledPackageFragment?.let { + partInternalNamesSet.addAll(it.partsNames) + } + partInternalNamesSet.sorted() + } + + private val superClassForInheritedPart = run { + val result = hashMapOf() + for (i in 1 ..partInternalNamesSorted.size - 1) { + result[partInternalNamesSorted[i]] = partInternalNamesSorted[i - 1] + } + result + } + + private val delegateGenerationTasks = hashMapOf Unit>() + + private fun getSuperClassForPart(partInternalName: String) = + if (shouldGeneratePartHierarchy) + superClassForInheritedPart[partInternalName] ?: J_L_OBJECT + else + J_L_OBJECT private val classBuilder = ClassBuilderOnDemand { val originFile = files.firstOrNull() - val actualPackageFragment = packageFragment ?: - compiledPackageFragment ?: - throw AssertionError("No package fragment for multifile facade $facadeFqName; files: $files") - val declarationOrigin = MultifileClass(originFile, actualPackageFragment, facadeFqName) - val classBuilder = state.factory.newVisitor(declarationOrigin, facadeClassType, files) - val filesWithCallables = files.filter { it.declarations.any { it is KtNamedFunction || it is KtProperty } } + val actualPackageFragment = packageFragment + ?: compiledPackageFragment + ?: throw AssertionError("No package fragment for multifile facade $facadeFqName; files: $files") - val singleSourceFile = if (previouslyCompiledCallables.isNotEmpty()) null else filesWithCallables.singleOrNull() + val declarationOrigin = MultifileClass(originFile, actualPackageFragment) - classBuilder.defineClass(singleSourceFile, Opcodes.V1_6, - FACADE_CLASS_ATTRIBUTES, - facadeClassType.internalName, - null, "java/lang/Object", ArrayUtil.EMPTY_STRING_ARRAY) - if (singleSourceFile != null) { - classBuilder.visitSource(singleSourceFile.name, null) + val singleSourceFile = + if (previouslyCompiledCallables.isEmpty()) + files.singleOrNull { it.hasDeclarationsForPartClass() } + else + null + + val superClassForFacade = + if (shouldGeneratePartHierarchy) + partInternalNamesSorted.last() + else + J_L_OBJECT + + state.factory.newVisitor(declarationOrigin, facadeClassType, files).apply { + defineClass(singleSourceFile, Opcodes.V1_6, FACADE_CLASS_ATTRIBUTES, + facadeClassType.internalName, null, superClassForFacade, ArrayUtil.EMPTY_STRING_ARRAY) + if (singleSourceFile != null) { + visitSource(singleSourceFile.name, null) + } + + if (shouldGeneratePartHierarchy) { + newMethod(OtherOrigin(actualPackageFragment), Opcodes.ACC_PRIVATE, "", "()V", null, null).apply { + visitCode() + visitVarInsn(Opcodes.ALOAD, 0) + visitMethodInsn(Opcodes.INVOKESPECIAL, superClassForFacade, "", "()V", false) + visitInsn(Opcodes.RETURN) + visitMaxs(1, 1) + visitEnd() + } + } } - - classBuilder } fun generate(errorHandler: CompilationErrorHandler) { - val generateCallableMemberTasks = HashMap Unit>() - val partFqNames = arrayListOf() + assert(delegateGenerationTasks.isEmpty()) { "generate() is called twice for facade class $facadeFqName" } - generateCodeForSourceFiles(errorHandler, generateCallableMemberTasks, partFqNames) + generateCodeForSourceFiles(errorHandler) - generateDelegatesToPreviouslyCompiledParts(generateCallableMemberTasks, partFqNames) + generateDelegatesToPreviouslyCompiledParts() - if (!partFqNames.isEmpty()) { - generateMultifileFacadeClass(generateCallableMemberTasks, partFqNames) + if (!partInternalNamesSorted.isEmpty()) { + generateMultifileFacadeClass() } done() } - private fun generateCodeForSourceFiles( - errorHandler: CompilationErrorHandler, - generateCallableMemberTasks: MutableMap Unit>, - partFqNames: MutableList - ) { + private fun generateCodeForSourceFiles(errorHandler: CompilationErrorHandler) { for (file in files) { ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() try { - generatePart(file, generateCallableMemberTasks, partFqNames) + generatePart(file) state.afterIndependentPart() } catch (e: ProcessCanceledException) { @@ -137,87 +187,103 @@ class MultifileClassCodegen( } } - private fun generateMultifileFacadeClass( - tasks: Map Unit>, - partFqNames: List - ) { - for (member in tasks.keys.sortedWith(MemberComparator.INSTANCE)) { - tasks[member]!!() + private fun generateMultifileFacadeClass() { + for (member in delegateGenerationTasks.keys.sortedWith(MemberComparator.INSTANCE)) { + delegateGenerationTasks[member]!!() } - writeKotlinMultifileFacadeAnnotationIfNeeded(partFqNames) + writeKotlinMultifileFacadeAnnotationIfNeeded() } fun generateClassOrObject(classOrObject: KtClassOrObject, packagePartContext: FieldOwnerContext) { MemberCodegen.genClassOrObject(packagePartContext, classOrObject, state, null) } - private fun generatePart( - file: KtFile, - generateCallableMemberTasks: MutableMap Unit>, - partFqNames: MutableList - ) { - val packageFragment = this.packageFragment ?: - throw AssertionError("File part $file of $facadeFqName: no package fragment") + private fun generatePart(file: KtFile) { + 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 = state.rootContext.intoMultifileClassPart(packageFragment, facadeClassType, partType, file) + val partClassFqName = file.getFileClassFqName() + val partInitializerClassFqName = FqName(partClassFqName.asString() + "__Init") + val partType = partClassFqName.toAsmType() + val partInitializerType = partInitializerClassFqName.toAsmType() + val partContext = state.rootContext.intoMultifileClassPart(packageFragment, facadeClassType, partType, partInitializerType, file) + generateNonPartClassDeclarations(file, partContext) + + if (!state.generateDeclaredClassFilter.shouldGeneratePackagePart(file) || !file.hasDeclarationsForPartClass()) return + + val partInternalName = partType.internalName + packagePartRegistry.addPart(partInternalName.substring(partInternalName.lastIndexOf('/') + 1)) + + val builder = state.factory.newVisitor(MultifileClassPart(file, packageFragment), partType, file) + + MultifileClassPartCodegen( + builder, file, packageFragment, + getSuperClassForPart(partInternalName), + shouldGeneratePartHierarchy, + partContext, state + ).generate() + + addDelegateGenerationTasksForDeclarationsInFile(file, packageFragment, partType) + } + + private fun generateNonPartClassDeclarations(file: KtFile, partContext: FieldOwnerContext) { for (declaration in file.declarations) { when (declaration) { - is KtProperty, is KtNamedFunction -> { - generatePart = true - } - is KtClassOrObject -> if (state.generateDeclaredClassFilter.shouldGenerateClass(declaration)) { - generateClassOrObject(declaration, partContext) - } - is KtScript -> if (state.generateDeclaredClassFilter.shouldGenerateScript(declaration)) { - ScriptCodegen.createScriptCodegen(declaration, state, partContext).generate() - } - } - } - - - if (!generatePart || !state.generateDeclaredClassFilter.shouldGeneratePackagePart(file)) return - - partFqNames.add(partClassInfo.fileClassFqName) - - val name = partType.internalName - packagePartRegistry.addPart(name.substring(name.lastIndexOf('/') + 1)) - - val builder = state.factory.newVisitor(MultifileClassPart(file, packageFragment, facadeFqName), partType, file) - - MultifileClassPartCodegen(builder, file, partType, facadeClassType, partContext, state).generate() - - val facadeContext = state.rootContext.intoMultifileClass(packageFragment, facadeClassType, partType) - val memberCodegen = createCodegenForPartOfMultifileFacade(facadeContext) - for (declaration in file.declarations) { - if (declaration is KtNamedFunction || declaration is KtProperty) { - val descriptor = state.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) - assert(descriptor is CallableMemberDescriptor) { "Expected callable member, was " + descriptor + " for " + declaration.text } - if (!Visibilities.isPrivate((descriptor as CallableMemberDescriptor).visibility) - && AsmUtil.getVisibilityAccessFlag(descriptor) != Opcodes.ACC_PRIVATE) { - generateCallableMemberTasks.put(descriptor, { memberCodegen.genFunctionOrProperty(declaration) }) - } + is KtClassOrObject -> + if (state.generateDeclaredClassFilter.shouldGenerateClass(declaration)) { + generateClassOrObject(declaration, partContext) + } + is KtScript -> + if (state.generateDeclaredClassFilter.shouldGenerateScript(declaration)) { + ScriptCodegen.createScriptCodegen(declaration, state, partContext).generate() + } } } } - private fun generateDelegatesToPreviouslyCompiledParts( - generateCallableMemberTasks: MutableMap Unit>, - partFqNames: MutableList - ) { - if (compiledPackageFragment == null) return + private fun addDelegateGenerationTasksForDeclarationsInFile(file: KtFile, packageFragment: PackageFragmentDescriptor, partType: Type) { + val facadeContext = state.rootContext.intoMultifileClass(packageFragment, facadeClassType, partType) + val memberCodegen = createCodegenForDelegatesInMultifileFacade(facadeContext) + for (declaration in file.declarations) { + if (declaration is KtNamedFunction || declaration is KtProperty) { + val descriptor = state.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) + if (descriptor !is CallableMemberDescriptor) { + throw AssertionError("Expected callable member, was " + descriptor + " for " + declaration.text) + } + addDelegateGenerationTaskIfNeeded(descriptor, { memberCodegen.genFunctionOrProperty(declaration) }) + } + } + } - partFqNames.addAll(compiledPackageFragment.partsNames.map { JvmClassName.byInternalName(it).fqNameForClassNameWithoutDollars }) + private fun shouldGenerateInFacade(descriptor: CallableMemberDescriptor): Boolean { + if (Visibilities.isPrivate(descriptor.visibility)) return false + if (AsmUtil.getVisibilityAccessFlag(descriptor) == Opcodes.ACC_PRIVATE) return false + + if (state.classBuilderMode == ClassBuilderMode.LIGHT_CLASSES) return true + + if (shouldGeneratePartHierarchy) { + if (descriptor !is PropertyDescriptor || !descriptor.isConst) return false + } + + return true + } + + private fun addDelegateGenerationTaskIfNeeded(callable: CallableMemberDescriptor, task: () -> Unit) { + if (shouldGenerateInFacade(callable)) { + delegateGenerationTasks[callable] = task + } + } + + private fun generateDelegatesToPreviouslyCompiledParts() { + if (compiledPackageFragment == null) return for (callable in previouslyCompiledCallables) { val partFqName = JvmFileClassUtil.getPartFqNameForDeserializedCallable(callable) val partType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(partFqName) - generateCallableMemberTasks[callable] = { generateDelegateToCompiledMember(callable, compiledPackageFragment, partType) } + addDelegateGenerationTaskIfNeeded(callable, { generateDelegateToCompiledMember(callable, compiledPackageFragment, partType) }) } } @@ -228,7 +294,7 @@ class MultifileClassCodegen( ) { val context = state.rootContext.intoMultifileClass(compiledPackageFragment, facadeClassType, partType) - val memberCodegen = createCodegenForPartOfMultifileFacade(context) + val memberCodegen = createCodegenForDelegatesInMultifileFacade(context) when (member) { is DeserializedSimpleFunctionDescriptor -> { @@ -254,20 +320,20 @@ class MultifileClassCodegen( } } - private fun writeKotlinMultifileFacadeAnnotationIfNeeded(partFqNames: List) { + private fun writeKotlinMultifileFacadeAnnotationIfNeeded() { if (state.classBuilderMode != ClassBuilderMode.FULL) return if (files.any { it.isScript }) return writeKotlinMetadata(classBuilder, KotlinClassHeader.Kind.MULTIFILE_CLASS) { av -> val arv = av.visitArray(JvmAnnotationNames.METADATA_DATA_FIELD_NAME) - for (internalName in partFqNames.map(AsmUtil::internalNameByFqNameWithoutInnerClasses).sorted()) { + for (internalName in partInternalNamesSorted) { arv.visit(null, internalName) } arv.visitEnd() } } - private fun createCodegenForPartOfMultifileFacade(facadeContext: FieldOwnerContext<*>): MemberCodegen = + private fun createCodegenForDelegatesInMultifileFacade(facadeContext: FieldOwnerContext<*>): MemberCodegen = object : MemberCodegen(state, null, facadeContext, null, classBuilder) { override fun generateDeclaration() = throw UnsupportedOperationException() override fun generateBody() = throw UnsupportedOperationException() @@ -282,15 +348,16 @@ class MultifileClassCodegen( } companion object { + private val J_L_OBJECT = AsmTypes.OBJECT_TYPE.internalName private val FACADE_CLASS_ATTRIBUTES = Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL or Opcodes.ACC_SUPER 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 } + ?: throw AssertionError("package fragment is null for " + file + "\n" + file.text) - assert(packageFqName == fragment!!.fqName) { "expected package fq name: " + packageFqName + ", actual: " + fragment.fqName } + assert(packageFqName == fragment.fqName) { "expected package fq name: " + packageFqName + ", actual: " + fragment.fqName } if (!fragments.contains(fragment)) { fragments.add(fragment) @@ -302,6 +369,15 @@ class MultifileClassCodegen( return fragments.firstOrNull() } + private fun KtFile.hasDeclarationsForPartClass() = + declarations.any { it is KtProperty || it is KtFunction } + + private fun FqName.toInternalName() = + AsmUtil.internalNameByFqNameWithoutInnerClasses(this) + + private fun FqName.toAsmType() = + AsmUtil.asmTypeByFqNameWithoutInnerClasses(this) + private fun getCompiledPackageFragment(facadeFqName: FqName, state: GenerationState): IncrementalPackageFragmentProvider.IncrementalPackageFragment.IncrementalMultifileClassPackageFragment? { if (!IncrementalCompilation.isEnabled()) return null diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt index a6bdb2dd54c..9e01d397be8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/MultifileClassPartCodegen.kt @@ -17,44 +17,104 @@ package org.jetbrains.kotlin.codegen import com.intellij.util.ArrayUtil -import org.jetbrains.kotlin.codegen.context.FieldOwnerContext +import org.jetbrains.kotlin.codegen.context.MultifileClassPartContext import org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.jvm.diagnostics.MultifileClass +import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin import org.jetbrains.kotlin.serialization.DescriptorSerializer +import org.jetbrains.org.objectweb.asm.MethodVisitor import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode import java.util.* class MultifileClassPartCodegen( v: ClassBuilder, file: KtFile, - private val filePartType: Type, - private val multifileClassType: Type, - partContext: FieldOwnerContext<*>, + private val packageFragment: PackageFragmentDescriptor, + private val superClassInternalName: String, + private val shouldGeneratePartHierarchy: Boolean, + private val partContext: MultifileClassPartContext, state: GenerationState ) : MemberCodegen(state, null, partContext, file, v) { + private val partType = partContext.filePartType + private val facadeClassType = partContext.multifileClassType + private val staticInitClassType = Type.getObjectType(partType.internalName + STATIC_INIT_CLASS_SUFFIX) + + private val partClassAttributes = + if (shouldGeneratePartHierarchy) + OPEN_PART_CLASS_ATTRIBUTES + else + FINAL_PART_CLASS_ATTRIBUTES + + private fun ClassBuilder.newSpecialMethod(originDescriptor: DeclarationDescriptor, name: String) = + newMethod(OtherOrigin(originDescriptor), Opcodes.ACC_STATIC or Opcodes.ACC_SUPER, name, "()V", null, null) + + private val staticInitClassBuilder = ClassBuilderOnDemand { + state.factory.newVisitor(MultifileClass(file, packageFragment), staticInitClassType, file).apply { + defineClass(file, Opcodes.V1_6, STATE_INITIALIZER_CLASS_ATTRIBUTES, + staticInitClassType.internalName, null, "java/lang/Object", ArrayUtil.EMPTY_STRING_ARRAY) + + visitSource(file.name, null) + } + } + + private val requiresDeferredStaticInitialization = + shouldGeneratePartHierarchy && file.declarations.any { + it is KtProperty && shouldInitializeProperty(it) + } + override fun generate() { if (state.classBuilderMode == ClassBuilderMode.LIGHT_CLASSES) return + super.generate() + + if (shouldGeneratePartHierarchy) { + v.newMethod(OtherOrigin(packageFragment), Opcodes.ACC_PUBLIC, "", "()V", null, null).apply { + visitCode() + visitVarInsn(Opcodes.ALOAD, 0) + visitMethodInsn(Opcodes.INVOKESPECIAL, superClassInternalName, "", "()V", false) + visitInsn(Opcodes.RETURN) + visitMaxs(1, 1) + visitEnd() + } + } + + if (requiresDeferredStaticInitialization) { + staticInitClassBuilder.apply { + newSpecialMethod(packageFragment, CLINIT_TRIGGER_NAME).apply { + visitCode() + visitInsn(Opcodes.RETURN) + visitMaxs(0, 0) + visitEnd() + } + + newSpecialMethod(packageFragment, "").apply { + visitCode() + visitMethodInsn(Opcodes.INVOKESTATIC, partType.internalName, DEFERRED_PART_CLINIT_NAME, "()V", false) + visitInsn(Opcodes.RETURN) + visitMaxs(0, 0) + visitEnd() + } + } + } } override fun generateDeclaration() { - v.defineClass(element, Opcodes.V1_6, - Opcodes.ACC_FINAL or Opcodes.ACC_SYNTHETIC or Opcodes.ACC_SUPER, - filePartType.internalName, - null, - "java/lang/Object", - ArrayUtil.EMPTY_STRING_ARRAY) + v.defineClass(element, Opcodes.V1_6, partClassAttributes, partType.internalName, null, superClassInternalName, ArrayUtil.EMPTY_STRING_ARRAY) v.visitSource(element.name, null) - generatePropertyMetadataArrayFieldIfNeeded(filePartType) + generatePropertyMetadataArrayFieldIfNeeded(partType) } override fun generateBody() { @@ -69,6 +129,20 @@ class MultifileClassPartCodegen( } } + override fun createClInitMethodVisitor(contextDescriptor: DeclarationDescriptor): MethodVisitor = + if (requiresDeferredStaticInitialization) + v.newSpecialMethod(contextDescriptor, DEFERRED_PART_CLINIT_NAME) + else + super.createClInitMethodVisitor(contextDescriptor) + + override fun done() { + super.done() + + if (staticInitClassBuilder.isComputed) { + staticInitClassBuilder.done() + } + } + override fun generateKotlinMetadataAnnotation() { val members = ArrayList() for (declaration in element.declarations) { @@ -89,11 +163,34 @@ class MultifileClassPartCodegen( writeKotlinMetadata(v, KotlinClassHeader.Kind.MULTIFILE_CLASS_PART) { av -> AsmUtil.writeAnnotationData(av, serializer, packageProto) - av.visit(JvmAnnotationNames.METADATA_MULTIFILE_CLASS_NAME_FIELD_NAME, multifileClassType.internalName) + av.visit(JvmAnnotationNames.METADATA_MULTIFILE_CLASS_NAME_FIELD_NAME, facadeClassType.internalName) } } override fun generateSyntheticParts() { generateSyntheticAccessors() } + + override fun beforeMethodBody(mv: MethodVisitor) { + if (requiresDeferredStaticInitialization) { + mv.visitMethodInsn(Opcodes.INVOKESTATIC, staticInitClassType.internalName, CLINIT_TRIGGER_NAME, "()V", false) + } + } + + companion object { + private val OPEN_PART_CLASS_ATTRIBUTES = Opcodes.ACC_SUPER + private val FINAL_PART_CLASS_ATTRIBUTES = Opcodes.ACC_SYNTHETIC or Opcodes.ACC_SUPER or Opcodes.ACC_FINAL + private val STATE_INITIALIZER_CLASS_ATTRIBUTES = Opcodes.ACC_SYNTHETIC or Opcodes.ACC_SUPER or Opcodes.ACC_FINAL + + private val STATIC_INIT_CLASS_SUFFIX = "__Clinit" + private val CLINIT_TRIGGER_NAME = "\$\$clinitTrigger" + private val DEFERRED_PART_CLINIT_NAME = "\$\$clinit" + + @JvmStatic fun isStaticInitTrigger(insn: AbstractInsnNode) = + insn.opcode == Opcodes.INVOKESTATIC + && insn is MethodInsnNode + && insn.owner.endsWith(STATIC_INIT_CLASS_SUFFIX) + && insn.name == CLINIT_TRIGGER_NAME + && insn.desc == "()V" + } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java index 5ad439bae76..24ce90bce0d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotated; import org.jetbrains.kotlin.descriptors.annotations.AnnotationSplitter; import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget; import org.jetbrains.kotlin.descriptors.annotations.Annotations; +import org.jetbrains.kotlin.fileClasses.JvmFileClassUtilKt; import org.jetbrains.kotlin.load.java.JvmAbi; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; @@ -184,6 +185,9 @@ public class PropertyCodegen { // Companion object properties always should have accessors, because their backing fields are moved/copied to the outer class if (isCompanionObject(descriptor.getContainingDeclaration())) return true; + // Non-const properties from multifile classes have accessors regardless of visibility + if (!descriptor.isConst() && JvmFileClassUtilKt.isInsideJvmMultifileClassFile(declaration)) return true; + // Private class properties have accessors only in cases when those accessors are non-trivial if (Visibilities.isPrivate(descriptor.getVisibility())) { return !isDefaultAccessor; 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 3361ed51f4a..d72aca11cb5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/CodegenContext.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/CodegenContext.java @@ -250,13 +250,14 @@ public abstract class CodegenContext { } @NotNull - public FieldOwnerContext intoMultifileClassPart( + public MultifileClassPartContext intoMultifileClassPart( @NotNull PackageFragmentDescriptor descriptor, @NotNull Type multifileClassType, @NotNull Type filePartType, + @NotNull Type filePartInitializerType, @NotNull KtFile sourceFile ) { - return new MultifileClassPartContext(descriptor, this, multifileClassType, filePartType, sourceFile); + return new MultifileClassPartContext(descriptor, this, multifileClassType, filePartType, filePartInitializerType, sourceFile); } @NotNull diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassPartContext.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassPartContext.java index 87ac6b61cb6..869968706bd 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassPartContext.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/MultifileClassPartContext.java @@ -24,16 +24,19 @@ import org.jetbrains.org.objectweb.asm.Type; public class MultifileClassPartContext extends MultifileClassContextBase implements DelegatingToPartContext, FacadePartWithSourceFile { private final KtFile sourceFile; + private final Type partInitializerType; public MultifileClassPartContext( PackageFragmentDescriptor descriptor, CodegenContext parent, Type multifileClassType, Type filePartType, + Type partInitializerType, @NotNull KtFile sourceFile ) { super(descriptor, parent, multifileClassType, filePartType); this.sourceFile = sourceFile; + this.partInitializerType = partInitializerType; } @Nullable @@ -42,6 +45,11 @@ public class MultifileClassPartContext extends MultifileClassContextBase impleme return getFilePartType(); } + @NotNull + public Type getPartInitializerType() { + return partInitializerType; + } + @NotNull @Override public KtFile getSourceFile() { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java index bcaad80282f..3e84a4946a1 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java @@ -66,9 +66,7 @@ import java.util.*; import static org.jetbrains.kotlin.codegen.AsmUtil.getMethodAsmFlags; import static org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive; -import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.API; -import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.addInlineMarker; -import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.getConstant; +import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.*; import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isFunctionLiteral; public class InlineCodegen extends CallGenerator { @@ -395,6 +393,7 @@ public class InlineCodegen extends CallGenerator { List infos = MethodInliner.processReturns(adapter, labelOwner, true, null); generateAndInsertFinallyBlocks(adapter, infos, ((StackValue.Local)remapper.remap(parameters.getArgsSizeOnStack() + 1).value).index); + removeStaticInitializationTrigger(adapter); removeFinallyMarkers(adapter); adapter.accept(new MethodBodyVisitor(codegen.v)); @@ -404,6 +403,21 @@ public class InlineCodegen extends CallGenerator { return result; } + private static void removeStaticInitializationTrigger(MethodNode methodNode) { + InsnList insnList = methodNode.instructions; + AbstractInsnNode insn = insnList.getFirst(); + while (insn != null) { + if (MultifileClassPartCodegen.isStaticInitTrigger(insn)) { + AbstractInsnNode clinitTriggerCall = insn; + insn = insn.getNext(); + insnList.remove(clinitTriggerCall); + } + else { + insn = insn.getNext(); + } + } + } + private InlineCallSiteInfo getInlineCallSiteInfo() { MethodContext context = codegen.getContext(); MemberCodegen parentCodegen = codegen.getParentCodegen(); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java index 3e0b4060f53..8d6d34dda83 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -973,13 +973,20 @@ public class KotlinTypeMapper { } @NotNull - private String updateMemberNameIfInternal(@NotNull String name, @NotNull CallableMemberDescriptor descriptor) { + private String updateMemberNameIfInternal(@NotNull String name, @NotNull CallableMemberDescriptor descriptor) { if (descriptor.getContainingDeclaration() instanceof ScriptDescriptor) { //script properties should be public return name; } if (DescriptorUtils.isTopLevelDeclaration(descriptor)) { + if (Visibilities.isPrivate(descriptor.getVisibility()) && !(descriptor instanceof ConstructorDescriptor) && !"".equals(name)) { + KtFile containingFile = DescriptorToSourceUtils.getContainingFile(descriptor); + assert containingFile != null : "Private descriptor accessed outside of corresponding file scope: " + descriptor; + if (JvmFileClassUtil.isFromMultifileClass(containingFile, descriptor)) { + return name + "$" + JvmAbi.sanitizeAsJavaIdentifier(containingFile.getName()); + } + } return name; } 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 d3e3259bb95..499fdc3eed3 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt @@ -18,12 +18,15 @@ package org.jetbrains.kotlin.fileClasses import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.load.java.descriptors.getImplClassNameForDeserialized import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor object JvmFileClassUtil { @@ -84,14 +87,21 @@ object JvmFileClassUtil { it.calleeExpression?.constructorReferenceExpression?.getReferencedName() == shortName } - @JvmStatic - private fun getLiteralStringFromRestrictedConstExpression(argumentExpression: KtExpression?): String? { + @JvmStatic private fun getLiteralStringFromRestrictedConstExpression(argumentExpression: KtExpression?): String? { val stringTemplate = argumentExpression as? KtStringTemplateExpression ?: return null val stringTemplateEntries = stringTemplate.entries if (stringTemplateEntries.size != 1) return null val singleEntry = stringTemplateEntries[0] as? KtLiteralStringTemplateEntry ?: return null return singleEntry.text } + + @JvmStatic fun isFromMultifileClass(declarationElement: KtElement, descriptor: DeclarationDescriptor): Boolean { + if (DescriptorUtils.isTopLevelDeclaration(descriptor)) { + val fileClassInfo = JvmFileClassUtil.getFileClassInfoNoResolve(declarationElement.getContainingKtFile()) + return fileClassInfo.withJvmMultifileClass + } + return false + } } internal class ParsedJvmFileClassAnnotations(val name: String, val multipleFiles: Boolean) 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 b57c02fdb52..866538c02d6 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 @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.resolve.jvm.diagnostics import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils @@ -68,9 +67,9 @@ fun PackagePart(file: KtFile, descriptor: PackageFragmentDescriptor): JvmDeclara /** * @param representativeFile one of the files representing this multifile class (will be used for diagnostics) */ -fun MultifileClass(representativeFile: KtFile?, descriptor: PackageFragmentDescriptor, multifileClassFqName: FqName): JvmDeclarationOrigin = +fun MultifileClass(representativeFile: KtFile?, descriptor: PackageFragmentDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(MULTIFILE_CLASS, representativeFile, descriptor) -fun MultifileClassPart(file: KtFile, descriptor: PackageFragmentDescriptor, multifileClassFqName: FqName): JvmDeclarationOrigin = +fun MultifileClassPart(file: KtFile, descriptor: PackageFragmentDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(MULTIFILE_CLASS_PART, file, descriptor) fun TraitImpl(element: KtClassOrObject, descriptor: ClassDescriptor): JvmDeclarationOrigin = JvmDeclarationOrigin(INTERFACE_DEFAULT_IMPL, element, descriptor) diff --git a/compiler/testData/codegen/box/multifileClasses/multifileClassPartsInitialization.kt b/compiler/testData/codegen/box/multifileClasses/multifileClassPartsInitialization.kt index 33b98e5aaf5..ad91a505238 100644 --- a/compiler/testData/codegen/box/multifileClasses/multifileClassPartsInitialization.kt +++ b/compiler/testData/codegen/box/multifileClasses/multifileClassPartsInitialization.kt @@ -25,3 +25,11 @@ val K: String = "K" package a val OK: String = O + K + +// FILE: irrelevant.kt + +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val X: Nothing = throw AssertionError("X should not be initialized") + diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToFun.kt b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToFun.kt new file mode 100644 index 00000000000..6cca423ba20 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToFun.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = (::ok)() + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +fun ok() = "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToInternalValInline.kt b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToInternalValInline.kt new file mode 100644 index 00000000000..c9a33db11d7 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToInternalValInline.kt @@ -0,0 +1,16 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = okInline() + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +internal val ok = run { "OK" } + +internal inline fun okInline() = + ::ok.get() \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToPrivateVal.kt b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToPrivateVal.kt new file mode 100644 index 00000000000..e31551e8efc --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToPrivateVal.kt @@ -0,0 +1,17 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = OK.okRef.get() + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +private val ok = run { "OK" } + +object OK { + val okRef = ::ok +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToVal.kt b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToVal.kt new file mode 100644 index 00000000000..d2f0e94ec42 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/callableRefToVal.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = ::OK.get() + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val OK = run { "OK" } \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/calls.kt b/compiler/testData/codegen/box/multifileClasses/optimized/calls.kt new file mode 100644 index 00000000000..800b7d555d3 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/calls.kt @@ -0,0 +1,29 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: Baz.java + +public class Baz { + public static String baz() { + return Util.foo() + Util.bar(); + } +} + +// FILE: bar.kt + +@file:JvmName("Util") +@file:JvmMultifileClass +public fun bar(): String = barx() + +public fun foox(): String = "O" + +// FILE: foo.kt + +@file:JvmName("Util") +@file:JvmMultifileClass +public fun foo(): String = foox() + +public fun barx(): String = "K" + +// FILE: test.kt + +fun box(): String = Baz.baz() diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/deferredStaticInitialization.kt b/compiler/testData/codegen/box/multifileClasses/optimized/deferredStaticInitialization.kt new file mode 100644 index 00000000000..58a2887956c --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/deferredStaticInitialization.kt @@ -0,0 +1,39 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = OK + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val O = run { "O" } + +// FILE: part2.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +const val K = "K" + +// FILE: part3.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val OK: String = run { O + K } + +// FILE: irrelevantPart.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val X1: Nothing = + throw AssertionError("X1 should not be initialized") + +// FILE: reallyIrrelevantPart.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val X2: Nothing = + throw AssertionError("X2 should not be initialized") \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/delegatedVal.kt b/compiler/testData/codegen/box/multifileClasses/optimized/delegatedVal.kt new file mode 100644 index 00000000000..5e6549d39a5 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/delegatedVal.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = OK + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val OK: String by lazy { "OK" } \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/initializePrivateVal.kt b/compiler/testData/codegen/box/multifileClasses/optimized/initializePrivateVal.kt new file mode 100644 index 00000000000..ca54134457f --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/initializePrivateVal.kt @@ -0,0 +1,15 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = ok() + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +private val OK = run { "OK" } + +fun ok() = OK \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/initializePublicVal.kt b/compiler/testData/codegen/box/multifileClasses/optimized/initializePublicVal.kt new file mode 100644 index 00000000000..c8cbb16a74e --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/initializePublicVal.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = OK + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +public val OK = run { "OK" } diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/overlappingFuns.kt b/compiler/testData/codegen/box/multifileClasses/optimized/overlappingFuns.kt new file mode 100644 index 00000000000..668fe84e269 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/overlappingFuns.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = ok() + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +private fun overlapping() = "oops #1" + +// FILE: part2.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +fun overlapping() = "OK" + +fun ok() = overlapping() + +// FILE: part3.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +private fun overlapping() = "oops #2" diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/overlappingVals.kt b/compiler/testData/codegen/box/multifileClasses/optimized/overlappingVals.kt new file mode 100644 index 00000000000..f3bb25d83fb --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/overlappingVals.kt @@ -0,0 +1,25 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = overlapping + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +private val overlapping = run { "oops #1" } + +// FILE: part2.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val overlapping = run { "OK" } + +// FILE: part3.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +private val overlapping = run { "oops #2" } diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlineFunCalledFromJava.kt b/compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlineFunCalledFromJava.kt new file mode 100644 index 00000000000..35ebe2825a7 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlineFunCalledFromJava.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = J.ok() + +// FILE: part1.kt +@file:[JvmName("MC") JvmMultifileClass] +package a + +val O = run { "O" } +const val K = "K" + +inline fun ok(): String { + return O + K +} + +// FILE: J.java +import a.MC; + +public class J { + public static String ok() { + return MC.ok(); + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlinedToDifferentPackage.kt b/compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlinedToDifferentPackage.kt new file mode 100644 index 00000000000..1fa15e51af7 --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlinedToDifferentPackage.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = ok {} + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +val O = run { "O" } +const val K = "K" + +inline fun ok(block: () -> Unit): String { + block() + return O + K +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/multifileClasses/optimized/valWithAccessor.kt b/compiler/testData/codegen/box/multifileClasses/optimized/valWithAccessor.kt new file mode 100644 index 00000000000..ada078b252e --- /dev/null +++ b/compiler/testData/codegen/box/multifileClasses/optimized/valWithAccessor.kt @@ -0,0 +1,17 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: box.kt + +import a.* + +fun box(): String = OK().ok + +// FILE: part1.kt +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +private val reallyOk = run { "OK" } + +class OK() { + val ok = reallyOk +} diff --git a/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt b/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt new file mode 100644 index 00000000000..a64de1c3012 --- /dev/null +++ b/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt @@ -0,0 +1,22 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: 1.kt + +@file:[JvmName("MultifileClass") JvmMultifileClass] +package a + +inline fun foo(body: () -> String): String = bar(body()) + +public fun bar(x: String): String = x + +inline fun inlineOnly(x: Any?): Boolean = x is T + +// FILE: 2.kt + +import a.foo +import a.inlineOnly + +fun box(): String { + if (!a.inlineOnly("OK")) return "fail 1" + return foo { "OK" } +} diff --git a/compiler/testData/codegen/bytecodeListing/InlineOnlyMultifile.txt b/compiler/testData/codegen/bytecodeListing/InlineOnlyMultifile.txt index 9eed46549fb..489d4bae512 100644 --- a/compiler/testData/codegen/bytecodeListing/InlineOnlyMultifile.txt +++ b/compiler/testData/codegen/bytecodeListing/InlineOnlyMultifile.txt @@ -7,4 +7,4 @@ public final class test/Foo { synthetic final class test/Foo__InlineOnlyMultifileKt { public final static method foo(): void private final static method inlineOnly(): void -} +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeListing/emptyMultifileFacade.txt b/compiler/testData/codegen/bytecodeListing/emptyMultifileFacade.txt index 1c3cb978a8c..054e3710133 100644 --- a/compiler/testData/codegen/bytecodeListing/emptyMultifileFacade.txt +++ b/compiler/testData/codegen/bytecodeListing/emptyMultifileFacade.txt @@ -3,5 +3,5 @@ public final class test/Foo @kotlin.Metadata synthetic final class test/Foo__EmptyMultifileFacadeKt { - private final static method privateOnly(): void -} + private final static method privateOnly$emptyMultifileFacade_kt(): void +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/multifileClasses/optimizedMultifileClassFacadeMethods.kt b/compiler/testData/codegen/bytecodeText/multifileClasses/optimizedMultifileClassFacadeMethods.kt new file mode 100644 index 00000000000..0753e91c07b --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/multifileClasses/optimizedMultifileClassFacadeMethods.kt @@ -0,0 +1,24 @@ +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: +JVM.INHERIT_MULTIFILE_PARTS +// FILE: bar.kt + +@file:JvmName("Util") +@file:JvmMultifileClass +public fun bar(): String = barx() + +public fun foox(): String = "O" + +// FILE: foo.kt + +@file:JvmName("Util") +@file:JvmMultifileClass +public fun foo(): String = foox() + +public fun barx(): String = "K" + +// @Util.class: +// 1 public final class Util extends Util__FooKt +// 0 public final static foo\(\) +// 0 public final static foox\(\) +// 0 public final static bar\(\) +// 0 public final static barx\(\) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 5ac6f7380ef..fa794e91d74 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -8469,6 +8469,99 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/samePartNameDifferentFacades.kt"); doTest(fileName); } + + @TestMetadata("compiler/testData/codegen/box/multifileClasses/optimized") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Optimized extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInOptimized() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/multifileClasses/optimized"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("callableRefToFun.kt") + public void testCallableRefToFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/callableRefToFun.kt"); + doTest(fileName); + } + + @TestMetadata("callableRefToInternalValInline.kt") + public void testCallableRefToInternalValInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/callableRefToInternalValInline.kt"); + doTest(fileName); + } + + @TestMetadata("callableRefToPrivateVal.kt") + public void testCallableRefToPrivateVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/callableRefToPrivateVal.kt"); + doTest(fileName); + } + + @TestMetadata("callableRefToVal.kt") + public void testCallableRefToVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/callableRefToVal.kt"); + doTest(fileName); + } + + @TestMetadata("calls.kt") + public void testCalls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/calls.kt"); + doTest(fileName); + } + + @TestMetadata("deferredStaticInitialization.kt") + public void testDeferredStaticInitialization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/deferredStaticInitialization.kt"); + doTest(fileName); + } + + @TestMetadata("delegatedVal.kt") + public void testDelegatedVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/delegatedVal.kt"); + doTest(fileName); + } + + @TestMetadata("initializePrivateVal.kt") + public void testInitializePrivateVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/initializePrivateVal.kt"); + doTest(fileName); + } + + @TestMetadata("initializePublicVal.kt") + public void testInitializePublicVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/initializePublicVal.kt"); + doTest(fileName); + } + + @TestMetadata("overlappingFuns.kt") + public void testOverlappingFuns() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/overlappingFuns.kt"); + doTest(fileName); + } + + @TestMetadata("overlappingVals.kt") + public void testOverlappingVals() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/overlappingVals.kt"); + doTest(fileName); + } + + @TestMetadata("valAccessFromInlineFunCalledFromJava.kt") + public void testValAccessFromInlineFunCalledFromJava() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlineFunCalledFromJava.kt"); + doTest(fileName); + } + + @TestMetadata("valAccessFromInlinedToDifferentPackage.kt") + public void testValAccessFromInlinedToDifferentPackage() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/valAccessFromInlinedToDifferentPackage.kt"); + doTest(fileName); + } + + @TestMetadata("valWithAccessor.kt") + public void testValWithAccessor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/multifileClasses/optimized/valWithAccessor.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/nonLocalReturns") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index df3cda8c680..3000b7cd2d1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -916,6 +916,12 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("inlineFromOptimizedMultifileClass.kt") + public void testInlineFromOptimizedMultifileClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt"); + doTest(fileName); + } + @TestMetadata("inlineFromOtherPackage.kt") public void testInlineFromOtherPackage() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/multifileClasses/inlineFromOtherPackage.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 64274044652..73e38b9ed6c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -1007,6 +1007,21 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } } + @TestMetadata("compiler/testData/codegen/bytecodeText/multifileClasses") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MultifileClasses extends AbstractBytecodeTextTest { + public void testAllFilesPresentInMultifileClasses() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("optimizedMultifileClassFacadeMethods.kt") + public void testOptimizedMultifileClassFacadeMethods() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/multifileClasses/optimizedMultifileClassFacadeMethods.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/bytecodeText/signature") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index a0ae2d93302..4d36e097c56 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -916,6 +916,12 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/multifileClasses"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("inlineFromOptimizedMultifileClass.kt") + public void testInlineFromOptimizedMultifileClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt"); + doTest(fileName); + } + @TestMetadata("inlineFromOtherPackage.kt") public void testInlineFromOtherPackage() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/multifileClasses/inlineFromOtherPackage.kt");