Do not lose information about contracts if call uses default value

It is safe to treat DefaultValueArgument as UNKNOWN_COMPUTATION, because
default arguments can't break smartcasts.

Possibly, they can add new ones, but it can be supported later.

^KT-25278 Fixed
This commit is contained in:
Dmitry Savvinov
2018-08-09 14:35:25 +03:00
parent cf62acc6d6
commit 57bafcf9e9
5 changed files with 63 additions and 10 deletions
@@ -34,8 +34,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
@@ -170,14 +169,36 @@ class EffectsExtractingVisitor(
arguments.addIfNotNull(extensionReceiver?.toComputation())
arguments.addIfNotNull(dispatchReceiver?.toComputation())
valueArgumentsByIndex?.mapTo(arguments) {
val valueArgument = (it as? ExpressionValueArgument)?.valueArgument ?: return null
when (valueArgument) {
is KtLambdaArgument -> valueArgument.getLambdaExpression()?.let { ESLambda(it) } ?: return null
else -> extractOrGetCached(valueArgument.getArgumentExpression() ?: return null)
}
} ?: return null
val passedValueArguments = valueArgumentsByIndex ?: return null
passedValueArguments.mapTo(arguments) { it.toComputation() ?: return null }
return arguments
}
}
private fun ResolvedValueArgument.toComputation(): Computation? {
return when (this) {
// Assume that we don't know anything about default arguments
// Note that we don't want to return 'null' here, because 'null' indicates that we can't
// analyze whole call, which is undesired for cases like `kotlin.test.assertNotNull`
is DefaultValueArgument -> UNKNOWN_COMPUTATION
// We prefer to throw away calls with varags completely, just to be safe
// Potentially, we could return UNKNOWN_COMPUTATION here too
is VarargValueArgument -> null
is ExpressionValueArgument -> valueArgument?.toComputation()
// Should be exhaustive
else -> throw IllegalStateException("Unexpected ResolvedValueArgument $this")
}
}
private fun ValueArgument.toComputation(): Computation? {
return when (this) {
is KtLambdaArgument -> getLambdaExpression()?.let { ESLambda(it) }
is KtValueArgument -> getArgumentExpression()?.let { extractOrGetCached(it) }
else -> null
}
}
}
@@ -0,0 +1,16 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseReturnsEffect
// !DIAGNOSTICS: -INVISIBLE_REFERENCE -INVISIBLE_MEMBER
import kotlin.internal.contracts.*
fun myAssert(condition: Boolean, message: String = "") {
contract {
returns() implies (condition)
}
if (!condition) throw kotlin.IllegalArgumentException(message)
}
fun test(x: Any?) {
myAssert(x is String)
<!DEBUG_INFO_SMARTCAST!>x<!>.length
}
@@ -0,0 +1,6 @@
package
public fun myAssert(/*0*/ condition: kotlin.Boolean, /*1*/ message: kotlin.String = ...): kotlin.Unit
Returns(WILDCARD) -> condition
public fun test(/*0*/ x: kotlin.Any?): kotlin.Unit
@@ -1211,6 +1211,11 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("callWithDefaultValue.kt")
public void testCallWithDefaultValue() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/callWithDefaultValue.kt");
}
@TestMetadata("catchExceptionSpilling.kt")
public void testCatchExceptionSpilling() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/catchExceptionSpilling.kt");
@@ -1211,6 +1211,11 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("callWithDefaultValue.kt")
public void testCallWithDefaultValue() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/callWithDefaultValue.kt");
}
@TestMetadata("catchExceptionSpilling.kt")
public void testCatchExceptionSpilling() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/smartcasts/catchExceptionSpilling.kt");