diff --git a/compiler/testData/diagnostics/nativeTests/threadLocal.kt b/compiler/testData/diagnostics/nativeTests/threadLocal.kt new file mode 100644 index 00000000000..68c8686af20 --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/threadLocal.kt @@ -0,0 +1,71 @@ +// FILE: annotation.kt +package kotlin.native.concurrent + +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +annotation class ThreadLocal + +// FILE: test.kt +import kotlin.native.concurrent.ThreadLocal +import kotlin.reflect.KProperty + +data class Point(val x: Double, val y: Double) + +class Person(val name: String) { + @ThreadLocal + var surname: String? = null +} + +abstract class Information { + abstract var field: String +} + +@ThreadLocal +class Person1(val name: String) { + var surname: String? = null + @ThreadLocal + val extraInfo: Information = object : Information() { + override var field: String = "extra info" + } +} + +@ThreadLocal +val extraInfo: Information = object : Information() { + override var field: String = "extra info" +} + +@ThreadLocal +val point1 = Point(1.0, 1.0) + +@ThreadLocal +val cornerPoint: Point + get() = point1 + +@ThreadLocal +val person = Person1("aaaaa") + +class Delegate { + val value: Int = 10 + operator fun getValue(thisRef: Any?, property: KProperty<*>): Int { + return value + } + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { + } +} + +@ThreadLocal +var field1: Int by Delegate() + +@ThreadLocal +object WithDelegate { + var field1: Int by Delegate() +} + +class Bar { + @ThreadLocal + object SomeObject { + var field1: Int = 10 + var field2: String? = null + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/nativeTests/threadLocal.txt b/compiler/testData/diagnostics/nativeTests/threadLocal.txt new file mode 100644 index 00000000000..c8bc3b98fd2 --- /dev/null +++ b/compiler/testData/diagnostics/nativeTests/threadLocal.txt @@ -0,0 +1,96 @@ +package + +@kotlin.native.concurrent.ThreadLocal public val cornerPoint: Point +@kotlin.native.concurrent.ThreadLocal public val extraInfo: Information +@kotlin.native.concurrent.ThreadLocal public var field1: kotlin.Int +@kotlin.native.concurrent.ThreadLocal public val person: Person1 +@kotlin.native.concurrent.ThreadLocal public val point1: Point + +public final class Bar { + public constructor Bar() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + @kotlin.native.concurrent.ThreadLocal public object SomeObject { + private constructor SomeObject() + public final var field1: kotlin.Int + public final var field2: kotlin.String? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} + +public final class Delegate { + public constructor Delegate() + public final val value: kotlin.Int = 10 + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun getValue(/*0*/ thisRef: kotlin.Any?, /*1*/ property: kotlin.reflect.KProperty<*>): kotlin.Int + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final operator fun setValue(/*0*/ thisRef: kotlin.Any?, /*1*/ property: kotlin.reflect.KProperty<*>, /*2*/ value: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public abstract class Information { + public constructor Information() + public abstract var field: kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final class Person { + public constructor Person(/*0*/ name: kotlin.String) + public final val name: kotlin.String + @kotlin.native.concurrent.ThreadLocal public final var surname: kotlin.String? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.native.concurrent.ThreadLocal public final class Person1 { + public constructor Person1(/*0*/ name: kotlin.String) + @kotlin.native.concurrent.ThreadLocal public final val extraInfo: Information + public final val name: kotlin.String + public final var surname: kotlin.String? + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public final data class Point { + public constructor Point(/*0*/ x: kotlin.Double, /*1*/ y: kotlin.Double) + public final val x: kotlin.Double + public final val y: kotlin.Double + public final operator /*synthesized*/ fun component1(): kotlin.Double + public final operator /*synthesized*/ fun component2(): kotlin.Double + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.Double = ..., /*1*/ y: kotlin.Double = ...): Point + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} + +@kotlin.native.concurrent.ThreadLocal public object WithDelegate { + private constructor WithDelegate() + public final var field1: kotlin.Int + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +package kotlin { + + package kotlin.native { + + package kotlin.native.concurrent { + + @kotlin.annotation.Target(allowedTargets = {AnnotationTarget.PROPERTY, AnnotationTarget.CLASS}) @kotlin.annotation.Retention(value = AnnotationRetention.BINARY) public final annotation class ThreadLocal : kotlin.Annotation { + public constructor ThreadLocal() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } + } + } +} diff --git a/compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt b/compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt index 6b15a4a9dac..42b1b988595 100644 --- a/compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt +++ b/compiler/testData/diagnostics/nativeTests/topLevelSingleton.kt @@ -65,7 +65,7 @@ class Bar2 { } } -@ThreadLocal +@ThreadLocal enum class Color(var rgb: Int) { RED(0xFF0000), GREEN(0x00FF00), diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java index 301f66c51d5..cefa4cabdb9 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsNativeTestGenerated.java @@ -33,6 +33,11 @@ public class DiagnosticsNativeTestGenerated extends AbstractDiagnosticsNativeTes runTest("compiler/testData/diagnostics/nativeTests/sharedImmutable.kt"); } + @TestMetadata("threadLocal.kt") + public void testThreadLocal() throws Exception { + runTest("compiler/testData/diagnostics/nativeTests/threadLocal.kt"); + } + @TestMetadata("throws.kt") public void testThrows() throws Exception { runTest("compiler/testData/diagnostics/nativeTests/throws.kt"); diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt index 75136490651..a9bec9d3117 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/DefaultErrorMessagesNative.kt @@ -29,8 +29,12 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy { ErrorsNative.VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL, "Variable in singleton without @ThreadLocal can't be changed after initialization" ) - put(ErrorsNative.ENUM_THREAD_LOCAL_INAPPLICABLE, "@ThreadLocal isn't applicable to enum classes") put(ErrorsNative.VARIABLE_IN_ENUM, "Variable in enum class can't be changed after initialization") + put( + ErrorsNative.INAPPLICABLE_THREAD_LOCAL, + "@ThreadLocal is applicable only to property with backing field, to property with delegation or to objects" + ) + put(ErrorsNative.INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL, "@ThreadLocal is applicable only to top level declarations") } } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt index bd258685231..46fde45fd7a 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/ErrorsNative.kt @@ -27,7 +27,9 @@ object ErrorsNative { @JvmField val VARIABLE_IN_SINGLETON_WITHOUT_THREAD_LOCAL = DiagnosticFactory0.create(Severity.WARNING) @JvmField - val ENUM_THREAD_LOCAL_INAPPLICABLE = DiagnosticFactory0.create(Severity.ERROR) + val INAPPLICABLE_THREAD_LOCAL = DiagnosticFactory0.create(Severity.ERROR) + @JvmField + val INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL = DiagnosticFactory0.create(Severity.ERROR) @JvmField val VARIABLE_IN_ENUM = DiagnosticFactory0.create(Severity.WARNING) init { diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeSharedImmutableChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeSharedImmutableChecker.kt index 0a81bae648a..4b700acdf65 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeSharedImmutableChecker.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeSharedImmutableChecker.kt @@ -9,12 +9,9 @@ package org.jetbrains.kotlin.resolve.konan.diagnostics import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor -import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtProperty -import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext @@ -24,26 +21,14 @@ object NativeSharedImmutableChecker : DeclarationChecker { private val sharedImmutableFqName = FqName("kotlin.native.concurrent.SharedImmutable") override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { - check(declaration, descriptor, context, ErrorsNative.INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY) { + check(sharedImmutableFqName, declaration, descriptor, context, ErrorsNative.INAPPLICABLE_SHARED_IMMUTABLE_PROPERTY) { val isVariable = descriptor is VariableDescriptor && descriptor.isVar val hasBackingField = descriptor is PropertyDescriptor && descriptor.hasBackingField(context.trace.bindingContext) val hasDelegate = declaration is KtProperty && declaration.delegate != null !isVariable && hasBackingField || hasDelegate } - check(declaration, descriptor, context, ErrorsNative.INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL) { + check(sharedImmutableFqName, declaration, descriptor, context, ErrorsNative.INAPPLICABLE_SHARED_IMMUTABLE_TOP_LEVEL) { DescriptorUtils.isTopLevelDeclaration(descriptor) } } - - fun check( - declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, - error: DiagnosticFactory0, successCondition: (DeclarationDescriptor) -> Boolean - ) { - if (successCondition(descriptor)) return - val sharedImmutableAnnotation = descriptor.annotations.findAnnotation(sharedImmutableFqName) - sharedImmutableAnnotation?.let { - val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(it) ?: declaration - context.trace.report(error.on(reportLocation)) - } - } } diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThreadLocalChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThreadLocalChecker.kt new file mode 100644 index 00000000000..b0082c5357e --- /dev/null +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeThreadLocalChecker.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.konan.diagnostics + + +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0 +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker +import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext +import org.jetbrains.kotlin.resolve.hasBackingField + +internal fun DeclarationChecker.check( + annotationFqName: FqName, declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext, + error: DiagnosticFactory0, successCondition: (DeclarationDescriptor) -> Boolean +) { + if (successCondition(descriptor)) return + descriptor.annotations.findAnnotation(annotationFqName)?.let { + val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(it) ?: declaration + context.trace.report(error.on(reportLocation)) + } +} + +object NativeThreadLocalChecker : DeclarationChecker { + private val threadLocalFqName = FqName("kotlin.native.concurrent.ThreadLocal") + + override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { + check(threadLocalFqName, declaration, descriptor, context, ErrorsNative.INAPPLICABLE_THREAD_LOCAL) { + val isVariable = descriptor is VariableDescriptor + val hasBackingField = descriptor is PropertyDescriptor && descriptor.hasBackingField(context.trace.bindingContext) + val hasDelegate = declaration is KtProperty && declaration.delegate != null + (isVariable && (hasBackingField || hasDelegate)) || + (descriptor is ClassDescriptor && descriptor.kind == ClassKind.OBJECT) + } + check(threadLocalFqName, declaration, descriptor, context, ErrorsNative.INAPPLICABLE_THREAD_LOCAL_TOP_LEVEL) { + DescriptorUtils.isTopLevelDeclaration(descriptor) || descriptor is ClassDescriptor && descriptor.kind == ClassKind.OBJECT + } + } +} diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt index f2fb983645a..998b5cfdfdb 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/diagnostics/NativeTopLevelSingletonChecker.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.resolve.konan.diagnostics - import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration @@ -20,14 +19,6 @@ object NativeTopLevelSingletonChecker : DeclarationChecker { private val threadLocalFqName = FqName("kotlin.native.concurrent.ThreadLocal") override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { - // @ThreadLocal on enum has no effect. - if (descriptor is ClassDescriptor && DescriptorUtils.isEnumClass(descriptor)) { - descriptor.annotations.findAnnotation(threadLocalFqName)?.let { - val reportLocation = DescriptorToSourceUtils.getSourceFromAnnotation(it) ?: declaration - context.trace.report(ErrorsNative.ENUM_THREAD_LOCAL_INAPPLICABLE.on(reportLocation)) - } - } - // Check variables inside singletons. if (descriptor !is PropertyDescriptor) return (descriptor.containingDeclaration as? ClassDescriptor)?.let { parent -> diff --git a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt index 7e2078624cc..0fd6172e627 100644 --- a/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt +++ b/native/frontend/src/org/jetbrains/kotlin/resolve/konan/platform/NativePlatformConfigurator.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker import org.jetbrains.kotlin.resolve.inline.ReasonableInlineRule import org.jetbrains.kotlin.resolve.jvm.checkers.SuperCallWithDefaultArgumentsChecker import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeSharedImmutableChecker +import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeThreadLocalChecker import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeThrowsChecker import org.jetbrains.kotlin.resolve.konan.diagnostics.NativeTopLevelSingletonChecker @@ -22,7 +23,7 @@ object NativePlatformConfigurator : PlatformConfiguratorBase( additionalCallCheckers = listOf(SuperCallWithDefaultArgumentsChecker()), additionalDeclarationCheckers = listOf( NativeThrowsChecker, NativeSharedImmutableChecker, - NativeTopLevelSingletonChecker + NativeTopLevelSingletonChecker, NativeThreadLocalChecker ) ) { override fun configureModuleComponents(container: StorageComponentContainer) {