Add checks for const modifier applicability

1. Must be initialized in-place
2. Can not be open/abstract
3. Can not be an override
4. Can not be delegated
5. Initializer must be a compile-time constant
6. No getters
7. `const` is not applicable to vars or locals
8. `const val` should be whether top-level property or object member
This commit is contained in:
Denis Zharkov
2015-09-21 16:17:42 +03:00
parent bde58d6eb8
commit b2b76d16d0
15 changed files with 432 additions and 9 deletions
@@ -133,6 +133,14 @@ public interface Errors {
DiagnosticFactory0<JetExpression> ANNOTATION_PARAMETER_MUST_BE_ENUM_CONST = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<JetExpression> ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT = DiagnosticFactory0.create(ERROR);
// Const
DiagnosticFactory0<PsiElement> CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> CONST_VAL_WITH_GETTER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> CONST_VAL_WITH_DELEGATE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, JetType> TYPE_CANT_BE_USED_FOR_CONST_VAL = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> CONST_VAL_WITHOUT_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<JetExpression> CONST_VAL_WITH_NON_CONST_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, String> INAPPLICABLE_TARGET_ON_PROPERTY = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<PsiElement> INAPPLICABLE_FIELD_TARGET_NO_BACKING_FIELD = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INAPPLICABLE_TARGET_PROPERTY_IMMUTABLE = DiagnosticFactory0.create(ERROR);
@@ -596,6 +596,13 @@ public class DefaultErrorMessages {
MAP.put(ANNOTATION_PARAMETER_MUST_BE_KCLASS_LITERAL, "An annotation parameter must be a class literal (T::class)");
MAP.put(ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT, "Default value of annotation parameter must be a compile-time constant");
MAP.put(CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT, "Const 'val' are only allowed on top level or in objects");
MAP.put(CONST_VAL_WITH_DELEGATE, "Const 'val' should not have a delegate");
MAP.put(CONST_VAL_WITH_GETTER, "Const 'val' should not have a getter");
MAP.put(TYPE_CANT_BE_USED_FOR_CONST_VAL, "Const 'val' has type ''{0}''. Only primitives and String are allowed", RENDER_TYPE);
MAP.put(CONST_VAL_WITHOUT_INITIALIZER, "Const 'val' should have an initializer");
MAP.put(CONST_VAL_WITH_NON_CONST_INITIALIZER, "Const 'val' initializer should be a constant value");
MAP.put(DEFAULT_VALUE_NOT_ALLOWED_IN_OVERRIDE, "An overriding function is not allowed to specify default values for its parameters");
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
public object ConstModifierChecker : DeclarationChecker {
override fun check(
declaration: JetDeclaration,
descriptor: DeclarationDescriptor,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext
) {
if (descriptor !is VariableDescriptor || !declaration.hasModifier(JetTokens.CONST_KEYWORD)) return
val containingDeclaration = descriptor.containingDeclaration
val constModifierPsiElement = declaration.modifierList!!.getModifier(JetTokens.CONST_KEYWORD)!!
if (descriptor.isVar) {
diagnosticHolder.report(Errors.WRONG_MODIFIER_TARGET.on(constModifierPsiElement, JetTokens.CONST_KEYWORD, "vars"))
return
}
if (containingDeclaration is ClassDescriptor && containingDeclaration.kind != ClassKind.OBJECT) {
diagnosticHolder.report(Errors.CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT.on(constModifierPsiElement))
return
}
if (declaration !is JetProperty || descriptor !is PropertyDescriptor) return
if (declaration.hasDelegate()) {
diagnosticHolder.report(Errors.CONST_VAL_WITH_DELEGATE.on(declaration.delegate!!))
return
}
if (descriptor is PropertyDescriptor && !descriptor.getter!!.isDefault) {
diagnosticHolder.report(Errors.CONST_VAL_WITH_GETTER.on(declaration.getter!!))
return
}
if (!descriptor.type.canBeUsedForConstVal()) {
diagnosticHolder.report(Errors.TYPE_CANT_BE_USED_FOR_CONST_VAL.on(constModifierPsiElement, descriptor.type))
return
}
if (declaration.initializer == null) {
diagnosticHolder.report(Errors.CONST_VAL_WITHOUT_INITIALIZER.on(constModifierPsiElement))
return
}
if (descriptor.compileTimeInitializer == null) {
diagnosticHolder.report(Errors.CONST_VAL_WITH_NON_CONST_INITIALIZER.on(declaration.initializer!!))
return
}
}
}
@@ -64,7 +64,8 @@ public object ModifierCheckerCore {
TAILREC_KEYWORD to EnumSet.of(FUNCTION),
EXTERNAL_KEYWORD to EnumSet.of(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER),
ANNOTATION_KEYWORD to EnumSet.of(ANNOTATION_CLASS),
CROSSINLINE_KEYWORD to EnumSet.of(VALUE_PARAMETER)
CROSSINLINE_KEYWORD to EnumSet.of(VALUE_PARAMETER),
CONST_KEYWORD to EnumSet.of(MEMBER_PROPERTY, TOP_LEVEL_PROPERTY)
)
// NOTE: redundant targets must be possible!
@@ -87,16 +88,21 @@ public object ModifierCheckerCore {
private fun buildCompatibilityMap(): Map<Pair<JetModifierKeywordToken, JetModifierKeywordToken>, Compatibility> {
val result = hashMapOf<Pair<JetModifierKeywordToken, JetModifierKeywordToken>, Compatibility>()
// Variance: in + out are incompatible
result += incompatibilityRegister(listOf(IN_KEYWORD, OUT_KEYWORD))
result += incompatibilityRegister(IN_KEYWORD, OUT_KEYWORD)
// Visibilities: incompatible
result += incompatibilityRegister(listOf(PRIVATE_KEYWORD, PROTECTED_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD))
result += incompatibilityRegister(PRIVATE_KEYWORD, PROTECTED_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD)
// Abstract + open + final + sealed: incompatible
result += incompatibilityRegister(listOf(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD, SEALED_KEYWORD))
result += incompatibilityRegister(ABSTRACT_KEYWORD, OPEN_KEYWORD, FINAL_KEYWORD, SEALED_KEYWORD)
// open is redundant to abstract & override
result += redundantRegister(ABSTRACT_KEYWORD, OPEN_KEYWORD)
result += redundantRegister(OVERRIDE_KEYWORD, OPEN_KEYWORD)
// abstract is redundant to sealed
result += redundantRegister(SEALED_KEYWORD, ABSTRACT_KEYWORD)
// const is incompatible with abstract, open, override
result += incompatibilityRegister(CONST_KEYWORD, ABSTRACT_KEYWORD)
result += incompatibilityRegister(CONST_KEYWORD, OPEN_KEYWORD)
result += incompatibilityRegister(CONST_KEYWORD, OVERRIDE_KEYWORD)
return result
}
@@ -109,7 +115,7 @@ public object ModifierCheckerCore {
}
private fun incompatibilityRegister(
list: List<JetModifierKeywordToken>
vararg list: JetModifierKeywordToken
): Map<Pair<JetModifierKeywordToken, JetModifierKeywordToken>, Compatibility> {
val result = hashMapOf<Pair<JetModifierKeywordToken, JetModifierKeywordToken>, Compatibility>()
for (first in list) {
@@ -38,7 +38,7 @@ public abstract class TargetPlatform(
}
}
private val DEFAULT_DECLARATION_CHECKERS = listOf(DataClassAnnotationChecker())
private val DEFAULT_DECLARATION_CHECKERS = listOf(DataClassAnnotationChecker(), ConstModifierChecker)
private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker())
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), AccessToPrivateTopLevelSymbolValidator())
@@ -529,8 +529,7 @@ private class ConstantExpressionEvaluatorVisitor(
}
if (DescriptorUtils.isObject(descriptor.getContainingDeclaration()) ||
DescriptorUtils.isStaticDeclaration(descriptor)) {
val returnType = descriptor.getType()
return KotlinBuiltIns.isPrimitiveType(returnType) || KotlinBuiltIns.isString(returnType)
return descriptor.type.canBeUsedForConstVal()
}
return false
}