diff --git a/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java b/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java index ecd3ea60595..f04a4fdaba5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/KtNodeTypes.java @@ -115,8 +115,8 @@ public interface KtNodeTypes { KtNodeType BREAK = new KtNodeType("BREAK", KtBreakExpression.class); KtNodeType IF = new KtNodeType("IF", KtIfExpression.class); KtNodeType CONDITION = new KtNodeType("CONDITION", KtContainerNode.class); - KtNodeType THEN = new KtNodeType("THEN", KtContainerNode.class); - KtNodeType ELSE = new KtNodeType("ELSE", KtContainerNode.class); + KtNodeType THEN = new KtNodeType("THEN", KtContainerNodeForControlStructureBody.class); + KtNodeType ELSE = new KtNodeType("ELSE", KtContainerNodeForControlStructureBody.class); KtNodeType TRY = new KtNodeType("TRY", KtTryExpression.class); KtNodeType CATCH = new KtNodeType("CATCH", KtCatchClause.class); KtNodeType FINALLY = new KtNodeType("FINALLY", KtFinallySection.class); @@ -124,7 +124,7 @@ public interface KtNodeTypes { KtNodeType WHILE = new KtNodeType("WHILE", KtWhileExpression.class); KtNodeType DO_WHILE = new KtNodeType("DO_WHILE", KtDoWhileExpression.class); KtNodeType LOOP_RANGE = new KtNodeType("LOOP_RANGE", KtContainerNode.class); - KtNodeType BODY = new KtNodeType("BODY", KtContainerNode.class); + KtNodeType BODY = new KtNodeType("BODY", KtContainerNodeForControlStructureBody.class); KtNodeType BLOCK = new KtNodeType("BLOCK", KtBlockExpression.class); IElementType LAMBDA_EXPRESSION = new IErrorCounterReparseableElementType("LAMBDA_EXPRESSION", KotlinLanguage.INSTANCE) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 26243d0bab3..d743cc2a46b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -180,6 +180,7 @@ public interface Errors { DiagnosticFactory0 ANNOTATION_PARAMETER_MUST_BE_KCLASS_LITERAL = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 ANNOTATION_PARAMETER_MUST_BE_ENUM_CONST = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 ANNOTATIONS_ON_BLOCK_LEVEL_EXPRESSION_ON_THE_SAME_LINE = DiagnosticFactory0.create(WARNING); DiagnosticFactory0 ILLEGAL_SINCE_KOTLIN_VALUE = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 NEWER_VERSION_IN_SINCE_KOTLIN = DiagnosticFactory1.create(WARNING); 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 a41da71c064..797a5d46f1f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -725,6 +725,10 @@ public class DefaultErrorMessages { MAP.put(ANNOTATION_PARAMETER_MUST_BE_KCLASS_LITERAL, "An annotation parameter must be a class literal (T::class)"); MAP.put(ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT, "Default value of annotation parameter must be a compile-time constant"); + MAP.put(ANNOTATIONS_ON_BLOCK_LEVEL_EXPRESSION_ON_THE_SAME_LINE, + "Annotations on block-level expressions are being parsed differently depending on presence of a new line after them. " + + "Use new line if whole block-level expression must be annotated or wrap annotated expression in parentheses"); + MAP.put(CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT, "Const 'val' are only allowed on top level or in objects"); MAP.put(CONST_VAL_WITH_DELEGATE, "Const 'val' should not have a delegate"); MAP.put(CONST_VAL_WITH_GETTER, "Const 'val' should not have a getter"); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtContainerNodeForControlStructureBody.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtContainerNodeForControlStructureBody.kt new file mode 100644 index 00000000000..40f11aa97ea --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtContainerNodeForControlStructureBody.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2016 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.psi + +import com.intellij.lang.ASTNode + +class KtContainerNodeForControlStructureBody(node: ASTNode) : KtContainerNode(node) 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 9fc0edf7e61..723cc63e39c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -1539,12 +1539,47 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { } protected void resolveAnnotationsOnExpression(KtAnnotatedExpression expression, ExpressionTypingContext context) { + if (isAnnotatedExpressionInBlockLevelBinary(expression)) { + context.trace.report(ANNOTATIONS_ON_BLOCK_LEVEL_EXPRESSION_ON_THE_SAME_LINE.on(expression)); + } + if (!(expression.getBaseExpression() instanceof KtObjectLiteralExpression)) { // annotations on object literals are resolved later inside LazyClassDescriptor components.annotationResolver.resolveAnnotationsWithArguments(context.scope, expression.getAnnotationEntries(), context.trace); } } + private static boolean isAnnotatedExpressionInBlockLevelBinary(KtAnnotatedExpression annotatedExpression) { + PsiElement current = annotatedExpression; + PsiElement parent = current.getParent(); + + // Here we implicitly assume that grammar rules are: + // blockLevelExpression = annotations expression + // expression = binaryExpression + // binaryExpression = prefixExpression prefixExpression + // prefixExpression = annotations expression + + // If there is no binary parent, annotations are being parsed the same way independently of newline after them + if (!(parent instanceof KtBinaryExpression)) return false; + + while (parent instanceof KtBinaryExpression) { + // if we came not from the left parent, there's no need to report an error + if (((KtBinaryExpression) parent).getLeft() != current) { + return false; + } + current = parent; + parent = parent.getParent(); + } + + return isParentForBlockLevelExpression(parent); + } + + private static boolean isParentForBlockLevelExpression(@Nullable PsiElement parent) { + return parent instanceof KtBlockExpression || + parent instanceof KtContainerNodeForControlStructureBody || + parent instanceof KtWhenEntry; + } + @Override public KotlinTypeInfo visitKtElement(@NotNull KtElement element, ExpressionTypingContext context) { context.trace.report(UNSUPPORTED.on(element, getClass().getCanonicalName())); diff --git a/compiler/testData/diagnostics/tests/annotations/blockLevelOnTheSameLineWarning.kt b/compiler/testData/diagnostics/tests/annotations/blockLevelOnTheSameLineWarning.kt new file mode 100644 index 00000000000..49a13881b87 --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/blockLevelOnTheSameLineWarning.kt @@ -0,0 +1,59 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +@Target(AnnotationTarget.EXPRESSION) +annotation class Ann1 +@Target(AnnotationTarget.EXPRESSION) +annotation class Ann2(val x: String) + +fun bar() {} +fun bar(block: () -> Unit) {} + +fun foo(y: IntArray) { + @Ann1 bar() + @Ann1 bar() { } + @Ann1 bar { } + + @Ann2("") bar() + @Ann2("") bar() { } + @Ann2("") bar { } + + @Ann1 @Ann2("") bar() + + var x = 1 + + @Ann1 ++x + @Ann1 x++ + @Ann2("") ++x + @Ann2("") x++ + @Ann1 @Ann2("") ++x + @Ann1 @Ann2("") x++ + + @Ann1 y[0] + + @Ann1 { x: Int -> x } + @Ann1 { x: Int -> x }(1) + @Ann1 object { fun foo() = 1 } + @Ann1 object { fun foo() = 1 }.foo() + + @Ann1() (x * x) + var z = 1 + @Ann1 x + z + + @Ann1 x = x + 2 + @Ann1 x += z + 2 + + @Ann1 x + 6 * 2 > 0 + @Ann1 x * 6 + 2 > 0 + + @Ann1 object { operator fun plus(x: Int) = 1 } + 1 + @Ann1 object { operator fun plus(x: Int) = 1 } + 1 * 4 > 0 + + @Ann1 x foo z + 8 + + 1 + @Ann1 x + 1 + @Ann1 x * z + 8 + + x foo @Ann1 z + 8 +} + +infix fun Int.foo(other: Int) = 1 diff --git a/compiler/testData/diagnostics/tests/annotations/blockLevelOnTheSameLineWarning.txt b/compiler/testData/diagnostics/tests/annotations/blockLevelOnTheSameLineWarning.txt new file mode 100644 index 00000000000..9e652f5f82e --- /dev/null +++ b/compiler/testData/diagnostics/tests/annotations/blockLevelOnTheSameLineWarning.txt @@ -0,0 +1,21 @@ +package + +public fun bar(): kotlin.Unit +public fun bar(/*0*/ block: () -> kotlin.Unit): kotlin.Unit +public fun foo(/*0*/ y: kotlin.IntArray): kotlin.Unit +public infix fun kotlin.Int.foo(/*0*/ other: kotlin.Int): kotlin.Int + +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.EXPRESSION}) public final annotation class Ann1 : kotlin.Annotation { + public constructor Ann1() + 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 +} + +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.EXPRESSION}) public final annotation class Ann2 : kotlin.Annotation { + public constructor Ann2(/*0*/ x: kotlin.String) + public final val x: kotlin.String + 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 +} diff --git a/compiler/testData/diagnostics/tests/annotations/options/assignment.kt b/compiler/testData/diagnostics/tests/annotations/options/assignment.kt index d4df8648343..2d1c469f2c4 100644 --- a/compiler/testData/diagnostics/tests/annotations/options/assignment.kt +++ b/compiler/testData/diagnostics/tests/annotations/options/assignment.kt @@ -3,7 +3,7 @@ annotation class ExprAnn fun foo(): Int { var a: Int - @ExprAnn a = 1 - @ExprAnn a += 1 + @ExprAnn a = 1 + @ExprAnn a += 1 return a -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/annotations/options/targets/expr.kt b/compiler/testData/diagnostics/tests/annotations/options/targets/expr.kt index 3a55789d3b0..12332e94465 100644 --- a/compiler/testData/diagnostics/tests/annotations/options/targets/expr.kt +++ b/compiler/testData/diagnostics/tests/annotations/options/targets/expr.kt @@ -7,5 +7,5 @@ fun transform(i: Int, tr: (Int) -> Int): Int = @base< @base @special fun foo(i: Int): Int { val j = @base @special i + 1 if (j == 1) return foo(@special @base 42) - return transform(@special j, @base @special { @special it * 2 }) + return transform(@special j, @base @special { @special it * 2 }) } diff --git a/compiler/testData/diagnostics/tests/annotations/options/targets/funtypeargs.kt b/compiler/testData/diagnostics/tests/annotations/options/targets/funtypeargs.kt index acca49127dc..416e85b4867 100644 --- a/compiler/testData/diagnostics/tests/annotations/options/targets/funtypeargs.kt +++ b/compiler/testData/diagnostics/tests/annotations/options/targets/funtypeargs.kt @@ -9,5 +9,5 @@ fun transform(i: Int, tr: (@special Int) -> fun foo(i: Int): Int { val j = @special i + 1 if (j == 1) return foo(@special 42) - return transform(@special j, @special { i: @base Int -> @base i * 2 }) + return transform(@special j, @special { i: @base Int -> @base i * 2 }) } diff --git a/compiler/testData/diagnostics/tests/suppress/oneWarning/onBlockStatementSameLine.kt b/compiler/testData/diagnostics/tests/suppress/oneWarning/onBlockStatementSameLine.kt index f00d4c49ebe..6234a1ce219 100644 --- a/compiler/testData/diagnostics/tests/suppress/oneWarning/onBlockStatementSameLine.kt +++ b/compiler/testData/diagnostics/tests/suppress/oneWarning/onBlockStatementSameLine.kt @@ -1,7 +1,7 @@ fun foo(x: Array, block: (T, Int) -> Int) { var r: Any? - @Suppress("UNCHECKED_CAST") r = block(x[0] as T, "" as Int) + @Suppress("UNCHECKED_CAST") r = block(x[0] as T, "" as Int) // to prevent unused assignment diagnostic for the above statement r.hashCode() @@ -9,11 +9,11 @@ fun foo(x: Array, block: (T, Int) -> Int) { var i = 1 if (i != 1) { - @Suppress("UNCHECKED_CAST") i += block(x[0] as T, "" as Int).toInt() + @Suppress("UNCHECKED_CAST") i += block(x[0] as T, "" as Int).toInt() } if (i != 1) @Suppress("UNCHECKED_CAST") i += block(x[0] as T, "" as Int).toInt() - if (i != 1) @Suppress("UNCHECKED_CAST") i += block(x[0] as T, "" as Int).toInt() + if (i != 1) @Suppress("UNCHECKED_CAST") i += block(x[0] as T, "" as Int).toInt() } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 094fa44f75e..b92218793ed 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -939,6 +939,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("blockLevelOnTheSameLineWarning.kt") + public void testBlockLevelOnTheSameLineWarning() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/blockLevelOnTheSameLineWarning.kt"); + doTest(fileName); + } + @TestMetadata("ConstructorCall.kt") public void testConstructorCall() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/annotations/ConstructorCall.kt"); diff --git a/idea/testData/highlighter/Annotations.kt b/idea/testData/highlighter/Annotations.kt index 9a9062a8028..7b4598b9c00 100644 --- a/idea/testData/highlighter/Annotations.kt +++ b/idea/testData/highlighter/Annotations.kt @@ -9,7 +9,7 @@ fun bar(block: () -> foo() { 1 + @Ann 2 - @Ann 3 + 4 + @Ann 3 + 4 bar @Ann { 1 }