diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index b06e07cd4b4..72b962e0ba9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -702,7 +702,7 @@ public interface Errors { DiagnosticFactory2>> DELEGATE_PD_METHOD_NONE_APPLICABLE = DiagnosticFactory2.create(WARNING); DiagnosticFactory1 COMPARE_TO_TYPE_MISMATCH = DiagnosticFactory1.create(ERROR); - + DiagnosticFactory1 YIELD_IS_RESERVED = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 UNDERSCORE_IS_RESERVED = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 INVALID_CHARACTERS = DiagnosticFactory1.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 38f5ee5dd26..75296694023 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -463,6 +463,7 @@ public class DefaultErrorMessages { MAP.put(COMPARE_TO_TYPE_MISMATCH, "''compareTo()'' must return Int, but returns {0}", RENDER_TYPE); MAP.put(UNDERSCORE_IS_RESERVED, "Names _, __, ___, ..., are reserved in Kotlin"); + MAP.put(YIELD_IS_RESERVED, "{0}", STRING); MAP.put(INVALID_CHARACTERS, "Name {0}", STRING); MAP.put(INAPPLICABLE_OPERATOR_MODIFIER, "''operator'' modifier is inapplicable on this function: {0}", STRING); 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 433e3606fd1..450a8d378fd 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -461,6 +461,25 @@ fun checkReservedPrefixWord(sink: DiagnosticSink, element: PsiElement, word: Str } } +fun checkReservedYield(expression: KtSimpleNameExpression?, sink: DiagnosticSink) { + // do not force identifier calculation for elements from stubs. + if (expression?.getReferencedName() != "yield") return + + val identifier = expression.getIdentifier() ?: return + + if (identifier.node.elementType == KtTokens.IDENTIFIER && "yield" == identifier.text) { + sink.report(Errors.YIELD_IS_RESERVED.on(identifier, "Identifier 'yield' is reserved. You ca call it via `yield`")) + } +} + +val MESSAGE_FOR_YIELD_BEFORE_LAMBDA = "Reserved yield block/lambda. Use 'yield() { ... }' or 'yield(fun...)'" + +fun checkReservedYieldBeforeLambda(element: PsiElement, sink: DiagnosticSink) { + KtPsiUtil.getPreviousWord(element, "yield")?.let { + sink.report(Errors.YIELD_IS_RESERVED.on(it, MESSAGE_FOR_YIELD_BEFORE_LAMBDA)) + } +} + fun KtElement.nonStaticOuterClasses(): Sequence { return generateSequence(containingClass()) { if (it.isInner()) it.containingClass() else null } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt index 2cd1a0a41df..a69ec17cc80 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TypeResolver.kt @@ -35,6 +35,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.psi.debugText.getDebugText +import org.jetbrains.kotlin.psi.psiUtil.checkReservedYield import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes import org.jetbrains.kotlin.resolve.PossiblyBareType.bare import org.jetbrains.kotlin.resolve.PossiblyBareType.type @@ -227,6 +228,8 @@ class TypeResolver( } val referenceExpression = type.referenceExpression ?: return + + checkReservedYield(referenceExpression, c.trace) c.trace.record(BindingContext.REFERENCE_TARGET, referenceExpression, classifier) result = resolveTypeForClassifier(c, classifier, qualifierResolutionResult, type, annotations) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java index bea2fc41d87..b9eec8abd4b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/ValueArgumentsToParametersMapper.java @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor; import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import org.jetbrains.kotlin.resolve.OverrideResolver; import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; import org.jetbrains.kotlin.resolve.calls.model.*; @@ -185,7 +186,9 @@ public class ValueArgumentsToParametersMapper { ValueArgumentName argumentName = argument.getArgumentName(); assert argumentName != null; ValueParameterDescriptor valueParameterDescriptor = parameterByName.get(argumentName.getAsName()); - KtReferenceExpression nameReference = argumentName.getReferenceExpression(); + KtSimpleNameExpression nameReference = argumentName.getReferenceExpression(); + + KtPsiUtilKt.checkReservedYield(nameReference, candidateCall.getTrace()); if (!candidate.hasStableParameterNames() && nameReference != null) { report(NAMED_ARGUMENTS_NOT_ALLOWED.on( nameReference, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index cae9028a5b8..d1bf78d9aa3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -37,6 +37,7 @@ import org.jetbrains.kotlin.lexer.KtKeywordToken; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt; @@ -162,6 +163,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { @Override public KotlinTypeInfo visitSimpleNameExpression(@NotNull KtSimpleNameExpression expression, ExpressionTypingContext context) { + KtPsiUtilKt.checkReservedYield(expression, context.trace); + // TODO : other members // TODO : type substitutions??? CallExpressionResolver callExpressionResolver = components.callExpressionResolver; @@ -942,6 +945,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { boolean isStatement ) { KtSimpleNameExpression labelExpression = expression.getTargetLabel(); + KtPsiUtilKt.checkReservedYield(labelExpression, context.trace); if (labelExpression != null) { PsiElement labelIdentifier = labelExpression.getIdentifier(); UnderscoreChecker.INSTANCE.checkIdentifier(labelIdentifier, context.trace, components.languageVersionSettings); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt index 9eef019cbba..06b5d08dbb3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DoubleColonExpressionResolver.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode +import org.jetbrains.kotlin.psi.psiUtil.checkReservedYield import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.calls.CallResolver @@ -632,6 +633,8 @@ class DoubleColonExpressionResolver( outerContext: ResolutionContext<*>, resolutionMode: ResolveArgumentsMode ): OverloadResolutionResults? { + checkReservedYield(reference, outerContext.trace) + // we should preserve information about `call` because callable references are analyzed two times, // otherwise there will be not completed calls in trace val call = outerContext.trace[BindingContext.CALL, reference] ?: CallMaker.makeCall(reference, receiver, null, reference, emptyList()) 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 bb41238e93e..f0138795280 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FunctionsTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/FunctionsTypingVisitor.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.types.expressions import com.google.common.collect.Lists +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.builtins.* import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor @@ -27,6 +28,8 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticUtils import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.checkReservedPrefixWord +import org.jetbrains.kotlin.psi.psiUtil.checkReservedYieldBeforeLambda import org.jetbrains.kotlin.psi.psiUtil.getAnnotationEntries import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext.EXPECTED_RETURN_TYPE @@ -137,6 +140,7 @@ internal class FunctionsTypingVisitor(facade: ExpressionTypingInternals) : Expre } override fun visitLambdaExpression(expression: KtLambdaExpression, context: ExpressionTypingContext): KotlinTypeInfo? { + checkReservedYieldBeforeLambda(expression, context.trace) if (!expression.functionLiteral.hasBody()) return null val expectedType = context.expectedType @@ -160,6 +164,10 @@ internal class FunctionsTypingVisitor(facade: ExpressionTypingInternals) : Expre return components.dataFlowAnalyzer.createCheckedTypeInfo(resultType, context, expression) } + private fun checkReservedYield(context: ExpressionTypingContext, expression: PsiElement) { + checkReservedPrefixWord(context.trace, expression, "yield", "yield block/lambda. Use 'yield() { ... }' or 'yield(fun...)'") + } + private fun createFunctionLiteralDescriptor( expression: KtLambdaExpression, context: ExpressionTypingContext diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LabelResolver.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LabelResolver.java index 9b4639d3e9f..585001d49de 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LabelResolver.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/LabelResolver.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; import org.jetbrains.kotlin.resolve.*; import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext; import org.jetbrains.kotlin.resolve.scopes.utils.ScopeUtilsKt; @@ -138,6 +139,8 @@ public class LabelResolver { @NotNull ResolutionContext context ) { KtSimpleNameExpression labelElement = expression.getTargetLabel(); + KtPsiUtilKt.checkReservedYield(labelElement, context.trace); + Name labelName = expression.getLabelNameAsName(); if (labelElement == null || labelName == null) return null; diff --git a/compiler/testData/diagnostics/tests/ReserveYield.kt b/compiler/testData/diagnostics/tests/ReserveYield.kt new file mode 100644 index 00000000000..9795db76671 --- /dev/null +++ b/compiler/testData/diagnostics/tests/ReserveYield.kt @@ -0,0 +1,70 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_EXPRESSION -UNREACHABLE_CODE -UNUSED_VARIABLE + +// FILE: 1.kt +package p1.yield + +import p1.yield.yield +import p1.yield.foo + +val yield = 5 +fun foo(){} + +fun bar(yield: Int = 4) {} + +fun yield(yield: Int) { + "$yield" + "${yield}" + + yield + val foo = yield + yield + val foo2 = yield + + bar(yield = 5) + + yield(4) + yield {} + + class yieldyield> + + return@yield + return@yield Unit + + val foo5: yield<*> +} + +fun yield(i: (Int) -> Unit) {} + +// FILE: 2.kt + +package p2.yield + +import p2.yield.yield +import p2.yield.foo + +val yield = 5 +fun foo(){} + +fun bar(yield: Int = 4) {} + +fun yield(yield: Int) { + "$`yield`" + "${`yield`}" + + `yield` + val foo = `yield` + `yield` + val foo2 = `yield` + + bar(`yield` = 5) + + `yield`(4) + `yield` {} + + class `yield`> + + return@`yield` + return@`yield` Unit + + val foo5: `yield`<*> +} + +fun yield(i: (Int) -> Unit) {} diff --git a/compiler/testData/diagnostics/tests/ReserveYield.txt b/compiler/testData/diagnostics/tests/ReserveYield.txt new file mode 100644 index 00000000000..d09d7702b63 --- /dev/null +++ b/compiler/testData/diagnostics/tests/ReserveYield.txt @@ -0,0 +1,23 @@ +package + +package p1 { + + package p1.yield { + public val yield: kotlin.Int = 5 + public fun bar(/*0*/ yield: kotlin.Int = ...): kotlin.Unit + public fun foo(): kotlin.Unit + public fun yield(/*0*/ i: (kotlin.Int) -> kotlin.Unit): kotlin.Unit + public fun yield(/*0*/ yield: kotlin.Int): kotlin.Unit + } +} + +package p2 { + + package p2.yield { + public val yield: kotlin.Int = 5 + public fun bar(/*0*/ yield: kotlin.Int = ...): kotlin.Unit + public fun foo(): kotlin.Unit + public fun yield(/*0*/ i: (kotlin.Int) -> kotlin.Unit): kotlin.Unit + public fun yield(/*0*/ yield: kotlin.Int): kotlin.Unit + } +} diff --git a/compiler/testData/diagnostics/tests/ReserveYield2.kt b/compiler/testData/diagnostics/tests/ReserveYield2.kt new file mode 100644 index 00000000000..2e579a30e1c --- /dev/null +++ b/compiler/testData/diagnostics/tests/ReserveYield2.kt @@ -0,0 +1,54 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_EXPRESSION -UNREACHABLE_CODE -UNUSED_VARIABLE -WRONG_ANNOTATION_TARGET -UNUSED_LAMBDA_EXPRESSION + +// FILE: 1.kt + +annotation class yield + +fun bar(p: Int) { + yield@ p + `yield`@ p + + @yield() p + @`yield`() p + + for (yield in 1..5) { + + } + { yield: Int -> } + + val (yield) = listOf(4) + +} + +fun listOf(vararg e: T): List = null!! +operator fun List.component1() = get(0) + +// FILE: 2.kt +package p3 + +enum class yield { + yield +} + +fun f1(yield: Int, foo: Int = yield) {} + +fun f2(foo: yield) {} + +// FILE: 3.kt +package p4 + +typealias yield = Number + +fun f1() {} +fun yield> f2() {} + +// FILE: 4.kt +object X { + fun yield() {} + + fun test3(yield: Int) { + X::yield + + yield::toInt + } +} diff --git a/compiler/testData/diagnostics/tests/ReserveYield2.txt b/compiler/testData/diagnostics/tests/ReserveYield2.txt new file mode 100644 index 00000000000..a029ff24ae2 --- /dev/null +++ b/compiler/testData/diagnostics/tests/ReserveYield2.txt @@ -0,0 +1,51 @@ +package + +public fun bar(/*0*/ p: kotlin.Int): kotlin.Unit +public fun listOf(/*0*/ vararg e: T /*kotlin.Array*/): kotlin.collections.List +public operator fun kotlin.collections.List.component1(): T + +public object X { + private constructor X() + 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 fun test3(/*0*/ yield: kotlin.Int): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + public final fun yield(): kotlin.Unit +} + +public final annotation class yield : kotlin.Annotation { + public constructor yield() + 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 open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +package p3 { + public fun f1(/*0*/ yield: kotlin.Int, /*1*/ foo: kotlin.Int = ...): kotlin.Unit + public fun f2(/*0*/ foo: p3.yield): kotlin.Unit + + public final enum class yield : kotlin.Enum { + enum entry yield + + private constructor yield() + public final override /*1*/ /*fake_override*/ val name: kotlin.String + public final override /*1*/ /*fake_override*/ val ordinal: kotlin.Int + protected final override /*1*/ /*fake_override*/ fun clone(): kotlin.Any + public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: p3.yield): kotlin.Int + public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + protected/*protected and package*/ final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun finalize(): kotlin.Unit + public final override /*1*/ /*fake_override*/ /*isHiddenForResolutionEverywhereBesideSupercalls*/ fun getDeclaringClass(): java.lang.Class! + public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + + // Static members + public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): p3.yield + public final /*synthesized*/ fun values(): kotlin.Array + } +} + +package p4 { + public fun f1(): kotlin.Unit + public fun f2(): kotlin.Unit + public typealias yield = kotlin.Number +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 4de5d52543b..295b1c10b76 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -596,6 +596,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("ReserveYield.kt") + public void testReserveYield() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/ReserveYield.kt"); + doTest(fileName); + } + + @TestMetadata("ReserveYield2.kt") + public void testReserveYield2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/ReserveYield2.kt"); + doTest(fileName); + } + @TestMetadata("ResolveOfJavaGenerics.kt") public void testResolveOfJavaGenerics() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/ResolveOfJavaGenerics.kt"); diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt index 125ba84ebc2..d372165e5e5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt @@ -98,7 +98,8 @@ class KotlinCleanupInspection : LocalInspectionTool(), CleanupLocalInspectionToo Errors.DEPRECATED_TYPE_PARAMETER_SYNTAX, Errors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS, Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, - ErrorsJs.WRONG_EXTERNAL_DECLARATION + ErrorsJs.WRONG_EXTERNAL_DECLARATION, + Errors.YIELD_IS_RESERVED ) private fun Diagnostic.isObsoleteLabel(): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index d1a8b927393..b8d671a6ef9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.codeInsight.intention.IntentionAction import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementAsConstructorParameter import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler @@ -478,5 +479,6 @@ class QuickFixRegistrar : QuickFixContributor { EXPERIMENTAL_FEATURE_WARNING.registerFactory(ChangeCoroutineSupportFix) UNRESOLVED_REFERENCE.registerFactory(CreateLabelFix) + YIELD_IS_RESERVED.registerFactory(UnsupportedYieldFix) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedYieldFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedYieldFix.kt new file mode 100644 index 00000000000..b95c509ab4a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/UnsupportedYieldFix.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.MESSAGE_FOR_YIELD_BEFORE_LAMBDA + +class UnsupportedYieldFix(psiElement: PsiElement): KotlinQuickFixAction(psiElement), CleanupFix { + override fun getFamilyName(): String = "Migrate unsupported yield syntax" + override fun getText(): String = familyName + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + val psiElement = element ?: return + + if (psiElement is KtCallExpression) { + val ktExpression = (psiElement as KtCallElement).calleeExpression ?: return + + // Add after "yield" reference in call + psiElement.addAfter(KtPsiFactory(psiElement).createCallArguments("()"), ktExpression) + } + + if (psiElement.node.elementType == KtTokens.IDENTIFIER) { + psiElement.replace(KtPsiFactory(psiElement).createIdentifier("`yield`")) + } + } + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic): IntentionAction? { + if (diagnostic.psiElement.text != "yield") return null + + val message = Errors.YIELD_IS_RESERVED.cast(diagnostic).a + if (message == MESSAGE_FOR_YIELD_BEFORE_LAMBDA) { + // Identifier -> Expression -> Call (normal call) or Identifier -> Operation Reference -> Binary Expression (for infix usage) + val grand = diagnostic.psiElement.parent.parent + if (grand is KtBinaryExpression || grand is KtCallExpression) { + return UnsupportedYieldFix(grand) + } + } + else { + return UnsupportedYieldFix(diagnostic.psiElement) + } + + return null + } + } +} \ No newline at end of file diff --git a/idea/testData/inspections/cleanup/cleanup.kt b/idea/testData/inspections/cleanup/cleanup.kt index c19d1adf313..908eba47d4c 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt +++ b/idea/testData/inspections/cleanup/cleanup.kt @@ -67,3 +67,48 @@ val x = C() willBeInfix 1 fun infixTest() { arrayListOf(1, 2, 3) map { it } } + + +fun bar(yield: Int = 4) {} + +fun yield(yield: Int) { + "$yield" + "${yield}" + + yield + val foo = yield + yield + val foo2 = yield + + bar(yield = 5) + + yield(4) + yield {} + + class yield> + + return@yield + return@yield Unit + + val foo5: yield<*> +} + +fun yield(i: (Int) -> Unit) {} + +annotation class yield + +@yield() +fun test2(p: Int) { + yield@ p + + @yield fun f() {} +} + +object X { + fun yield() {} + + fun test3(yield: Int) { + X::yield + + yield::toInt + } +} diff --git a/idea/testData/inspections/cleanup/cleanup.kt.after b/idea/testData/inspections/cleanup/cleanup.kt.after index eff61ccc3d0..2524b1b6df5 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt.after +++ b/idea/testData/inspections/cleanup/cleanup.kt.after @@ -66,3 +66,48 @@ val x = C() willBeInfix 1 fun infixTest() { arrayListOf(1, 2, 3).map { it } } + + +fun bar(yield: Int = 4) {} + +fun yield(yield: Int) { + "$`yield`" + "${`yield`}" + + `yield` + val foo = `yield` + `yield` + val foo2 = `yield` + + bar(`yield` = 5) + + yield(4) + yield() {} + + class yield> + + return@`yield` + return@`yield` Unit + + val foo5: `yield`<*> +} + +fun yield(i: (Int) -> Unit) {} + +annotation class yield + +@`yield`() +fun test2(p: Int) { + `yield`@ p + + @`yield` fun f() {} +} + +object X { + fun yield() {} + + fun test3(yield: Int) { + X::`yield` + + `yield`::toInt + } +} diff --git a/idea/testData/quickfix/yieldUnsupported/yieldAsSimpleName.kt b/idea/testData/quickfix/yieldUnsupported/yieldAsSimpleName.kt new file mode 100644 index 00000000000..8bd17e4cb41 --- /dev/null +++ b/idea/testData/quickfix/yieldUnsupported/yieldAsSimpleName.kt @@ -0,0 +1,6 @@ +// "Migrate unsupported yield syntax" "true" +object yield {} + +fun test() { + val foo = yield +} diff --git a/idea/testData/quickfix/yieldUnsupported/yieldAsSimpleName.kt.after b/idea/testData/quickfix/yieldUnsupported/yieldAsSimpleName.kt.after new file mode 100644 index 00000000000..c0291f4d9a4 --- /dev/null +++ b/idea/testData/quickfix/yieldUnsupported/yieldAsSimpleName.kt.after @@ -0,0 +1,6 @@ +// "Migrate unsupported yield syntax" "true" +object yield {} + +fun test() { + val foo = `yield` +} diff --git a/idea/testData/quickfix/yieldUnsupported/yieldBeforeLambda.kt b/idea/testData/quickfix/yieldUnsupported/yieldBeforeLambda.kt new file mode 100644 index 00000000000..fb4ef9eff8f --- /dev/null +++ b/idea/testData/quickfix/yieldUnsupported/yieldBeforeLambda.kt @@ -0,0 +1,8 @@ +// "Migrate unsupported yield syntax" "true" +object yield { + operator fun invoke(f: () -> Unit) = f() +} + +fun test() { + yield { } +} diff --git a/idea/testData/quickfix/yieldUnsupported/yieldBeforeLambda.kt.after b/idea/testData/quickfix/yieldUnsupported/yieldBeforeLambda.kt.after new file mode 100644 index 00000000000..241522ed95f --- /dev/null +++ b/idea/testData/quickfix/yieldUnsupported/yieldBeforeLambda.kt.after @@ -0,0 +1,8 @@ +// "Migrate unsupported yield syntax" "true" +object yield { + operator fun invoke(f: () -> Unit) = f() +} + +fun test() { + yield() { } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index c6e1dfb2100..22acfc72179 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -10575,4 +10575,25 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { doTest(fileName); } } + + @TestMetadata("idea/testData/quickfix/yieldUnsupported") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class YieldUnsupported extends AbstractQuickFixTest { + public void testAllFilesPresentInYieldUnsupported() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/yieldUnsupported"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("yieldAsSimpleName.kt") + public void testYieldAsSimpleName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/yieldUnsupported/yieldAsSimpleName.kt"); + doTest(fileName); + } + + @TestMetadata("yieldBeforeLambda.kt") + public void testYieldBeforeLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/yieldUnsupported/yieldBeforeLambda.kt"); + doTest(fileName); + } + } }