diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/KtPostfixTemplateProvider.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/KtPostfixTemplateProvider.kt index 8c34822f563..903efbfca33 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/KtPostfixTemplateProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/KtPostfixTemplateProvider.kt @@ -103,23 +103,43 @@ internal object KtPostfixTemplatePsiInfo : PostfixTemplatePsiInfo() { } internal fun createExpressionSelector( + // Do not suggest expressions like 'val a = 1'/'for ...' + checkCanBeUsedAsValue: Boolean = true, statementsOnly: Boolean = false, - predicate: ((KotlinType) -> Boolean)? = null -): PostfixTemplateExpressionSelectorBase = - KtExpressionPostfixTemplateSelector { - if (this !is KtExpression) return@KtExpressionPostfixTemplateSelector false - if (statementsOnly && !isStatement()) return@KtExpressionPostfixTemplateSelector false - - if (predicate == null) return@KtExpressionPostfixTemplateSelector true - - getType(analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION))?.let { predicate(it) } ?: false - } - -private fun PsiElement.isStatement() = parent is KtBlockExpression + typePredicate: ((KotlinType) -> Boolean)? = null +): PostfixTemplateExpressionSelectorBase = KtExpressionPostfixTemplateSelector(statementsOnly, checkCanBeUsedAsValue, typePredicate) private class KtExpressionPostfixTemplateSelector( - filter: PsiElement.() -> Boolean -) : PostfixTemplateExpressionSelectorBase(Condition(filter)) { + statementsOnly: Boolean, + checkCanBeUsedAsValue: Boolean, + typePredicate: ((KotlinType) -> Boolean)? +) : PostfixTemplateExpressionSelectorBase(createFilter(statementsOnly, checkCanBeUsedAsValue, typePredicate)) { + companion object { + private fun createFilter( + statementsOnly: Boolean, + checkCanBeUsedAsValue: Boolean, + typePredicate: ((KotlinType) -> Boolean)? + ): Condition = Condition { + if (it !is KtExpression) return@Condition false + + // Can't be independent expressions + if (it.isSelector || it.parent is KtUserType || it.isOperationReference || it is KtBlockExpression) return@Condition false + + // Both KtLambdaExpression and KtFunctionLiteral have the same offset, so we add only one of them -> KtLambdaExpression + if (it is KtFunctionLiteral) return@Condition false + + if (statementsOnly && it.parent !is KtBlockExpression) return@Condition false + if (checkCanBeUsedAsValue && !it.canBeUsedAsValue()) return@Condition false + + typePredicate == null || it.getType(it.analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION))?.let { typePredicate(it) } ?: false + } + + private fun KtExpression.canBeUsedAsValue() = + !KtPsiUtil.isAssignment(this) && + !this.isNamedDeclaration && + this !is KtLoopExpression + } + override fun getExpressions(context: PsiElement, document: Document, offset: Int): List { val expressions = super.getExpressions(context, document, offset) @@ -137,27 +157,24 @@ private class KtExpressionPostfixTemplateSelector( ) = context.parentsWithSelf .filterIsInstance() .takeWhile { - it !is KtBlockExpression && - !it.isEffectivelyDeclaration() - }.filter { - !it.isSelector && - it.parent !is KtUserType && - !it.isOperationReference && - !KtPsiUtil.isAssignment(it) && - // Both KtLambdaExpression and KtFunctionLiteral have the same offset, so we add only one of them -> KtLambdaExpression - it !is KtFunctionLiteral + !it.isBlockBodyInDeclaration }.toList() } private val KtExpression.isOperationReference: Boolean get() = this.node.elementType == KtNodeTypes.OPERATION_REFERENCE -private fun KtElement.isEffectivelyDeclaration() = - this is KtNamedDeclaration && - // function literal is an expression while it's also a subtype of KtNamedDeclaration - this !is KtFunctionLiteral && - // !(fun (a) = 1) - (this !is KtNamedFunction || this.name == null) +private val KtElement.isBlockBodyInDeclaration: Boolean + get() = this is KtBlockExpression && (parent as? KtElement)?.isNamedDeclarationWithBody == true + +private val KtElement.isNamedDeclaration: Boolean + get() = this is KtNamedDeclaration && !isAnonymousFunction + +private val KtElement.isNamedDeclarationWithBody: Boolean + get() = this is KtDeclarationWithBody && !isAnonymousFunction + +private val KtDeclaration.isAnonymousFunction: Boolean + get() = this is KtFunctionLiteral || (this is KtNamedFunction && this.name == null) private val KtExpression.isSelector: Boolean get() = parent is KtQualifiedExpression && (parent as KtQualifiedExpression).selectorExpression == this diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/stringBasedPostfixTemplates.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/stringBasedPostfixTemplates.kt index e54e311ab80..fc733a10957 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/stringBasedPostfixTemplates.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/stringBasedPostfixTemplates.kt @@ -46,7 +46,7 @@ internal class KtForEachPostfixTemplate( name, "for (item in expr)", "for (\$name$ in \$expr$) {\n \$END$\n}", - createExpressionSelector(statementsOnly = true, predicate = KotlinType::containsIteratorMethod) + createExpressionSelector(statementsOnly = true, typePredicate = KotlinType::containsIteratorMethod) ) { override fun setVariables(template: Template, element: PsiElement) { val name = MacroCallNode(SuggestVariableNameMacro()) @@ -63,13 +63,13 @@ internal object KtAssertPostfixTemplate : ConstantStringBasedPostfixTemplate( "assert", "assert(expr) { \"\" }", "assert(\$expr$) { \"\$END$\" }", - createExpressionSelector(statementsOnly = true, predicate = KotlinType::isBoolean) + createExpressionSelector(statementsOnly = true, typePredicate = KotlinType::isBoolean) ) internal object KtParenthesizedPostfixTemplate : ConstantStringBasedPostfixTemplate( "par", "(expr)", "(\$expr$)\$END$", - createExpressionSelector(statementsOnly = false) + createExpressionSelector() ) internal object KtSoutPostfixTemplate : ConstantStringBasedPostfixTemplate( @@ -90,5 +90,5 @@ internal object KtWhilePostfixTemplate : ConstantStringBasedPostfixTemplate( "while", "while (expr) {}", "while (\$expr$) {\n\$END$\n}", - createExpressionSelector(statementsOnly = true, predicate = KotlinType::isBoolean) + createExpressionSelector(statementsOnly = true, typePredicate = KotlinType::isBoolean) ) diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/surrounderBasedPostfixTemplates.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/surrounderBasedPostfixTemplates.kt index 19901b19c6b..dc155cc726c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/surrounderBasedPostfixTemplates.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/postfix/surrounderBasedPostfixTemplates.kt @@ -29,14 +29,14 @@ import org.jetbrains.kotlin.types.typeUtil.isBoolean internal object KtIfExpressionPostfixTemplate : SurroundPostfixTemplateBase( "if", "if (expr)", - KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false) { it.isBoolean() } + KtPostfixTemplatePsiInfo, createExpressionSelector { it.isBoolean() } ) { override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false) } internal object KtElseExpressionPostfixTemplate : SurroundPostfixTemplateBase( "else", "if (!expr)", - KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false) { it.isBoolean() } + KtPostfixTemplatePsiInfo, createExpressionSelector { it.isBoolean() } ) { override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false) override fun getWrappedExpression(expression: PsiElement?) = (expression as KtExpression).negate() @@ -44,7 +44,7 @@ internal object KtElseExpressionPostfixTemplate : SurroundPostfixTemplateBase( internal class KtNotNullPostfixTemplate(val name: String) : SurroundPostfixTemplateBase( name, "if (expr != null)", - KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false, predicate = TypeUtils::isNullableType) + KtPostfixTemplatePsiInfo, createExpressionSelector(typePredicate = TypeUtils::isNullableType) ) { override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false) override fun getTail() = "!= null" @@ -52,7 +52,7 @@ internal class KtNotNullPostfixTemplate(val name: String) : SurroundPostfixTempl internal object KtIsNullPostfixTemplate : SurroundPostfixTemplateBase( "null", "if (expr == null)", - KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false, predicate = TypeUtils::isNullableType) + KtPostfixTemplatePsiInfo, createExpressionSelector(typePredicate = TypeUtils::isNullableType) ) { override fun getSurrounder() = KotlinWithIfExpressionSurrounder(withElse = false) override fun getTail() = "== null" @@ -60,14 +60,19 @@ internal object KtIsNullPostfixTemplate : SurroundPostfixTemplateBase( internal object KtWhenExpressionPostfixTemplate : SurroundPostfixTemplateBase( "when", "when (expr)", - KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false) + KtPostfixTemplatePsiInfo, createExpressionSelector() ) { override fun getSurrounder() = KotlinWhenSurrounder() } internal object KtTryPostfixTemplate : SurroundPostfixTemplateBase( "try", "try { code } catch (e: Exception) { }", - KtPostfixTemplatePsiInfo, createExpressionSelector(statementsOnly = false) + KtPostfixTemplatePsiInfo, + createExpressionSelector( + checkCanBeUsedAsValue = false, + // Do not suggest 'val x = try { init } catch (e: Exception) { }' + statementsOnly = true + ) ) { override fun getSurrounder() = KotlinTryCatchSurrounder() } diff --git a/idea/testData/codeInsight/postfix/assertNonTopLevel.kt b/idea/testData/codeInsight/postfix/assertNonTopLevel.kt new file mode 100644 index 00000000000..a1213942a9d --- /dev/null +++ b/idea/testData/codeInsight/postfix/assertNonTopLevel.kt @@ -0,0 +1,3 @@ +fun foo(x: Boolean) { + foo(x.assert) +} diff --git a/idea/testData/codeInsight/postfix/assertNonTopLevel.kt.after b/idea/testData/codeInsight/postfix/assertNonTopLevel.kt.after new file mode 100644 index 00000000000..fa5a81265c2 --- /dev/null +++ b/idea/testData/codeInsight/postfix/assertNonTopLevel.kt.after @@ -0,0 +1,3 @@ +fun foo(x: Boolean) { + foo(x.assert ) +} diff --git a/idea/testData/codeInsight/postfix/noParOnLoop.kt b/idea/testData/codeInsight/postfix/noParOnLoop.kt new file mode 100644 index 00000000000..055e043e1bd --- /dev/null +++ b/idea/testData/codeInsight/postfix/noParOnLoop.kt @@ -0,0 +1,5 @@ +fun foo() { + for (i in 1..100) { + + }.par +} diff --git a/idea/testData/codeInsight/postfix/noParOnLoop.kt.after b/idea/testData/codeInsight/postfix/noParOnLoop.kt.after new file mode 100644 index 00000000000..1b14566dd9c --- /dev/null +++ b/idea/testData/codeInsight/postfix/noParOnLoop.kt.after @@ -0,0 +1,5 @@ +fun foo() { + for (i in 1..100) { + + }.par +} diff --git a/idea/testData/codeInsight/postfix/tryOnFor.kt b/idea/testData/codeInsight/postfix/tryOnFor.kt new file mode 100644 index 00000000000..9bdb423c567 --- /dev/null +++ b/idea/testData/codeInsight/postfix/tryOnFor.kt @@ -0,0 +1,4 @@ +fun foo() { + for (i in 1..100) { + }.try +} diff --git a/idea/testData/codeInsight/postfix/tryOnFor.kt.after b/idea/testData/codeInsight/postfix/tryOnFor.kt.after new file mode 100644 index 00000000000..347cb9d5691 --- /dev/null +++ b/idea/testData/codeInsight/postfix/tryOnFor.kt.after @@ -0,0 +1,7 @@ +fun foo() { + try { + for (i in 1..100) { + } + } catch(e: Exception) { + } +} diff --git a/idea/testData/codeInsight/postfix/tryOnStatement.kt b/idea/testData/codeInsight/postfix/tryOnStatement.kt new file mode 100644 index 00000000000..65f3ca5288b --- /dev/null +++ b/idea/testData/codeInsight/postfix/tryOnStatement.kt @@ -0,0 +1,3 @@ +fun foo() { + val x = 1.try +} diff --git a/idea/testData/codeInsight/postfix/tryOnStatement.kt.after b/idea/testData/codeInsight/postfix/tryOnStatement.kt.after new file mode 100644 index 00000000000..e130875643d --- /dev/null +++ b/idea/testData/codeInsight/postfix/tryOnStatement.kt.after @@ -0,0 +1,6 @@ +fun foo() { + try { + val x = 1 + } catch(e: Exception) { + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/postfix/PostfixTemplateProviderTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/postfix/PostfixTemplateProviderTestGenerated.java index 46066e3dd22..b7a109de023 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/postfix/PostfixTemplateProviderTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/postfix/PostfixTemplateProviderTestGenerated.java @@ -41,6 +41,12 @@ public class PostfixTemplateProviderTestGenerated extends AbstractPostfixTemplat doTest(fileName); } + @TestMetadata("assertNonTopLevel.kt") + public void testAssertNonTopLevel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/assertNonTopLevel.kt"); + doTest(fileName); + } + @TestMetadata("doNotProposeWrappingIncrement.kt") public void testDoNotProposeWrappingIncrement() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/doNotProposeWrappingIncrement.kt"); @@ -71,6 +77,12 @@ public class PostfixTemplateProviderTestGenerated extends AbstractPostfixTemplat doTest(fileName); } + @TestMetadata("noParOnLoop.kt") + public void testNoParOnLoop() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/noParOnLoop.kt"); + doTest(fileName); + } + @TestMetadata("noReturnForNonStatement.kt") public void testNoReturnForNonStatement() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/noReturnForNonStatement.kt"); @@ -137,6 +149,18 @@ public class PostfixTemplateProviderTestGenerated extends AbstractPostfixTemplat doTest(fileName); } + @TestMetadata("tryOnFor.kt") + public void testTryOnFor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/tryOnFor.kt"); + doTest(fileName); + } + + @TestMetadata("tryOnStatement.kt") + public void testTryOnStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/tryOnStatement.kt"); + doTest(fileName); + } + @TestMetadata("val.kt") public void testVal() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/postfix/val.kt");