diff --git a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt index 4526ff61d06..99db8d0ba54 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/PartialBodyResolveFilter.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.resolve.lazy import com.intellij.psi.PsiElement +import com.intellij.psi.PsiRecursiveElementVisitor import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -38,6 +39,7 @@ class PartialBodyResolveFilter( private val statementMarks = StatementMarks() private val globalProbablyNothingCallableNames = ProbablyNothingCallableNames.getInstance(declaration.project) + private val globalProbablyContractedCallableNames = ProbablyContractedCallableNames.getInstance(declaration.project) private val contextNothingFunctionNames = HashSet() private val contextNothingVariableNames = HashSet() @@ -169,6 +171,19 @@ class PartialBodyResolveFilter( } } + override fun visitCallExpression(expression: KtCallExpression) { + super.visitCallExpression(expression) + + val nameReference = expression.calleeExpression as? KtNameReferenceExpression ?: return + if (!globalProbablyContractedCallableNames.isProbablyContractedCallableName(nameReference.getReferencedName())) return + + val mentionedSmartCastName = expression.findMentionedName(filter) + + if (mentionedSmartCastName != null) { + addPlace(mentionedSmartCastName, expression) + } + } + override fun visitBinaryWithTypeRHSExpression(expression: KtBinaryExpressionWithTypeRHS) { expression.acceptChildren(this) @@ -259,6 +274,25 @@ class PartialBodyResolveFilter( return map } + private fun KtExpression.findMentionedName(filter: (SmartCastName) -> Boolean): SmartCastName? { + var foundMentionedName: SmartCastName? = null + + val visitor = object : PsiRecursiveElementVisitor() { + override fun visitElement(element: PsiElement) { + if (foundMentionedName != null) return + + if (element !is KtSimpleNameExpression) super.visitElement(element) + if (element !is KtExpression) return + + element.smartCastExpressionName()?.takeIf(filter)?.let { foundMentionedName = it } + } + } + + accept(visitor) + + return foundMentionedName + } + /** * Returns names of expressions that would possibly be smart cast * in then (first component) and else (second component) diff --git a/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/ProbablyContractedCallableNames.kt b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/ProbablyContractedCallableNames.kt new file mode 100644 index 00000000000..49ffd7ca700 --- /dev/null +++ b/idea/ide-common/src/org/jetbrains/kotlin/resolve/lazy/ProbablyContractedCallableNames.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.lazy + +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.project.Project + +interface ProbablyContractedCallableNames { + fun isProbablyContractedCallableName(name: String): Boolean + + companion object { + fun getInstance(project: Project): ProbablyContractedCallableNames = + ServiceManager.getService(project, ProbablyContractedCallableNames::class.java)!! + } +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ProbablyContractedCallableNamesImpl.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ProbablyContractedCallableNamesImpl.kt new file mode 100644 index 00000000000..79ea72a0d4f --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ProbablyContractedCallableNamesImpl.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.project + +import com.intellij.openapi.project.Project +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.PsiModificationTracker +import org.jetbrains.kotlin.idea.stubindex.KotlinProbablyContractedFunctionShortNameIndex +import org.jetbrains.kotlin.resolve.lazy.ProbablyContractedCallableNames + +class ProbablyContractedCallableNamesImpl(project: Project) : ProbablyContractedCallableNames { + private val functionNames = CachedValuesManager.getManager(project).createCachedValue( + { + CachedValueProvider.Result.create( + KotlinProbablyContractedFunctionShortNameIndex.getInstance().getAllKeys(project), + PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT + ) + }, + false + ) + + override fun isProbablyContractedCallableName(name: String): Boolean = name in functionNames.value +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/KotlinProbablyContractedFunctionShortNameIndex.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/KotlinProbablyContractedFunctionShortNameIndex.kt new file mode 100644 index 00000000000..e3b346d6485 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/stubindex/KotlinProbablyContractedFunctionShortNameIndex.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.stubindex + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndex +import com.intellij.psi.stubs.StubIndexKey +import org.jetbrains.kotlin.psi.KtNamedFunction + +class KotlinProbablyContractedFunctionShortNameIndex : StringStubIndexExtension() { + override fun getKey(): StubIndexKey = KEY + + override fun get(name: String, project: Project, scope: GlobalSearchScope): MutableCollection = + StubIndex.getElements(KEY, name, project, scope, KtNamedFunction::class.java) + + companion object { + private val KEY: StubIndexKey = + KotlinIndexUtil.createIndexKey(KotlinProbablyContractedFunctionShortNameIndex::class.java) + + private val ourInstance = KotlinProbablyContractedFunctionShortNameIndex() + + @JvmStatic + fun getInstance(): KotlinProbablyContractedFunctionShortNameIndex = ourInstance + } +} \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/smartCast/functionWithContract.kt b/idea/idea-completion/testData/basic/common/smartCast/functionWithContract.kt new file mode 100644 index 00000000000..861896b47c3 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/smartCast/functionWithContract.kt @@ -0,0 +1,15 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+ReadDeserializedContracts -XXLanguage:+UseReturnsEffect + +interface Foo { + val x: Int + + fun f() +} + +fun test(x: Any?) { + require(x is Foo) + x. +} + +// EXIST: x +// EXIST: f diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index 8c217f6e31d..1c8d519c480 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -2218,6 +2218,11 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/smartCast"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("functionWithContract.kt") + public void testFunctionWithContract() throws Exception { + runTest("idea/idea-completion/testData/basic/common/smartCast/functionWithContract.kt"); + } + @TestMetadata("MemberExtensionAfterThisSmartCast.kt") public void testMemberExtensionAfterThisSmartCast() throws Exception { runTest("idea/idea-completion/testData/basic/common/smartCast/MemberExtensionAfterThisSmartCast.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 9a2625dc0dc..48ee073b548 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -2218,6 +2218,11 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/smartCast"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("functionWithContract.kt") + public void testFunctionWithContract() throws Exception { + runTest("idea/idea-completion/testData/basic/common/smartCast/functionWithContract.kt"); + } + @TestMetadata("MemberExtensionAfterThisSmartCast.kt") public void testMemberExtensionAfterThisSmartCast() throws Exception { runTest("idea/idea-completion/testData/basic/common/smartCast/MemberExtensionAfterThisSmartCast.kt"); diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 6b448f7e7c2..facef8ea4c8 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -354,6 +354,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -701,6 +704,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index a0e9e3e824c..06a953a13bd 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -354,6 +354,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -698,6 +701,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 186ba6cbb5c..86b0c9052c5 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -354,6 +354,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -701,6 +704,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index cd37d19e7e6..9cd3d615c78 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -355,6 +355,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -702,6 +705,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 800529b5a53..ced665e82e7 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -354,6 +354,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -701,6 +704,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index fc3c0d9bff4..3d5482fbbcb 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -354,6 +354,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -701,6 +704,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/src/META-INF/plugin.xml.as33 b/idea/src/META-INF/plugin.xml.as33 index e230d495ea8..edda657a83a 100644 --- a/idea/src/META-INF/plugin.xml.as33 +++ b/idea/src/META-INF/plugin.xml.as33 @@ -353,6 +353,9 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + + @@ -700,6 +703,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/testData/resolve/partialBodyResolve/ContractFromBinary.completion b/idea/testData/resolve/partialBodyResolve/ContractFromBinary.completion new file mode 100644 index 00000000000..74b6db26850 --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/ContractFromBinary.completion @@ -0,0 +1,17 @@ +Resolve target: value-parameter x: kotlin.Any? smart-cast to kotlin.String +---------------------------------------------- +// COMPILER_ARGUMENTS: -XXLanguage:+ReadDeserializedContracts -XXLanguage:+UseReturnsEffect +package test + +fun irrelevantConsume(y: Any?) {} + +fun testContractFromBinaryDependency(x: Any?, y: Any?) { + require(x is String) + + require(y is String) + /* STATEMENT DELETED: irrelevantConsume(x) */ + + x.length + + /* STATEMENT DELETED: require(x is Int) */ +} diff --git a/idea/testData/resolve/partialBodyResolve/ContractFromBinary.dump b/idea/testData/resolve/partialBodyResolve/ContractFromBinary.dump new file mode 100644 index 00000000000..41ed5d60a2b --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/ContractFromBinary.dump @@ -0,0 +1,17 @@ +Resolve target: value-parameter x: kotlin.Any? smart-cast to kotlin.String +---------------------------------------------- +// COMPILER_ARGUMENTS: -XXLanguage:+ReadDeserializedContracts -XXLanguage:+UseReturnsEffect +package test + +fun irrelevantConsume(y: Any?) {} + +fun testContractFromBinaryDependency(x: Any?, y: Any?) { + require(x is String) + + /* STATEMENT DELETED: require(y is String) */ + /* STATEMENT DELETED: irrelevantConsume(x) */ + + x.length + + /* STATEMENT DELETED: require(x is Int) */ +} \ No newline at end of file diff --git a/idea/testData/resolve/partialBodyResolve/ContractFromBinary.kt b/idea/testData/resolve/partialBodyResolve/ContractFromBinary.kt new file mode 100644 index 00000000000..017239a4dea --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/ContractFromBinary.kt @@ -0,0 +1,15 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+ReadDeserializedContracts -XXLanguage:+UseReturnsEffect +package test + +fun irrelevantConsume(y: Any?) {} + +fun testContractFromBinaryDependency(x: Any?, y: Any?) { + require(x is String) + + require(y is String) + irrelevantConsume(x) + + x.length + + require(x is Int) +} \ No newline at end of file diff --git a/idea/testData/resolve/partialBodyResolve/ContractFromSource.completion b/idea/testData/resolve/partialBodyResolve/ContractFromSource.completion new file mode 100644 index 00000000000..4710faa24e3 --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/ContractFromSource.completion @@ -0,0 +1,22 @@ +Resolve target: value-parameter x: kotlin.Any? smart-cast to kotlin.String +---------------------------------------------- +// COMPILER_ARGUMENTS: -XXLanguage:+AllowContractsForCustomFunctions -XXLanguage:+UseReturnsEffect +@file:Suppress("INVISIBLE_MEMBER") +package test + +import kotlin.internal.contracts.* + +fun myRequire(x: Boolean) { + contract { + returns() implies x + } +} + +fun testContractFromSource(x: Any?, y: Any?) { + myRequire(x is String) + myRequire(y is String) + + x.length + + /* STATEMENT DELETED: myRequire(x is Int) */ +} diff --git a/idea/testData/resolve/partialBodyResolve/ContractFromSource.dump b/idea/testData/resolve/partialBodyResolve/ContractFromSource.dump new file mode 100644 index 00000000000..938100b2f93 --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/ContractFromSource.dump @@ -0,0 +1,22 @@ +Resolve target: value-parameter x: kotlin.Any? smart-cast to kotlin.String +---------------------------------------------- +// COMPILER_ARGUMENTS: -XXLanguage:+AllowContractsForCustomFunctions -XXLanguage:+UseReturnsEffect +@file:Suppress("INVISIBLE_MEMBER") +package test + +import kotlin.internal.contracts.* + +fun myRequire(x: Boolean) { + contract { + returns() implies x + } +} + +fun testContractFromSource(x: Any?, y: Any?) { + myRequire(x is String) + /* STATEMENT DELETED: myRequire(y is String) */ + + x.length + + /* STATEMENT DELETED: myRequire(x is Int) */ +} diff --git a/idea/testData/resolve/partialBodyResolve/ContractFromSource.kt b/idea/testData/resolve/partialBodyResolve/ContractFromSource.kt new file mode 100644 index 00000000000..700ffac9d0a --- /dev/null +++ b/idea/testData/resolve/partialBodyResolve/ContractFromSource.kt @@ -0,0 +1,20 @@ +// COMPILER_ARGUMENTS: -XXLanguage:+AllowContractsForCustomFunctions -XXLanguage:+UseReturnsEffect +@file:Suppress("INVISIBLE_MEMBER") +package test + +import kotlin.internal.contracts.* + +fun myRequire(x: Boolean) { + contract { + returns() implies x + } +} + +fun testContractFromSource(x: Any?, y: Any?) { + myRequire(x is String) + myRequire(y is String) + + x.length + + myRequire(x is Int) +} \ No newline at end of file diff --git a/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.completion b/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.completion index 12b0bad85aa..35d9459df6c 100644 --- a/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.completion +++ b/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.completion @@ -9,7 +9,10 @@ fun foo(p: Int) { val v2 = v1 * v1 val v3 = v1 * v2 - /* STATEMENT DELETED: run { val v2 = 1 println(v2) } */ + run { + val v2 = 1 + println(v2) + } print(v2) diff --git a/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.dump b/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.dump index 54003178edb..a1790a07d96 100644 --- a/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.dump +++ b/idea/testData/resolve/partialBodyResolve/DeclarationsBefore.dump @@ -9,7 +9,10 @@ fun foo(p: Int) { val v2 = v1 * v1 /* STATEMENT DELETED: val v3 = v1 * v2 */ - /* STATEMENT DELETED: run { val v2 = 1 println(v2) } */ + run { + val v2 = 1 + println(v2) + } print(v2) diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java index cfe6a874b16..492a5a7672f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/PartialBodyResolveTestGenerated.java @@ -59,6 +59,16 @@ public class PartialBodyResolveTestGenerated extends AbstractPartialBodyResolveT runTest("idea/testData/resolve/partialBodyResolve/ClassInitializerHasNoValue.kt"); } + @TestMetadata("ContractFromBinary.kt") + public void testContractFromBinary() throws Exception { + runTest("idea/testData/resolve/partialBodyResolve/ContractFromBinary.kt"); + } + + @TestMetadata("ContractFromSource.kt") + public void testContractFromSource() throws Exception { + runTest("idea/testData/resolve/partialBodyResolve/ContractFromSource.kt"); + } + @TestMetadata("DeclarationsBefore.kt") public void testDeclarationsBefore() throws Exception { runTest("idea/testData/resolve/partialBodyResolve/DeclarationsBefore.kt");