Prohibit expressions of Result type as left operands of ?./!!/?:

#KT-26659 In Progress
This commit is contained in:
Mikhail Zarechenskiy
2018-09-10 08:59:58 +03:00
parent b1cd49dd7b
commit 864e21dd1b
9 changed files with 129 additions and 11 deletions
@@ -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
@@ -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(
@@ -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, "?:"))
}
}
}
}
}
}
@@ -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
}
@@ -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
}
}
@@ -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?
@@ -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)
@@ -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)