From 7bc89b8871d7db2926336f5cf4506ea26670aad5 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Fri, 20 Oct 2017 17:31:42 +0300 Subject: [PATCH] Report error on illegal variable declaration in when subject --- .../jetbrains/kotlin/diagnostics/Errors.java | 2 + .../rendering/DefaultErrorMessages.java | 1 + .../PatternMatchingTypingVisitor.kt | 60 +++++++++++++++---- ...portedVariableDeclarationsInWhenSubject.kt | 41 +++++++++++++ ...ortedVariableDeclarationsInWhenSubject.txt | 22 +++++++ .../checkers/DiagnosticsTestGenerated.java | 6 ++ .../DiagnosticsUsingJavacTestGenerated.java | 6 ++ 7 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.kt create mode 100644 compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 8f6b0f9cf26..48a7a5c3a56 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -935,6 +935,8 @@ public interface Errors { DiagnosticFactory0 COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 DUPLICATE_LABEL_IN_WHEN = DiagnosticFactory0.create(WARNING); + DiagnosticFactory1 ILLEGAL_DECLARATION_IN_WHEN_SUBJECT = DiagnosticFactory1.create(ERROR); + // Type mismatch DiagnosticFactory2 TYPE_MISMATCH = DiagnosticFactory2.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 ecb29dc30bd..066b0105f7e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -545,6 +545,7 @@ public class DefaultErrorMessages { MAP.put(REDUNDANT_ELSE_IN_WHEN, "'when' is exhaustive so 'else' is redundant here"); MAP.put(COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, "Deprecated syntax. Use '||' instead of commas in when-condition for 'when' without argument"); MAP.put(DUPLICATE_LABEL_IN_WHEN, "Duplicate label in when"); + MAP.put(ILLEGAL_DECLARATION_IN_WHEN_SUBJECT, "Illegal variable declaration in 'when' subject: {0}. Should be a simple val with an initializer", STRING); MAP.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", RENDER_WHEN_MISSING_CASES); MAP.put(NON_EXHAUSTIVE_WHEN, "''when'' expression on enum is recommended to be exhaustive, add {0}", RENDER_WHEN_MISSING_CASES); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt index d4c665ba9c6..fdaf7eeaa54 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -93,8 +93,8 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping ) { protected abstract fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns): DataFlowValue - abstract fun makeValueArgument(): ValueArgument - abstract val valueExpression: KtExpression + abstract fun makeValueArgument(): ValueArgument? + abstract val valueExpression: KtExpression? private var _dataFlowValue: DataFlowValue? = null val dataFlowValue get() = _dataFlowValue!! @@ -161,6 +161,20 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping override val valueExpression: KtExpression get() = error("Should not be called for Subject.None") } + + + class Error( + element: KtElement?, + typeInfo: KotlinTypeInfo?, + scopeWithSubject: LexicalScope? + ) : Subject(element, typeInfo, scopeWithSubject) { + override fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns): DataFlowValue = + DataFlowValue.nullValue(builtIns) + + override fun makeValueArgument(): ValueArgument? = null + + override val valueExpression: KtExpression? get() = null + } } fun visitWhenExpression( @@ -240,15 +254,30 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping return createTypeInfo(resultType, resultDataFlowInfo, branchesTypeInfo.jumpOutPossible, contextWithExpectedType.dataFlowInfo) } - private fun processVariableSubject(subjectVariable: KtProperty, contextBeforeSubject: ExpressionTypingContext): Subject.Variable { + private fun processVariableSubject(subjectVariable: KtProperty, contextBeforeSubject: ExpressionTypingContext): Subject { val trace = contextBeforeSubject.trace + var hasIllegalDeclarationInSubject = false if (!components.languageVersionSettings.supportsFeature(LanguageFeature.VariableDeclarationInWhenSubject)) { trace.report(UNSUPPORTED_FEATURE.on( subjectVariable, Pair(LanguageFeature.VariableDeclarationInWhenSubject, components.languageVersionSettings) )) } + else { + val illegalDeclarationString = when { + subjectVariable.isVar -> "var" + subjectVariable.initializer == null -> "variable without initializer" + subjectVariable.hasDelegateExpression() -> "delegated property" + subjectVariable.getter != null || subjectVariable.setter != null -> "property with accessors" + else -> null + } + + if (illegalDeclarationString != null) { + hasIllegalDeclarationInSubject = true + trace.report(Errors.ILLEGAL_DECLARATION_IN_WHEN_SUBJECT.on(subjectVariable, illegalDeclarationString)) + } + } val scopeWithSubjectVariable = ExpressionTypingUtils.newWritableScopeImpl(contextBeforeSubject, LexicalScopeKind.WHEN, components.overloadChecker) @@ -262,7 +291,11 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping // NB typeInfo returned by 'localVariableResolver.process(...)' treats local variable declaration as a statement, // so 'typeInfo' above it has type 'kotlin.Unit'. // Propagate declared variable type as a "subject expression" type. - return Subject.Variable(subjectVariable, descriptor, typeInfo.replaceType(descriptor.type), scopeWithSubjectVariable) + val subjectTypeInfo = typeInfo.replaceType(descriptor.type) + return if (hasIllegalDeclarationInSubject) + Subject.Error(subjectVariable, subjectTypeInfo, scopeWithSubjectVariable) + else + Subject.Variable(subjectVariable, descriptor, subjectTypeInfo, scopeWithSubjectVariable) } private fun inferTypeForWhenExpression( @@ -460,7 +493,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping return } - val argumentForSubject = subject.makeValueArgument() + val argumentForSubject = subject.makeValueArgument() ?: return val typeInfo = facade.checkInExpression( condition, condition.operationReference, argumentForSubject, rangeExpression, context @@ -500,14 +533,17 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping override fun visitWhenConditionWithExpression(condition: KtWhenConditionWithExpression) { val expression = condition.expression ?: return - val basicDataFlowInfo = checkTypeForExpressionCondition( - context, expression, subject.type, subject is Subject.None, subject.dataFlowValue - ) + val subjectValueExpression = subject.valueExpression + + val basicDataFlowInfo = checkTypeForExpressionCondition(context, expression, subject.type, subject is Subject.None, subject.dataFlowValue) val moduleDescriptor = DescriptorUtils.getContainingModule(context.scope.ownerDescriptor) - val dataFlowInfoFromES = components.effectSystem.getDataFlowInfoWhenEquals( - subject.valueExpression, expression, context.trace, moduleDescriptor - ) - newDataFlowInfo = basicDataFlowInfo.and(dataFlowInfoFromES) + newDataFlowInfo = if (subjectValueExpression != null) { + val dataFlowInfoFromES = components.effectSystem.getDataFlowInfoWhenEquals(subject.valueExpression, expression, context.trace, moduleDescriptor) + basicDataFlowInfo.and(dataFlowInfoFromES) + } + else { + basicDataFlowInfo + } } override fun visitKtElement(element: KtElement) { diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.kt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.kt new file mode 100644 index 00000000000..4ef31f66f42 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.kt @@ -0,0 +1,41 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER +// WITH_RUNTIME + +data class P(val x: Any, val y: Any) + +fun foo(): Any = 42 +fun String.bar(): Any = 42 + + +fun testSimpleValInWhenSubject() { + when (val y = foo()) { + } +} + +fun testValWithoutInitializerWhenSubject() { + when (val y: Any) { + is String -> y.length + } +} + +fun testVarInWhenSubject() { + when (var y = foo()) { + is String -> y.length + } +} + +fun testDestructuringInWhenSubject() { + when (val (y1, y2) = P(1, 2)) { + } +} + +fun testExtensionValInWhenSubject() { + when (val String.len get() = bar()) { + } +} + +fun testDelegatedValInWhenSubject() { + when (val y by lazy { 42 }) { + } +} diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.txt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.txt new file mode 100644 index 00000000000..8f1369a93af --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.txt @@ -0,0 +1,22 @@ +package + +public fun foo(): kotlin.Any +public fun testDelegatedValInWhenSubject(): kotlin.Unit +public fun testDestructuringInWhenSubject(): kotlin.Unit +public fun testExtensionValInWhenSubject(): kotlin.Unit +public fun testSimpleValInWhenSubject(): kotlin.Unit +public fun testValWithoutInitializerWhenSubject(): kotlin.Unit +public fun testVarInWhenSubject(): kotlin.Unit +public fun kotlin.String.bar(): kotlin.Any + +public final data class P { + public constructor P(/*0*/ x: kotlin.Any, /*1*/ y: kotlin.Any) + public final val x: kotlin.Any + public final val y: kotlin.Any + public final operator /*synthesized*/ fun component1(): kotlin.Any + public final operator /*synthesized*/ fun component2(): kotlin.Any + public final /*synthesized*/ fun copy(/*0*/ x: kotlin.Any = ..., /*1*/ y: kotlin.Any = ...): P + public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index bed8dcd4d78..4d8747a0938 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -22489,6 +22489,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt"); doTest(fileName); } + + @TestMetadata("unsupportedVariableDeclarationsInWhenSubject.kt") + public void testUnsupportedVariableDeclarationsInWhenSubject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.kt"); + doTest(fileName); + } } } } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 99a0a88a687..51767af99e1 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -22489,6 +22489,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt"); doTest(fileName); } + + @TestMetadata("unsupportedVariableDeclarationsInWhenSubject.kt") + public void testUnsupportedVariableDeclarationsInWhenSubject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedVariableDeclarationsInWhenSubject.kt"); + doTest(fileName); + } } } }