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
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.synthetic
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
@@ -28,34 +29,61 @@ import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes
import org.jetbrains.kotlin.storage.StorageManager
class JavaSyntheticScopes(
private val project: Project,
private val moduleDescriptor: ModuleDescriptor,
storageManager: StorageManager,
lookupTracker: LookupTracker,
languageVersionSettings: LanguageVersionSettings,
samConventionResolver: SamConversionResolver,
deprecationResolver: DeprecationResolver
): SyntheticScopes {
override val scopes = run {
val javaSyntheticPropertiesScope = JavaSyntheticPropertiesScope(storageManager, lookupTracker)
private val project: Project,
private val moduleDescriptor: ModuleDescriptor,
storageManager: StorageManager,
lookupTracker: LookupTracker,
languageVersionSettings: LanguageVersionSettings,
samConventionResolver: SamConversionResolver,
deprecationResolver: DeprecationResolver
) : SyntheticScopes {
override val scopes: Collection<SyntheticScope>
// New Inference disables SAM-adapters scope, because it knows how to perform SAM-conversion in resolution
// However, some outer clients (mostly in IDE) sometimes would like to look at synthetic SAM-produced descriptors
// (e.g., completion)
val scopesWithForceEnabledSamAdapters: Collection<SyntheticScope>
init {
val newInferenceEnabled = languageVersionSettings.supportsFeature(LanguageFeature.NewInference)
val javaSyntheticPropertiesScope = JavaSyntheticPropertiesScope(storageManager, lookupTracker)
val scopesFromExtensions = SyntheticScopeProviderExtension
.getInstances(project)
.flatMap { it.getScopes(moduleDescriptor, javaSyntheticPropertiesScope) }
listOf(
javaSyntheticPropertiesScope,
SamAdapterFunctionsScope(
storageManager, languageVersionSettings, samConventionResolver, deprecationResolver,
lookupTracker
val samAdapterFunctionsScope = SamAdapterFunctionsScope(
storageManager,
samConventionResolver,
deprecationResolver,
lookupTracker,
samViaSyntheticScopeDisabled = newInferenceEnabled
)
scopes = listOf(javaSyntheticPropertiesScope, samAdapterFunctionsScope) + scopesFromExtensions
if (newInferenceEnabled) {
val forceEnabledSamAdapterFunctionsScope = SamAdapterFunctionsScope(
storageManager,
samConventionResolver,
deprecationResolver,
lookupTracker,
samViaSyntheticScopeDisabled = false
)
) + scopesFromExtensions
scopesWithForceEnabledSamAdapters =
listOf(javaSyntheticPropertiesScope, forceEnabledSamAdapterFunctionsScope) + scopesFromExtensions
} else {
scopesWithForceEnabledSamAdapters = scopes
}
}
}
interface SyntheticScopeProviderExtension {
companion object : ProjectExtensionDescriptor<SyntheticScopeProviderExtension>(
"org.jetbrains.kotlin.syntheticScopeProviderExtension", SyntheticScopeProviderExtension::class.java)
"org.jetbrains.kotlin.syntheticScopeProviderExtension", SyntheticScopeProviderExtension::class.java
)
fun getScopes(moduleDescriptor: ModuleDescriptor, javaSyntheticPropertiesScope: JavaSyntheticPropertiesScope): List<SyntheticScope>
}
@@ -55,12 +55,11 @@ val SAM_LOOKUP_NAME = Name.special("<SAM-CONSTRUCTOR>")
class SamAdapterFunctionsScope(
storageManager: StorageManager,
private val languageVersionSettings: LanguageVersionSettings,
private val samResolver: SamConversionResolver,
private val deprecationResolver: DeprecationResolver,
private val lookupTracker: LookupTracker
private val lookupTracker: LookupTracker,
private val samViaSyntheticScopeDisabled: Boolean
) : SyntheticScope.Default() {
private val samViaSyntheticScopeDisabled = languageVersionSettings.supportsFeature(LanguageFeature.NewInference)
private val extensionForFunction =
storageManager.createMemoizedFunctionWithNullableValues<FunctionDescriptor, FunctionDescriptor> { function ->
@@ -17,8 +17,14 @@
package org.jetbrains.kotlin.synthetic
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.synthetic.SyntheticMemberDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.sam.SamAdapterDescriptor
import org.jetbrains.kotlin.load.java.sam.SamConstructorDescriptor
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCallImpl
import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
fun FunctionDescriptor.hasJavaOriginInHierarchy(): Boolean {
@@ -55,3 +61,15 @@ fun syntheticVisibility(originalDescriptor: DeclarationDescriptorWithVisibility,
}
}
fun <D : CallableDescriptor> ResolvedCall<D>.isResolvedWithSamConversions(): Boolean {
return if (this is NewResolvedCallImpl<D>) {
// New inference
this.resolvedCallAtom.argumentsWithConversion.isNotEmpty()
} else {
// Old Inference
this.resultingDescriptor is SamAdapterDescriptor<*> ||
this.resultingDescriptor is SamConstructorDescriptor ||
this.resultingDescriptor is SamAdapterExtensionFunctionDescriptor
}
}
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.resolve.scopes.utils.collectAllFromMeAndParent
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.synthetic.JavaSyntheticScopes
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
@@ -442,7 +443,7 @@ class ReferenceVariantsHelper(
process(descriptor as CallableDescriptor)
}
val syntheticScopes = resolutionFacade.getFrontendService(SyntheticScopes::class.java)
val syntheticScopes = resolutionFacade.getFrontendService(SyntheticScopes::class.java).forceEnableSamAdapters()
if (kindFilter.acceptsKinds(DescriptorKindFilter.VARIABLES_MASK)) {
val lookupLocation = (scope.ownerDescriptor.toSourceElement.getPsi() as? KtElement)?.let { KotlinLookupLocation(it) }
?: NoLookupLocation.FROM_IDE
@@ -477,7 +478,21 @@ fun ResolutionScope.collectSyntheticStaticMembersAndConstructors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): List<FunctionDescriptor> {
val syntheticScopes = resolutionFacade.getFrontendService(SyntheticScopes::class.java)
val syntheticScopes = resolutionFacade.getFrontendService(SyntheticScopes::class.java).forceEnableSamAdapters()
return (syntheticScopes.collectSyntheticStaticFunctions(this) + syntheticScopes.collectSyntheticConstructors(this))
.filter { kindFilter.accepts(it) && nameFilter(it.name) }
}
// New Inference disables scope with synthetic SAM-adapters because it uses conversions for resolution
// However, sometimes we need to pretend that we have those synthetic members, for example:
// - to show both option (with SAM-conversion signature, and without) in completion
// - for various intentions and checks (see RedundantSamConstructorInspection, ConflictingExtensionPropertyIntention and other)
// TODO(dsavvinov): review clients, rewrite them to not rely on synthetic adapetrs
fun SyntheticScopes.forceEnableSamAdapters(): SyntheticScopes {
return if (this !is JavaSyntheticScopes)
this
else
object : SyntheticScopes {
override val scopes: Collection<SyntheticScope> = this@forceEnableSamAdapters.scopesWithForceEnabledSamAdapters
}
}
@@ -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!!
@@ -22,7 +22,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getType
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor
import org.jetbrains.kotlin.synthetic.isResolvedWithSamConversions
class JavaMapForEachInspection : AbstractApplicabilityBasedInspection<KtDotQualifiedExpression>(
KtDotQualifiedExpression::class.java
@@ -37,7 +37,8 @@ class JavaMapForEachInspection : AbstractApplicabilityBasedInspection<KtDotQuali
val context = element.analyze(BodyResolveMode.PARTIAL)
if (!element.receiverExpression.getType(context).isMap(DefaultBuiltIns.Instance)) return false
return callExpression.getResolvedCall(context)?.resultingDescriptor is SamAdapterExtensionFunctionDescriptor
val resolvedCall = callExpression.getResolvedCall(context) ?: return false
return resolvedCall.isResolvedWithSamConversions()
}
override fun inspectionTarget(element: KtDotQualifiedExpression) = element.callExpression?.calleeExpression ?: element
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.forceEnableSamAdapters
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
@@ -32,6 +33,7 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
import org.jetbrains.kotlin.resolve.calls.CallResolver
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess
import org.jetbrains.kotlin.resolve.calls.tower.NewResolvedCallImpl
import org.jetbrains.kotlin.resolve.calls.util.DelegatingCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes
@@ -128,7 +130,12 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
if (!resolutionResults.isSuccess) return false
val samAdapterOriginalDescriptor = SamCodegenUtil.getOriginalIfSamAdapter(resolutionResults.resultingDescriptor) ?: return false
val samAdapterOriginalDescriptor =
if (resolutionResults.resultingCall is NewResolvedCallImpl<*>)
resolutionResults.resultingDescriptor
else
SamCodegenUtil.getOriginalIfSamAdapter(resolutionResults.resultingDescriptor) ?: return false
return samAdapterOriginalDescriptor.original == originalCall.resultingDescriptor.original
}
@@ -176,7 +183,7 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
val originalFunctionDescriptor = functionResolvedCall.resultingDescriptor.original as? FunctionDescriptor ?: return emptyList()
val containingClass = originalFunctionDescriptor.containingDeclaration as? ClassDescriptor ?: return emptyList()
val syntheticScopes = functionCall.getResolutionFacade().getFrontendService(SyntheticScopes::class.java)
val syntheticScopes = functionCall.getResolutionFacade().getFrontendService(SyntheticScopes::class.java).forceEnableSamAdapters()
// SAM adapters for static functions
val contributedFunctions = syntheticScopes.collectSyntheticStaticFunctions(containingClass.staticScope)