Report diagnostics about experimentality on unsigned literals

#KT-25580 Fixed
This commit is contained in:
Mikhail Zarechenskiy
2018-08-06 14:03:27 +03:00
parent 55e7def3c1
commit 659289b41a
13 changed files with 134 additions and 14 deletions
@@ -255,6 +255,9 @@ public interface Errors {
DiagnosticFactory1<KtAnnotationEntry, String> ANNOTATION_TARGETS_NON_EXISTENT_ACCESSOR = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, FqName> EXPERIMENTAL_UNSIGNED_LITERALS = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, FqName> EXPERIMENTAL_UNSIGNED_LITERALS_ERROR = DiagnosticFactory1.create(ERROR);
// Const
DiagnosticFactory0<PsiElement> CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> CONST_VAL_WITH_GETTER = DiagnosticFactory0.create(ERROR);
@@ -156,6 +156,9 @@ public class DefaultErrorMessages {
MAP.put(ANNOTATION_TARGETS_NON_EXISTENT_ACCESSOR, "An accessor will not be generated for ''{0}'' so the annotation effectively has a ''SOURCE'' retention", STRING);
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS, "Unsigned literals are experimental and their usages should be marked with ''@{0}'' or ''@UseExperimental({0}::class)''", TO_STRING);
MAP.put(EXPERIMENTAL_UNSIGNED_LITERALS_ERROR, "Unsigned literals are experimental and their usages must be marked with ''@{0}'' or ''@UseExperimental({0}::class)''", TO_STRING);
MAP.put(REDUNDANT_MODIFIER, "Modifier ''{0}'' is redundant because ''{1}'' is present", TO_STRING, TO_STRING);
MAP.put(REDUNDANT_OPEN_IN_INTERFACE, "Modifier 'open' is redundant for abstract interface members");
MAP.put(REDUNDANT_MODIFIER_IN_GETTER, "Visibility modifiers are redundant in getter");
@@ -21,7 +21,9 @@ import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.AnalysisFlag
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -45,7 +47,7 @@ import org.jetbrains.kotlin.utils.addIfNotNull
class ExperimentalUsageChecker(project: Project) : CallChecker {
private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project)
internal data class Experimentality(val annotationFqName: FqName, val severity: Severity) {
data class Experimentality(val annotationFqName: FqName, val severity: Severity) {
enum class Severity { WARNING, ERROR }
companion object {
@@ -53,6 +55,11 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
}
}
data class ExperimentalityDiagnostics(
val warning: DiagnosticFactory1<PsiElement, FqName>,
val error: DiagnosticFactory1<PsiElement, FqName>
)
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val experimentalities =
resolvedCall.resultingDescriptor.loadExperimentalities(moduleAnnotationsResolver, context.languageVersionSettings)
@@ -73,21 +80,37 @@ class ExperimentalUsageChecker(project: Project) : CallChecker {
private val EXPERIMENTAL_SHORT_NAME = EXPERIMENTAL_FQ_NAME.shortName()
private val USE_EXPERIMENTAL_SHORT_NAME = USE_EXPERIMENTAL_FQ_NAME.shortName()
private fun reportNotAcceptedExperimentalities(
private val EXPERIMENTAL_API_DIAGNOSTICS = ExperimentalityDiagnostics(
Errors.EXPERIMENTAL_API_USAGE, Errors.EXPERIMENTAL_API_USAGE_ERROR
)
fun reportNotAcceptedExperimentalities(
experimentalities: Collection<Experimentality>, element: PsiElement, context: CheckerContext
) {
reportNotAcceptedExperimentalities(
experimentalities, element, context.languageVersionSettings, context.trace, EXPERIMENTAL_API_DIAGNOSTICS
)
}
fun reportNotAcceptedExperimentalities(
experimentalities: Collection<Experimentality>,
element: PsiElement,
languageVersionSettings: LanguageVersionSettings,
trace: BindingTrace,
diagnostics: ExperimentalityDiagnostics
) {
for ((annotationFqName, severity) in experimentalities) {
if (!element.isExperimentalityAccepted(annotationFqName, context)) {
if (!element.isExperimentalityAccepted(annotationFqName, languageVersionSettings, trace.bindingContext)) {
val diagnostic = when (severity) {
Experimentality.Severity.WARNING -> Errors.EXPERIMENTAL_API_USAGE
Experimentality.Severity.ERROR -> Errors.EXPERIMENTAL_API_USAGE_ERROR
Experimentality.Severity.WARNING -> diagnostics.warning
Experimentality.Severity.ERROR -> diagnostics.error
}
context.trace.report(diagnostic.on(element, annotationFqName))
trace.reportDiagnosticOnce(diagnostic.on(element, annotationFqName))
}
}
}
private fun DeclarationDescriptor.loadExperimentalities(
fun DeclarationDescriptor.loadExperimentalities(
moduleAnnotationsResolver: ModuleAnnotationsResolver,
languageVersionSettings: LanguageVersionSettings
): Set<Experimentality> {
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.resolve.constants.evaluate
import com.intellij.openapi.project.Project
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.TypeConversionUtil
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.checkers.ExperimentalUsageChecker
import org.jetbrains.kotlin.resolve.constants.*
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.types.KotlinType
@@ -45,8 +47,11 @@ import java.util.*
class ConstantExpressionEvaluator(
internal val module: ModuleDescriptor,
internal val languageVersionSettings: LanguageVersionSettings
internal val languageVersionSettings: LanguageVersionSettings,
project: Project
) {
private val moduleAnnotationsResolver = ModuleAnnotationsResolver.getInstance(project)
fun updateNumberType(
numberType: KotlinType,
expression: KtExpression?,
@@ -268,6 +273,8 @@ class ConstantExpressionEvaluator(
val visitor = ConstantExpressionEvaluatorVisitor(this, trace)
val constant = visitor.evaluate(expression, expectedType) ?: return null
checkExperimentalityOfConstantLiteral(expression, constant, expectedType, trace)
return if (!constant.isError) constant else null
}
@@ -279,8 +286,39 @@ class ConstantExpressionEvaluator(
return evaluateExpression(expression, trace, expectedType)?.toConstantValue(expectedType)
}
private fun checkExperimentalityOfConstantLiteral(
expression: KtExpression,
constant: CompileTimeConstant<*>,
expectedType: KotlinType?,
trace: BindingTrace
) {
if (constant.isError) return
if (!constant.parameters.isUnsignedNumberLiteral && !constant.parameters.isUnsignedLongNumberLiteral) return
val constantType = when {
constant is TypedCompileTimeConstant<*> -> constant.type
expectedType != null -> constant.toConstantValue(expectedType).getType(module)
else -> return
}
if (!UnsignedTypes.isUnsignedType(constantType)) return
with(ExperimentalUsageChecker) {
val descriptor = constantType.constructor.declarationDescriptor ?: return
val experimentalities = descriptor.loadExperimentalities(moduleAnnotationsResolver, languageVersionSettings)
reportNotAcceptedExperimentalities(
experimentalities, expression, languageVersionSettings, trace, EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS
)
}
}
companion object {
private val EXPERIMENTAL_UNSIGNED_LITERALS_DIAGNOSTICS = ExperimentalUsageChecker.ExperimentalityDiagnostics(
Errors.EXPERIMENTAL_UNSIGNED_LITERALS, Errors.EXPERIMENTAL_UNSIGNED_LITERALS_ERROR
)
@JvmStatic
fun getConstant(expression: KtExpression, bindingContext: BindingContext): CompileTimeConstant<*>? {
val constant = getPossiblyErrorConstant(expression, bindingContext) ?: return null