Prohibit expressions of Result type as left operands of ?./!!/?:
#KT-26659 In Progress
This commit is contained in:
@@ -337,6 +337,7 @@ public interface Errors {
|
||||
// Result class
|
||||
|
||||
DiagnosticFactory0<PsiElement> RESULT_CLASS_IN_RETURN_TYPE = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, String> RESULT_CLASS_WITH_NULLABLE_OPERATOR = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
// Secondary constructors
|
||||
|
||||
|
||||
+1
@@ -666,6 +666,7 @@ public class DefaultErrorMessages {
|
||||
MAP.put(SECONDARY_CONSTRUCTOR_WITH_BODY_INSIDE_INLINE_CLASS, "Secondary constructors with bodies are reserved for for future releases");
|
||||
|
||||
MAP.put(RESULT_CLASS_IN_RETURN_TYPE, "'kotlin.Result' cannot be used as a return type");
|
||||
MAP.put(RESULT_CLASS_WITH_NULLABLE_OPERATOR, "Expression of type 'kotlin.Result' cannot be used as a left operand of ''{0}''", STRING);
|
||||
|
||||
MAP.put(VARIANCE_ON_TYPE_PARAMETER_NOT_ALLOWED, "Variance annotations are only allowed for type parameters of classes and interfaces");
|
||||
MAP.put(BOUND_ON_TYPE_ALIAS_PARAMETER_NOT_ALLOWED, "Bounds are not allowed on type alias parameters");
|
||||
|
||||
@@ -132,7 +132,7 @@ private val DEFAULT_CALL_CHECKERS = listOf(
|
||||
CallableReferenceCompatibilityChecker(), LateinitIntrinsicApplicabilityChecker,
|
||||
UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker(),
|
||||
PrimitiveNumericComparisonCallChecker, LambdaWithSuspendModifierCallChecker,
|
||||
UselessElvisCallChecker()
|
||||
UselessElvisCallChecker(), ResultTypeWithNullableOperatorsChecker()
|
||||
)
|
||||
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
|
||||
private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf(
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.resolve.calls.checkers
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.reportDiagnosticOnce
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.checkers.isResultType
|
||||
import org.jetbrains.kotlin.types.expressions.ControlStructureTypingUtils
|
||||
|
||||
class ResultTypeWithNullableOperatorsChecker : CallChecker {
|
||||
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
|
||||
val name = resolvedCall.resultingDescriptor.name
|
||||
val operationNode = resolvedCall.call.callOperationNode
|
||||
|
||||
when {
|
||||
operationNode?.elementType == KtTokens.SAFE_ACCESS -> {
|
||||
val receiver = resolvedCall.resultingDescriptor.dispatchReceiverParameter ?: return
|
||||
if (receiver.type.isResultType()) {
|
||||
context.trace.report(Errors.RESULT_CLASS_WITH_NULLABLE_OPERATOR.on(operationNode!!.psi, "?."))
|
||||
}
|
||||
}
|
||||
|
||||
name == ControlStructureTypingUtils.ResolveConstruct.EXCL_EXCL.specialFunctionName -> {
|
||||
if (resolvedCall.resultingDescriptor.returnType?.isResultType() == true) {
|
||||
context.trace.report(Errors.RESULT_CLASS_WITH_NULLABLE_OPERATOR.on(reportOn, "!!"))
|
||||
}
|
||||
}
|
||||
|
||||
name == ControlStructureTypingUtils.ResolveConstruct.ELVIS.specialFunctionName -> {
|
||||
val elvisBinaryExpression = resolvedCall.call.callElement as? KtBinaryExpression ?: return
|
||||
val left = elvisBinaryExpression.left ?: return
|
||||
val leftType = context.trace.getType(left) ?: return
|
||||
|
||||
if (leftType.isResultType()) {
|
||||
context.trace.reportDiagnosticOnce(Errors.RESULT_CLASS_WITH_NULLABLE_OPERATOR.on(reportOn, "?:"))
|
||||
}
|
||||
|
||||
// Additional check for case `a ?: b ?: c`, where `b` is Result
|
||||
// This is needed because inference will give common supertype for `a ?: b` which might not be Result
|
||||
if (left is KtBinaryExpression) {
|
||||
val lastExpression = left.right ?: return
|
||||
val lastExpressionType = context.trace.getType(lastExpression) ?: return
|
||||
|
||||
if (lastExpressionType.isResultType()) {
|
||||
context.trace.reportDiagnosticOnce(Errors.RESULT_CLASS_WITH_NULLABLE_OPERATOR.on(reportOn, "?:"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class ResultClassInReturnTypeChecker : DeclarationChecker {
|
||||
companion object {
|
||||
private const val RESULT_NAME = "Result"
|
||||
internal const val RESULT_NAME = "Result"
|
||||
}
|
||||
|
||||
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
|
||||
@@ -29,8 +29,7 @@ class ResultClassInReturnTypeChecker : DeclarationChecker {
|
||||
}
|
||||
|
||||
private fun isForbiddenReturnType(returnType: KotlinType, declarationDescriptor: DeclarationDescriptor): Boolean {
|
||||
val descriptor = returnType.constructor.declarationDescriptor ?: return false
|
||||
if (!descriptor.isResultClass()) return false
|
||||
if (!returnType.isResultType()) return false
|
||||
|
||||
if (declarationDescriptor is PropertyDescriptor || declarationDescriptor is PropertyGetterDescriptor) {
|
||||
val visibility = (declarationDescriptor as DeclarationDescriptorWithVisibility).effectiveVisibility()
|
||||
@@ -46,11 +45,15 @@ class ResultClassInReturnTypeChecker : DeclarationChecker {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun DeclarationDescriptor.isResultClass(): Boolean {
|
||||
val container = containingDeclaration ?: return false
|
||||
return container is PackageFragmentDescriptor &&
|
||||
container.fqName == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME &&
|
||||
name.asString() == RESULT_NAME
|
||||
}
|
||||
}
|
||||
internal fun KotlinType.isResultType(): Boolean {
|
||||
return this.constructor.declarationDescriptor?.isResultClass() == true
|
||||
}
|
||||
|
||||
private fun DeclarationDescriptor.isResultClass(): Boolean {
|
||||
val container = containingDeclaration ?: return false
|
||||
return container is PackageFragmentDescriptor &&
|
||||
container.fqName == KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME &&
|
||||
name.asString() == ResultClassInReturnTypeChecker.RESULT_NAME
|
||||
}
|
||||
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
// !DIAGNOSTICS: -UNUSED_EXPRESSION
|
||||
|
||||
fun <T> id(x: T): T = x
|
||||
|
||||
private val asFun: () -> Result<Int>? = TODO()
|
||||
private val Int.intResult: Result<Int>?
|
||||
get() = null
|
||||
|
||||
fun returnInt(): Int? = 0
|
||||
|
||||
|
||||
fun nullableOperators(r1: Result<Int>?, b: Boolean) {
|
||||
if (b) {
|
||||
r1<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>!!<!>
|
||||
asFun()<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>!!<!>
|
||||
returnInt()?.intResult<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>!!<!>.toString()
|
||||
}
|
||||
|
||||
if (b) {
|
||||
id(r1)<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>!!<!>
|
||||
}
|
||||
|
||||
if (b) {
|
||||
r1<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?.<!>toString()
|
||||
returnInt()?.intResult<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?.<!>toString()
|
||||
asFun()<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?.<!>toString()
|
||||
id(r1)<!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?.<!>toString()
|
||||
}
|
||||
|
||||
if (b) {
|
||||
r1 <!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?:<!> 0
|
||||
r1 <!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?:<!> r1
|
||||
asFun() <!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?:<!> r1 <!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?:<!> 0
|
||||
id(asFun()) <!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?:<!> 0
|
||||
|
||||
returnInt() ?: returnInt() ?: asFun() <!RESULT_CLASS_WITH_NULLABLE_OPERATOR!>?:<!> 0
|
||||
}
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
package
|
||||
|
||||
private val asFun: () -> kotlin.Result<kotlin.Int>?
|
||||
private val kotlin.Int.intResult: kotlin.Result<kotlin.Int>?
|
||||
public fun </*0*/ T> id(/*0*/ x: T): T
|
||||
public fun nullableOperators(/*0*/ r1: kotlin.Result<kotlin.Int>?, /*1*/ b: kotlin.Boolean): kotlin.Unit
|
||||
public fun returnInt(): kotlin.Int?
|
||||
+5
@@ -1663,6 +1663,11 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/coroutines/usageOfResultTypeInReturnType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("usageOfResultTypeWithNullableOperators.kt")
|
||||
public void testUsageOfResultTypeWithNullableOperators() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/coroutines/usageOfResultTypeWithNullableOperators.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/callableReference")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java
Generated
+5
@@ -1663,6 +1663,11 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/coroutines/usageOfResultTypeInReturnType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("usageOfResultTypeWithNullableOperators.kt")
|
||||
public void testUsageOfResultTypeWithNullableOperators() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/testsWithStdLib/coroutines/usageOfResultTypeWithNullableOperators.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/diagnostics/testsWithStdLib/coroutines/callableReference")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user