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:
@@ -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>
|
||||
}
|
||||
+2
-3
@@ -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
|
||||
}
|
||||
}
|
||||
+17
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user