diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java index a8c63c543e5..fe35ec8b0c9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.cfg; import com.intellij.psi.PsiElement; +import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; @@ -285,6 +286,6 @@ public final class WhenChecker { } public static void checkReservedPrefix(@NotNull BindingTrace trace, @NotNull KtWhenExpression expression) { - KtPsiUtilKt.checkReservedPrefixWord(trace, expression.getWhenKeyword(), "sealed", "sealed when"); + KtPsiUtilKt.checkReservedPrefixWord(trace, expression.getWhenKeyword(), "sealed", TokenSet.EMPTY, "sealed when"); } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/lexer/KtTokens.java b/compiler/frontend/src/org/jetbrains/kotlin/lexer/KtTokens.java index 16d789374d1..b4a3c930577 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/lexer/KtTokens.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/lexer/KtTokens.java @@ -241,11 +241,15 @@ public interface KtTokens { TokenSet OPERATIONS = TokenSet.create(AS_KEYWORD, AS_SAFE, IS_KEYWORD, IN_KEYWORD, DOT, PLUSPLUS, MINUSMINUS, EXCLEXCL, MUL, PLUS, MINUS, EXCL, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ, EXCLEQ, ANDAND, OROR, SAFE_ACCESS, ELVIS, - // MAP, FILTER, RANGE, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ, NOT_IN, NOT_IS, IDENTIFIER); + TokenSet BINARY_OPERATIONS = TokenSet.create(AS_KEYWORD, AS_SAFE, IS_KEYWORD, IN_KEYWORD, MUL, PLUS, + MINUS, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ, EXCLEQ, ANDAND, OROR, + ELVIS, RANGE, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ, + NOT_IN, NOT_IS); + TokenSet AUGMENTED_ASSIGNMENTS = TokenSet.create(PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ); TokenSet ALL_ASSIGNMENTS = TokenSet.create(EQ, PLUSEQ, MINUSEQ, MULTEQ, PERCEQ, DIVEQ); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiUtil.java index 001bdd76780..f2f1368d2a0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiUtil.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtPsiUtil.java @@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.codeInsight.CommentUtilCore; import com.intellij.util.containers.ContainerUtil; @@ -608,9 +609,21 @@ public class KtPsiUtil { return prev; } + /** + * Example: + * code: async* {} + * element = "{}" + * word = "async" + * suffixTokens = [+, -, *, /, %] + * + * result = async + */ @Nullable - public static PsiElement getPreviousWord(@NotNull PsiElement element, @NotNull String word) { + public static PsiElement getPreviousWord(@NotNull PsiElement element, @NotNull String word, @NotNull TokenSet suffixTokens) { PsiElement prev = prevLeafIgnoringWhitespaceAndComments(element); + if (prev != null && suffixTokens.contains(prev.getNode().getElementType())) { + prev = PsiTreeUtil.prevLeaf(prev, false); + } if (prev != null && prev.getNode().getElementType() == KtTokens.IDENTIFIER && word.equals(prev.getText())) { return prev; } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index e5e420ec5fd..5fc4ec49759 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -23,6 +23,7 @@ import com.intellij.psi.PsiParameter import com.intellij.psi.PsiParameterList import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.stubs.StubElement +import com.intellij.psi.tree.TokenSet import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.diagnostics.DiagnosticSink @@ -434,8 +435,8 @@ fun canPlaceAfterSimpleNameEntry(element: PsiElement?): Boolean { return !BAD_NEIGHBOUR_FOR_SIMPLE_TEMPLATE_ENTRY_PATTERN.matches(entryText) } -fun checkReservedPrefixWord(sink: DiagnosticSink, element: PsiElement, word: String, message: String) { - KtPsiUtil.getPreviousWord(element, word)?.let { +fun checkReservedPrefixWord(sink: DiagnosticSink, element: PsiElement, word: String, suffixTokens: TokenSet, message: String) { + KtPsiUtil.getPreviousWord(element, word, suffixTokens)?.let { sink.report(Errors.UNSUPPORTED.on(it, message)) } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FunctionsTypingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FunctionsTypingVisitor.kt index 1cbf4b6f4cd..8a97920a74b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FunctionsTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FunctionsTypingVisitor.kt @@ -159,7 +159,7 @@ internal class FunctionsTypingVisitor(facade: ExpressionTypingInternals) : Expre } private fun checkReservedAsync(context: ExpressionTypingContext, expression: PsiElement) { - checkReservedPrefixWord(context.trace, expression, "async", "async block/lambda. Use 'async() { ... }' or 'async(fun...)'") + checkReservedPrefixWord(context.trace, expression, "async", KtTokens.BINARY_OPERATIONS, "async block/lambda. Use 'async() { ... }' or 'async(fun...)'") } private fun createFunctionLiteralDescriptor( diff --git a/compiler/testData/diagnostics/tests/ReservedAsync.kt b/compiler/testData/diagnostics/tests/ReservedAsync.kt index f14e04ac806..0072cb6827f 100644 --- a/compiler/testData/diagnostics/tests/ReservedAsync.kt +++ b/compiler/testData/diagnostics/tests/ReservedAsync.kt @@ -20,4 +20,26 @@ fun test(foo: Any) { foo async (fun () {}) async (fun () {}) +} + +object async { + operator fun plus(f: () -> Unit) = f() + operator fun minus(f: () -> Unit) = f() + operator fun times(f: () -> Unit) = f() + operator fun div(f: () -> Unit) = f() + operator fun mod(f: () -> Unit) = f() +} + +fun test() { + async+ {} + async- {} + async* {} + async/ {} + async% {} + + async + {} + async - {} + async * {} + async / {} + async % {} } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/ReservedAsync.txt b/compiler/testData/diagnostics/tests/ReservedAsync.txt index 8f9046d89b8..bdf3fe84a85 100644 --- a/compiler/testData/diagnostics/tests/ReservedAsync.txt +++ b/compiler/testData/diagnostics/tests/ReservedAsync.txt @@ -1,5 +1,18 @@ package public fun async(/*0*/ f: () -> kotlin.Unit): kotlin.Unit +public fun test(): kotlin.Unit public fun test(/*0*/ foo: kotlin.Any): kotlin.Unit public infix fun kotlin.Any.async(/*0*/ f: () -> kotlin.Unit): kotlin.Unit + +public object async { + private constructor async() + public final operator fun div(/*0*/ f: () -> kotlin.Unit): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public final operator fun minus(/*0*/ f: () -> kotlin.Unit): kotlin.Unit + public final operator fun mod(/*0*/ f: () -> kotlin.Unit): kotlin.Unit + public final operator fun plus(/*0*/ f: () -> kotlin.Unit): kotlin.Unit + public final operator fun times(/*0*/ f: () -> kotlin.Unit): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt index 458b9e6819f..145917f96e5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedAsyncFix.kt @@ -23,6 +23,7 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.codeInsight.surroundWith.expression.KotlinParenthesesSurrounder +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtFile @@ -33,8 +34,15 @@ public class UnsupportedAsyncFix(val psiElement: PsiElement): KotlinQuickFixActi override fun getText(): String = familyName override fun invoke(project: Project, editor: Editor?, file: KtFile) { - if (element is KtBinaryExpression && element.right != null) { - KotlinParenthesesSurrounder.surroundWithParentheses(element.right!!) + if (element is KtBinaryExpression) { + if (element.operationToken != KtTokens.IDENTIFIER) { + // async+ {} + element.addBefore(KtPsiFactory(element).createWhiteSpace(), element.operationReference) + } + else if (element.right != null) { + // foo async {} + KotlinParenthesesSurrounder.surroundWithParentheses(element.right!!) + } } if (element is KtCallExpression) { diff --git a/idea/testData/inspections/cleanup/cleanup.kt b/idea/testData/inspections/cleanup/cleanup.kt index c3f30fe45c9..ca7f333190a 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt +++ b/idea/testData/inspections/cleanup/cleanup.kt @@ -79,6 +79,9 @@ fun infixTest() { fun async(f: () -> Unit) {} infix fun Any.async(f: () -> Unit) {} +object async { + operator fun times(f: () -> Unit) = f() +} fun test(foo: Any) { async { } @@ -94,5 +97,7 @@ fun test(foo: Any) { foo async (fun () {}) async (fun () {}) + + async* {} } diff --git a/idea/testData/inspections/cleanup/cleanup.kt.after b/idea/testData/inspections/cleanup/cleanup.kt.after index e8e02411504..cbf14d05546 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt.after +++ b/idea/testData/inspections/cleanup/cleanup.kt.after @@ -78,6 +78,9 @@ fun infixTest() { fun async(f: () -> Unit) {} infix fun Any.async(f: () -> Unit) {} +object async { + operator fun times(f: () -> Unit) = f() +} fun test(foo: Any) { async() { } @@ -93,5 +96,7 @@ fun test(foo: Any) { foo async (fun () {}) async (fun () {}) + + async * {} } diff --git a/idea/testData/quickfix/asyncUnsupported/asyncWithTimes.kt b/idea/testData/quickfix/asyncUnsupported/asyncWithTimes.kt new file mode 100644 index 00000000000..f766d113331 --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncWithTimes.kt @@ -0,0 +1,8 @@ +// "Migrate unsupported async syntax" "true" +object async { + operator fun times(f: () -> Unit) = f() +} + +fun test() { + async* { } +} diff --git a/idea/testData/quickfix/asyncUnsupported/asyncWithTimes.kt.after b/idea/testData/quickfix/asyncUnsupported/asyncWithTimes.kt.after new file mode 100644 index 00000000000..3c51c5b7350 --- /dev/null +++ b/idea/testData/quickfix/asyncUnsupported/asyncWithTimes.kt.after @@ -0,0 +1,8 @@ +// "Migrate unsupported async syntax" "true" +object async { + operator fun times(f: () -> Unit) = f() +} + +fun test() { + async * { } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 945e12cb17d..aa8cb3101c4 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -540,6 +540,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/asyncUnsupported/asyncWithLambdaAndComment.kt"); doTest(fileName); } + + @TestMetadata("asyncWithTimes.kt") + public void testAsyncWithTimes() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/asyncUnsupported/asyncWithTimes.kt"); + doTest(fileName); + } } @TestMetadata("idea/testData/quickfix/autoImports")