KT-17074: Make completion respect DslMarker's

#KT-17074 fixed
This commit is contained in:
Simon Ogorodnik
2017-07-06 00:32:32 +03:00
parent 3c04ca4397
commit cc8933c82b
12 changed files with 460 additions and 24 deletions
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.resolve.calls.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.diagnostics.Errors
@@ -31,6 +30,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
import org.jetbrains.kotlin.types.KotlinType
object DslScopeViolationCallChecker : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
@@ -55,28 +55,29 @@ object DslScopeViolationCallChecker : CallChecker {
if (receiversUntilOneFromTheCall.isEmpty()) return
val callDslMarkers = callImplicitReceiver.extractDslMarkerFqNames()
val callDslMarkers = callImplicitReceiver.type.extractDslMarkerFqNames()
if (callDslMarkers.isEmpty()) return
val closestAnotherReceiverWithSameDslMarker =
receiversUntilOneFromTheCall.firstOrNull { receiver -> receiver.extractDslMarkerFqNames().any(callDslMarkers::contains) }
receiversUntilOneFromTheCall.firstOrNull { receiver -> receiver.type.extractDslMarkerFqNames().any(callDslMarkers::contains) }
if (closestAnotherReceiverWithSameDslMarker != null) {
// TODO: report receivers configuration (what's one is used and what's one is the closest)
context.trace.report(Errors.DSL_SCOPE_VIOLATION.on(reportOn, resolvedCall.resultingDescriptor))
}
}
}
private fun ReceiverValue.extractDslMarkerFqNames(): Set<FqName> {
val result = mutableSetOf<FqName>()
fun KotlinType.extractDslMarkerFqNames(): Set<FqName> {
val result = mutableSetOf<FqName>()
result.addAll(type.annotations.extractDslMarkerFqNames())
result.addAll(annotations.extractDslMarkerFqNames())
type.constructor.declarationDescriptor?.getAllSuperClassifiers()?.asIterable()
?.flatMapTo(result) { it.annotations.extractDslMarkerFqNames() }
constructor.declarationDescriptor?.getAllSuperClassifiers()?.asIterable()
?.flatMapTo(result) { it.annotations.extractDslMarkerFqNames() }
return result
}
return result
}
private fun Annotations.extractDslMarkerFqNames() =
@@ -210,7 +210,7 @@ sealed class CallTypeAndReceiver<TReceiver : KtElement?, out TCallType : CallTyp
}
}
data class ReceiverType(val type: KotlinType, val receiverIndex: Int)
data class ReceiverType(val type: KotlinType, val receiverIndex: Int, val implicit: Boolean = false)
fun CallTypeAndReceiver<*, *>.receiverTypes(
bindingContext: BindingContext,
@@ -227,7 +227,8 @@ fun CallTypeAndReceiver<*, *>.receiverTypesWithIndex(
contextElement: PsiElement,
moduleDescriptor: ModuleDescriptor,
resolutionFacade: ResolutionFacade,
stableSmartCastsOnly: Boolean
stableSmartCastsOnly: Boolean,
withImplicitReceiversWhenExplicitPresent: Boolean = false
): Collection<ReceiverType>? {
val receiverExpression: KtExpression?
when (this) {
@@ -279,25 +280,35 @@ fun CallTypeAndReceiver<*, *>.receiverTypesWithIndex(
else -> throw RuntimeException() //TODO: see KT-9394
}
val receiverValues = if (receiverExpression != null) {
val resolutionScope = contextElement.getResolutionScope(bindingContext, resolutionFacade)
val expressionReceiver = receiverExpression?.let {
val receiverType =
bindingContext.getType(receiverExpression) ?:
(bindingContext.get(BindingContext.QUALIFIER, receiverExpression) as? ClassQualifier)?.descriptor?.classValueType ?:
(bindingContext.get(BindingContext.QUALIFIER, receiverExpression) as? TypeAliasQualifier)?.classDescriptor?.classValueType ?:
return emptyList()
listOf(ExpressionReceiver.create(receiverExpression, receiverType, bindingContext))
}
else {
val resolutionScope = contextElement.getResolutionScope(bindingContext, resolutionFacade)
resolutionScope.getImplicitReceiversWithInstance().map { it.value }
ExpressionReceiver.create(receiverExpression, receiverType, bindingContext)
}
val implicitReceiverValues = resolutionScope.getImplicitReceiversWithInstance().map { it.value }
val dataFlowInfo = bindingContext.getDataFlowInfoBefore(contextElement)
val result = ArrayList<ReceiverType>()
for ((receiverIndex, receiverValue) in receiverValues.withIndex()) {
var receiverIndex = 0
fun addReceiverType(receiverValue: ReceiverValue, implicit: Boolean) {
val types = receiverValueTypes(receiverValue, dataFlowInfo, bindingContext, moduleDescriptor, stableSmartCastsOnly)
types.mapTo(result) { ReceiverType(it, receiverIndex) }
types.mapTo(result) { ReceiverType(it, receiverIndex, implicit) }
receiverIndex++
}
if (withImplicitReceiversWhenExplicitPresent || expressionReceiver == null) {
implicitReceiverValues.forEach { addReceiverType(it, true) }
}
if (expressionReceiver != null) {
addReceiverType(expressionReceiver, false)
}
return result
}
@@ -27,6 +27,7 @@ import com.intellij.patterns.PatternCondition
import com.intellij.patterns.StandardPatterns
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.ModuleOrigin
import org.jetbrains.kotlin.idea.caches.resolve.OriginCapability
@@ -36,10 +37,13 @@ import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.core.*
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.checkers.DslScopeViolationCallChecker.extractDslMarkerFqNames
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
@@ -48,6 +52,7 @@ import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import java.util.*
import kotlin.collections.LinkedHashMap
class CompletionSessionConfiguration(
val useBetterPrefixMatcherForNonImportedClasses: Boolean,
@@ -374,12 +379,30 @@ abstract class CompletionSession(
callTypeAndReceiver: CallTypeAndReceiver<*, *>): Collection<ReceiverType>? {
var receiverTypes = callTypeAndReceiver.receiverTypesWithIndex(
bindingContext, nameExpression, moduleDescriptor, resolutionFacade,
stableSmartCastsOnly = true /* we don't include smart cast receiver types for "unstable" receiver value to mark members grayed */)
stableSmartCastsOnly = true, /* we don't include smart cast receiver types for "unstable" receiver value to mark members grayed */
withImplicitReceiversWhenExplicitPresent = true)
if (callTypeAndReceiver is CallTypeAndReceiver.SAFE || isDebuggerContext) {
receiverTypes = receiverTypes?.map { ReceiverType(it.type.makeNotNullable(), it.receiverIndex) }
}
if (receiverTypes != null && nameExpression.languageVersionSettings.supportsFeature(LanguageFeature.DslMarkersSupport)) {
val typesByDslScopes = LinkedHashMap<FqName, MutableList<ReceiverType>>()
receiverTypes
.mapNotNull { receiver ->
val dslMarkers = receiver.type.extractDslMarkerFqNames()
(receiver to dslMarkers).takeIf { dslMarkers.isNotEmpty() }
}
.forEach { (v, dslMarkers) -> dslMarkers.forEach { typesByDslScopes.getOrPut(it, { mutableListOf() }) += v } }
val shadowedDslReceivers = mutableSetOf<ReceiverType>()
typesByDslScopes.flatMapTo(shadowedDslReceivers) { (_, v) -> v.asSequence().drop(1).asIterable() }
receiverTypes -= shadowedDslReceivers
}
return receiverTypes
}
}
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.descriptorUtil.overriddenTreeUniqueAsSequence
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.findOriginalTopMostOverriddenDescriptors
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver
import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.types.KotlinType
@@ -47,6 +48,7 @@ import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.ifEmpty
import java.util.*
interface AbstractLookupElementFactory {
@@ -347,9 +349,26 @@ class LookupElementFactory(
receiverTypes: Collection<ReceiverType>,
onReceiverTypeMismatch: CallableWeight?
): CallableWeight? {
val receiverParameter = extensionReceiverParameter
?: dispatchReceiverParameter
?: return null
val bothReceivers = listOfNotNull(extensionReceiverParameter, dispatchReceiverParameter)
val receiverTypesForFirstReceiver = receiverTypes.filterNot { it.implicit }.ifEmpty { receiverTypes }
val weights = bothReceivers.zip(generateSequence(receiverTypesForFirstReceiver) { receiverTypes }.asIterable())
.map { (receiverParameter, receiverTypes) ->
callableWeightBasedOnReceiver(receiverTypes, onReceiverTypeMismatch, receiverParameter)
}
if (weights.any { it == onReceiverTypeMismatch }) return onReceiverTypeMismatch
return weights.firstOrNull()
}
private fun CallableDescriptor.callableWeightBasedOnReceiver(
receiverTypes: Collection<ReceiverType>,
onReceiverTypeMismatch: CallableWeight?,
receiverParameter: ReceiverParameterDescriptor
): CallableWeight? {
if ((receiverParameter.value as? TransientReceiver)?.type?.isFunctionType == true) return null
val matchingReceiverIndices = HashSet<Int>()
var bestReceiverType: ReceiverType? = null
@@ -0,0 +1,60 @@
@DslMarker
annotation class SimpleDsl
@SimpleDsl
class DslRoot {
fun container(body: DslContainer.() -> Unit) {
body(DslContainer())
}
}
@SimpleDsl
class DslContainer {
fun child(body: DslChild.() -> Unit) {
body(DslChild())
}
fun otherChild(body: DslOtherChild.() -> Unit) {
body(DslOtherChild())
}
}
@SimpleDsl
class DslChild {
operator fun String.unaryMinus() {
println(this)
}
}
@DslMarker
annotation class SimpleOtherDsl
@SimpleOtherDsl
class DslOtherChild {
fun otherThing() {
}
}
fun dsl(body: DslRoot.() -> Unit) {
body(DslRoot())
}
fun test() {
dsl {
container {
child {
-"Some"
}
otherChild {
otherThing()
<caret>
}
}
}
}
// EXIST: {"lookupString":"otherThing", "attributes": ["bold"]}
// EXIST: {"lookupString":"child", "attributes": ["bold"]}
// EXIST: {"lookupString":"otherChild", "attributes": ["bold"]}
@@ -0,0 +1,47 @@
@DslMarker
annotation class SimpleDsl
class DslRoot {
}
interface DslContainer
@SimpleDsl
class DslContainerOne : DslContainer {
fun DslContainer.one() {
println(this)
}
}
@SimpleDsl
class DslContainerTwo : DslContainer {
fun DslContainer.two() {
println(this)
}
}
fun DslRoot.containerOne(body: DslContainerOne.() -> Unit) {
body(DslContainerOne())
}
fun DslRoot.containerTwo(body: DslContainerTwo.() -> Unit) {
body(DslContainerTwo())
}
fun dsl(body: DslRoot.() -> Unit) {
body(DslRoot())
}
fun test() {
dsl {
containerOne {
containerTwo {
<caret>
}
}
}
}
// EXIST: {"lookupString":"two", "attributes":""}
// EXIST: {"lookupString":"one", "attributes":["grayed"]}
@@ -0,0 +1,41 @@
@DslMarker
annotation class SimpleDsl
@SimpleDsl
class DslRoot {
fun container(body: DslContainer.() -> Unit) {
body(DslContainer())
}
}
@SimpleDsl
class DslContainer {
fun child(body: DslChild.() -> Unit) {
body(DslChild())
}
}
@SimpleDsl
class DslChild {
operator fun String.unaryMinus() {
println(this)
}
}
fun dsl(body: DslRoot.() -> Unit) {
body(DslRoot())
}
fun test() {
dsl {
container {
child {
-"Some"
<caret>
}
}
}
}
// EXIST: {"lookupString":"container", "attributes":["grayed"]}
// EXIST: {"lookupString":"child", "attributes":["grayed"]}
@@ -0,0 +1,71 @@
@DslMarker
annotation class SimpleDsl
@SimpleDsl
class DslRoot {
fun container(body: DslContainer.() -> Unit) {
body(DslContainer())
}
}
@SimpleDsl
class DslContainer {
fun child(body: DslChild.() -> Unit) {
body(DslChild())
}
fun otherChild(body: DslOtherChild.() -> Unit) {
body(DslOtherChild())
}
fun anotherChild(body: DslAnotherChild.() -> Unit) {
body(DslAnotherChild())
}
}
@SimpleDsl
class DslChild {
operator fun String.unaryMinus() {
println(this)
}
}
@DslMarker
annotation class SimpleOtherDsl
@SimpleOtherDsl
class DslOtherChild {
fun otherThing() {
}
}
@SimpleOtherDsl @SimpleDsl
class DslAnotherChild {
}
fun dsl(body: DslRoot.() -> Unit) {
body(DslRoot())
}
fun test() {
dsl {
container {
child {
-"Some"
}
otherChild {
otherThing()
anotherChild {
<caret>
}
}
}
}
}
// EXIST: {lookupString:"otherThing", attributes:["grayed"]}
// EXIST: {lookupString:"child", attributes:["grayed"]}
// EXIST: {lookupString:"otherChild", attributes:["grayed"]}
@@ -0,0 +1,38 @@
@DslMarker
annotation class SimpleDsl
@SimpleDsl
class DslRoot {
fun container(body: DslContainer.() -> Unit) {
body(DslContainer())
}
}
@SimpleDsl
class DslContainer {
fun child(body: DslChild.() -> Unit) {
body(DslChild())
}
}
@SimpleDsl
class DslChild {
operator fun String.unaryMinus() {
println(this)
}
}
fun dsl(body: DslRoot.() -> Unit) {
body(DslRoot())
}
fun test() {
dsl {
container {
<caret>
}
}
}
// EXIST: {"lookupString":"child", "attributes":["bold"]}
// EXIST: {"lookupString":"container", "attributes":["grayed"]}
@@ -0,0 +1,35 @@
@DslMarker
annotation class SimpleDsl
@SimpleDsl
class DslRoot {
fun container(body: DslContainer.() -> Unit) {
body(DslContainer())
}
}
@SimpleDsl
class DslContainer {
fun child(body: DslChild.() -> Unit) {
body(DslChild())
}
}
@SimpleDsl
class DslChild {
operator fun String.unaryMinus() {
println(this)
}
}
fun dsl(body: DslRoot.() -> Unit) {
body(DslRoot())
}
fun test() {
dsl {
<caret>
}
}
// EXIST: {"lookupString":"container", "attributes":["bold"]}
@@ -1310,6 +1310,51 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/dslMarker")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class DslMarker extends AbstractJSBasicCompletionTest {
@TestMetadata("2dslsInsideOtherChild.kt")
public void test2dslsInsideOtherChild() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/2dslsInsideOtherChild.kt");
doTest(fileName);
}
@TestMetadata("2receivers.kt")
public void test2receivers() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/2receivers.kt");
doTest(fileName);
}
public void testAllFilesPresentInDslMarker() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/dslMarker"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("child.kt")
public void testChild() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/child.kt");
doTest(fileName);
}
@TestMetadata("compositeDsl.kt")
public void testCompositeDsl() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/compositeDsl.kt");
doTest(fileName);
}
@TestMetadata("container.kt")
public void testContainer() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/container.kt");
doTest(fileName);
}
@TestMetadata("root.kt")
public void testRoot() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/root.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -1310,6 +1310,51 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/dslMarker")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class DslMarker extends AbstractJvmBasicCompletionTest {
@TestMetadata("2dslsInsideOtherChild.kt")
public void test2dslsInsideOtherChild() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/2dslsInsideOtherChild.kt");
doTest(fileName);
}
@TestMetadata("2receivers.kt")
public void test2receivers() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/2receivers.kt");
doTest(fileName);
}
public void testAllFilesPresentInDslMarker() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/basic/common/dslMarker"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("child.kt")
public void testChild() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/child.kt");
doTest(fileName);
}
@TestMetadata("compositeDsl.kt")
public void testCompositeDsl() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/compositeDsl.kt");
doTest(fileName);
}
@TestMetadata("container.kt")
public void testContainer() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/container.kt");
doTest(fileName);
}
@TestMetadata("root.kt")
public void testRoot() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/basic/common/dslMarker/root.kt");
doTest(fileName);
}
}
@TestMetadata("idea/idea-completion/testData/basic/common/extensionFunctionTypeValues")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)