Skip to content

Commit

Permalink
Fix CastToNullableType warning on same or parent type (#6677)
Browse files Browse the repository at this point in the history
  • Loading branch information
atulgpt committed Jan 3, 2024
1 parent 3e946a4 commit 44f22f1
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpressionWithTypeRHS
import org.jetbrains.kotlin.psi.KtNullableType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.typeUtil.supertypes

/**
* Reports unsafe cast to nullable types.
Expand All @@ -25,6 +28,8 @@ import org.jetbrains.kotlin.psi.KtNullableType
* }
* </compliant>
*/

@RequiresTypeResolution
class CastToNullableType(config: Config = Config.empty) : Rule(config) {
override val issue: Issue = Issue(
javaClass.simpleName,
Expand All @@ -39,6 +44,11 @@ class CastToNullableType(config: Config = Config.empty) : Rule(config) {
if (operationReference.getReferencedNameElementType() != KtTokens.AS_KEYWORD) return
if (expression.left.text == KtTokens.NULL_KEYWORD.value) return
val nullableTypeElement = expression.right?.typeElement as? KtNullableType ?: return
val expressionType =
bindingContext[BindingContext.EXPRESSION_TYPE_INFO, expression.left]?.type ?: return
val castedType = bindingContext[BindingContext.TYPE, expression.right] ?: return

if (expressionType == castedType || expressionType.supertypes().contains(castedType)) return

val message = "Use the safe cast ('as? ${nullableTypeElement.innerType?.text}')" +
" instead of 'as ${nullableTypeElement.text}'."
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package io.gitlab.arturbosch.detekt.rules.bugs

import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
import io.gitlab.arturbosch.detekt.test.assertThat
import io.gitlab.arturbosch.detekt.test.compileAndLint
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.Test

class CastToNullableTypeSpec {
@KotlinCoreEnvironmentTest
class CastToNullableTypeSpec(private val env: KotlinCoreEnvironment) {
private val subject = CastToNullableType()

@Test
Expand All @@ -14,20 +17,31 @@ class CastToNullableTypeSpec {
val x: String? = a as String?
}
""".trimIndent()
val findings = subject.compileAndLint(code)
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
assertThat(findings).hasStartSourceLocation(2, 24)
assertThat(findings[0]).hasMessage("Use the safe cast ('as? String') instead of 'as String?'.")
}

@Test
fun `casting to nullable parent types is allowed`() {
val code = """
fun foo(a: String?) {
val x = a as CharSequence?
}
""".trimIndent()
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

@Test
fun `safe casting`() {
val code = """
fun foo(a: Any?) {
val x: String? = a as? String
}
""".trimIndent()
val findings = subject.compileAndLint(code)
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

Expand All @@ -38,7 +52,7 @@ class CastToNullableTypeSpec {
val x = a is String?
}
""".trimIndent()
val findings = subject.compileAndLint(code)
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

Expand All @@ -49,7 +63,51 @@ class CastToNullableTypeSpec {
val x = null as String?
}
""".trimIndent()
val findings = subject.compileAndLint(code)
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

// https://github.com/detekt/detekt/issues/6676
@Test
fun `cast to same type in alias form allowed`() {
val code = """
typealias Alias = String
fun test(s: String?) {
@Suppress("USELESS_CAST") // Casts is useful to convert String to typealias.
val a = s as Alias?
print(a)
}
""".trimIndent()
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

@Test
fun `cast to parent type in alias form allowed`() {
val code = """
typealias Alias = CharSequence
fun test(s: String?) {
val a = s as Alias?
print(a)
}
""".trimIndent()
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

@Test
fun `cast to different type in alias form not allowed`() {
val code = """
typealias Alias = String
fun test(s: Any?) {
val a = s as Alias?
print(a)
}
""".trimIndent()
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).hasSize(1)
}
}

0 comments on commit 44f22f1

Please sign in to comment.