From d8c7ffe77237de8bc7b0123e9770afe436daf398 Mon Sep 17 00:00:00 2001 From: Bawnorton Date: Mon, 9 Nov 2026 14:26:44 +0000 Subject: [PATCH 1/2] Allow mixin selectors to resolve source methods for navigation --- .../reference/AbstractMethodReference.kt | 42 ++++++++----------- .../mixin/reference/MixinSelectors.kt | 13 +++++- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt b/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt index 94f6abb02..ed6f048df 100644 --- a/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt +++ b/src/main/kotlin/platform/mixin/reference/AbstractMethodReference.kt @@ -27,7 +27,6 @@ import com.demonwav.mcdev.platform.mixin.util.MemberInfo import com.demonwav.mcdev.platform.mixin.util.bytecode import com.demonwav.mcdev.platform.mixin.util.findMethods import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceMethod -import com.demonwav.mcdev.platform.mixin.util.findSourceElement import com.demonwav.mcdev.platform.mixin.util.findUpstreamMixin import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.util.MemberReference @@ -116,6 +115,10 @@ abstract class AbstractMethodReference : PolyReferenceResolver(), MixinReference } fun resolve(context: PsiElement): Sequence? { + return resolveWithSelectors(context)?.map { it.second } + } + + private fun resolveWithSelectors(context: PsiElement): Sequence>? { val allowStatic = context.parentOfType()?.hasModifierProperty(PsiModifier.STATIC) ?: true val targets = getTargets(context) ?: return null val targetedMethods = when (context) { @@ -124,30 +127,20 @@ abstract class AbstractMethodReference : PolyReferenceResolver(), MixinReference } val selectors = targetedMethods.mapNotNull { parseSelector(it, context) } - return resolve(targets, selectors, allowStatic) - } - - private fun resolve( - targets: Collection, - selectors: List, - allowStatic: Boolean, - ): Sequence { - return targets.asSequence() - .flatMap { target -> - selectors.asSequence().map { it.getCustomOwner(target) to it } - } - .groupBy({ it.first }, { it.second }) - .asSequence() - .flatMap { (target, selectors) -> - target.findMethods(selectors, allowStatic) - .map { ClassAndMethodNode(target, it) } + return targets.asSequence().flatMap { target -> + selectors.asSequence().flatMap { selector -> + val owner = selector.getCustomOwner(target) + owner.findMethods(listOf(selector), allowStatic) + .map { selector to ClassAndMethodNode(owner, it) } } + } } fun resolveForNavigation(context: PsiElement): Array? { - return resolve(context)?.mapNotNull { - it.method.findSourceElement( - it.clazz, + return resolveWithSelectors(context)?.mapNotNull { (selector, target) -> + selector.findSourceMethod( + target.clazz, + target.method, context.project, scope = context.resolveScope, canDecompile = true, @@ -156,9 +149,10 @@ abstract class AbstractMethodReference : PolyReferenceResolver(), MixinReference } override fun resolveReference(context: PsiElement): Array { - return resolve(context)?.mapNotNull { - it.method.findSourceElement( - it.clazz, + return resolveWithSelectors(context)?.mapNotNull { (selector, target) -> + selector.findSourceMethod( + target.clazz, + target.method, context.project, scope = context.resolveScope, canDecompile = false, diff --git a/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt b/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt index 7c8d25d3c..7656e6bbc 100644 --- a/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt +++ b/src/main/kotlin/platform/mixin/reference/MixinSelectors.kt @@ -24,6 +24,7 @@ import com.demonwav.mcdev.platform.mixin.util.MemberInfo import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.DESC import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.SLICE +import com.demonwav.mcdev.platform.mixin.util.findSourceElement import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.util.MemberMatcher import com.demonwav.mcdev.util.Quantifier @@ -42,11 +43,11 @@ import com.demonwav.mcdev.util.resolveTypeArray import com.intellij.openapi.editor.Editor import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiCallExpression import com.intellij.psi.PsiClass -import com.intellij.psi.PsiElement import com.intellij.psi.PsiLiteral import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifierList @@ -61,6 +62,8 @@ import com.intellij.psi.util.InheritanceUtil import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.parentOfType +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode import java.util.Locale import java.util.regex.PatternSyntaxException import org.objectweb.asm.Type @@ -98,6 +101,14 @@ interface MixinSelector : MemberMatcher { val next: MixinSelector? get() = null fun withQuantifier(quantifier: Quantifier): MixinSelector + + fun findSourceMethod( + owner: ClassNode, + method: MethodNode, + project: Project, + scope: GlobalSearchScope, + canDecompile: Boolean, + ): PsiElement? = method.findSourceElement(owner, project, scope, canDecompile) } class MixinMemberParser : MixinSelectorParser { From bb661247dc69b53126be810c1d927f47f9e3916a Mon Sep 17 00:00:00 2001 From: Bawnorton Date: Mon, 9 Nov 2026 14:27:14 +0000 Subject: [PATCH 2/2] Navigate injection targets through selector source methods --- .../handlers/injectionPoint/AtResolver.kt | 13 +++++--- .../injectionPoint/FieldInjectionPoint.kt | 17 ++++++++--- .../mixin/reference/target/TargetReference.kt | 30 ++++++++++++++++--- .../kotlin/platform/mixin/util/AsmUtil.kt | 3 ++ .../mixin/util/SourceCodeLocationInfo.kt | 4 ++- 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt index cc7497965..48f204123 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt @@ -44,6 +44,7 @@ import com.demonwav.mcdev.util.countIsAtLeast import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.equivalentTo import com.demonwav.mcdev.util.findMethods +import com.demonwav.mcdev.util.findContainingClass import com.demonwav.mcdev.util.internalName import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.psi.JavaPsiFacade @@ -93,6 +94,7 @@ class AtResolver( private val at: PsiAnnotation, private val targetClass: ClassNode, private val targetMethod: MethodNode, + private val sourceElement: PsiElement? = null, ) { companion object { fun getInjectionPoint(at: PsiAnnotation): InjectionPoint<*>? { @@ -241,8 +243,8 @@ class AtResolver( val project = at.project // Resolve the target source class - val targetPsiClass = getTargetClass(target) - .findSourceClass(project, GlobalSearchScope.allScope(project), canDecompile = true) + val targetPsiClass = sourceElement?.findContainingClass() + ?: getTargetClass(target).findSourceClass(project, GlobalSearchScope.allScope(project), canDecompile = true) ?: return emptyList() val targetPsiFile = targetPsiClass.containingFile ?: return emptyList() @@ -252,13 +254,16 @@ class AtResolver( // Find the element in the desugared class, first by directly searching and then by searching in the original // and reverse mapping it into the desugared class. - val desugaredTargetElement = when { + val desugaredTargetElement = sourceElement?.let { original -> + DesugarUtil.getOriginalToDesugaredMap(desugaredTargetClass)[original] + ?.firstOrNull { it is PsiParameterListOwner } + } ?: when { targetMethod.isClinit -> desugaredTargetClass.initializers.firstOrNull { it.hasModifierProperty(PsiModifier.STATIC) } else -> desugaredTargetClass.findMethods(targetMethod.memberReference).firstOrNull() } ?: run { - val originalTargetElement = targetMethod.findSourceElement( + val originalTargetElement = sourceElement ?: targetMethod.findSourceElement( getTargetClass(target), project, GlobalSearchScope.allScope(project), diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt index a8d49927a..8ab714f23 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt @@ -26,6 +26,7 @@ import com.demonwav.mcdev.platform.mixin.util.fakeResolve import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceField import com.demonwav.mcdev.util.Quantifier import com.demonwav.mcdev.util.constantValue +import com.demonwav.mcdev.util.descriptor import com.intellij.codeInsight.completion.JavaLookupElementBuilder import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.editor.Editor @@ -126,16 +127,24 @@ class FieldInjectionPoint : QualifiedInjectionPoint() { private val opcode: Int, private val arrayAccess: ArrayAccessType?, ) : NavigationVisitor() { + private var bytecodeOwner: String? = null + + override fun configureBytecodeTarget(classNode: ClassNode, methodNode: MethodNode) { + bytecodeOwner = classNode.name + } + override fun visitReferenceExpression(expression: PsiReferenceExpression) { if (expression !is PsiMethodReferenceExpression) { // early out for if the name does not match val name = expression.referenceName if (name == null || selector.canEverMatch(name)) { (expression.resolve() as? PsiField)?.let { resolved -> - var matches = selector.matchField( - resolved, - QualifiedMember.resolveQualifier(expression) ?: targetClass, - ) + val qualifier = QualifiedMember.resolveQualifier(expression) ?: targetClass + var matches = if (qualifier.qualifiedName == targetClass.qualifiedName && bytecodeOwner != null) { + resolved.descriptor?.let { selector.matchField(bytecodeOwner!!, resolved.name, it) } == true + } else { + selector.matchField(resolved, qualifier) + } if (matches && opcode != -1) { // check if we match the opcode val isStatic = opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC diff --git a/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt b/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt index 6d072d31c..7836a78ee 100644 --- a/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt +++ b/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt @@ -29,6 +29,7 @@ import com.demonwav.mcdev.platform.mixin.util.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT import com.demonwav.mcdev.util.findQualifiedClass +import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.ifEmpty import com.demonwav.mcdev.util.insideAnnotationAttribute import com.demonwav.mcdev.util.reference.PolyReferenceResolver @@ -85,14 +86,17 @@ object TargetReference : PolyReferenceResolver(), MixinReference { * Null is returned when no parent annotation handler could be found, in which case we shouldn't mark this * reference as unresolved. */ - private fun getTargets(at: PsiAnnotation, forUnresolved: Boolean): List? { - val (handler, annotation) = generateSequence(at.parent) { it.parent } + private fun getInjector(at: PsiAnnotation): Pair? = + generateSequence(at.parent) { it.parent } .filterIsInstance() .flatMap { it.owner?.annotations?.asSequence() ?: emptySequence() } .mapNotNull { annotation -> (MixinAnnotationHandler.forMixinAnnotation(annotation) as? InjectorAnnotationHandler) ?.let { it to annotation } - }.firstOrNull() ?: return null + }.firstOrNull() + + private fun getTargets(at: PsiAnnotation, forUnresolved: Boolean): List? { + val (handler, annotation) = getInjector(at) ?: return null if (forUnresolved && handler.isSoft) { return null } @@ -113,7 +117,25 @@ object TargetReference : PolyReferenceResolver(), MixinReference { fun resolveNavigationTargets(context: PsiElement): Array? { val at = context.parentOfType() ?: return null val targets = getTargets(at, false) ?: return null - return targets.flatMap { AtResolver(at, it.clazz, it.method).resolveNavigationTargets() }.toTypedArray() + val injector = getInjector(at)?.second + val methodAttr = injector?.findAttributeValue("method") + val selectors = methodAttr?.computeStringArray()?.mapNotNull { parseMixinSelector(it, methodAttr) }.orEmpty() + return targets.flatMap { target -> + val sourceElement = selectors.firstNotNullOfOrNull { selector -> + if (selector.matchMethod(target.method, target.clazz)) { + selector.findSourceMethod( + target.clazz, + target.method, + context.project, + context.resolveScope, + canDecompile = true, + ) + } else { + null + } + } + AtResolver(at, target.clazz, target.method, sourceElement).resolveNavigationTargets() + }.toTypedArray() } override fun resolveReference(context: PsiElement): Array { diff --git a/src/main/kotlin/platform/mixin/util/AsmUtil.kt b/src/main/kotlin/platform/mixin/util/AsmUtil.kt index 86173eb97..ce8db1c9d 100644 --- a/src/main/kotlin/platform/mixin/util/AsmUtil.kt +++ b/src/main/kotlin/platform/mixin/util/AsmUtil.kt @@ -1028,6 +1028,9 @@ fun MethodNode.findSourceElement( return psiClass.childrenOfType().firstOrNull { it.hasModifierProperty(PsiModifier.STATIC) } ?: psiClass } + if (hasAccess(Opcodes.ACC_SYNTHETIC) && psiClass !is PsiCompiledElement) { + findAssociatedLambda(project, scope, clazz, this)?.let { return it } + } psiClass.findMethods(memberReference).firstOrNull()?.let { return it } if (psiClass is PsiCompiledElement) { // don't walk into stub compiled elements to look for lambdas diff --git a/src/main/kotlin/platform/mixin/util/SourceCodeLocationInfo.kt b/src/main/kotlin/platform/mixin/util/SourceCodeLocationInfo.kt index 2c7eadb12..8b1953a86 100644 --- a/src/main/kotlin/platform/mixin/util/SourceCodeLocationInfo.kt +++ b/src/main/kotlin/platform/mixin/util/SourceCodeLocationInfo.kt @@ -53,6 +53,7 @@ class SourceCodeLocationInfo(val index: Int, val lineNumber: Int?, val indexInLi private var currentLine: Int? = null private var countThisLine = 0 private var myResult: T? = null + private var indexedResult: T? = null override fun accept(t: T): Boolean { val line = document?.let(t::lineNumber) @@ -70,6 +71,7 @@ class SourceCodeLocationInfo(val index: Int, val lineNumber: Int?, val indexInLi } if (count++ == index) { + indexedResult = t if (lineNumber == null) { myResult = t return true @@ -79,7 +81,7 @@ class SourceCodeLocationInfo(val index: Int, val lineNumber: Int?, val indexInLi return false } - override val result get() = myResult + override val result get() = myResult ?: indexedResult } } }