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
@@ -44,7 +44,9 @@ class ScriptContentLoader(private val project: Project) {
// TODO: consider advanced matching using semantic similar to actual resolving
scriptDefinition.acceptedAnnotations.find { ann ->
psiAnn.typeName.let { it == ann.simpleName || it == ann.qualifiedName }
}?.let { constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>) }
}?.let {
constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass<out Annotation>, project)
}
}
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.script
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
@@ -35,9 +36,9 @@ internal val KtAnnotationEntry.typeName: String get() = (typeReference?.typeElem
internal fun String?.orAnonymous(kind: String = ""): String =
this ?: "<anonymous" + (if (kind.isNotBlank()) " $kind" else "") + ">"
internal fun constructAnnotation(psi: KtAnnotationEntry, targetClass: KClass<out Annotation>): Annotation {
internal fun constructAnnotation(psi: KtAnnotationEntry, targetClass: KClass<out Annotation>, project: Project): Annotation {
val module = ModuleDescriptorImpl(Name.special("<script-annotations-preprocessing>"), LockBasedStorageManager(), DefaultBuiltIns.Instance)
val evaluator = ConstantExpressionEvaluator(module, LanguageVersionSettingsImpl.DEFAULT)
val evaluator = ConstantExpressionEvaluator(module, LanguageVersionSettingsImpl.DEFAULT, project)
val trace = BindingTraceContext()
val valueArguments = psi.valueArguments.map { arg ->
@@ -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
@@ -0,0 +1,34 @@
// !USE_EXPERIMENTAL: kotlin.Experimental
// !API_VERSION: 1.3
// !DIAGNOSTICS: -UNUSED_EXPRESSION, -UNUSED_VARIABLE, -UNUSED_PARAMETER
fun test() {
<!EXPERIMENTAL_UNSIGNED_LITERALS!>42u<!>
<!EXPERIMENTAL_UNSIGNED_LITERALS!>21UL<!>
val list = listOf(
<!EXPERIMENTAL_UNSIGNED_LITERALS!>1u<!>,
<!EXPERIMENTAL_UNSIGNED_LITERALS!>0xFFu<!>,
<!EXPERIMENTAL_UNSIGNED_LITERALS!>0xbbU<!>
)
takeAll(
<!EXPERIMENTAL_UNSIGNED_LITERALS!>1u<!>,
<!EXPERIMENTAL_UNSIGNED_LITERALS!>2u<!>,
<!EXPERIMENTAL_UNSIGNED_LITERALS!>3u<!>,
<!EXPERIMENTAL_UNSIGNED_LITERALS!>4u<!>,
<!EXPERIMENTAL_UNSIGNED_LITERALS!>5u<!>
)
@UseExperimental(ExperimentalUnsignedTypes::class) 42u
}
fun takeAll(
b: <!EXPERIMENTAL_API_USAGE!>UByte<!>,
s: <!EXPERIMENTAL_API_USAGE!>UShort<!>,
i: <!EXPERIMENTAL_API_USAGE!>UInt<!>,
l: <!EXPERIMENTAL_API_USAGE!>ULong<!>,
vararg uints: <!EXPERIMENTAL_API_USAGE!>UInt<!>
) {}
const val unsignedConst = <!EXPERIMENTAL_UNSIGNED_LITERALS!>0u<!>
const val unsignedLongConst = <!EXPERIMENTAL_UNSIGNED_LITERALS!>0uL<!>
@@ -0,0 +1,6 @@
package
public const val unsignedConst: kotlin.UInt = 0.toUInt()
public const val unsignedLongConst: kotlin.ULong = 0.toULong()
public fun takeAll(/*0*/ b: kotlin.UByte, /*1*/ s: kotlin.UShort, /*2*/ i: kotlin.UInt, /*3*/ l: kotlin.ULong, /*4*/ vararg uints: kotlin.UInt /*kotlin.UIntArray*/): kotlin.Unit
public fun test(): kotlin.Unit
@@ -65,7 +65,7 @@ abstract class AbstractCompileTimeConstantEvaluatorTest : AbstractAnnotationDesc
private fun evaluateInitializer(context: BindingContext, property: VariableDescriptor): CompileTimeConstant<*>? {
val propertyDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(property) as KtProperty
return ConstantExpressionEvaluator(property.module, LanguageVersionSettingsImpl.DEFAULT).evaluateExpression(
return ConstantExpressionEvaluator(property.module, LanguageVersionSettingsImpl.DEFAULT, project).evaluateExpression(
propertyDeclaration.initializer!!,
DelegatingBindingTrace(context, "trace for evaluating compile time constant"),
property.type
@@ -2279,6 +2279,11 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalOnWholeModule.kt");
}
@TestMetadata("experimentalUnsignedLiterals.kt")
public void testExperimentalUnsignedLiterals() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.kt");
}
@TestMetadata("incorrectTargetsForExperimentalAnnotation.kt")
public void testIncorrectTargetsForExperimentalAnnotation() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectTargetsForExperimentalAnnotation.kt");
@@ -2279,6 +2279,11 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalOnWholeModule.kt");
}
@TestMetadata("experimentalUnsignedLiterals.kt")
public void testExperimentalUnsignedLiterals() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.kt");
}
@TestMetadata("incorrectTargetsForExperimentalAnnotation.kt")
public void testIncorrectTargetsForExperimentalAnnotation() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/experimental/incorrectTargetsForExperimentalAnnotation.kt");
@@ -56,7 +56,7 @@ class ExtractableSubstringInfo(
val tempContext = expr.analyzeInContext(scope, template)
val trace = DelegatingBindingTrace(tempContext, "Evaluate '$literal'")
val languageVersionSettings = facade.getFrontendService(LanguageVersionSettings::class.java)
val value = ConstantExpressionEvaluator(module, languageVersionSettings).evaluateExpression(expr, trace)
val value = ConstantExpressionEvaluator(module, languageVersionSettings, facade.project).evaluateExpression(expr, trace)
if (value == null || value.isError) return stringType
return value.toConstantValue(TypeUtils.NO_EXPECTED_TYPE).getType(module)
@@ -53,7 +53,7 @@ class KotlinLightConstantExpressionEvaluator : ConstantExpressionEvaluator {
val resolutionFacade = expressionToCompute.getResolutionFacade()
val evaluator = FrontendConstantExpressionEvaluator(
resolutionFacade.moduleDescriptor, expressionToCompute.languageVersionSettings
resolutionFacade.moduleDescriptor, expressionToCompute.languageVersionSettings, resolutionFacade.project
)
val evaluatorTrace = DelegatingBindingTrace(resolutionFacade.analyze(expressionToCompute), "Evaluating annotation argument")