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:
@@ -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!!
|
||||
|
||||
Reference in New Issue
Block a user