Allow to force-require syntetic SAM-adapters even in NI

This is needed for some IDE clients, particularly, completion: even
though presenting only non-converted member (e.g., 'foo(Consumer<Int>')
is nominally OK, as resolution with NI is smart enough to accept 'foo { }'
for such a call, it is inconvenient for users (for example, hitting
enter would insert round brackets instead of a figure brackets)

This commit adds very-very narrow API (borderline hacky) in
JavaSyntheticScopes, to allow clients explicitly ask for a scopes with
force-enabled synthetic conversions. It fixes several tests, which had
started to fail after corresponding commit about NI and SAM-adapters
(fe5976d7f4), e.g.:
- Java8BasicCompletionTestGenerated.testCollectionMethods
- Java8BasicCompletionTestGenerated.testStreamMethods
- JvmBasicCompletionTestGenerated$Common$StaticMembers.testJavaStaticMethods
- JvmBasicCompletionTestGenerated$Java.testSAMAdaptersStatic
- JvmWithLibBasicCompletionTestGenerated.testSamAdapter
- JvmWithLibBasicCompletionTestGenerated.testSamAdapterAndGenerics

Note that changes are made in ReferenceVariantsHelper, which is used by
several other clients in IDE. Presumably, those changes are needed for
them too.
This commit is contained in:
Dmitry Savvinov
2019-05-29 14:32:39 +03:00
parent eda975d317
commit dca23f871c
8 changed files with 146 additions and 36 deletions
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMemberDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.util.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.forceEnableSamAdapters
import org.jetbrains.kotlin.idea.core.extension.KotlinIndicesHelperExtension
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.frontendService
@@ -466,7 +467,7 @@ class KotlinIndicesHelper(
processor(descriptor)
// SAM-adapter
val syntheticScopes = resolutionFacade.getFrontendService(SyntheticScopes::class.java)
val syntheticScopes = resolutionFacade.getFrontendService(SyntheticScopes::class.java).forceEnableSamAdapters()
syntheticScopes.collectSyntheticStaticFunctions(container.staticScope, descriptor.name, NoLookupLocation.FROM_IDE)
.filterIsInstance<SamAdapterDescriptor<*>>()
.firstOrNull { it.baseDescriptorForSynthetic.original == descriptor.original }
@@ -22,11 +22,15 @@ import com.intellij.psi.impl.source.codeStyle.CodeEditUtil
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.builtins.isFunctionOrSuspendFunctionType
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.extensions.DeclarationAttributeAltererExtension
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.hasJvmFieldAnnotation
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
@@ -41,6 +45,7 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.OverridingUtil
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
import org.jetbrains.kotlin.resolve.calls.components.SamConversionTransformer
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
@@ -131,25 +136,61 @@ fun KtCallExpression.canMoveLambdaOutsideParentheses(): Boolean {
if (callee is KtNameReferenceExpression) {
val lambdaArgumentCount = valueArguments.count { it.getArgumentExpression()?.unpackFunctionLiteral() != null }
val referenceArgumentCount = valueArguments.count { it.getArgumentExpression() is KtCallableReferenceExpression }
val bindingContext = analyze(BodyResolveMode.PARTIAL)
val resolutionFacade = getResolutionFacade()
val samConversionTransformer = resolutionFacade.frontendService<SamConversionTransformer>()
val languageVersionSettings = resolutionFacade.frontendService<LanguageVersionSettings>()
val bindingContext = analyze(resolutionFacade, BodyResolveMode.PARTIAL)
val targets = bindingContext[BindingContext.REFERENCE_TARGET, callee]?.let { listOf(it) }
?: bindingContext[BindingContext.AMBIGUOUS_REFERENCE_TARGET, callee]
?: listOf()
val candidates = targets.filterIsInstance<FunctionDescriptor>()
// if there are functions among candidates but none of them have last function parameter then not show the intention
if (candidates.isNotEmpty() && candidates.none { candidate ->
val params = candidate.valueParameters
val lastParamType = params.lastOrNull()?.type
(lastParamType?.isFunctionOrSuspendFunctionType == true || lastParamType?.isTypeParameter() == true) && params.count {
it.type.let { type -> type.isFunctionOrSuspendFunctionType || type.isTypeParameter() }
} == lambdaArgumentCount + referenceArgumentCount
}
) return false
val areAllCandidatesWithoutLastFunctionParameter = candidates.none {
it.allowsMoveOfLastParameterOutsideParentheses(
lambdaArgumentCount + referenceArgumentCount,
samConversionTransformer,
languageVersionSettings.supportsFeature(LanguageFeature.NewInference)
)
}
if (candidates.isNotEmpty() && areAllCandidatesWithoutLastFunctionParameter) return false
}
return true
}
private fun FunctionDescriptor.allowsMoveOfLastParameterOutsideParentheses(
lambdaAndCallableReferencesInOriginalCallCount: Int,
samConversionTransformer: SamConversionTransformer,
newInferenceEnabled: Boolean
): Boolean {
fun KotlinType.allowsMoveOutsideParentheses(): Boolean {
// Fast-path
if (isFunctionOrSuspendFunctionType || isTypeParameter()) return true
// Also check if it can be SAM-converted
// Note that it is not necessary in OI, where we provide synthetic candidate descriptors with already
// converted types, but in NI it is performed by conversions, so we check it explicitly
// Also note that 'newInferenceEnabled' is essentially a micro-optimization, as there are no
// harm in just calling 'samConversionTransformer' on all candidates.
return newInferenceEnabled && samConversionTransformer.getFunctionTypeForPossibleSamType(this.unwrap()) != null
}
val params = valueParameters
val lastParamType = params.lastOrNull()?.type ?: return false
if (!lastParamType.allowsMoveOutsideParentheses()) return false
val movableParametersOfCandidateCount = params.count { it.type.allowsMoveOutsideParentheses() }
return movableParametersOfCandidateCount == lambdaAndCallableReferencesInOriginalCallCount
}
fun KtCallExpression.moveFunctionLiteralOutsideParentheses() {
assert(lambdaArguments.isEmpty())
val argumentList = valueArgumentList!!