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 3e25cfb6088..1d7c4fa4c68 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -31,6 +31,8 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.annotations.Annotations; import org.jetbrains.kotlin.diagnostics.Diagnostic; +import org.jetbrains.kotlin.diagnostics.Errors; +import org.jetbrains.kotlin.lexer.JetKeywordToken; import org.jetbrains.kotlin.lexer.JetTokens; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.*; @@ -73,8 +75,7 @@ import java.util.Iterator; import java.util.List; import static org.jetbrains.kotlin.diagnostics.Errors.*; -import static org.jetbrains.kotlin.lexer.JetTokens.AS_KEYWORD; -import static org.jetbrains.kotlin.lexer.JetTokens.AS_SAFE; +import static org.jetbrains.kotlin.lexer.JetTokens.*; import static org.jetbrains.kotlin.resolve.BindingContext.*; import static org.jetbrains.kotlin.resolve.calls.context.ContextDependency.DEPENDENT; import static org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT; @@ -115,6 +116,10 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { @Override public JetTypeInfo visitConstantExpression(@NotNull JetConstantExpression expression, ExpressionTypingContext context) { + if (expression.getNode().getElementType() == JetNodeTypes.CHARACTER_CONSTANT) { + checkStringPrefixAndSuffix(expression, context); + } + CompileTimeConstant compileTimeConstant = components.constantExpressionEvaluator.evaluateExpression( expression, context.trace, context.expectedType ); @@ -1372,6 +1377,9 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { @Override public JetTypeInfo visitStringTemplateExpression(@NotNull JetStringTemplateExpression expression, ExpressionTypingContext contextWithExpectedType) { final ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT); + + checkStringPrefixAndSuffix(expression, context); + class StringTemplateVisitor extends JetVisitorVoid { private JetTypeInfo typeInfo = TypeInfoFactoryKt.noTypeInfo(context); @@ -1402,6 +1410,27 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { contextWithExpectedType); } + private static void checkStringPrefixAndSuffix(@NotNull PsiElement expression, ExpressionTypingContext context) { + checkStringPrefixOrSuffix(PsiTreeUtil.prevLeaf(expression), context); + checkStringPrefixOrSuffix(PsiTreeUtil.nextLeaf(expression), context); + } + + private static void checkStringPrefixOrSuffix(PsiElement prefixOrSuffix, ExpressionTypingContext context) { + if (illegalStringPrefixOrSuffix(prefixOrSuffix)) { + context.trace.report(Errors.UNSUPPORTED.on(prefixOrSuffix, "string prefixes and suffixes")); + } + } + + private static boolean illegalStringPrefixOrSuffix(@Nullable PsiElement element) { + if (element == null) return false; + + IElementType elementType = element.getNode().getElementType(); + return elementType == IDENTIFIER || + elementType == INTEGER_LITERAL || + elementType == FLOAT_LITERAL || + elementType instanceof JetKeywordToken; + } + @Override public JetTypeInfo visitAnnotatedExpression(@NotNull JetAnnotatedExpression expression, ExpressionTypingContext context) { return visitAnnotatedExpression(expression, context, false); diff --git a/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt b/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt new file mode 100644 index 00000000000..e60d4b6170c --- /dev/null +++ b/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt @@ -0,0 +1,39 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +infix fun Any?.foo(a: Any) {} +operator fun Any?.contains(a: Any): Boolean = true + +fun test(a: Any) { + + a foo"" + a foo"asd" + a foo"$a" + a foo"asd${a}sfsa" + a foo"""sdf""" + a foo'd' + a foo'' + + a foo""foo a + a foo"asd"foo a + a foo"$a"foo a + a foo"asd${a}sfsa"foo a + a foo"""sdf"""foo a + a foo'd'foo a + a foo''foo a + + a in"foo" + a in"""foo""" + a in's' + a in'' + + a !in"foo" + a !in"""foo""" + a !in's' + a !in'' + + if("s"is Any) {} + test("s"as Any) + + a foo""1 + a foo""1.0 +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.txt b/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.txt new file mode 100644 index 00000000000..84fa1c09fc4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/StringPrefixAndSuffix.txt @@ -0,0 +1,5 @@ +package + +public fun test(/*0*/ a: kotlin.Any): kotlin.Unit +public operator fun kotlin.Any?.contains(/*0*/ a: kotlin.Any): kotlin.Boolean +public infix fun kotlin.Any?.foo(/*0*/ a: kotlin.Any): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java index d2160a6f0a5..3a1e83bc189 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/JetDiagnosticsTestGenerated.java @@ -589,6 +589,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest { doTest(fileName); } + @TestMetadata("StringPrefixAndSuffix.kt") + public void testStringPrefixAndSuffix() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/StringPrefixAndSuffix.kt"); + doTest(fileName); + } + @TestMetadata("StringTemplates.kt") public void testStringTemplates() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/StringTemplates.kt");