FIR DFA: fix logic clear aliasing

The logic should clear back aliases as well. To ensure all back aliases
don't lose any information, statements on the original variable are
copied over to its aliases.
This commit is contained in:
Tianyu Geng
2021-07-26 21:44:46 -07:00
committed by teamcityserver
parent e495c722c7
commit 7e2f15f532
14 changed files with 233 additions and 9 deletions
@@ -28217,6 +28217,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt"); runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt");
} }
@Test
@TestMetadata("aliasing.kt")
public void testAliasing() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/aliasing.kt");
}
@Test @Test
public void testAllFilesPresentInVariables() throws Exception { public void testAllFilesPresentInVariables() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -28217,6 +28217,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt"); runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt");
} }
@Test
@TestMetadata("aliasing.kt")
public void testAliasing() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/aliasing.kt");
}
@Test @Test
public void testAllFilesPresentInVariables() throws Exception { public void testAllFilesPresentInVariables() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -396,7 +396,7 @@ private fun checkApplicabilityForArgumentType(
if (!csBuilder.addSubtypeConstraintIfCompatible(argumentType, expectedType, position)) { if (!csBuilder.addSubtypeConstraintIfCompatible(argumentType, expectedType, position)) {
val smartcastExpression = argument as? FirExpressionWithSmartcast val smartcastExpression = argument as? FirExpressionWithSmartcast
if (smartcastExpression != null && smartcastExpression.smartcastStability != SmartcastStability.STABLE_VALUE) { if (smartcastExpression != null && !smartcastExpression.isStable) {
val unstableType = smartcastExpression.smartcastType.coneType val unstableType = smartcastExpression.smartcastType.coneType
if (csBuilder.addSubtypeConstraintIfCompatible(unstableType, expectedType, position)) { if (csBuilder.addSubtypeConstraintIfCompatible(unstableType, expectedType, position)) {
sink.reportDiagnostic(UnstableSmartCast(smartcastExpression, expectedType)) sink.reportDiagnostic(UnstableSmartCast(smartcastExpression, expectedType))
@@ -202,13 +202,68 @@ abstract class PersistentLogicSystem(context: ConeInferenceContext) : LogicSyste
) )
} }
/**
* For example, consider `var b = a`. In this case, `b` is an alias of `a`. In other words, `a` is the original variable of `b`.
*
* For back alias, consider the following.
* ```
* var b = a
* var c = b
* ```
* Here, if the current `alias` references `b`, `c` is a back alias of `b`. So if one calls this method with `b`, we must also
* remove aliasing between `b` and `c`. But before removing aliasing, we need to copy any statements that apply to `b` to `c`.
*/
override fun removeLocalVariableAlias(flow: PersistentFlow, alias: RealVariable) { override fun removeLocalVariableAlias(flow: PersistentFlow, alias: RealVariable) {
val original = flow.directAliasMap[alias]?.variable ?: return val backAliases = flow.backwardsAliasMap[alias] ?: emptyList()
flow.directAliasMap = flow.directAliasMap.remove(alias) for (backAlias in backAliases) {
val variables = flow.backwardsAliasMap.getValue(original) flow.logicStatements[alias]?.let { it ->
flow.backwardsAliasMap = flow.backwardsAliasMap.put(original, variables - alias) val newStatements = it.map { it.replaceVariable(alias, backAlias) }
val replacedStatements =
flow.logicStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements.toPersistentList()
flow.logicStatements = flow.logicStatements.put(backAlias, replacedStatements)
}
flow.approvedTypeStatements[alias]?.let { it ->
val newStatements = it.replaceVariable(alias, backAlias)
val replacedStatements =
flow.approvedTypeStatements[backAlias]?.let { existing -> existing + newStatements } ?: newStatements
flow.approvedTypeStatements = flow.approvedTypeStatements.put(backAlias, replacedStatements.toPersistent())
}
flow.approvedTypeStatementsDiff[alias]?.let { it ->
val newStatements = it.replaceVariable(alias, backAlias)
val replacedStatements =
flow.approvedTypeStatementsDiff[backAlias]?.let { existing -> existing + newStatements } ?: newStatements
flow.approvedTypeStatementsDiff = flow.approvedTypeStatementsDiff.put(backAlias, replacedStatements.toPersistent())
}
}
val original = flow.directAliasMap[alias]?.variable
if (original != null) {
flow.directAliasMap = flow.directAliasMap.remove(alias)
val variables = flow.backwardsAliasMap.getValue(original)
flow.backwardsAliasMap = flow.backwardsAliasMap.put(original, variables - alias)
}
flow.backwardsAliasMap = flow.backwardsAliasMap.remove(alias)
for (backAlias in backAliases) {
flow.directAliasMap = flow.directAliasMap.remove(backAlias)
}
} }
private fun Implication.replaceVariable(from: RealVariable, to: RealVariable): Implication {
return Implication(condition.replaceVariable(from, to), effect.replaceVariable(from, to))
}
private fun <T : Statement<T>> Statement<T>.replaceVariable(from: RealVariable, to: RealVariable): T {
val statement = when (this) {
is OperationStatement -> if (variable == from) copy(variable = to) else this
is PersistentTypeStatement -> if (variable == from) copy(variable = to) else this
is MutableTypeStatement -> if (variable == from) MutableTypeStatement(to, exactType, exactNotType) else this
else -> throw IllegalArgumentException("unknown type of statement $this")
}
@Suppress("UNCHECKED_CAST")
return statement as T
}
@OptIn(DfaInternals::class) @OptIn(DfaInternals::class)
private fun PersistentFlow.getApprovedTypeStatements(variable: RealVariable): MutableTypeStatement { private fun PersistentFlow.getApprovedTypeStatements(variable: RealVariable): MutableTypeStatement {
var flow = this var flow = this
@@ -882,4 +882,5 @@ open class FirDeclarationsResolveTransformer(transformer: FirBodyResolveTransfor
private val FirFunction.bodyResolved: Boolean private val FirFunction.bodyResolved: Boolean
get() = body !is FirLazyBlock && body?.typeRef is FirResolvedTypeRef get() = body !is FirLazyBlock && body?.typeRef is FirResolvedTypeRef
} }
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirConstExpression import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.types.impl.FirImplicitBuiltinTypeRef import org.jetbrains.kotlin.fir.types.impl.FirImplicitBuiltinTypeRef
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
@@ -51,6 +52,14 @@ val FirExpression.isNullLiteral: Boolean
this.value == null && this.value == null &&
this.source != null this.source != null
@OptIn(ExperimentalContracts::class)
fun FirExpression.isStableSmartcast(): Boolean {
contract {
returns(true) implies (this@isStableSmartcast is FirExpressionWithSmartcast)
}
return this is FirExpressionWithSmartcast && this.isStable
}
private val FirTypeRef.classLikeTypeOrNull: ConeClassLikeType? private val FirTypeRef.classLikeTypeOrNull: ConeClassLikeType?
get() = when (this) { get() = when (this) {
is FirImplicitBuiltinTypeRef -> type is FirImplicitBuiltinTypeRef -> type
@@ -0,0 +1,65 @@
fun test() {
var a: Any? = null
var b = a
var c = b
// Now both `b` and `c` are aliases of `a`.
if (a is String) {
b.length // OK
c.length // OK
}
if (b is String) {
a.length // OK
c.length // OK
}
if (c is String) {
a.length // OK
b.length // OK
}
b = 3 // break `b` -> `a`
if (a is String) {
b.<!UNRESOLVED_REFERENCE!>length<!> // error
c.length // OK, since `c` is aliased to `a`
}
if (b is String) {
a.<!UNRESOLVED_REFERENCE!>length<!> // error
c.<!UNRESOLVED_REFERENCE!>length<!> // error
}
if (c is String) {
a.length // OK, since `c` is alised to `a`
b.<!UNRESOLVED_REFERENCE!>length<!> // error
}
a = 2 // break `c` -> `a`
if (a is String) {
b.<!UNRESOLVED_REFERENCE!>length<!> // error
c.<!UNRESOLVED_REFERENCE!>length<!> // error
}
if (b is String) {
a.<!UNRESOLVED_REFERENCE!>length<!> // error
c.<!UNRESOLVED_REFERENCE!>length<!> // error
}
if (c is String) {
a.<!UNRESOLVED_REFERENCE!>length<!> // error
b.<!UNRESOLVED_REFERENCE!>length<!> // error
}
c = b // create aliasing `c` -> `b`
c.unaryPlus() // OK due to aliasing
b = ""
c.unaryPlus() // OK since `c` should carry all typing information that was on `b` before `b = ""`.
c = ""
c.length // OK
c.<!UNRESOLVED_REFERENCE!>unaryPlus<!>() // error
}
fun test2() {
var a: Any? = null
a = ""
var b = a
a = 3
b.length // OK
b.<!UNRESOLVED_REFERENCE!>unaryPlus<!>() // error
}
@@ -0,0 +1,65 @@
fun test() {
var a: Any? = null
var b = a
var c = b
// Now both `b` and `c` are aliases of `a`.
if (a is String) {
b.<!UNRESOLVED_REFERENCE!>length<!> // OK
c.<!UNRESOLVED_REFERENCE!>length<!> // OK
}
if (b is String) {
a.<!UNRESOLVED_REFERENCE!>length<!> // OK
c.<!UNRESOLVED_REFERENCE!>length<!> // OK
}
if (c is String) {
a.<!UNRESOLVED_REFERENCE!>length<!> // OK
b.<!UNRESOLVED_REFERENCE!>length<!> // OK
}
b = 3 // break `b` -> `a`
if (a is String) {
b.<!UNRESOLVED_REFERENCE!>length<!> // error
c.<!UNRESOLVED_REFERENCE!>length<!> // OK, since `c` is aliased to `a`
}
if (<!USELESS_IS_CHECK!>b is String<!>) {
a.<!UNRESOLVED_REFERENCE!>length<!> // error
c.<!UNRESOLVED_REFERENCE!>length<!> // error
}
if (c is String) {
a.<!UNRESOLVED_REFERENCE!>length<!> // OK, since `c` is alised to `a`
b.<!UNRESOLVED_REFERENCE!>length<!> // error
}
a = 2 // break `c` -> `a`
if (<!USELESS_IS_CHECK!>a is String<!>) {
b.<!UNRESOLVED_REFERENCE!>length<!> // error
c.<!UNRESOLVED_REFERENCE!>length<!> // error
}
if (<!USELESS_IS_CHECK!>b is String<!>) {
a.<!UNRESOLVED_REFERENCE!>length<!> // error
c.<!UNRESOLVED_REFERENCE!>length<!> // error
}
if (c is String) {
a.<!UNRESOLVED_REFERENCE!>length<!> // error
b.<!UNRESOLVED_REFERENCE!>length<!> // error
}
c = b // create aliasing `c` -> `b`
<!DEBUG_INFO_SMARTCAST!>c<!>.unaryPlus() // OK due to aliasing
b = ""
<!DEBUG_INFO_SMARTCAST!>c<!>.unaryPlus() // OK since `c` should carry all typing information that was on `b` before `b = ""`.
c = ""
<!DEBUG_INFO_SMARTCAST!>c<!>.length // OK
c.<!UNRESOLVED_REFERENCE!>unaryPlus<!>() // error
}
fun test2() {
var a: Any? = <!VARIABLE_WITH_REDUNDANT_INITIALIZER!>null<!>
a = ""
var b = a
a = 3
<!DEBUG_INFO_SMARTCAST!>b<!>.length // OK
b.<!UNRESOLVED_REFERENCE!>unaryPlus<!>() // error
}
@@ -0,0 +1,4 @@
package
public fun test(): kotlin.Unit
public fun test2(): kotlin.Unit
@@ -5,7 +5,7 @@ fun foo() {
val y = x val y = x
x = null x = null
if (y != null) { if (y != null) {
x.hashCode() x<!UNSAFE_CALL!>.<!>hashCode()
} }
} }
@@ -11,7 +11,7 @@ operator fun Long?.inc() = this?.let { it + 1 }
fun bar(arg: Long?): Long { fun bar(arg: Long?): Long {
var i = arg var i = arg
if (i++ == 5L) { if (i++ == 5L) {
return i<!UNSAFE_CALL!>--<!> + i return i<!UNSAFE_CALL!>--<!> <!UNSAFE_OPERATOR_CALL!>+<!> i
} }
if (i++ == 7L) { if (i++ == 7L) {
return i++ <!UNSAFE_OPERATOR_CALL!>+<!> i return i++ <!UNSAFE_OPERATOR_CALL!>+<!> i
@@ -28307,6 +28307,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt"); runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt");
} }
@Test
@TestMetadata("aliasing.kt")
public void testAliasing() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/aliasing.kt");
}
@Test @Test
public void testAllFilesPresentInVariables() throws Exception { public void testAllFilesPresentInVariables() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -28217,6 +28217,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt"); runTest("compiler/testData/diagnostics/tests/smartCasts/variables/accessorAndFunction.kt");
} }
@Test
@TestMetadata("aliasing.kt")
public void testAliasing() throws Exception {
runTest("compiler/testData/diagnostics/tests/smartCasts/variables/aliasing.kt");
}
@Test @Test
public void testAllFilesPresentInVariables() throws Exception { public void testAllFilesPresentInVariables() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true); KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/smartCasts/variables"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kts?$"), true);
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneTypeSafe import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.fir.types.isStableSmartcast
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirSafe import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirSafe
import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartCast import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartCast
import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartcastKind import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartcastKind
@@ -41,13 +42,13 @@ internal class KtFirSmartcastProvider(
(extensionReceiver !is FirExpressionWithSmartcast || !extensionReceiver.isStable) (extensionReceiver !is FirExpressionWithSmartcast || !extensionReceiver.isStable)
) return emptyList() ) return emptyList()
buildList { buildList {
(dispatchReceiver as? FirExpressionWithSmartcast)?.takeIf { it.isStable }?.let { smartCasted -> dispatchReceiver.takeIf { it.isStableSmartcast() }?.let { smartCasted ->
ImplicitReceiverSmartCast( ImplicitReceiverSmartCast(
smartCasted.typeRef.coneTypeSafe<ConeKotlinType>()?.asKtType() ?: return@let null, smartCasted.typeRef.coneTypeSafe<ConeKotlinType>()?.asKtType() ?: return@let null,
ImplicitReceiverSmartcastKind.DISPATCH ImplicitReceiverSmartcastKind.DISPATCH
) )
}?.let(::add) }?.let(::add)
(extensionReceiver as? FirExpressionWithSmartcast)?.takeIf { it.isStable }?.let { smartCasted -> extensionReceiver.takeIf { it.isStableSmartcast() }?.let { smartCasted ->
ImplicitReceiverSmartCast( ImplicitReceiverSmartCast(
smartCasted.typeRef.coneTypeSafe<ConeKotlinType>()?.asKtType() ?: return@let null, smartCasted.typeRef.coneTypeSafe<ConeKotlinType>()?.asKtType() ?: return@let null,
ImplicitReceiverSmartcastKind.EXTENSION ImplicitReceiverSmartcastKind.EXTENSION