Check language feature support for local and top-level lateinit vars

This commit is contained in:
Dmitry Petrov
2017-06-02 12:07:45 +03:00
parent 01cce59c35
commit bbf9bf91fc
19 changed files with 83 additions and 32 deletions
@@ -91,14 +91,17 @@ object ModifierCheckerCore {
) )
private val featureDependencies = mapOf( private val featureDependencies = mapOf(
SUSPEND_KEYWORD to LanguageFeature.Coroutines, SUSPEND_KEYWORD to listOf(LanguageFeature.Coroutines),
INLINE_KEYWORD to LanguageFeature.InlineProperties, INLINE_KEYWORD to listOf(LanguageFeature.InlineProperties),
HEADER_KEYWORD to LanguageFeature.MultiPlatformProjects, HEADER_KEYWORD to listOf(LanguageFeature.MultiPlatformProjects),
IMPL_KEYWORD to LanguageFeature.MultiPlatformProjects IMPL_KEYWORD to listOf(LanguageFeature.MultiPlatformProjects),
LATEINIT_KEYWORD to listOf(LanguageFeature.LateinitTopLevelProperties, LanguageFeature.LateinitLocalVariables)
) )
private val featureDependenciesTargets = mapOf( private val featureDependenciesTargets = mapOf(
LanguageFeature.InlineProperties to setOf(PROPERTY, PROPERTY_GETTER, PROPERTY_SETTER) LanguageFeature.InlineProperties to setOf(PROPERTY, PROPERTY_GETTER, PROPERTY_SETTER),
LanguageFeature.LateinitLocalVariables to setOf(LOCAL_VARIABLE),
LanguageFeature.LateinitTopLevelProperties to setOf(TOP_LEVEL_PROPERTY)
) )
// NOTE: deprecated targets must be possible! // NOTE: deprecated targets must be possible!
@@ -272,28 +275,29 @@ object ModifierCheckerCore {
): Boolean { ): Boolean {
val modifier = node.elementType as KtModifierKeywordToken val modifier = node.elementType as KtModifierKeywordToken
val dependency = featureDependencies[modifier] ?: return true val dependencies = featureDependencies[modifier] ?: return true
for (dependency in dependencies) {
val featureSupport = languageVersionSettings.getFeatureSupport(dependency)
val featureSupport = languageVersionSettings.getFeatureSupport(dependency) val diagnosticData = dependency to languageVersionSettings
if (featureSupport == LanguageFeature.State.ENABLED_WITH_ERROR || featureSupport == LanguageFeature.State.DISABLED) {
val restrictedTargets = featureDependenciesTargets[dependency]
if (restrictedTargets != null && actualTargets.intersect(restrictedTargets).isEmpty()) {
continue
}
val diagnosticData = dependency to languageVersionSettings if (featureSupport == LanguageFeature.State.DISABLED) {
if (featureSupport == LanguageFeature.State.ENABLED_WITH_ERROR || featureSupport == LanguageFeature.State.DISABLED) { trace.report(Errors.UNSUPPORTED_FEATURE.on(node.psi, diagnosticData))
val restrictedTargets = featureDependenciesTargets[dependency] }
if (restrictedTargets != null && actualTargets.intersect(restrictedTargets).isEmpty()) { else {
return true trace.report(Errors.EXPERIMENTAL_FEATURE_ERROR.on(node.psi, diagnosticData))
}
return false
} }
if (featureSupport == LanguageFeature.State.DISABLED) { if (featureSupport == LanguageFeature.State.ENABLED_WITH_WARNING) {
trace.report(Errors.UNSUPPORTED_FEATURE.on(node.psi, diagnosticData)) trace.report(Errors.EXPERIMENTAL_FEATURE_WARNING.on(node.psi, diagnosticData))
} }
else {
trace.report(Errors.EXPERIMENTAL_FEATURE_ERROR.on(node.psi, diagnosticData))
}
return false
}
if (featureSupport == LanguageFeature.State.ENABLED_WITH_WARNING) {
trace.report(Errors.EXPERIMENTAL_FEATURE_WARNING.on(node.psi, diagnosticData))
} }
return true return true
@@ -1,3 +1,5 @@
// LANGUAGE_VERSION: 1.2
fun runNoInline(f: () -> Unit) = f() fun runNoInline(f: () -> Unit) = f()
fun box(): String { fun box(): String {
@@ -1,3 +1,5 @@
// LANGUAGE_VERSION: 1.2
fun box(): String { fun box(): String {
lateinit var ok: String lateinit var ok: String
run { run {
@@ -1,3 +1,4 @@
// LANGUAGE_VERSION: 1.2
// WITH_REFLECT // WITH_REFLECT
// IGNORE_BACKEND: JS, NATIVE // IGNORE_BACKEND: JS, NATIVE
@@ -1,3 +1,4 @@
// LANGUAGE_VERSION: 1.2
// WITH_REFLECT // WITH_REFLECT
// IGNORE_BACKEND: JS, NATIVE // IGNORE_BACKEND: JS, NATIVE
@@ -1,3 +1,4 @@
// LANGUAGE_VERSION: 1.2
// WITH_REFLECT // WITH_REFLECT
// IGNORE_BACKEND: JS, NATIVE // IGNORE_BACKEND: JS, NATIVE
@@ -1,3 +1,4 @@
// LANGUAGE_VERSION: 1.2
// WITH_REFLECT // WITH_REFLECT
// IGNORE_BACKEND: JS, NATIVE // IGNORE_BACKEND: JS, NATIVE
@@ -1,3 +1,4 @@
// LANGUAGE_VERSION: 1.2
// WITH_REFLECT // WITH_REFLECT
// IGNORE_BACKEND: JS, NATIVE // IGNORE_BACKEND: JS, NATIVE
// FILE: lateinit.kt // FILE: lateinit.kt
@@ -1,3 +1,5 @@
// LANGUAGE_VERSION: 1.2
// FILE: lateinit.kt // FILE: lateinit.kt
private lateinit var s: String private lateinit var s: String
@@ -1,3 +1,5 @@
// LANGUAGE_VERSION: 1.2
lateinit var ok: String lateinit var ok: String
fun box(): String { fun box(): String {
@@ -1,3 +1,4 @@
// LANGUAGE_VERSION: 1.2
// WITH_REFLECT // WITH_REFLECT
// IGNORE_BACKEND: JS, NATIVE // IGNORE_BACKEND: JS, NATIVE
@@ -1,3 +1,4 @@
// LANGUAGE_VERSION: 1.2
// WITH_REFLECT // WITH_REFLECT
// IGNORE_BACKEND: JS, NATIVE // IGNORE_BACKEND: JS, NATIVE
@@ -19,7 +19,7 @@ public abstract class A<T: Any, V: String?>(<!INAPPLICABLE_LATEINIT_MODIFIER!>la
private set private set
fun a() { fun a() {
lateinit var <!UNUSED_VARIABLE!>a<!>: String <!UNSUPPORTED_FEATURE!>lateinit<!> var <!UNUSED_VARIABLE!>a<!>: String
} }
<!INAPPLICABLE_LATEINIT_MODIFIER!>lateinit<!> var e1: V <!INAPPLICABLE_LATEINIT_MODIFIER!>lateinit<!> var e1: V
@@ -46,8 +46,8 @@ public abstract class A<T: Any, V: String?>(<!INAPPLICABLE_LATEINIT_MODIFIER!>la
<!INAPPLICABLE_LATEINIT_MODIFIER!>lateinit<!> var String.e12: String <!INAPPLICABLE_LATEINIT_MODIFIER!>lateinit<!> var String.e12: String
} }
<!INAPPLICABLE_LATEINIT_MODIFIER!>lateinit<!> val topLevel: String <!INAPPLICABLE_LATEINIT_MODIFIER, UNSUPPORTED_FEATURE!>lateinit<!> val topLevel: String
lateinit var topLevelMutable: String <!UNSUPPORTED_FEATURE!>lateinit<!> var topLevelMutable: String
public interface Intf { public interface Intf {
<!INAPPLICABLE_LATEINIT_MODIFIER!>lateinit<!> var str: String <!INAPPLICABLE_LATEINIT_MODIFIER!>lateinit<!> var str: String
@@ -1,3 +1,5 @@
// !LANGUAGE: +LateinitTopLevelProperties
object Delegate { object Delegate {
operator fun getValue(instance: Any?, property: Any) : String = "" operator fun getValue(instance: Any?, property: Any) : String = ""
operator fun setValue(instance: Any?, property: Any, value: String) {} operator fun setValue(instance: Any?, property: Any, value: String) {}
@@ -1,4 +1,5 @@
// !DIAGNOSTICS: -UNUSED_VALUE -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE // !DIAGNOSTICS: -UNUSED_VALUE -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
// !LANGUAGE: +LateinitLocalVariables
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@@ -1,3 +1,5 @@
// !LANGUAGE: +LateinitLocalVariables
fun test() { fun test() {
lateinit var s: String lateinit var s: String
s = "" s = ""
@@ -1,3 +1,5 @@
// !LANGUAGE: +LateinitLocalVariables
fun test1() { fun test1() {
lateinit var s: String lateinit var s: String
s.length s.length
@@ -57,6 +57,8 @@ enum class LanguageFeature(
SafeCastCheckBoundSmartCasts(KOTLIN_1_2), SafeCastCheckBoundSmartCasts(KOTLIN_1_2),
BooleanElvisBoundSmartCasts(KOTLIN_1_2), BooleanElvisBoundSmartCasts(KOTLIN_1_2),
CapturedInClosureSmartCasts(KOTLIN_1_2), CapturedInClosureSmartCasts(KOTLIN_1_2),
LateinitTopLevelProperties(KOTLIN_1_2),
LateinitLocalVariables(KOTLIN_1_2),
// Experimental features // Experimental features
@@ -29,6 +29,7 @@ import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.* import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget.*
@@ -328,6 +329,9 @@ object KeywordCompletion {
fun isKeywordCorrectlyApplied(keywordTokenType: KtKeywordToken, file: KtFile): Boolean { fun isKeywordCorrectlyApplied(keywordTokenType: KtKeywordToken, file: KtFile): Boolean {
val elementAt = file.findElementAt(prefixText.length)!! val elementAt = file.findElementAt(prefixText.length)!!
val languageVersionSettings = ModuleUtilCore.findModuleForPsiElement(position)?.languageVersionSettings
?: LanguageVersionSettingsImpl.DEFAULT
when { when {
!elementAt.node!!.elementType.matchesKeyword(keywordTokenType) -> return false !elementAt.node!!.elementType.matchesKeyword(keywordTokenType) -> return false
@@ -335,7 +339,7 @@ object KeywordCompletion {
isErrorElementBefore(elementAt) -> return false isErrorElementBefore(elementAt) -> return false
!isSupportedAtLanguageLevel(keywordTokenType, position) -> return false !isModifierSupportedAtLanguageLevel(keywordTokenType, languageVersionSettings) -> return false
keywordTokenType !is KtModifierKeywordToken -> return true keywordTokenType !is KtModifierKeywordToken -> return true
@@ -358,10 +362,13 @@ object KeywordCompletion {
is KtFile -> listOf(CLASS_ONLY, INTERFACE, OBJECT, ENUM_CLASS, ANNOTATION_CLASS, TOP_LEVEL_FUNCTION, TOP_LEVEL_PROPERTY, FUNCTION, PROPERTY) is KtFile -> listOf(CLASS_ONLY, INTERFACE, OBJECT, ENUM_CLASS, ANNOTATION_CLASS, TOP_LEVEL_FUNCTION, TOP_LEVEL_PROPERTY, FUNCTION, PROPERTY)
else -> null else -> listOf()
} }
val modifierTargets = ModifierCheckerCore.possibleTargetMap[keywordTokenType] val modifierTargets = ModifierCheckerCore.possibleTargetMap[keywordTokenType]?.intersect(possibleTargets)
if (modifierTargets != null && possibleTargets != null && possibleTargets.none { it in modifierTargets }) return false if (modifierTargets != null && possibleTargets.isNotEmpty() &&
!modifierTargets.any {
isModifierTargetSupportedAtLanguageLevel(keywordTokenType, it, languageVersionSettings)
}) return false
val ownerDeclaration = container?.getParentOfType<KtDeclaration>(strict = true) val ownerDeclaration = container?.getParentOfType<KtDeclaration>(strict = true)
val parentTarget = when (ownerDeclaration) { val parentTarget = when (ownerDeclaration) {
@@ -416,9 +423,7 @@ object KeywordCompletion {
} }
} }
private fun isSupportedAtLanguageLevel(keyword: KtKeywordToken, position: PsiElement): Boolean { private fun isModifierSupportedAtLanguageLevel(keyword: KtKeywordToken, languageVersionSettings: LanguageVersionSettings): Boolean {
val languageVersionSettings = ModuleUtilCore.findModuleForPsiElement(position)?.languageVersionSettings
?: LanguageVersionSettingsImpl.DEFAULT
val feature = when (keyword) { val feature = when (keyword) {
KtTokens.TYPE_ALIAS_KEYWORD -> LanguageFeature.TypeAliases KtTokens.TYPE_ALIAS_KEYWORD -> LanguageFeature.TypeAliases
KtTokens.HEADER_KEYWORD, KtTokens.IMPL_KEYWORD -> LanguageFeature.MultiPlatformProjects KtTokens.HEADER_KEYWORD, KtTokens.IMPL_KEYWORD -> LanguageFeature.MultiPlatformProjects
@@ -428,6 +433,24 @@ object KeywordCompletion {
return languageVersionSettings.supportsFeature(feature) return languageVersionSettings.supportsFeature(feature)
} }
private fun isModifierTargetSupportedAtLanguageLevel(
keyword: KtKeywordToken,
target: KotlinTarget,
languageVersionSettings: LanguageVersionSettings
): Boolean {
if (keyword == KtTokens.LATEINIT_KEYWORD) {
val feature = when (target) {
TOP_LEVEL_PROPERTY -> LanguageFeature.LateinitTopLevelProperties
LOCAL_VARIABLE -> LanguageFeature.LateinitLocalVariables
else -> return true
}
return languageVersionSettings.supportsFeature(feature)
}
else {
return true
}
}
// builds text within scope (or from the start of the file) before position element excluding almost all declarations // builds text within scope (or from the start of the file) before position element excluding almost all declarations
private fun buildReducedContextBefore(builder: StringBuilder, position: PsiElement, scope: PsiElement?) { private fun buildReducedContextBefore(builder: StringBuilder, position: PsiElement, scope: PsiElement?) {
if (position == scope) return if (position == scope) return