Report error on illegal variable declaration in when subject

This commit is contained in:
Dmitry Petrov
2017-10-20 17:31:42 +03:00
parent 4fe894c03d
commit 7bc89b8871
7 changed files with 126 additions and 12 deletions
@@ -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);
@@ -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);
@@ -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) {