diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt index cfe8db0d4b1..d4fb97b7693 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/WhenChecker.kt @@ -297,7 +297,7 @@ object WhenChecker { } private fun whenSubjectType(expression: KtWhenExpression, context: BindingContext) = - expression.subjectExpression?.let { context.get(SMARTCAST, it) ?: context.getType(it) } ?: null + expression.subjectExpression?.let { context.get(SMARTCAST, it)?.defaultType ?: context.getType(it) } ?: null @JvmStatic fun getEnumMissingCases( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index 4e8ff281346..a1a6b696a1a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemCompleter; import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo; import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue; +import org.jetbrains.kotlin.resolve.calls.smartcasts.ExplicitSmartCasts; import org.jetbrains.kotlin.resolve.calls.smartcasts.ImplicitSmartCasts; import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant; import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics; @@ -143,7 +144,7 @@ public interface BindingContext { WritableSlice> INDEXED_LVALUE_GET = Slices.createSimpleSlice(); WritableSlice> INDEXED_LVALUE_SET = Slices.createSimpleSlice(); - WritableSlice SMARTCAST = Slices.createSimpleSlice(); + WritableSlice SMARTCAST = new BasicWritableSlice(DO_NOTHING); WritableSlice SMARTCAST_NULL = Slices.createSimpleSlice(); WritableSlice IMPLICIT_RECEIVER_SMARTCAST = new BasicWritableSlice(DO_NOTHING); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt index 9c056672cfa..f9402311eff 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CandidateResolver.kt @@ -377,7 +377,8 @@ class CandidateResolver( val spreadElement = argument.getSpreadElement() if (spreadElement != null && !type.isFlexible() && type.isMarkedNullable) { val dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, type, context) - val smartCastResult = SmartCastManager.checkAndRecordPossibleCast(dataFlowValue, expectedType, expression, context, calleeExpression = null, recordExpressionType = false) + val smartCastResult = SmartCastManager.checkAndRecordPossibleCast(dataFlowValue, expectedType, expression, context, + call = null, recordExpressionType = false) if (smartCastResult == null || !smartCastResult.isCorrect) { context.trace.report(Errors.SPREAD_OF_NULLABLE.on(spreadElement)) } @@ -531,7 +532,7 @@ class CandidateResolver( val smartCastResult = SmartCastManager.checkAndRecordPossibleCast( dataFlowValue, expectedReceiverParameterType, { possibleSmartCast -> isCandidateVisibleOrExtensionReceiver(receiverArgument, possibleSmartCast, isDispatchReceiver) }, - expression, this, candidateCall.call.calleeExpression, recordExpressionType = true + expression, this, candidateCall.call, recordExpressionType = true ) if (smartCastResult == null) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ExplicitSmartCasts.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ExplicitSmartCasts.kt new file mode 100644 index 00000000000..dc461b392c0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/ExplicitSmartCasts.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.resolve.calls.smartcasts + +import org.jetbrains.kotlin.psi.Call +import org.jetbrains.kotlin.types.KotlinType + +interface ExplicitSmartCasts { + fun type(call: Call?): KotlinType? + + val defaultType: KotlinType? + + operator fun plus(smartCast: SingleSmartCast): ExplicitSmartCasts +} + +data class SingleSmartCast(val call: Call?, val type: KotlinType) : ExplicitSmartCasts { + override fun type(call: Call?) = if (call == this.call) type else null + + override val defaultType: KotlinType get() = type + + override fun plus(smartCast: SingleSmartCast) = + if (this == smartCast) this + else MultipleSmartCasts(mapOf(call to type, smartCast.call to smartCast.type)) +} + +data class MultipleSmartCasts internal constructor(val map: Map) : ExplicitSmartCasts { + override fun type(call: Call?) = map[call] + + override val defaultType: KotlinType? get() = null + + override fun plus(smartCast: SingleSmartCast) = MultipleSmartCasts(map + mapOf(smartCast.call to smartCast.type)) +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt index 493caa1f584..950fd1de151 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/SmartCastManager.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Errors.SMARTCAST_IMPOSSIBLE +import org.jetbrains.kotlin.psi.Call import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.BindingContext @@ -114,11 +115,14 @@ class SmartCastManager { type: KotlinType, trace: BindingTrace, dataFlowValue: DataFlowValue, + call: Call?, recordExpressionType: Boolean ) { if (KotlinBuiltIns.isNullableNothing(type)) return if (dataFlowValue.isStable) { - trace.record(SMARTCAST, expression, type) + val oldSmartCasts = trace[SMARTCAST, expression] + val newSmartCast = SingleSmartCast(call, type) + trace.record(SMARTCAST, expression, oldSmartCasts?.let { it + newSmartCast } ?: newSmartCast) if (recordExpressionType) { //TODO //Why the expression type is rewritten for receivers and is not rewritten for arguments? Is it necessary? @@ -135,10 +139,10 @@ class SmartCastManager { expectedType: KotlinType, expression: KtExpression?, c: ResolutionContext<*>, - calleeExpression: KtExpression?, + call: Call?, recordExpressionType: Boolean ): SmartCastResult? { - return checkAndRecordPossibleCast(dataFlowValue, expectedType, null, expression, c, calleeExpression, recordExpressionType) + return checkAndRecordPossibleCast(dataFlowValue, expectedType, null, expression, c, call, recordExpressionType) } fun checkAndRecordPossibleCast( @@ -147,13 +151,14 @@ class SmartCastManager { additionalPredicate: ((KotlinType) -> Boolean)?, expression: KtExpression?, c: ResolutionContext<*>, - calleeExpression: KtExpression?, + call: Call?, recordExpressionType: Boolean ): SmartCastResult? { + val calleeExpression = call?.calleeExpression for (possibleType in c.dataFlowInfo.getCollectedTypes(dataFlowValue)) { if (ArgumentTypeResolver.isSubtypeOfForArgumentType(possibleType, expectedType) && (additionalPredicate == null || additionalPredicate(possibleType))) { if (expression != null) { - recordCastOrError(expression, possibleType, c.trace, dataFlowValue, recordExpressionType) + recordCastOrError(expression, possibleType, c.trace, dataFlowValue, call, recordExpressionType) } else if (calleeExpression != null && dataFlowValue.isStable) { val receiver = (dataFlowValue.identifierInfo as? IdentifierInfo.Receiver)?.value @@ -188,12 +193,12 @@ class SmartCastManager { if (ArgumentTypeResolver.isSubtypeOfForArgumentType(dataFlowValue.type, nullableExpectedType) && (additionalPredicate == null || additionalPredicate(dataFlowValue.type))) { if (!immanentlyNotNull && expression != null) { - recordCastOrError(expression, dataFlowValue.type, c.trace, dataFlowValue, recordExpressionType) + recordCastOrError(expression, dataFlowValue.type, c.trace, dataFlowValue, call, recordExpressionType) } return SmartCastResult(dataFlowValue.type, immanentlyNotNull || dataFlowValue.isStable) } - return checkAndRecordPossibleCast(dataFlowValue, nullableExpectedType, expression, c, calleeExpression, recordExpressionType) + return checkAndRecordPossibleCast(dataFlowValue, nullableExpectedType, expression, c, call, recordExpressionType) } return null diff --git a/compiler/testData/diagnostics/tests/smartCasts/multipleResolvedCalls.kt b/compiler/testData/diagnostics/tests/smartCasts/multipleResolvedCalls.kt new file mode 100644 index 00000000000..c269f02b0a6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/multipleResolvedCalls.kt @@ -0,0 +1,19 @@ +interface I1 +interface I2 +interface I3 +interface I4 +interface I5 + +operator fun I1.component1() = 1 +operator fun I2.component2() = 2 +operator fun I3.component3() = 3 +operator fun I4.component4() = 4 +operator fun I5.component5() = 5 + +fun test(x: Any): Int { + if (x is I1 && x is I2 && x is I3 && x is I4 && x is I5) { + val (t1, t2, t3, t4, t5) = x + return t1 + t2 + t3 + t4 + t5 + } + else return 0 +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/multipleResolvedCalls.txt b/compiler/testData/diagnostics/tests/smartCasts/multipleResolvedCalls.txt new file mode 100644 index 00000000000..bfb83e361d3 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/multipleResolvedCalls.txt @@ -0,0 +1,38 @@ +package + +public fun test(/*0*/ x: kotlin.Any): kotlin.Int +public operator fun I1.component1(): kotlin.Int +public operator fun I2.component2(): kotlin.Int +public operator fun I3.component3(): kotlin.Int +public operator fun I4.component4(): kotlin.Int +public operator fun I5.component5(): kotlin.Int + +public interface I1 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I2 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I3 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I4 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +public interface I5 { + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ 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 3bb57c44253..5da9bc89e14 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -17577,6 +17577,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("multipleResolvedCalls.kt") + public void testMultipleResolvedCalls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/multipleResolvedCalls.kt"); + doTest(fileName); + } + @TestMetadata("noErrorCheckForPackageLevelVal.kt") public void testNoErrorCheckForPackageLevelVal() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/noErrorCheckForPackageLevelVal.kt"); diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt index 80e556bed87..105cdccd6d4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/VariablesHighlightingVisitor.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext.* +import org.jetbrains.kotlin.resolve.calls.smartcasts.MultipleSmartCasts import org.jetbrains.kotlin.resolve.calls.tasks.isDynamic import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver @@ -101,9 +102,19 @@ internal class VariablesHighlightingVisitor(holder: AnnotationHolder, bindingCon val smartCast = bindingContext.get(SMARTCAST, expression) if (smartCast != null) { - holder.createInfoAnnotation(getSmartCastTarget(expression), - "Smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)) - .textAttributes = SMART_CAST_VALUE + val defaultType = smartCast.defaultType + if (defaultType != null) { + holder.createInfoAnnotation(getSmartCastTarget(expression), + "Smart cast to " + DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(defaultType)) + .textAttributes = SMART_CAST_VALUE + } + else if (smartCast is MultipleSmartCasts) { + for ((call, type) in smartCast.map) { + holder.createInfoAnnotation(getSmartCastTarget(expression), + "Smart cast to ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(type)} (for $call call)") + .textAttributes = SMART_CAST_VALUE + } + } } super.visitExpression(expression) diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt index 83c40bd976b..a805c322a7e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -160,7 +160,7 @@ private fun KtCodeFragment.markSmartCasts() { val factory = KtPsiFactory(project) getContentElement()?.forEachDescendantOfType { expression -> - val smartCast = bindingContext.get(SMARTCAST, expression) + val smartCast = bindingContext.get(SMARTCAST, expression)?.defaultType if (smartCast != null) { val smartCastedExpression = factory.createExpressionByPattern( "($0 as ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)})", diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt index 7066bf58c08..edd20d7fea4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddFunctionParametersFix.kt @@ -93,7 +93,7 @@ class AddFunctionParametersFix( validator.addName(parameters[i].name.asString()) val argumentType = expression?.let { val bindingContext = it.analyze() - bindingContext[BindingContext.SMARTCAST, it] ?: bindingContext.getType(it) + bindingContext[BindingContext.SMARTCAST, it]?.defaultType ?: bindingContext.getType(it) } val parameterType = parameters[i].type diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt index 52f56e558b4..7975584b0d1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/ExtractionData.kt @@ -210,7 +210,7 @@ data class ExtractionData( val qualifiedExpression = newRef.getQualifiedExpressionForSelector() if (qualifiedExpression != null) { val smartCastTarget = originalResolveResult.originalRefExpr.parent as KtExpression - smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget] + smartCast = originalContext[BindingContext.SMARTCAST, smartCastTarget]?.defaultType possibleTypes = getPossibleTypes(smartCastTarget, originalResolveResult.resolvedCall, originalContext) val receiverDescriptor = (originalResolveResult.resolvedCall?.dispatchReceiver as? ImplicitReceiver)?.declarationDescriptor @@ -221,7 +221,7 @@ data class ExtractionData( } else { if (newRef.getParentOfTypeAndBranch { callableReference } != null) continue - smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr] + smartCast = originalContext[BindingContext.SMARTCAST, originalResolveResult.originalRefExpr]?.defaultType possibleTypes = getPossibleTypes(originalResolveResult.originalRefExpr, originalResolveResult.resolvedCall, originalContext) shouldSkipPrimaryReceiver = false } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt index 88c6636e9b8..62d6a2b4947 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractionEngine/inferParameterInfo.kt @@ -308,7 +308,7 @@ private fun suggestParameterType( } parameterExpression != null -> - (if (useSmartCastsIfPossible) bindingContext[BindingContext.SMARTCAST, parameterExpression] else null) + (if (useSmartCastsIfPossible) bindingContext[BindingContext.SMARTCAST, parameterExpression]?.defaultType else null) ?: bindingContext.getType(parameterExpression) ?: (parameterExpression as? KtReferenceExpression)?.let { (bindingContext[BindingContext.REFERENCE_TARGET, it] as? CallableDescriptor)?.returnType diff --git a/idea/testData/checker/infos/multipleResolvedCalls.kt b/idea/testData/checker/infos/multipleResolvedCalls.kt new file mode 100644 index 00000000000..76a6c1e9152 --- /dev/null +++ b/idea/testData/checker/infos/multipleResolvedCalls.kt @@ -0,0 +1,19 @@ +interface I1 +interface I2 +interface I3 +interface I4 +interface I5 + +operator fun I1.component1() = 1 +operator fun I2.component2() = 2 +operator fun I3.component3() = 3 +operator fun I4.component4() = 4 +operator fun I5.component5() = 5 + +fun test(x: Any): Int { + if (x is I1 && x is I2 && x is I3 && x is I4 && x is I5) { + val (t1, t2, t3, t4, t5) = x + return t1 + t2 + t3 + t4 + t5 + } + else return 0 +} diff --git a/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java b/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java index 51f504336be..e7fc5893458 100644 --- a/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/checkers/PsiCheckerTestGenerated.java @@ -886,6 +886,12 @@ public class PsiCheckerTestGenerated extends AbstractPsiCheckerTest { doTestWithInfos(fileName); } + @TestMetadata("multipleResolvedCalls.kt") + public void testMultipleResolvedCalls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/checker/infos/multipleResolvedCalls.kt"); + doTestWithInfos(fileName); + } + @TestMetadata("PropertiesWithBackingFields.kt") public void testPropertiesWithBackingFields() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/checker/infos/PropertiesWithBackingFields.kt");