From 659289b41a68047e08d864b573b735dd4d24a6d8 Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Mon, 6 Aug 2018 14:03:27 +0300 Subject: [PATCH] Report diagnostics about experimentality on unsigned literals #KT-25580 Fixed --- .../kotlin/script/ScriptContentLoader.kt | 4 +- .../script/scriptAnnotationsPreprocessing.kt | 5 ++- .../jetbrains/kotlin/diagnostics/Errors.java | 3 ++ .../rendering/DefaultErrorMessages.java | 3 ++ .../checkers/ExperimentalUsageChecker.kt | 37 +++++++++++++---- .../evaluate/ConstantExpressionEvaluator.kt | 40 ++++++++++++++++++- .../experimentalUnsignedLiterals.kt | 34 ++++++++++++++++ .../experimentalUnsignedLiterals.txt | 6 +++ ...bstractCompileTimeConstantEvaluatorTest.kt | 2 +- .../DiagnosticsTestWithStdLibGenerated.java | 5 +++ ...ticsTestWithStdLibUsingJavacGenerated.java | 5 +++ .../introduce/ExtractableSubstringInfo.kt | 2 +- .../KotlinLightConstantExpressionEvaluator.kt | 2 +- 13 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.txt diff --git a/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt b/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt index b99801fe61f..d65b3287351 100644 --- a/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt +++ b/compiler/frontend.script/src/org/jetbrains/kotlin/script/ScriptContentLoader.kt @@ -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) } + }?.let { + constructAnnotation(psiAnn, classLoader.loadClass(it.qualifiedName).kotlin as KClass, project) + } } } } diff --git a/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt b/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt index 5706e6221c7..c4386cc80b8 100644 --- a/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt +++ b/compiler/frontend.script/src/org/jetbrains/kotlin/script/scriptAnnotationsPreprocessing.kt @@ -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 ?: "" -internal fun constructAnnotation(psi: KtAnnotationEntry, targetClass: KClass): Annotation { +internal fun constructAnnotation(psi: KtAnnotationEntry, targetClass: KClass, project: Project): Annotation { val module = ModuleDescriptorImpl(Name.special(""), 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 -> diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 41364d42c08..61dabaf642e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -255,6 +255,9 @@ public interface Errors { DiagnosticFactory1 ANNOTATION_TARGETS_NON_EXISTENT_ACCESSOR = DiagnosticFactory1.create(WARNING); + DiagnosticFactory1 EXPERIMENTAL_UNSIGNED_LITERALS = DiagnosticFactory1.create(WARNING); + DiagnosticFactory1 EXPERIMENTAL_UNSIGNED_LITERALS_ERROR = DiagnosticFactory1.create(ERROR); + // Const DiagnosticFactory0 CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 CONST_VAL_WITH_GETTER = DiagnosticFactory0.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 4695b214dfd..f7dc88def7b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -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"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt index 6f367ca4023..0fcf3ab4da4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExperimentalUsageChecker.kt @@ -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, + val error: DiagnosticFactory1 + ) + 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, element: PsiElement, context: CheckerContext + ) { + reportNotAcceptedExperimentalities( + experimentalities, element, context.languageVersionSettings, context.trace, EXPERIMENTAL_API_DIAGNOSTICS + ) + } + + fun reportNotAcceptedExperimentalities( + experimentalities: Collection, + 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 { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt index 9d6e793d5a4..3424f0b1cef 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt @@ -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 diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.kt b/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.kt new file mode 100644 index 00000000000..fce397386b9 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.kt @@ -0,0 +1,34 @@ +// !USE_EXPERIMENTAL: kotlin.Experimental +// !API_VERSION: 1.3 +// !DIAGNOSTICS: -UNUSED_EXPRESSION, -UNUSED_VARIABLE, -UNUSED_PARAMETER + +fun test() { + 42u + 21UL + val list = listOf( + 1u, + 0xFFu, + 0xbbU + ) + + takeAll( + 1u, + 2u, + 3u, + 4u, + 5u + ) + + @UseExperimental(ExperimentalUnsignedTypes::class) 42u +} + +fun takeAll( + b: UByte, + s: UShort, + i: UInt, + l: ULong, + vararg uints: UInt +) {} + +const val unsignedConst = 0u +const val unsignedLongConst = 0uL diff --git a/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.txt b/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.txt new file mode 100644 index 00000000000..e0d90417b71 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/experimental/experimentalUnsignedLiterals.txt @@ -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 diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/resolve/constants/evaluate/AbstractCompileTimeConstantEvaluatorTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/resolve/constants/evaluate/AbstractCompileTimeConstantEvaluatorTest.kt index 9c8c8243943..265d513489a 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/resolve/constants/evaluate/AbstractCompileTimeConstantEvaluatorTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/resolve/constants/evaluate/AbstractCompileTimeConstantEvaluatorTest.kt @@ -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 diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index 2049674f1f4..849e36d473f 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -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"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java index cf1d571b708..1909266fe2c 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java @@ -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"); diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractableSubstringInfo.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractableSubstringInfo.kt index 1978c214c9b..d69f144e0b0 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractableSubstringInfo.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractableSubstringInfo.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) diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt index 94bc5830f93..b620850cc77 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt @@ -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")