Report diagnostics about experimentality on unsigned literals
#KT-25580 Fixed
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
+3
@@ -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");
|
||||
|
||||
+30
-7
@@ -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> {
|
||||
|
||||
+39
-1
@@ -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
|
||||
|
||||
+34
@@ -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<!>
|
||||
Vendored
+6
@@ -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
|
||||
+1
-1
@@ -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
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
Generated
+5
@@ -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");
|
||||
|
||||
+1
-1
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user