diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java index efccc9d8545..fb89ee0b894 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/util/CallMaker.java @@ -324,6 +324,12 @@ public class CallMaker { return new ExpressionValueArgument(expression, expression, true); } + @NotNull + public static ValueArgument makeExternalValueArgument(@NotNull KtExpression expression, @NotNull KtElement reportErrorsOn) { + return new ExpressionValueArgument(expression, reportErrorsOn, true); + } + + @NotNull public static Call makePropertyCall(@Nullable Receiver explicitReceiver, @Nullable ASTNode callOperationNode, @NotNull KtSimpleNameExpression nameExpression) { return makeCallWithExpressions(nameExpression, explicitReceiver, callOperationNode, nameExpression, Collections.emptyList()); 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 d6b675f53c5..d45608fe3bc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.psi.* @@ -30,9 +31,7 @@ import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.calls.checkers.RttiExpressionInformation import org.jetbrains.kotlin.resolve.calls.checkers.RttiOperation import org.jetbrains.kotlin.resolve.calls.context.ContextDependency.INDEPENDENT -import org.jetbrains.kotlin.resolve.calls.smartcasts.ConditionalDataFlowInfo -import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo -import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue +import org.jetbrains.kotlin.resolve.calls.smartcasts.* import org.jetbrains.kotlin.resolve.calls.util.CallMaker import org.jetbrains.kotlin.resolve.checkers.PrimitiveNumericComparisonCallChecker import org.jetbrains.kotlin.resolve.scopes.LexicalScope @@ -86,6 +85,84 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping override fun visitWhenExpression(expression: KtWhenExpression, context: ExpressionTypingContext) = visitWhenExpression(expression, context, false) + private sealed class Subject( + val element: KtElement?, + val typeInfo: KotlinTypeInfo?, + val scopeWithSubject: LexicalScope?, + val type: KotlinType = typeInfo?.type ?: ErrorUtils.createErrorType("Unknown type") + ) { + + protected abstract fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns): DataFlowValue + abstract fun makeValueArgument(): ValueArgument + abstract val valueExpression: KtExpression + + private var _dataFlowValue: DataFlowValue? = null + val dataFlowValue get() = _dataFlowValue!! + + fun initDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns) { + _dataFlowValue = createDataFlowValue(contextAfterSubject, builtIns) + } + + val dataFlowInfo get() = typeInfo?.dataFlowInfo + + val jumpOutPossible get() = typeInfo?.jumpOutPossible ?: false + + class Expression( + val expression: KtExpression, + typeInfo: KotlinTypeInfo + ) : Subject(expression, typeInfo, null) { + override fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns) = + DataFlowValueFactory.createDataFlowValue(expression, type, contextAfterSubject) + + override fun makeValueArgument(): ValueArgument = + CallMaker.makeExternalValueArgument(expression) + + override val valueExpression: KtExpression + get() = expression + } + + + class Variable( + val variable: KtProperty, + val descriptor: VariableDescriptor, + typeInfo: KotlinTypeInfo, + scopeWithSubject: LexicalScope + ) : Subject(variable, typeInfo, scopeWithSubject) { + private val initializer = variable.initializer!! + + override fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns) = + DataFlowValue( + IdentifierInfo.Variable( + descriptor, + DataFlowValue.Kind.STABLE_VALUE, + contextAfterSubject.trace.bindingContext[BindingContext.BOUND_INITIALIZER_VALUE, descriptor] + ), + descriptor.type + ) + + override fun makeValueArgument(): ValueArgument = + CallMaker.makeExternalValueArgument( + KtPsiFactory(variable.project, true).createExpression(variable.name!!), + initializer + ) + + override val valueExpression: KtExpression + get() = initializer + } + + + class None : Subject(null, null, null) { + override fun createDataFlowValue(contextAfterSubject: ExpressionTypingContext, builtIns: KotlinBuiltIns) = + DataFlowValue.nullValue(builtIns) + + override fun makeValueArgument(): ValueArgument = + error("Should not be called for Subject.None") + + override val valueExpression: KtExpression + get() = error("Should not be called for Subject.None") + } + } + fun visitWhenExpression( expression: KtWhenExpression, contextWithExpectedType: ExpressionTypingContext, @@ -104,63 +181,67 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping val subjectExpression = expression.subjectExpression val subjectVariable = expression.subjectVariable - val subjectTypeInfo: KotlinTypeInfo? - val scopeWithSubjectVariable: LexicalScope? + val subject = when { + subjectVariable != null -> { + if (!components.languageVersionSettings.supportsFeature(LanguageFeature.VariableDeclarationInWhenSubject)) { + trace.report(Errors.UNSUPPORTED_FEATURE.on( + subjectVariable, + Pair(LanguageFeature.VariableDeclarationInWhenSubject, components.languageVersionSettings) + )) + } - if (subjectVariable != null) { - if (!components.languageVersionSettings.supportsFeature(LanguageFeature.VariableDeclarationInWhenSubject)) { - trace.report(Errors.UNSUPPORTED_FEATURE.on( - subjectVariable, - Pair(LanguageFeature.VariableDeclarationInWhenSubject, components.languageVersionSettings) - )) + val scopeWithSubjectVariable = ExpressionTypingUtils.newWritableScopeImpl(contextBeforeSubject, LexicalScopeKind.WHEN, components.overloadChecker) + + // Destructuring causes SOE in UAST :( + val resolveResult = components.localVariableResolver.process(subjectVariable, contextBeforeSubject, contextBeforeSubject.scope, facade) + val typeInfo = resolveResult.first + val descriptor = resolveResult.second + + scopeWithSubjectVariable.addVariableDescriptor(descriptor) + + // 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. + Subject.Variable(subjectVariable, descriptor, typeInfo.replaceType(descriptor.type), scopeWithSubjectVariable) } - scopeWithSubjectVariable = ExpressionTypingUtils.newWritableScopeImpl(contextBeforeSubject, LexicalScopeKind.WHEN, components.overloadChecker) + subjectExpression != null -> + Subject.Expression(subjectExpression, facade.getTypeInfo(subjectExpression, contextBeforeSubject)) - val (typeInfo, descriptor) = components.localVariableResolver.process(subjectVariable, contextBeforeSubject, contextBeforeSubject.scope, facade) - // 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. - subjectTypeInfo = typeInfo.replaceType(descriptor.type) - scopeWithSubjectVariable.addVariableDescriptor(descriptor) - } - else { - scopeWithSubjectVariable = null - - subjectTypeInfo = subjectExpression?.let { facade.getTypeInfo(it, contextBeforeSubject) } + else -> Subject.None() } val contextAfterSubject = run { - var context = contextBeforeSubject - if (subjectTypeInfo != null) { - context = context.replaceDataFlowInfo(subjectTypeInfo.dataFlowInfo) - } - if (scopeWithSubjectVariable != null) { - context = context.replaceScope(scopeWithSubjectVariable) - } - context + var result = contextBeforeSubject + subject.scopeWithSubject?.let { result = result.replaceScope(it) } + subject.dataFlowInfo?.let { result = result.replaceDataFlowInfo(it) } + result } - val contextWithExpectedTypeAndSubjectVariable = scopeWithSubjectVariable?.let { contextWithExpectedType.replaceScope(it) } ?: - contextWithExpectedType + val contextWithExpectedTypeAndSubjectVariable = + subject.scopeWithSubject?.let { contextWithExpectedType.replaceScope(it) } ?: + contextWithExpectedType - val subjectType = subjectTypeInfo?.type ?: ErrorUtils.createErrorType("Unknown type") - val jumpOutPossibleInSubject: Boolean = subjectTypeInfo?.jumpOutPossible ?: false - val subjectDataFlowValue = subjectExpression?.let { - facade.components.dataFlowValueFactory.createDataFlowValue(it, subjectType, contextAfterSubject) - } ?: DataFlowValue.nullValue(components.builtIns) +// val subjectType = subjectTypeInfo?.type ?: ErrorUtils.createErrorType("Unknown type") +// val jumpOutPossibleInSubject: Boolean = subjectTypeInfo?.jumpOutPossible ?: false +// val subjectDataFlowValue = subjectExpression?.let { +// facade.components.dataFlowValueFactory.createDataFlowValue(it, subjectType, contextAfterSubject) +// } ?: DataFlowValue.nullValue(components.builtIns) + subject.initDataFlowValue(contextAfterSubject, components.builtIns) val possibleTypesForSubject = subjectTypeInfo?.dataFlowInfo?.getStableTypes( subjectDataFlowValue, components.languageVersionSettings ) ?: emptySet() checkSmartCastsInSubjectIfRequired(expression, contextBeforeSubject, subjectType, possibleTypesForSubject) + val possibleTypesForSubject = subject.typeInfo?.dataFlowInfo?.getStableTypes(subject.dataFlowValue) ?: emptySet() + checkSmartCastsInSubjectIfRequired(expression, contextBeforeSubject, subject.type, possibleTypesForSubject) - val dataFlowInfoForEntries = analyzeConditionsInWhenEntries(expression, contextAfterSubject, subjectDataFlowValue, subjectType) + val dataFlowInfoForEntries = analyzeConditionsInWhenEntries(expression, contextAfterSubject, subject) val whenReturnType = inferTypeForWhenExpression(expression, contextWithExpectedTypeAndSubjectVariable, contextAfterSubject, dataFlowInfoForEntries) val whenResultValue = whenReturnType?.let { facade.components.dataFlowValueFactory.createDataFlowValue(expression, it, contextAfterSubject) } val branchesTypeInfo = - joinWhenExpressionBranches(expression, contextAfterSubject, whenReturnType, jumpOutPossibleInSubject, whenResultValue) + joinWhenExpressionBranches(expression, contextAfterSubject, whenReturnType, subject.jumpOutPossible, whenResultValue) val isExhaustive = WhenChecker.isWhenExhaustive(expression, trace) @@ -219,20 +300,17 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping } private fun analyzeConditionsInWhenEntries( - expression: KtWhenExpression, - contextAfterSubject: ExpressionTypingContext, - subjectDataFlowValue: DataFlowValue, - subjectType: KotlinType + expression: KtWhenExpression, + contextAfterSubject: ExpressionTypingContext, + subject: Subject ): ArrayList { - val subjectExpression = expression.subjectExpression - val argumentDataFlowInfos = ArrayList() var inputDataFlowInfo = contextAfterSubject.dataFlowInfo for (whenEntry in expression.entries) { val conditionsInfo = analyzeWhenEntryConditions( - whenEntry, - contextAfterSubject.replaceDataFlowInfo(inputDataFlowInfo), - subjectExpression, subjectType, subjectDataFlowValue + whenEntry, + contextAfterSubject.replaceDataFlowInfo(inputDataFlowInfo), + subject ) inputDataFlowInfo = inputDataFlowInfo.and(conditionsInfo.elseInfo) @@ -273,12 +351,11 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping } currentDataFlowInfo = - if (entryType != null && KotlinBuiltIns.isNothing(entryType)) - currentDataFlowInfo - else if (currentDataFlowInfo != null) - currentDataFlowInfo.or(entryDataFlowInfo) - else - entryDataFlowInfo + when { + entryType != null && KotlinBuiltIns.isNothing(entryType) -> currentDataFlowInfo + currentDataFlowInfo != null -> currentDataFlowInfo.or(entryDataFlowInfo) + else -> entryDataFlowInfo + } jumpOutPossible = jumpOutPossible or entryTypeInfo.jumpOutPossible } @@ -342,11 +419,9 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping } private fun analyzeWhenEntryConditions( - whenEntry: KtWhenEntry, - context: ExpressionTypingContext, - subjectExpression: KtExpression?, - subjectType: KotlinType, - subjectDataFlowValue: DataFlowValue + whenEntry: KtWhenEntry, + context: ExpressionTypingContext, + subject: Subject ): ConditionalDataFlowInfo { if (whenEntry.isElse) { return ConditionalDataFlowInfo(context.dataFlowInfo) @@ -355,10 +430,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping var entryInfo: ConditionalDataFlowInfo? = null var contextForCondition = context for (condition in whenEntry.conditions) { - val conditionInfo = checkWhenCondition( - subjectExpression, subjectType, condition, - contextForCondition, subjectDataFlowValue - ) + val conditionInfo = checkWhenCondition(subject, condition, contextForCondition) entryInfo = entryInfo?.let { ConditionalDataFlowInfo(it.thenInfo.or(conditionInfo.thenInfo), it.elseInfo.and(conditionInfo.elseInfo)) } ?: conditionInfo @@ -370,23 +442,24 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping } private fun checkWhenCondition( - subjectExpression: KtExpression?, - subjectType: KotlinType, - condition: KtWhenCondition, - context: ExpressionTypingContext, - subjectDataFlowValue: DataFlowValue + subject: Subject, + condition: KtWhenCondition, + context: ExpressionTypingContext ): ConditionalDataFlowInfo { var newDataFlowInfo = noChange(context) + condition.accept(object : KtVisitorVoid() { override fun visitWhenConditionInRange(condition: KtWhenConditionInRange) { val rangeExpression = condition.rangeExpression ?: return - if (subjectExpression == null) { + + if (subject is Subject.None) { context.trace.report(EXPECTED_CONDITION.on(condition)) val dataFlowInfo = facade.getTypeInfo(rangeExpression, context).dataFlowInfo newDataFlowInfo = ConditionalDataFlowInfo(dataFlowInfo) return } - val argumentForSubject = CallMaker.makeExternalValueArgument(subjectExpression) + + val argumentForSubject = subject.makeValueArgument() val typeInfo = facade.checkInExpression( condition, condition.operationReference, argumentForSubject, rangeExpression, context @@ -400,43 +473,40 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping } override fun visitWhenConditionIsPattern(condition: KtWhenConditionIsPattern) { - if (subjectExpression == null) { + if (subject is Subject.None) { context.trace.report(EXPECTED_CONDITION.on(condition)) } - val typeReference = condition.typeReference - if (typeReference != null) { - val result = checkTypeForIs(context, condition, condition.isNegated, subjectType, typeReference, subjectDataFlowValue) - newDataFlowInfo = if (condition.isNegated) { - ConditionalDataFlowInfo(result.elseInfo, result.thenInfo) - } else { - result - } - val rhsType = context.trace[BindingContext.TYPE, typeReference] - if (subjectExpression != null) { - val rttiInformation = RttiExpressionInformation( - subject = subjectExpression, - sourceType = subjectType, + + val typeReference = condition.typeReference ?: return + + val result = checkTypeForIs(context, condition, condition.isNegated, subject.type, typeReference, subject.dataFlowValue) + newDataFlowInfo = if (condition.isNegated) ConditionalDataFlowInfo(result.elseInfo, result.thenInfo) else result + val rhsType = context.trace[BindingContext.TYPE, typeReference] + + if (subject !is Subject.None) { + val rttiInformation = RttiExpressionInformation( + subject = subject.element!!, + sourceType = subject.type, targetType = rhsType, operation = if (condition.isNegated) RttiOperation.NOT_IS else RttiOperation.IS - ) - components.rttiExpressionCheckers.forEach { - it.check(rttiInformation, condition, context.trace) - } + ) + components.rttiExpressionCheckers.forEach { + it.check(rttiInformation, condition, context.trace) } } + } override fun visitWhenConditionWithExpression(condition: KtWhenConditionWithExpression) { - val expression = condition.expression - if (expression != null) { - val basicDataFlowInfo = checkTypeForExpressionCondition( - context, expression, subjectType, subjectExpression, subjectDataFlowValue - ) - val moduleDescriptor = DescriptorUtils.getContainingModule(context.scope.ownerDescriptor) - val dataFlowInfoFromES = - components.effectSystem.getDataFlowInfoWhenEquals(subjectExpression, expression, context.trace, moduleDescriptor) - newDataFlowInfo = basicDataFlowInfo.and(dataFlowInfoFromES) - } + val expression = condition.expression ?: return + 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) } override fun visitKtElement(element: KtElement) { diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.kt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.kt new file mode 100644 index 00000000000..a919270f610 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.kt @@ -0,0 +1,8 @@ +// !LANGUAGE: +VariableDeclarationInWhenSubject + +fun test(x: Any?) = + when (val y = x) { + is String -> "String, length = ${y.length}" + null -> "Null" + else -> "Any, hashCode = ${y.hashCode()}" + } diff --git a/compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.txt b/compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.txt new file mode 100644 index 00000000000..eb5e7e81a82 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.txt @@ -0,0 +1,3 @@ +package + +public fun test(/*0*/ x: kotlin.Any?): kotlin.String diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 45bc52ed56b..bed8dcd4d78 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -22478,6 +22478,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("smartCastsOnSubjectVariable.kt") + public void testSmartCastsOnSubjectVariable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.kt"); + doTest(fileName); + } + @TestMetadata("unsupportedFeature.kt") public void testUnsupportedFeature() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index 2c58d812520..99a0a88a687 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -22478,6 +22478,12 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing doTest(fileName); } + @TestMetadata("smartCastsOnSubjectVariable.kt") + public void testSmartCastsOnSubjectVariable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/smartCastsOnSubjectVariable.kt"); + doTest(fileName); + } + @TestMetadata("unsupportedFeature.kt") public void testUnsupportedFeature() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/withSubjectVariable/unsupportedFeature.kt");