Uast: handling resolve to deserialized methods (KT-24679)

This commit is contained in:
Nicolay Mitropolsky
2018-05-30 19:06:56 +03:00
parent 2ede37496c
commit 12707a6443
12 changed files with 202 additions and 73 deletions
@@ -1,16 +1,16 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.caches.lightClasses
package org.jetbrains.kotlin.type
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.Type.*
internal object MapPsiToAsmDesc {
object MapPsiToAsmDesc {
fun typeDesc(type: PsiType): String = when (type) {
PsiType.VOID -> primitive(VOID_TYPE)
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKind
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor
import org.jetbrains.kotlin.type.MapPsiToAsmDesc
interface LightMemberOriginForCompiledElement : LightMemberOrigin {
override val originKind: JvmDeclarationOriginKind
@@ -16,14 +16,10 @@
package org.jetbrains.uast.kotlin
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiType
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils
@@ -39,21 +35,6 @@ class KotlinUFunctionCallExpression(
givenParent: UElement?,
private val _resolvedCall: ResolvedCall<*>?
) : KotlinAbstractUExpression(givenParent), UCallExpressionEx, KotlinUElementWithType {
companion object {
fun resolveSource(descriptor: DeclarationDescriptor, source: PsiElement?): PsiMethod? {
if (descriptor is ConstructorDescriptor && descriptor.isPrimary
&& source is KtClassOrObject && source.primaryConstructor == null
&& source.secondaryConstructors.isEmpty()) {
return source.toLightClass()?.constructors?.firstOrNull()
}
return when (source) {
is KtFunction -> LightClassUtil.getLightClassMethod(source)
is PsiMethod -> source
else -> null
}
}
}
constructor(psi: KtCallElement, uastParent: UElement?) : this(psi, uastParent, null)
@@ -124,8 +105,8 @@ class KotlinUFunctionCallExpression(
override fun resolve(): PsiMethod? {
val descriptor = resolvedCall?.resultingDescriptor ?: return null
val source = descriptor.toSource() ?: return null
return resolveSource(descriptor, source)
val source = descriptor.toSource()
return resolveSource(psi, descriptor, source)
}
override fun accept(visitor: UastVisitor) {
@@ -40,21 +40,6 @@ class KotlinUFunctionCallExpression(
givenParent: UElement?,
private val _resolvedCall: ResolvedCall<*>?
) : KotlinAbstractUExpression(givenParent), UCallExpression, KotlinUElementWithType {
companion object {
fun resolveSource(descriptor: DeclarationDescriptor, source: PsiElement?): PsiMethod? {
if (descriptor is ConstructorDescriptor && descriptor.isPrimary
&& source is KtClassOrObject && source.primaryConstructor == null
&& source.secondaryConstructors.isEmpty()) {
return source.toLightClass()?.constructors?.firstOrNull()
}
return when (source) {
is KtFunction -> LightClassUtil.getLightClassMethod(source)
is PsiMethod -> source
else -> null
}
}
}
constructor(psi: KtCallElement, uastParent: UElement?) : this(psi, uastParent, null)
@@ -106,8 +91,8 @@ class KotlinUFunctionCallExpression(
override fun resolve(): PsiMethod? {
val descriptor = resolvedCall?.resultingDescriptor ?: return null
val source = descriptor.toSource() ?: return null
return resolveSource(descriptor, source)
val source = descriptor.toSource()
return resolveSource(psi, descriptor, source)
}
override fun accept(visitor: UastVisitor) {
@@ -159,7 +159,7 @@ open class KotlinUSimpleReferenceExpression(
override fun resolve(): PsiMethod? {
val source = accessorDescriptor.toSource()
return KotlinUFunctionCallExpression.resolveSource(accessorDescriptor, source)
return resolveSource(psi, accessorDescriptor, source)
}
}
@@ -154,7 +154,7 @@ open class KotlinUSimpleReferenceExpression(
override fun resolve(): PsiMethod? {
val source = accessorDescriptor.toSource()
return KotlinUFunctionCallExpression.resolveSource(accessorDescriptor, source)
return resolveSource(psi, accessorDescriptor, source)
}
}
@@ -157,7 +157,7 @@ open class KotlinUSimpleReferenceExpression(
override fun resolve(): PsiMethod? {
val source = accessorDescriptor.toSource()
return KotlinUFunctionCallExpression.resolveSource(accessorDescriptor, source)
return resolveSource(psi, accessorDescriptor, source)
}
}
@@ -19,27 +19,27 @@ package org.jetbrains.uast.kotlin
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiPrimitiveType
import com.intellij.psi.PsiType
import com.intellij.psi.*
import com.intellij.psi.impl.cache.TypeInfo
import com.intellij.psi.impl.compiled.ClsTypeElementImpl
import com.intellij.psi.impl.compiled.SignatureParsing
import com.intellij.psi.impl.compiled.StubBuildingVisitor
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTypesUtil
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype
import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaPackageFragment
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryPackageSourceElement
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMemberSignature
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
@@ -47,10 +47,10 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstructor
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeApproximator
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.type.MapPsiToAsmDesc
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isInterface
import org.jetbrains.uast.*
import java.lang.ref.WeakReference
@@ -64,9 +64,9 @@ internal inline fun String?.orAnonymous(kind: String = ""): String = this ?: "<a
internal fun DeclarationDescriptor.toSource(): PsiElement? {
return try {
DescriptorToSourceUtils.getEffectiveReferencedDescriptors(this)
.asSequence()
.mapNotNull { DescriptorToSourceUtils.getSourceFromDescriptor(it) }
.firstOrNull()
.asSequence()
.mapNotNull { DescriptorToSourceUtils.getSourceFromDescriptor(it) }
.firstOrNull()
}
catch (e: Exception) {
Logger.getInstance("DeclarationDescriptor.toSource").error(e)
@@ -74,18 +74,104 @@ internal fun DeclarationDescriptor.toSource(): PsiElement? {
}
}
internal fun resolveSource(context: KtElement, descriptor: DeclarationDescriptor, source: PsiElement?): PsiMethod? {
if (descriptor is ConstructorDescriptor && descriptor.isPrimary
&& source is KtClassOrObject && source.primaryConstructor == null
&& source.secondaryConstructors.isEmpty()
) {
return source.toLightClass()?.constructors?.firstOrNull()
}
return when (source) {
is KtFunction -> LightClassUtil.getLightClassMethod(source)
is PsiMethod -> source
null -> resolveDeserialized(context, descriptor)
else -> null
}
}
private fun resolveDeserialized(context: KtElement, descriptor: DeclarationDescriptor): PsiMethod? {
if (descriptor !is DeserializedCallableMemberDescriptor) return null
val containingDeclaration = descriptor.containingDeclaration
val psiClass = when (containingDeclaration) {
is LazyJavaPackageFragment -> {
val binaryPackageSourceElement = containingDeclaration.source as? KotlinJvmBinaryPackageSourceElement ?: return null
val containingBinaryClass = binaryPackageSourceElement.getContainingBinaryClass(descriptor) ?: return null
val containingClassQualifiedName = containingBinaryClass.classId.asSingleFqName().asString()
JavaPsiFacade.getInstance(context.project).findClass(containingClassQualifiedName, context.resolveScope) ?: return null
}
is DeserializedClassDescriptor -> {
val declaredPsiType = containingDeclaration.defaultType.toPsiType(null, context, false)
(declaredPsiType as? PsiClassType)?.resolve() ?: return null
}
else -> return null
}
val proto = descriptor.proto
val nameResolver = descriptor.nameResolver
val typeTable = descriptor.typeTable
return when (proto) {
is ProtoBuf.Function -> {
val signature = JvmProtoBufUtil.getJvmMethodSignature(proto, nameResolver, typeTable)
?: getMethodSignatureFromDescriptor(context, descriptor)
?: return null
psiClass.methods.firstOrNull { it.name == signature.name && it.matchesDesc(signature.desc) }
}
is ProtoBuf.Constructor -> {
val signature = JvmProtoBufUtil.getJvmConstructorSignature(proto, nameResolver, typeTable)
?: getMethodSignatureFromDescriptor(context, descriptor)
?: return null
psiClass.constructors.firstOrNull { it.matchesDesc(signature.desc) }
}
else -> null
}
}
private fun PsiMethod.matchesDesc(desc: String) = desc == buildString {
parameterList.parameters.joinTo(this, separator = "", prefix = "(", postfix = ")") { MapPsiToAsmDesc.typeDesc(it.type) }
append(MapPsiToAsmDesc.typeDesc(returnType ?: PsiType.VOID))
}
private fun getMethodSignatureFromDescriptor(context: KtElement, descriptor: CallableDescriptor): JvmMemberSignature? {
fun PsiType.raw() = (this as? PsiClassType)?.rawType() ?: PsiPrimitiveType.getUnboxedType(this) ?: this
fun KotlinType.toPsiType() = toPsiType(null, context, false).raw()
val originalDescriptor = descriptor.original
val receiverType = originalDescriptor.extensionReceiverParameter?.type?.toPsiType()
val parameterTypes = listOfNotNull(receiverType) + originalDescriptor.valueParameters.map { it.type.toPsiType() }
val returnType = originalDescriptor.returnType?.toPsiType() ?: PsiType.VOID
val desc = parameterTypes.joinToString("", prefix = "(", postfix = ")") { MapPsiToAsmDesc.typeDesc(it) } +
MapPsiToAsmDesc.typeDesc(returnType)
return JvmMemberSignature.Method(descriptor.name.asString(), desc)
}
internal fun <T> lz(initializer: () -> T) = lazy(LazyThreadSafetyMode.SYNCHRONIZED, initializer)
internal fun KotlinType.toPsiType(source: UElement, element: KtElement, boxed: Boolean): PsiType {
internal fun KotlinType.toPsiType(source: UElement, element: KtElement, boxed: Boolean): PsiType =
toPsiType(source.getParentOfType<UDeclaration>(false)?.psi, element, boxed)
internal fun KotlinType.toPsiType(lightDeclaration: PsiModifierListOwner?, context: KtElement, boxed: Boolean): PsiType {
if (this.isError) return UastErrorType
(constructor.declarationDescriptor as? TypeAliasDescriptor)?.let { typeAlias ->
return typeAlias.expandedType.toPsiType(source, element, boxed)
return typeAlias.expandedType.toPsiType(lightDeclaration, context, boxed)
}
(constructor.declarationDescriptor as? TypeParameterDescriptor)?.let { typeParameter ->
return CommonSupertypes.commonSupertype(typeParameter.upperBounds).toPsiType(lightDeclaration, context, boxed)
}
if (arguments.isEmpty()) {
val typeFqName = this.constructor.declarationDescriptor?.fqNameSafe?.asString()
fun PsiPrimitiveType.orBoxed() = if (boxed) getBoxedType(element) else this
fun PsiPrimitiveType.orBoxed() = if (boxed) getBoxedType(context) else this
val psiType = when (typeFqName) {
"kotlin.Int" -> PsiType.INT.orBoxed()
"kotlin.Long" -> PsiType.LONG.orBoxed()
@@ -95,11 +181,11 @@ internal fun KotlinType.toPsiType(source: UElement, element: KtElement, boxed: B
"kotlin.Char" -> PsiType.CHAR.orBoxed()
"kotlin.Double" -> PsiType.DOUBLE.orBoxed()
"kotlin.Float" -> PsiType.FLOAT.orBoxed()
"kotlin.String" -> PsiType.getJavaLangString(element.manager, GlobalSearchScope.projectScope(element.project))
"kotlin.String" -> PsiType.getJavaLangString(context.manager, GlobalSearchScope.projectScope(context.project))
else -> {
val typeConstructor = this.constructor
if (typeConstructor is IntegerValueTypeConstructor) {
TypeUtils.getDefaultPrimitiveNumberType(typeConstructor).toPsiType(source, element, boxed)
TypeUtils.getDefaultPrimitiveNumberType(typeConstructor).toPsiType(lightDeclaration, context, boxed)
} else {
null
}
@@ -110,13 +196,13 @@ internal fun KotlinType.toPsiType(source: UElement, element: KtElement, boxed: B
if (this.containsLocalTypes()) return UastErrorType
val project = element.project
val project = context.project
val typeMapper = ServiceManager.getService(project, KotlinUastBindingContextProviderService::class.java)
.getTypeMapper(element) ?: return UastErrorType
.getTypeMapper(context) ?: return UastErrorType
val signatureWriter = BothSignatureWriter(BothSignatureWriter.Mode.TYPE)
val typeMappingMode = if (boxed) TypeMappingMode.GENERIC_ARGUMENT else TypeMappingMode.DEFAULT
val approximatedType = TypeApproximator().approximateDeclarationType(this, true, element.languageVersionSettings)
val approximatedType = TypeApproximator().approximateDeclarationType(this, true, context.languageVersionSettings)
typeMapper.mapType(approximatedType, signatureWriter, typeMappingMode)
val signature = StringCharacterIterator(signatureWriter.toString())
@@ -125,7 +211,7 @@ internal fun KotlinType.toPsiType(source: UElement, element: KtElement, boxed: B
val typeInfo = TypeInfo.fromString(javaType, false)
val typeText = TypeInfo.createTypeText(typeInfo) ?: return UastErrorType
return ClsTypeElementImpl(source.getParentOfType<UDeclaration>(false)?.psi ?: element, typeText, '\u0000').type
return ClsTypeElementImpl(lightDeclaration ?: context, typeText, '\u0000').type
}
private fun KotlinType.containsLocalTypes(): Boolean {
@@ -168,8 +254,8 @@ internal fun PsiElement.getMaybeLightElement(context: UElement): PsiElement? {
}
internal fun KtElement.resolveCallToDeclaration(
context: KotlinAbstractUElement,
resultingDescriptor: DeclarationDescriptor? = null
context: KotlinAbstractUElement,
resultingDescriptor: DeclarationDescriptor? = null
): PsiElement? {
val descriptor = resultingDescriptor ?: run {
val resolvedCall = getResolvedCall(analyze()) ?: return null
@@ -191,7 +277,7 @@ internal fun KtExpression.unwrapBlockOrParenthesis(): KtExpression {
internal fun KtElement.analyze(): BindingContext {
if(containingFile !is KtFile) return BindingContext.EMPTY // EA-114080, EA-113475
return ServiceManager.getService(project, KotlinUastBindingContextProviderService::class.java)
?.getBindingContext(this) ?: BindingContext.EMPTY
?.getBindingContext(this) ?: BindingContext.EMPTY
}
internal inline fun <reified T : UDeclaration, reified P : PsiElement> unwrap(element: P): P {
@@ -205,7 +291,7 @@ internal fun KtExpression.getExpectedType(): KotlinType? = analyze()[BindingCont
internal fun KtTypeReference.getType(): KotlinType? = analyze()[BindingContext.TYPE, this]
internal fun KotlinType.getFunctionalInterfaceType(source: UElement, element: KtElement): PsiType? =
takeIf { it.isInterface() && !it.isBuiltinFunctionalTypeOrSubtype }?.toPsiType(source, element, false)
takeIf { it.isInterface() && !it.isBuiltinFunctionalTypeOrSubtype }?.toPsiType(source, element, false)
internal fun KotlinULambdaExpression.getFunctionalInterfaceType(): PsiType? {
val parent = psi.parent
+19
View File
@@ -0,0 +1,19 @@
class A {
fun foo() {}
inline fun inlineFoo() {
}
}
fun bar() {
A().foo()
A().inlineFoo()
listOf(A()).forEach { println(it) } // inline from stdlib
listOf("").joinToString() // not inline from stdlib
listOf("").size // property from stdlib
listOf("").last() // overloaded extension from stdlib
mutableMapOf(1 to "1").entries.first().setValue("123") // call on nested method in stdlib
val intRange = 0L..3L
intRange.contains(2 as Int) // extension-fun with @JvmName("longRangeContains")
IntRange(1, 2) // constructor from stdlib
}
@@ -8,6 +8,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.uast.*
import org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin
import org.jetbrains.uast.test.env.findElementByText
@@ -340,6 +341,25 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
assertArguments(listOf("\"foo\"", "1"), "object : Parent(b = 1, a = \"foo\")\n")
}
@Test
fun testResolvedDeserializedMethod() = doTest("Resolve") { _, file ->
val barMethod = file.findElementByTextFromPsi<UElement>("bar").getParentOfType<UMethod>()!!
fun UElement.assertResolveCall(callText: String, methodName: String = callText.substringBefore("(")) {
this.findElementByTextFromPsi<UCallExpression>(callText).let {
val resolve = it.resolve().sure { "resolving '$callText'" }
assertEquals(methodName, resolve.name)
}
}
barMethod.assertResolveCall("foo()")
barMethod.assertResolveCall("inlineFoo()")
barMethod.assertResolveCall("forEach { println(it) }", "forEach")
barMethod.assertResolveCall("joinToString()")
barMethod.assertResolveCall("last()")
barMethod.assertResolveCall("setValue(\"123\")")
barMethod.assertResolveCall("contains(2 as Int)", "longRangeContains")
barMethod.assertResolveCall("IntRange(1, 2)")
}
}
fun <T, R> Iterable<T>.assertedFind(value: R, transform: (T) -> R): T =
@@ -5,8 +5,10 @@ import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.asJava.toLightAnnotation
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.uast.*
import org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin
import org.jetbrains.uast.test.env.findElementByText
@@ -194,4 +196,22 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
Assert.assertTrue((originalTypeParameters.declarations.single() as UParameter).type.isValid)
}
}
@Test
fun testResolvedDeserializedMethod() = doTest("Resolve") { _, file ->
val barMethod = file.psi.findElementAt(file.psi.text.indexOf("bar")).sure { "sourceElement" }
.parentsWithSelf.mapNotNull { it.toUElementOfType<UMethod>() }.firstOrNull().sure { "parent UMethod" }
fun UElement.assertResolveCall(callText: String, methodName: String = callText.substringBefore("(")) {
this.findElementByTextFromPsi<UCallExpression>(callText).let {
val resolve = it.resolve().sure { "resolving '$callText'" }
assertEquals(methodName, resolve.name)
}
}
barMethod.assertResolveCall("foo()")
barMethod.assertResolveCall("inlineFoo()")
barMethod.assertResolveCall("forEach { println(it) }", "forEach")
barMethod.assertResolveCall("joinToString()")
barMethod.assertResolveCall("last()")
}
}
@@ -6,9 +6,10 @@ import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.asJava.toLightAnnotation
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.uast.*
import org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin
import org.jetbrains.uast.test.env.findElementByText
@@ -306,7 +307,23 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
}
}
@Test
fun testResolvedDeserializedMethod() = doTest("Resolve") { _, file ->
val barMethod = file.findElementByTextFromPsi<UElement>("bar").sourcePsiElement.sure { "sourceElement" }
.parentsWithSelf.mapNotNull { it.toUElementOfType<UMethod>() }.firstOrNull().sure { "parent UMethod" }
fun UElement.assertResolveCall(callText: String, methodName: String = callText.substringBefore("(")) {
this.findElementByTextFromPsi<UCallExpression>(callText).let {
val resolve = it.resolve().sure { "resolving '$callText'" }
assertEquals(methodName, resolve.name)
}
}
barMethod.assertResolveCall("foo()")
barMethod.assertResolveCall("inlineFoo()")
barMethod.assertResolveCall("forEach { println(it) }", "forEach")
barMethod.assertResolveCall("joinToString()")
barMethod.assertResolveCall("last()")
}
}
fun <T, R> Iterable<T>.assertedFind(value: R, transform: (T) -> R): T = find { transform(it) == value } ?: throw AssertionError("'$value' not found, only ${this.joinToString { transform(it).toString() }}")