Report error on illegal variable declaration in when subject
This commit is contained in:
@@ -935,6 +935,8 @@ public interface Errors {
|
||||
DiagnosticFactory0<PsiElement> COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> DUPLICATE_LABEL_IN_WHEN = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
DiagnosticFactory1<PsiElement, String> ILLEGAL_DECLARATION_IN_WHEN_SUBJECT = DiagnosticFactory1.create(ERROR);
|
||||
|
||||
// Type mismatch
|
||||
|
||||
DiagnosticFactory2<KtExpression, KotlinType, KotlinType> TYPE_MISMATCH = DiagnosticFactory2.create(ERROR);
|
||||
|
||||
+1
@@ -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);
|
||||
|
||||
+48
-12
@@ -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) {
|
||||
|
||||
+41
@@ -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 (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>val y: Any<!>) {
|
||||
is String -> <!UNINITIALIZED_VARIABLE!>y<!>.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun testVarInWhenSubject() {
|
||||
when (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>var y = foo()<!>) {
|
||||
is String -> y.<!UNRESOLVED_REFERENCE!>length<!>
|
||||
}
|
||||
}
|
||||
|
||||
fun testDestructuringInWhenSubject() {
|
||||
when (val (y1, y2) = P(1, 2)) {
|
||||
}
|
||||
}
|
||||
|
||||
fun testExtensionValInWhenSubject() {
|
||||
when (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>val <!LOCAL_EXTENSION_PROPERTY, DEBUG_INFO_MISSING_UNRESOLVED!>String<!>.<!VARIABLE_WITH_NO_TYPE_NO_INITIALIZER!>len<!><!><!SYNTAX!><!> <!UNRESOLVED_REFERENCE!>get<!>(<!SYNTAX!><!>) = <!UNRESOLVED_REFERENCE!>bar<!>()<!SYNTAX!>)<!> <!UNUSED_LAMBDA_EXPRESSION!>{
|
||||
}<!>
|
||||
}
|
||||
|
||||
fun testDelegatedValInWhenSubject() {
|
||||
when (<!ILLEGAL_DECLARATION_IN_WHEN_SUBJECT!>val y by <!UNRESOLVED_REFERENCE!>lazy<!> { 42 }<!>) {
|
||||
}
|
||||
}
|
||||
+22
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+6
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user