Skip to content

More Mixin Fixes #2471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Minecraft Development for IntelliJ
*
* https://mcdev.io/
*
* Copyright (C) 2025 minecraft-dev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, version 3.0 only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.demonwav.mcdev.platform.mixin.completion

import com.demonwav.mcdev.platform.mixin.reference.InjectionPointReference
import com.demonwav.mcdev.util.reference.findContextElement
import com.intellij.codeInsight.AutoPopupController
import com.intellij.codeInsight.editorActions.TypedHandlerDelegate
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile

class InjectionPointTypedHandlerDelegate : TypedHandlerDelegate() {
override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result {
if (charTyped != ':' || !file.language.isKindOf(JavaLanguage.INSTANCE)) {
return Result.CONTINUE
}
AutoPopupController.getInstance(project).autoPopupMemberLookup(editor) {
val offset = editor.caretModel.offset
val element = it.findElementAt(offset)?.findContextElement()
InjectionPointReference.ELEMENT_PATTERN.accepts(element)
}
return Result.CONTINUE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,14 @@ class MixinCompletionContributor : CompletionContributor() {
}

// Process methods and fields from target class
findShadowTargets(psiClass, start, superMixin != null)
val elements = findShadowTargets(psiClass, start, superMixin != null)
.map { it.createLookupElement(psiClass.project) }
.filter { prefixMatcher.prefixMatches(it) }
.filter(filter, position)
.map { PrioritizedLookupElement.withExplicitProximity(it, 1) }
.forEach(r::addElement)
.toList()

r.addAllElements(elements)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ object MEExpressionMatchUtil {

val filteredLocals = localInfo.matchLocals(
module, targetClass, targetMethod, actualInsn,
CollectVisitor.Mode.MATCH_ALL
CollectVisitor.Mode.RESOLUTION
) ?: return@addMember false
filteredLocals.any { it.index == virtualInsn.`var` }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler {
annotation: PsiAnnotation,
targetClass: ClassNode,
targetMethod: MethodNode,
mode: CollectVisitor.Mode = CollectVisitor.Mode.MATCH_ALL,
mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION,
): List<CollectVisitor.Result<*>> {
val cache = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) {
ConcurrentHashMap<Pair<ClassAndMethodNode, CollectVisitor.Mode>, List<CollectVisitor.Result<*>>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class ModifyVariableHandler : InjectorAnnotationHandler() {
val at = annotation.findAttributeValue("at") as? PsiAnnotation
val atCode = at?.findAttributeValue("value")?.constantStringValue
val isLoadStore = atCode != null && InjectionPoint.byAtCode(atCode) is AbstractLoadInjectionPoint
val mode = if (isLoadStore) CollectVisitor.Mode.COMPLETION else CollectVisitor.Mode.MATCH_ALL
val mode = if (isLoadStore) CollectVisitor.Mode.COMPLETION else CollectVisitor.Mode.RESOLUTION
val targets = resolveInstructions(annotation, targetClass, targetMethod, mode)

val targetParamsGroup = ParameterGroup(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
import com.demonwav.mcdev.platform.mixin.reference.isMiscDynamicSelector
import com.demonwav.mcdev.platform.mixin.reference.parseMixinSelector
import com.demonwav.mcdev.platform.mixin.reference.target.TargetReference
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.SLICE
import com.demonwav.mcdev.platform.mixin.util.InjectionPointSpecifier
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.SHIFT
import com.demonwav.mcdev.platform.mixin.util.findSourceClass
import com.demonwav.mcdev.platform.mixin.util.findSourceElement
Expand Down Expand Up @@ -55,7 +55,6 @@ import com.intellij.psi.PsiReference
import com.intellij.psi.PsiReferenceExpression
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiUtil
import com.intellij.psi.util.parentOfType
import com.intellij.psi.util.parents
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
Expand Down Expand Up @@ -92,10 +91,9 @@ class AtResolver(
var atCode = at.qualifiedName?.let { InjectionPointAnnotation.atCodeFor(it) }
?: at.findDeclaredAttributeValue("value")?.constantStringValue ?: return null

// remove slice selector
val isInSlice = at.parentOfType<PsiAnnotation>()?.hasQualifiedName(SLICE) ?: false
if (isInSlice) {
if (SliceSelector.entries.any { atCode.endsWith(":${it.name}") }) {
// remove specifier
if (InjectionPointSpecifier.isAllowed(at)) {
if (InjectionPointSpecifier.entries.any { atCode.endsWith(":${it.name}") }) {
atCode = atCode.substringBeforeLast(':')
}
}
Expand Down Expand Up @@ -181,7 +179,7 @@ class AtResolver(
at,
target,
getTargetClass(target),
CollectVisitor.Mode.MATCH_FIRST,
CollectVisitor.Mode.RESOLUTION,
)
if (collectVisitor == null) {
// syntax error in target
Expand All @@ -192,32 +190,24 @@ class AtResolver(
InsnResolutionInfo.Failure()
}
}
collectVisitor.visit(targetMethod)
return if (collectVisitor.result.isEmpty()) {
InsnResolutionInfo.Failure(collectVisitor.filterToBlame)
} else {
null
}
return collectVisitor.visit(targetMethod) as? InsnResolutionInfo.Failure
}

fun resolveInstructions(mode: CollectVisitor.Mode = CollectVisitor.Mode.MATCH_ALL): List<CollectVisitor.Result<*>> {
return (getInstructionResolutionInfo(mode) as? InsnResolutionInfo.Success)?.results ?: emptyList()
fun resolveInstructions(
mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION,
): Sequence<CollectVisitor.Result<*>> {
return getInstructionResolutionInfo(mode).results
}

fun getInstructionResolutionInfo(mode: CollectVisitor.Mode = CollectVisitor.Mode.MATCH_ALL): InsnResolutionInfo {
fun getInstructionResolutionInfo(mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION): InsnResolutionInfo<*> {
val injectionPoint = getInjectionPoint(at) ?: return InsnResolutionInfo.Failure()
val targetAttr = at.findAttributeValue("target")
val target = targetAttr?.let { parseMixinSelector(it) }

val collectVisitor = injectionPoint.createCollectVisitor(at, target, getTargetClass(target), mode)
?: return InsnResolutionInfo.Failure()
collectVisitor.visit(targetMethod)
val result = collectVisitor.result
return if (result.isEmpty()) {
InsnResolutionInfo.Failure(collectVisitor.filterToBlame)
} else {
InsnResolutionInfo.Success(result)
}

return collectVisitor.visit(targetMethod)
}

fun resolveNavigationTargets(): List<PsiElement> {
Expand Down Expand Up @@ -277,6 +267,7 @@ class AtResolver(
sourceResults.forEach(matcher::accept)
matcher.result
}
.toList()
}

fun collectTargetVariants(completionHandler: (LookupElementBuilder) -> LookupElementBuilder): List<Any> {
Expand All @@ -291,11 +282,13 @@ class AtResolver(
CollectVisitor.Mode.COMPLETION
)
?: return emptyList()
visitor.visit(targetMethod)
return visitor.result

return visitor.visit(targetMethod)
.results
.mapNotNull { result ->
injectionPoint.createLookup(getTargetClass(target), result)?.let { completionHandler(it) }
}
.toList()
}
return doCollectVariants(injectionPoint)
}
Expand All @@ -305,23 +298,19 @@ class AtResolver(
}
}

sealed class InsnResolutionInfo {
class Success(val results: List<CollectVisitor.Result<*>>) : InsnResolutionInfo()
class Failure(val filterToBlame: String? = null) : InsnResolutionInfo() {
sealed class InsnResolutionInfo<out T : PsiElement>(val results: Sequence<CollectVisitor.Result<T>>) {
class Success<T : PsiElement>(results: Sequence<CollectVisitor.Result<T>>) : InsnResolutionInfo<T>(results)
class Failure(val filterStats: Map<String, Int> = emptyMap()) : InsnResolutionInfo<Nothing>(emptySequence()) {
infix fun combine(other: Failure): Failure {
return if (filterToBlame != null) {
this
} else {
other
val result = LinkedHashMap(this.filterStats)
for ((key, value) in other.filterStats) {
result[key] = (result[key] ?: 0) + value
}
return Failure(result)
}
}
}

enum class SliceSelector {
FIRST, LAST, ONE
}

object QualifiedMember {
fun resolveQualifier(reference: PsiQualifiedReference): PsiClass? {
val qualifier = reference.qualifier ?: return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,15 +310,15 @@ class ConstantInjectionPoint : InjectionPoint<PsiElement>() {
private val constantInfo: ConstantInfo?,
private val expectedType: Type? = null,
) : CollectVisitor<PsiElement>(mode) {
override fun accept(methodNode: MethodNode) {
methodNode.instructions?.iterator()?.forEachRemaining { insn ->
override fun accept(methodNode: MethodNode) = sequence {
for (insn in methodNode.instructions ?: emptyList()) {
val constant = (
insn.computeConstantValue(constantInfo?.expandConditions ?: emptySet())
?: return@forEachRemaining
?: continue
).let { if (it is NullSentinel) null else it }

if (constantInfo != null && constant != constantInfo.constant) {
return@forEachRemaining
continue
}

if (expectedType != null && constant != null) {
Expand All @@ -328,7 +328,7 @@ class ConstantInjectionPoint : InjectionPoint<PsiElement>() {
expectedType.className != CommonClassNames.JAVA_LANG_STRING
)
) {
return@forEachRemaining
continue
}

// then check if we expect any class literal
Expand All @@ -337,14 +337,14 @@ class ConstantInjectionPoint : InjectionPoint<PsiElement>() {
expectedType.className != CommonClassNames.JAVA_LANG_CLASS
)
) {
return@forEachRemaining
continue
}

// otherwise we expect a primitive literal
if (expectedType.sort in Type.BOOLEAN..Type.DOUBLE &&
constant::class.javaPrimitiveType?.let(Type::getType) != expectedType
) {
return@forEachRemaining
continue
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,10 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() {
private val selector: MixinSelector,
private val ldc: String?,
) : CollectVisitor<PsiMethod>(mode) {
override fun accept(methodNode: MethodNode) {
val insns = methodNode.instructions ?: return
override fun accept(methodNode: MethodNode) = sequence {
val insns = methodNode.instructions ?: return@sequence
var seenStringConstant: String? = null
insns.iterator().forEachRemaining { insn ->
for (insn in insns) {
if (insn is MethodInsnNode) {
// make sure we're coming from a string constant
if (seenStringConstant != null) {
Expand All @@ -225,7 +225,7 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() {
}
}

private fun processMethodInsn(insn: MethodInsnNode) {
private suspend fun SequenceScope<Result<PsiMethod>>.processMethodInsn(insn: MethodInsnNode) {
// must take a string and return void
if (insn.desc != "(Ljava/lang/String;)V") return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,23 +124,24 @@ class CtorHeadInjectionPoint : InjectionPoint<PsiElement>() {
mode: Mode,
private val enforce: EnforceMode,
) : HeadInjectionPoint.MyCollectVisitor(project, clazz, mode) {
override fun accept(methodNode: MethodNode) {
val insns = methodNode.instructions ?: return
override fun accept(methodNode: MethodNode) = sequence {
val insns = methodNode.instructions ?: return@sequence

if (!methodNode.isConstructor) {
super.accept(methodNode)
return
yieldAll(super.accept(methodNode))
return@sequence
}

val delegateCtorCall = methodNode.findDelegateConstructorCall() ?: run {
super.accept(methodNode)
return
val delegateCtorCall = methodNode.findDelegateConstructorCall()
if (delegateCtorCall == null) {
yieldAll(super.accept(methodNode))
return@sequence
}

if (enforce == EnforceMode.POST_DELEGATE) {
val insn = delegateCtorCall.next ?: return
val insn = delegateCtorCall.next ?: return@sequence
addResult(insn, methodNode.findOrConstructSourceMethod(clazz, project))
return
return@sequence
}

// Although Mumfrey's original intention was to target the last *unique* field store,
Expand All @@ -155,7 +156,7 @@ class CtorHeadInjectionPoint : InjectionPoint<PsiElement>() {
(insn as FieldInsnNode).owner == clazz.name
} ?: delegateCtorCall

val lastFieldStoreNext = lastFieldStore.next ?: return
val lastFieldStoreNext = lastFieldStore.next ?: return@sequence
addResult(lastFieldStoreNext, methodNode.findOrConstructSourceMethod(clazz, project))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,23 +196,23 @@ class FieldInjectionPoint : QualifiedInjectionPoint<PsiField>() {
private val arrayAccess: ArrayAccessType?,
private val fuzz: Int,
) : CollectVisitor<PsiField>(mode) {
override fun accept(methodNode: MethodNode) {
val insns = methodNode.instructions ?: return
insns.iterator().forEachRemaining { insn ->
if (insn !is FieldInsnNode) return@forEachRemaining
override fun accept(methodNode: MethodNode) = sequence {
val insns = methodNode.instructions ?: return@sequence
for (insn in insns) {
if (insn !is FieldInsnNode) continue
if (mode != Mode.COMPLETION) {
if (opcode != -1 && opcode != insn.opcode) {
return@forEachRemaining
continue
}
if (!selector.matchField(insn.owner, insn.name, insn.desc)) {
return@forEachRemaining
continue
}
}
val actualInsn = if (arrayAccess == null) {
insn
} else {
findArrayInsn(insn, arrayAccess)
} ?: return@forEachRemaining
} ?: continue
val fieldNode = insn.fakeResolve()
val psiField = fieldNode.field.findOrConstructSourceField(
fieldNode.clazz,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ class HeadInjectionPoint : InjectionPoint<PsiElement>() {
protected val clazz: ClassNode,
mode: Mode,
) : CollectVisitor<PsiElement>(mode) {
override fun accept(methodNode: MethodNode) {
val insns = methodNode.instructions ?: return
val firstInsn = Iterable { insns.iterator() }.firstOrNull { it.opcode >= 0 } ?: return
override fun accept(methodNode: MethodNode) = sequence {
val insns = methodNode.instructions ?: return@sequence
val firstInsn = insns.firstOrNull { it.opcode >= 0 } ?: return@sequence
addResult(firstInsn, methodNode.findOrConstructSourceMethod(clazz, project))
}
}
Expand Down
Loading