From 905e67e713b5c2f0f504b211b560cb8979d3600a Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Wed, 18 Jan 2017 15:47:21 +0300 Subject: [PATCH] Data flow information: take language version into account while processing safe calls and bound values #KT-14350 Fixed --- .../kotlin/resolve/LocalVariableResolver.kt | 3 +- .../resolve/calls/CallExpressionResolver.kt | 6 ++- .../resolve/calls/smartcasts/DataFlowInfo.kt | 11 ++-- .../smartcasts/DelegatingDataFlowInfo.kt | 46 ++++++++++------ .../BasicExpressionTypingVisitor.java | 29 +++++++---- .../ControlStructureTypingVisitor.java | 17 +++--- .../types/expressions/DataFlowAnalyzer.java | 14 +++-- .../expressions/ExpressionTypingServices.java | 3 +- .../ExpressionTypingVisitorForStatements.java | 2 +- .../PatternMatchingTypingVisitor.kt | 14 +++-- .../expressions/PreliminaryLoopVisitor.kt | 6 ++- .../diagnostics/tests/smartCasts/level_1_0.kt | 52 +++++++++++++++++++ .../tests/smartCasts/level_1_0.txt | 8 +++ .../checkers/DiagnosticsTestGenerated.java | 6 +++ .../kotlin/config/LanguageVersionSettings.kt | 1 + 15 files changed, 165 insertions(+), 53 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/smartCasts/level_1_0.kt create mode 100644 compiler/testData/diagnostics/tests/smartCasts/level_1_0.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt index eba540aa8c7..a04a2bbf9ed 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LocalVariableResolver.kt @@ -110,7 +110,8 @@ class LocalVariableResolver( DescriptorUtils.getContainingModuleOrNull(scope.ownerDescriptor)) // We cannot say here anything new about initializerDataFlowValue // except it has the same value as variableDataFlowValue - typeInfo = typeInfo.replaceDataFlowInfo(dataFlowInfo.assign(variableDataFlowValue, initializerDataFlowValue)) + typeInfo = typeInfo.replaceDataFlowInfo(dataFlowInfo.assign(variableDataFlowValue, initializerDataFlowValue, + languageVersionSettings)) } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt index 1449d62c3f8..ef80268a291 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/CallExpressionResolver.kt @@ -20,6 +20,7 @@ import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor @@ -70,7 +71,8 @@ class CallExpressionResolver( private val argumentTypeResolver: ArgumentTypeResolver, private val dataFlowAnalyzer: DataFlowAnalyzer, private val builtIns: KotlinBuiltIns, - private val qualifiedExpressionResolver: QualifiedExpressionResolver + private val qualifiedExpressionResolver: QualifiedExpressionResolver, + private val languageVersionSettings: LanguageVersionSettings ) { private lateinit var expressionTypingServices: ExpressionTypingServices @@ -323,7 +325,7 @@ class CallExpressionResolver( // Additional "receiver != null" information should be applied if we consider a safe call if (receiverCanBeNull) { initialDataFlowInfoForArguments = initialDataFlowInfoForArguments.disequate( - receiverDataFlowValue, DataFlowValue.nullValue(builtIns)) + receiverDataFlowValue, DataFlowValue.nullValue(builtIns), languageVersionSettings) } else if (receiver is ReceiverValue) { reportUnnecessarySafeCall(context.trace, receiver.type, element.node, receiver) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt index 5f94a011779..01ee09571e4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DataFlowInfo.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts import com.google.common.collect.SetMultimap +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.types.KotlinType /** @@ -61,25 +62,25 @@ interface DataFlowInfo { * Call this function to clear all data flow information about * the given data flow value. Useful when we are not sure how this value can be changed, e.g. in a loop. */ - fun clearValueInfo(value: DataFlowValue): DataFlowInfo + fun clearValueInfo(value: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo /** * Call this function when b is assigned to a */ - fun assign(a: DataFlowValue, b: DataFlowValue): DataFlowInfo + fun assign(a: DataFlowValue, b: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo /** * Call this function when it's known than a == b. * sameTypes should be true iff we have guarantee that a and b have the same type */ - fun equate(a: DataFlowValue, b: DataFlowValue, sameTypes: Boolean): DataFlowInfo + fun equate(a: DataFlowValue, b: DataFlowValue, sameTypes: Boolean, languageVersionSettings: LanguageVersionSettings): DataFlowInfo /** * Call this function when it's known than a != b */ - fun disequate(a: DataFlowValue, b: DataFlowValue): DataFlowInfo + fun disequate(a: DataFlowValue, b: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo - fun establishSubtyping(value: DataFlowValue, type: KotlinType): DataFlowInfo + fun establishSubtyping(value: DataFlowValue, type: KotlinType, languageVersionSettings: LanguageVersionSettings): DataFlowInfo /** * Call this function to add data flow information from other to this and return sum as the result diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt index f552422a080..c197ba2c11d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/smartcasts/DelegatingDataFlowInfo.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.resolve.calls.smartcasts import com.google.common.collect.* import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils @@ -85,21 +87,25 @@ internal class DelegatingDataFlowInfo private constructor( nullabilityInfo[key] ?: parent?.getCollectedNullability(key) ?: key.immanentNullability } - private fun putNullability(map: MutableMap, value: DataFlowValue, - nullability: Nullability, affectReceiver: Boolean = true): Boolean { + private fun putNullability(map: MutableMap, + value: DataFlowValue, + nullability: Nullability, + languageVersionSettings: LanguageVersionSettings, + affectReceiver: Boolean = true): Boolean { map.put(value, nullability) val identifierInfo = value.identifierInfo - if (affectReceiver && !nullability.canBeNull()) { + if (affectReceiver && !nullability.canBeNull() && + languageVersionSettings.supportsFeature(LanguageFeature.SafeCallBoundSmartCasts)) { when (identifierInfo) { is IdentifierInfo.Qualified -> { val receiverType = identifierInfo.receiverType if (identifierInfo.safe && receiverType != null) { - putNullability(map, DataFlowValue(identifierInfo.receiverInfo, receiverType), nullability) + putNullability(map, DataFlowValue(identifierInfo.receiverInfo, receiverType), nullability, languageVersionSettings) } } is IdentifierInfo.Variable -> identifierInfo.bound?.let { - putNullability(map, it, nullability) + putNullability(map, it, nullability, languageVersionSettings) } } } @@ -138,16 +144,16 @@ internal class DelegatingDataFlowInfo private constructor( * @param value */ - override fun clearValueInfo(value: DataFlowValue): DataFlowInfo { + override fun clearValueInfo(value: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo { val builder = Maps.newHashMap() - putNullability(builder, value, Nullability.UNKNOWN) + putNullability(builder, value, Nullability.UNKNOWN, languageVersionSettings) return create(this, ImmutableMap.copyOf(builder), EMPTY_TYPE_INFO, value) } - override fun assign(a: DataFlowValue, b: DataFlowValue): DataFlowInfo { + override fun assign(a: DataFlowValue, b: DataFlowValue, languageVersionSettings: LanguageVersionSettings): DataFlowInfo { val nullability = Maps.newHashMap() val nullabilityOfB = getStableNullability(b) - putNullability(nullability, a, nullabilityOfB, affectReceiver = false) + putNullability(nullability, a, nullabilityOfB, languageVersionSettings, affectReceiver = false) val newTypeInfo = newTypeInfo() var typesForB = getStableTypes(b) @@ -163,13 +169,15 @@ internal class DelegatingDataFlowInfo private constructor( return create(this, ImmutableMap.copyOf(nullability), if (newTypeInfo.isEmpty) EMPTY_TYPE_INFO else newTypeInfo, a) } - override fun equate(a: DataFlowValue, b: DataFlowValue, sameTypes: Boolean): DataFlowInfo { + override fun equate( + a: DataFlowValue, b: DataFlowValue, sameTypes: Boolean, languageVersionSettings: LanguageVersionSettings + ): DataFlowInfo { val builder = Maps.newHashMap() val nullabilityOfA = getStableNullability(a) val nullabilityOfB = getStableNullability(b) - var changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB)) or - putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA)) + var changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB), languageVersionSettings) or + putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA), languageVersionSettings) // NB: == has no guarantees of type equality, see KT-11280 for the example val newTypeInfo = newTypeInfo() @@ -219,17 +227,21 @@ internal class DelegatingDataFlowInfo private constructor( return types } - override fun disequate(a: DataFlowValue, b: DataFlowValue): DataFlowInfo { + override fun disequate( + a: DataFlowValue, b: DataFlowValue, languageVersionSettings: LanguageVersionSettings + ): DataFlowInfo { val builder = Maps.newHashMap() val nullabilityOfA = getStableNullability(a) val nullabilityOfB = getStableNullability(b) - val changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB.invert())) or - putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA.invert())) + val changed = putNullability(builder, a, nullabilityOfA.refine(nullabilityOfB.invert()), languageVersionSettings) or + putNullability(builder, b, nullabilityOfB.refine(nullabilityOfA.invert()), languageVersionSettings) return if (changed) create(this, ImmutableMap.copyOf(builder), EMPTY_TYPE_INFO) else this } - override fun establishSubtyping(value: DataFlowValue, type: KotlinType): DataFlowInfo { + override fun establishSubtyping( + value: DataFlowValue, type: KotlinType, languageVersionSettings: LanguageVersionSettings + ): DataFlowInfo { if (value.type == type) return this if (getCollectedTypes(value).contains(type)) return this if (!value.type.isFlexible() && value.type.isSubtypeOf(type)) return this @@ -237,7 +249,7 @@ internal class DelegatingDataFlowInfo private constructor( newTypeInfo.put(value, type) val builder = Maps.newHashMap() if (!type.isMarkedNullable) { - putNullability(builder, value, NOT_NULL) + putNullability(builder, value, NOT_NULL, languageVersionSettings) } val newNullabilityInfo = if (type.isMarkedNullable) EMPTY_NULLABILITY_INFO else ImmutableMap.copyOf(builder) return create(this, newNullabilityInfo, newTypeInfo) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 2af3eadccc0..cae9028a5b8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.KtNodeTypes; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.config.LanguageFeature; +import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.diagnostics.Diagnostic; import org.jetbrains.kotlin.diagnostics.Errors; @@ -182,7 +183,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { if (resultType != null) { DataFlowValue innerValue = DataFlowValueFactory.createDataFlowValue(innerExpression, resultType, context); DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(expression, resultType, context); - result = result.replaceDataFlowInfo(result.getDataFlowInfo().assign(resultValue, innerValue)); + result = result.replaceDataFlowInfo(result.getDataFlowInfo().assign(resultValue, innerValue, + components.languageVersionSettings)); } return result; } @@ -316,7 +318,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { DataFlowInfo dataFlowInfo = typeInfo.getDataFlowInfo(); if (operationType == AS_KEYWORD) { DataFlowValue value = createDataFlowValue(left, subjectType, context); - typeInfo = typeInfo.replaceDataFlowInfo(dataFlowInfo.establishSubtyping(value, targetType)); + typeInfo = typeInfo.replaceDataFlowInfo(dataFlowInfo.establishSubtyping(value, targetType, + components.languageVersionSettings)); } } @@ -859,7 +862,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { result = receiverType; // Also record data flow information for x++ value (= x) DataFlowValue returnValue = DataFlowValueFactory.createDataFlowValue(expression, receiverType, contextWithExpectedType); - typeInfo = typeInfo.replaceDataFlowInfo(typeInfo.getDataFlowInfo().assign(returnValue, receiverValue)); + typeInfo = typeInfo.replaceDataFlowInfo(typeInfo.getDataFlowInfo().assign(returnValue, receiverValue, + components.languageVersionSettings)); } } } @@ -911,7 +915,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { } else { DataFlowValue value = createDataFlowValue(baseExpression, baseType, context); - baseTypeInfo = baseTypeInfo.replaceDataFlowInfo(dataFlowInfo.disequate(value, DataFlowValue.nullValue(components.builtIns))); + baseTypeInfo = baseTypeInfo.replaceDataFlowInfo(dataFlowInfo.disequate(value, DataFlowValue.nullValue(components.builtIns), + components.languageVersionSettings)); } KotlinType resultingType = TypeUtils.makeNotNullable(baseType); if (context.contextDependency == DEPENDENT) { @@ -1320,16 +1325,19 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { DataFlowValue nullValue = DataFlowValue.nullValue(components.builtIns); // left argument is considered not-null if it's not-null also in right part or if we have jump in right part if (jumpInRight || !rightDataFlowInfo.getStableNullability(leftValue).canBeNull()) { - dataFlowInfo = dataFlowInfo.disequate(leftValue, nullValue); + dataFlowInfo = dataFlowInfo.disequate(leftValue, nullValue, components.languageVersionSettings); if (left instanceof KtBinaryExpressionWithTypeRHS) { - dataFlowInfo = establishSubtypingForTypeRHS((KtBinaryExpressionWithTypeRHS) left, dataFlowInfo, context); + dataFlowInfo = establishSubtypingForTypeRHS((KtBinaryExpressionWithTypeRHS) left, dataFlowInfo, context, + components.languageVersionSettings); } } DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(expression, type, context); - dataFlowInfo = dataFlowInfo.assign(resultValue, leftValue).disequate(resultValue, nullValue); + dataFlowInfo = + dataFlowInfo.assign(resultValue, leftValue, components.languageVersionSettings) + .disequate(resultValue, nullValue, components.languageVersionSettings); if (!jumpInRight) { DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, context); - rightDataFlowInfo = rightDataFlowInfo.assign(resultValue, rightValue); + rightDataFlowInfo = rightDataFlowInfo.assign(resultValue, rightValue, components.languageVersionSettings); dataFlowInfo = dataFlowInfo.or(rightDataFlowInfo); } } @@ -1354,7 +1362,8 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { private static DataFlowInfo establishSubtypingForTypeRHS( @NotNull KtBinaryExpressionWithTypeRHS left, @NotNull DataFlowInfo dataFlowInfo, - @NotNull ExpressionTypingContext context + @NotNull ExpressionTypingContext context, + @NotNull LanguageVersionSettings languageVersionSettings ) { IElementType operationType = left.getOperationReference().getReferencedNameElementType(); if (operationType == AS_SAFE) { @@ -1364,7 +1373,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { DataFlowValue underSafeAsValue = createDataFlowValue(underSafeAs, underSafeAsType, context); KotlinType targetType = context.trace.get(BindingContext.TYPE, left.getRight()); if (targetType != null) { - return dataFlowInfo.establishSubtyping(underSafeAsValue, targetType); + return dataFlowInfo.establishSubtyping(underSafeAsValue, targetType, languageVersionSettings); } } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java index 8de289cc79e..d8b95687fb2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ControlStructureTypingVisitor.java @@ -155,9 +155,9 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { if (resultType != null && thenType != null && elseType != null) { DataFlowValue resultValue = DataFlowValueFactory.createDataFlowValue(ifExpression, resultType, context); DataFlowValue thenValue = DataFlowValueFactory.createDataFlowValue(thenBranch, thenType, context); - thenDataFlowInfo = thenDataFlowInfo.assign(resultValue, thenValue); + thenDataFlowInfo = thenDataFlowInfo.assign(resultValue, thenValue, components.languageVersionSettings); DataFlowValue elseValue = DataFlowValueFactory.createDataFlowValue(elseBranch, elseType, context); - elseDataFlowInfo = elseDataFlowInfo.assign(resultValue, elseValue); + elseDataFlowInfo = elseDataFlowInfo.assign(resultValue, elseValue, components.languageVersionSettings); } loopBreakContinuePossible |= thenTypeInfo.getJumpOutPossible() || elseTypeInfo.getJumpOutPossible(); @@ -229,7 +229,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { // Preliminary analysis PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression); context = context.replaceDataFlowInfo( - loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo) + loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo, components.languageVersionSettings) ); KtExpression condition = expression.getCondition(); @@ -261,7 +261,8 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { if (body != null && KtPsiUtil.isTrueConstant(condition)) { // We should take data flow info from the first jump point, // but without affecting changing variables - dataFlowInfo = dataFlowInfo.and(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(bodyTypeInfo.getJumpFlowInfo())); + dataFlowInfo = dataFlowInfo.and(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(bodyTypeInfo.getJumpFlowInfo(), + components.languageVersionSettings)); } return components.dataFlowAnalyzer .checkType(bodyTypeInfo.replaceType(components.builtIns.getUnitType()), expression, contextWithExpectedType) @@ -325,7 +326,7 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { // Preliminary analysis PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression); context = context.replaceDataFlowInfo( - loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo) + loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo, components.languageVersionSettings) ); // Here we must record data flow information at the end of the body (or at the first jump, to be precise) and // .and it with entrance data flow information, because do-while body is executed at least once @@ -369,7 +370,8 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { if (body != null) { // We should take data flow info from the first jump point, // but without affecting changing variables - dataFlowInfo = dataFlowInfo.and(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(bodyTypeInfo.getJumpFlowInfo())); + dataFlowInfo = dataFlowInfo.and(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(bodyTypeInfo.getJumpFlowInfo(), + components.languageVersionSettings)); } return components.dataFlowAnalyzer .checkType(bodyTypeInfo.replaceType(components.builtIns.getUnitType()), expression, contextWithExpectedType) @@ -388,7 +390,8 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor { contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT); // Preliminary analysis PreliminaryLoopVisitor loopVisitor = PreliminaryLoopVisitor.visitLoop(expression); - context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo)); + context = context.replaceDataFlowInfo(loopVisitor.clearDataFlowInfoForAssignedLocalVariables(context.dataFlowInfo, + components.languageVersionSettings)); KtExpression loopRange = expression.getLoopRange(); KotlinType expectedParameterType = null; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java index 14701d604a0..e54c25f488a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/DataFlowAnalyzer.java @@ -22,6 +22,7 @@ import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; +import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.diagnostics.DiagnosticUtilsKt; import org.jetbrains.kotlin.incremental.KotlinLookupLocation; @@ -55,19 +56,22 @@ public class DataFlowAnalyzer { private final KotlinBuiltIns builtIns; private final SmartCastManager smartCastManager; private final ExpressionTypingFacade facade; + private final LanguageVersionSettings languageVersionSettings; public DataFlowAnalyzer( @NotNull Iterable additionalTypeCheckers, @NotNull ConstantExpressionEvaluator constantExpressionEvaluator, @NotNull KotlinBuiltIns builtIns, @NotNull SmartCastManager smartCastManager, - @NotNull ExpressionTypingFacade facade + @NotNull ExpressionTypingFacade facade, + @NotNull LanguageVersionSettings languageVersionSettings ) { this.additionalTypeCheckers = additionalTypeCheckers; this.constantExpressionEvaluator = constantExpressionEvaluator; this.builtIns = builtIns; this.smartCastManager = smartCastManager; this.facade = facade; + this.languageVersionSettings = languageVersionSettings; } // NB: use this method only for functions from 'Any' @@ -176,10 +180,14 @@ public class DataFlowAnalyzer { if (equals == conditionValue) { // this means: equals && conditionValue || !equals && !conditionValue boolean byIdentity = operationToken == KtTokens.EQEQEQ || operationToken == KtTokens.EXCLEQEQEQ || typeHasEqualsFromAny(lhsType, condition); - result.set(context.dataFlowInfo.equate(leftValue, rightValue, byIdentity).and(expressionFlowInfo)); + result.set(context.dataFlowInfo + .equate(leftValue, rightValue, byIdentity, languageVersionSettings) + .and(expressionFlowInfo)); } else { - result.set(context.dataFlowInfo.disequate(leftValue, rightValue).and(expressionFlowInfo)); + result.set(context.dataFlowInfo + .disequate(leftValue, rightValue, languageVersionSettings) + .and(expressionFlowInfo)); } } else { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java index 57745cd9c12..918084ae65a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingServices.java @@ -274,7 +274,8 @@ public class ExpressionTypingServices { statementExpression, result.getType(), context); DataFlowValue blockExpressionValue = DataFlowValueFactory.createDataFlowValue( (KtBlockExpression) statementExpression.getParent(), result.getType(), context); - result = result.replaceDataFlowInfo(result.getDataFlowInfo().assign(blockExpressionValue, lastExpressionValue)); + result = result.replaceDataFlowInfo(result.getDataFlowInfo().assign(blockExpressionValue, lastExpressionValue, + expressionTypingComponents.languageVersionSettings)); } } else { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java index 03c336085a2..4f0431070f5 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/ExpressionTypingVisitorForStatements.java @@ -355,7 +355,7 @@ public class ExpressionTypingVisitorForStatements extends ExpressionTypingVisito DataFlowValue leftValue = DataFlowValueFactory.createDataFlowValue(left, expectedType, context); DataFlowValue rightValue = DataFlowValueFactory.createDataFlowValue(right, rightType, context); // We cannot say here anything new about rightValue except it has the same value as leftValue - resultInfo = resultInfo.replaceDataFlowInfo(dataFlowInfo.assign(leftValue, rightValue)); + resultInfo = resultInfo.replaceDataFlowInfo(dataFlowInfo.assign(leftValue, rightValue, components.languageVersionSettings)); } } else { 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 6027cbcfa17..65d80b23cac 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PatternMatchingTypingVisitor.kt @@ -214,7 +214,7 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping val entryDataFlowInfo = if (whenResultValue != null && entryType != null) { val entryValue = DataFlowValueFactory.createDataFlowValue(entryExpression, entryType, contextAfterSubject) - entryTypeInfo.dataFlowInfo.assign(whenResultValue, entryValue) + entryTypeInfo.dataFlowInfo.assign(whenResultValue, entryValue, components.languageVersionSettings) } else { entryTypeInfo.dataFlowInfo @@ -407,8 +407,12 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping val result = noChange(newContext) return ConditionalDataFlowInfo( result.thenInfo.equate(subjectDataFlowValue, expressionDataFlowValue, - DataFlowAnalyzer.typeHasEqualsFromAny(subjectType, expression)), - result.elseInfo.disequate(subjectDataFlowValue, expressionDataFlowValue)) + DataFlowAnalyzer.typeHasEqualsFromAny(subjectType, expression), + components.languageVersionSettings), + result.elseInfo.disequate(subjectDataFlowValue, + expressionDataFlowValue, + components.languageVersionSettings) + ) } private fun checkTypeForIs( @@ -438,7 +442,9 @@ class PatternMatchingTypingVisitor internal constructor(facade: ExpressionTyping if (CastDiagnosticsUtil.isCastErased(subjectType, targetType, KotlinTypeChecker.DEFAULT)) { context.trace.report(Errors.CANNOT_CHECK_FOR_ERASED.on(typeReferenceAfterIs, targetType)) } - return ConditionalDataFlowInfo(context.dataFlowInfo.establishSubtyping(subjectDataFlowValue, targetType), context.dataFlowInfo) + return context.dataFlowInfo.let { + ConditionalDataFlowInfo(it.establishSubtyping(subjectDataFlowValue, targetType, components.languageVersionSettings), it) + } } private fun noChange(context: ExpressionTypingContext) = ConditionalDataFlowInfo(context.dataFlowInfo) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt index 993be46b376..1c02e5673ab 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/PreliminaryLoopVisitor.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.types.expressions +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.psi.KtLoopExpression import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo @@ -29,7 +30,8 @@ import java.util.* */ class PreliminaryLoopVisitor private constructor() : AssignedVariablesSearcher() { - fun clearDataFlowInfoForAssignedLocalVariables(dataFlowInfo: DataFlowInfo): DataFlowInfo { + fun clearDataFlowInfoForAssignedLocalVariables(dataFlowInfo: DataFlowInfo, + languageVersionSettings: LanguageVersionSettings): DataFlowInfo { var resultFlowInfo = dataFlowInfo val nullabilityMap = resultFlowInfo.completeNullabilityInfo val valueSetToClear = LinkedHashSet() @@ -44,7 +46,7 @@ class PreliminaryLoopVisitor private constructor() : AssignedVariablesSearcher() } } for (valueToClear in valueSetToClear) { - resultFlowInfo = resultFlowInfo.clearValueInfo(valueToClear) + resultFlowInfo = resultFlowInfo.clearValueInfo(valueToClear, languageVersionSettings) } return resultFlowInfo } diff --git a/compiler/testData/diagnostics/tests/smartCasts/level_1_0.kt b/compiler/testData/diagnostics/tests/smartCasts/level_1_0.kt new file mode 100644 index 00000000000..5e61f53943b --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/level_1_0.kt @@ -0,0 +1,52 @@ +// !LANGUAGE: -SafeCallBoundSmartCasts + +fun foo(arg: Int?) { + val x = arg + if (x != null) { + arg.hashCode() + } + val y: Any? = arg + if (y != null) { + arg.hashCode() + } + val yy: Any? + yy = arg + if (yy != null) { + arg.hashCode() + } + var z = arg + z = z?.let { 42 } + if (z != null) { + arg.hashCode() + } +} + +fun kt6840_1(s: String?) { + val hash = s?.hashCode() + if (hash != null) { + s.length + } +} + +fun kt6840_2(s: String?) { + if (s?.hashCode() != null) { + s.length + } +} + +fun kt1635(s: String?) { + s?.hashCode()!! + s.hashCode() +} + +fun kt2127() { + val s: String? = "" + if (s?.length != null) { + s.hashCode() + } +} + +fun kt3356(s: String?): Int { + if (s?.length != 1) return 0 + return s.length +} diff --git a/compiler/testData/diagnostics/tests/smartCasts/level_1_0.txt b/compiler/testData/diagnostics/tests/smartCasts/level_1_0.txt new file mode 100644 index 00000000000..e038e7ea036 --- /dev/null +++ b/compiler/testData/diagnostics/tests/smartCasts/level_1_0.txt @@ -0,0 +1,8 @@ +package + +public fun foo(/*0*/ arg: kotlin.Int?): kotlin.Unit +public fun kt1635(/*0*/ s: kotlin.String?): kotlin.Unit +public fun kt2127(): kotlin.Unit +public fun kt3356(/*0*/ s: kotlin.String?): kotlin.Int +public fun kt6840_1(/*0*/ s: kotlin.String?): kotlin.Unit +public fun kt6840_2(/*0*/ s: kotlin.String?): kotlin.Unit diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 440569c10ef..5b9e234341e 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -19453,6 +19453,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("level_1_0.kt") + public void testLevel_1_0() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/level_1_0.kt"); + doTest(fileName); + } + @TestMetadata("localClassChanges.kt") public void testLocalClassChanges() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/smartCasts/localClassChanges.kt"); diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 4a04b108528..c7e93fe3a09 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -39,6 +39,7 @@ enum class LanguageFeature(val sinceVersion: LanguageVersion?) { OperatorProvideDelegate(KOTLIN_1_1), ShortSyntaxForPropertyGetters(KOTLIN_1_1), RefinedSamAdaptersPriority(KOTLIN_1_1), + SafeCallBoundSmartCasts(KOTLIN_1_1), // Experimental features MultiPlatformProjects(null),