diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnsafeCastFromDynamicInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnsafeCastFromDynamicInspection.kt index e2211bbd44b..0c848614aa5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnsafeCastFromDynamicInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnsafeCastFromDynamicInspection.kt @@ -5,14 +5,15 @@ package org.jetbrains.kotlin.idea.inspections -import com.intellij.codeInspection.LocalInspectionToolSession -import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.* +import com.intellij.openapi.project.Project import com.intellij.psi.PsiElementVisitor import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.psi.expressionVisitor +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getType import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.isDynamic @@ -24,8 +25,26 @@ class UnsafeCastFromDynamicInspection : AbstractKotlinInspection() { val actualType = expression.getType(context) ?: return if (actualType.isDynamic() && !expectedType.isDynamic() && !TypeUtils.noExpectedType(expectedType)) { - holder.registerProblem(expression, "Implicit (unsafe) cast from dynamic to $expectedType") + holder.registerProblem( + expression, + "Implicit (unsafe) cast from dynamic to $expectedType", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + CastExplicitlyFix(expectedType)) } }) } } + +private class CastExplicitlyFix(private val type: KotlinType) : LocalQuickFix { + override fun getName() = "Cast explicitly" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val expression = descriptor.psiElement as? KtExpression ?: return + val typeName = type.constructor.declarationDescriptor?.name ?: return + val pattern = if (type.isMarkedNullable) "$0 as? $1" else "$0 as $1" + val newExpression = KtPsiFactory(expression).createExpressionByPattern(pattern, expression, typeName) + expression.replace(newExpression) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unsafeCastFromDynamic/.inspection b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/.inspection new file mode 100644 index 00000000000..d4918294a40 --- /dev/null +++ b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.UnsafeCastFromDynamicInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unsafeCastFromDynamic/binaryExpression.kt b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/binaryExpression.kt new file mode 100644 index 00000000000..9db280ec866 --- /dev/null +++ b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/binaryExpression.kt @@ -0,0 +1,6 @@ +// JS +fun test() { + val d: dynamic = Any() + var i: Int = 0 + i = d + d +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unsafeCastFromDynamic/binaryExpression.kt.after b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/binaryExpression.kt.after new file mode 100644 index 00000000000..2907864c359 --- /dev/null +++ b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/binaryExpression.kt.after @@ -0,0 +1,6 @@ +// JS +fun test() { + val d: dynamic = Any() + var i: Int = 0 + i = (d + d) as Int +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unsafeCastFromDynamic/nullable.kt b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/nullable.kt new file mode 100644 index 00000000000..04ad0fc0bd1 --- /dev/null +++ b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/nullable.kt @@ -0,0 +1,6 @@ +// JS +fun test() { + val d: dynamic = Any() + var s: String? = null + s = d +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unsafeCastFromDynamic/nullable.kt.after b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/nullable.kt.after new file mode 100644 index 00000000000..369a7e41e73 --- /dev/null +++ b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/nullable.kt.after @@ -0,0 +1,6 @@ +// JS +fun test() { + val d: dynamic = Any() + var s: String? = null + s = d as? String +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unsafeCastFromDynamic/simple.kt b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/simple.kt new file mode 100644 index 00000000000..9d599d149ba --- /dev/null +++ b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/simple.kt @@ -0,0 +1,6 @@ +// JS +fun test() { + val d: dynamic = Any() + var s: String = "" + s = d +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/unsafeCastFromDynamic/simple.kt.after b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/simple.kt.after new file mode 100644 index 00000000000..df6af5e7a7d --- /dev/null +++ b/idea/testData/inspectionsLocal/unsafeCastFromDynamic/simple.kt.after @@ -0,0 +1,6 @@ +// JS +fun test() { + val d: dynamic = Any() + var s: String = "" + s = d as String +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 5931a92d115..f0379b91e96 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -4081,6 +4081,33 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/unsafeCastFromDynamic") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class UnsafeCastFromDynamic extends AbstractLocalInspectionTest { + public void testAllFilesPresentInUnsafeCastFromDynamic() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/unsafeCastFromDynamic"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("binaryExpression.kt") + public void testBinaryExpression() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unsafeCastFromDynamic/binaryExpression.kt"); + doTest(fileName); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unsafeCastFromDynamic/nullable.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/unsafeCastFromDynamic/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/unusedReceiverParameter") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)