Skip to content

Commit 8f2c118

Browse files
Merge pull request #2471 from LlamaLad7/fix/mixin-fixes-2
More Mixin Fixes
2 parents 94886bc + a9c3fab commit 8f2c118

29 files changed

+351
-240
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2025 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.platform.mixin.completion
22+
23+
import com.demonwav.mcdev.platform.mixin.reference.InjectionPointReference
24+
import com.demonwav.mcdev.util.reference.findContextElement
25+
import com.intellij.codeInsight.AutoPopupController
26+
import com.intellij.codeInsight.editorActions.TypedHandlerDelegate
27+
import com.intellij.lang.java.JavaLanguage
28+
import com.intellij.openapi.editor.Editor
29+
import com.intellij.openapi.project.Project
30+
import com.intellij.psi.PsiFile
31+
32+
class InjectionPointTypedHandlerDelegate : TypedHandlerDelegate() {
33+
override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result {
34+
if (charTyped != ':' || !file.language.isKindOf(JavaLanguage.INSTANCE)) {
35+
return Result.CONTINUE
36+
}
37+
AutoPopupController.getInstance(project).autoPopupMemberLookup(editor) {
38+
val offset = editor.caretModel.offset
39+
val element = it.findElementAt(offset)?.findContextElement()
40+
InjectionPointReference.ELEMENT_PATTERN.accepts(element)
41+
}
42+
return Result.CONTINUE
43+
}
44+
}

src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,14 @@ class MixinCompletionContributor : CompletionContributor() {
106106
}
107107

108108
// Process methods and fields from target class
109-
findShadowTargets(psiClass, start, superMixin != null)
109+
val elements = findShadowTargets(psiClass, start, superMixin != null)
110110
.map { it.createLookupElement(psiClass.project) }
111111
.filter { prefixMatcher.prefixMatches(it) }
112112
.filter(filter, position)
113113
.map { PrioritizedLookupElement.withExplicitProximity(it, 1) }
114-
.forEach(r::addElement)
114+
.toList()
115+
116+
r.addAllElements(elements)
115117
}
116118
}
117119
}

src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ object MEExpressionMatchUtil {
221221

222222
val filteredLocals = localInfo.matchLocals(
223223
module, targetClass, targetMethod, actualInsn,
224-
CollectVisitor.Mode.MATCH_ALL
224+
CollectVisitor.Mode.RESOLUTION
225225
) ?: return@addMember false
226226
filteredLocals.any { it.index == virtualInsn.`var` }
227227
}

src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler {
141141
annotation: PsiAnnotation,
142142
targetClass: ClassNode,
143143
targetMethod: MethodNode,
144-
mode: CollectVisitor.Mode = CollectVisitor.Mode.MATCH_ALL,
144+
mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION,
145145
): List<CollectVisitor.Result<*>> {
146146
val cache = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) {
147147
ConcurrentHashMap<Pair<ClassAndMethodNode, CollectVisitor.Mode>, List<CollectVisitor.Result<*>>>()

src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class ModifyVariableHandler : InjectorAnnotationHandler() {
4848
val at = annotation.findAttributeValue("at") as? PsiAnnotation
4949
val atCode = at?.findAttributeValue("value")?.constantStringValue
5050
val isLoadStore = atCode != null && InjectionPoint.byAtCode(atCode) is AbstractLoadInjectionPoint
51-
val mode = if (isLoadStore) CollectVisitor.Mode.COMPLETION else CollectVisitor.Mode.MATCH_ALL
51+
val mode = if (isLoadStore) CollectVisitor.Mode.COMPLETION else CollectVisitor.Mode.RESOLUTION
5252
val targets = resolveInstructions(annotation, targetClass, targetMethod, mode)
5353

5454
val targetParamsGroup = ParameterGroup(

src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
2525
import com.demonwav.mcdev.platform.mixin.reference.isMiscDynamicSelector
2626
import com.demonwav.mcdev.platform.mixin.reference.parseMixinSelector
2727
import com.demonwav.mcdev.platform.mixin.reference.target.TargetReference
28-
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.SLICE
28+
import com.demonwav.mcdev.platform.mixin.util.InjectionPointSpecifier
2929
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.SHIFT
3030
import com.demonwav.mcdev.platform.mixin.util.findSourceClass
3131
import com.demonwav.mcdev.platform.mixin.util.findSourceElement
@@ -55,7 +55,6 @@ import com.intellij.psi.PsiReference
5555
import com.intellij.psi.PsiReferenceExpression
5656
import com.intellij.psi.search.GlobalSearchScope
5757
import com.intellij.psi.util.PsiUtil
58-
import com.intellij.psi.util.parentOfType
5958
import com.intellij.psi.util.parents
6059
import org.objectweb.asm.tree.ClassNode
6160
import org.objectweb.asm.tree.MethodNode
@@ -92,10 +91,9 @@ class AtResolver(
9291
var atCode = at.qualifiedName?.let { InjectionPointAnnotation.atCodeFor(it) }
9392
?: at.findDeclaredAttributeValue("value")?.constantStringValue ?: return null
9493

95-
// remove slice selector
96-
val isInSlice = at.parentOfType<PsiAnnotation>()?.hasQualifiedName(SLICE) ?: false
97-
if (isInSlice) {
98-
if (SliceSelector.entries.any { atCode.endsWith(":${it.name}") }) {
94+
// remove specifier
95+
if (InjectionPointSpecifier.isAllowed(at)) {
96+
if (InjectionPointSpecifier.entries.any { atCode.endsWith(":${it.name}") }) {
9997
atCode = atCode.substringBeforeLast(':')
10098
}
10199
}
@@ -181,7 +179,7 @@ class AtResolver(
181179
at,
182180
target,
183181
getTargetClass(target),
184-
CollectVisitor.Mode.MATCH_FIRST,
182+
CollectVisitor.Mode.RESOLUTION,
185183
)
186184
if (collectVisitor == null) {
187185
// syntax error in target
@@ -192,32 +190,24 @@ class AtResolver(
192190
InsnResolutionInfo.Failure()
193191
}
194192
}
195-
collectVisitor.visit(targetMethod)
196-
return if (collectVisitor.result.isEmpty()) {
197-
InsnResolutionInfo.Failure(collectVisitor.filterToBlame)
198-
} else {
199-
null
200-
}
193+
return collectVisitor.visit(targetMethod) as? InsnResolutionInfo.Failure
201194
}
202195

203-
fun resolveInstructions(mode: CollectVisitor.Mode = CollectVisitor.Mode.MATCH_ALL): List<CollectVisitor.Result<*>> {
204-
return (getInstructionResolutionInfo(mode) as? InsnResolutionInfo.Success)?.results ?: emptyList()
196+
fun resolveInstructions(
197+
mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION,
198+
): Sequence<CollectVisitor.Result<*>> {
199+
return getInstructionResolutionInfo(mode).results
205200
}
206201

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

212207
val collectVisitor = injectionPoint.createCollectVisitor(at, target, getTargetClass(target), mode)
213208
?: return InsnResolutionInfo.Failure()
214-
collectVisitor.visit(targetMethod)
215-
val result = collectVisitor.result
216-
return if (result.isEmpty()) {
217-
InsnResolutionInfo.Failure(collectVisitor.filterToBlame)
218-
} else {
219-
InsnResolutionInfo.Success(result)
220-
}
209+
210+
return collectVisitor.visit(targetMethod)
221211
}
222212

223213
fun resolveNavigationTargets(): List<PsiElement> {
@@ -277,6 +267,7 @@ class AtResolver(
277267
sourceResults.forEach(matcher::accept)
278268
matcher.result
279269
}
270+
.toList()
280271
}
281272

282273
fun collectTargetVariants(completionHandler: (LookupElementBuilder) -> LookupElementBuilder): List<Any> {
@@ -291,11 +282,13 @@ class AtResolver(
291282
CollectVisitor.Mode.COMPLETION
292283
)
293284
?: return emptyList()
294-
visitor.visit(targetMethod)
295-
return visitor.result
285+
286+
return visitor.visit(targetMethod)
287+
.results
296288
.mapNotNull { result ->
297289
injectionPoint.createLookup(getTargetClass(target), result)?.let { completionHandler(it) }
298290
}
291+
.toList()
299292
}
300293
return doCollectVariants(injectionPoint)
301294
}
@@ -305,23 +298,19 @@ class AtResolver(
305298
}
306299
}
307300

308-
sealed class InsnResolutionInfo {
309-
class Success(val results: List<CollectVisitor.Result<*>>) : InsnResolutionInfo()
310-
class Failure(val filterToBlame: String? = null) : InsnResolutionInfo() {
301+
sealed class InsnResolutionInfo<out T : PsiElement>(val results: Sequence<CollectVisitor.Result<T>>) {
302+
class Success<T : PsiElement>(results: Sequence<CollectVisitor.Result<T>>) : InsnResolutionInfo<T>(results)
303+
class Failure(val filterStats: Map<String, Int> = emptyMap()) : InsnResolutionInfo<Nothing>(emptySequence()) {
311304
infix fun combine(other: Failure): Failure {
312-
return if (filterToBlame != null) {
313-
this
314-
} else {
315-
other
305+
val result = LinkedHashMap(this.filterStats)
306+
for ((key, value) in other.filterStats) {
307+
result[key] = (result[key] ?: 0) + value
316308
}
309+
return Failure(result)
317310
}
318311
}
319312
}
320313

321-
enum class SliceSelector {
322-
FIRST, LAST, ONE
323-
}
324-
325314
object QualifiedMember {
326315
fun resolveQualifier(reference: PsiQualifiedReference): PsiClass? {
327316
val qualifier = reference.qualifier ?: return null

src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -310,15 +310,15 @@ class ConstantInjectionPoint : InjectionPoint<PsiElement>() {
310310
private val constantInfo: ConstantInfo?,
311311
private val expectedType: Type? = null,
312312
) : CollectVisitor<PsiElement>(mode) {
313-
override fun accept(methodNode: MethodNode) {
314-
methodNode.instructions?.iterator()?.forEachRemaining { insn ->
313+
override fun accept(methodNode: MethodNode) = sequence {
314+
for (insn in methodNode.instructions ?: emptyList()) {
315315
val constant = (
316316
insn.computeConstantValue(constantInfo?.expandConditions ?: emptySet())
317-
?: return@forEachRemaining
317+
?: continue
318318
).let { if (it is NullSentinel) null else it }
319319

320320
if (constantInfo != null && constant != constantInfo.constant) {
321-
return@forEachRemaining
321+
continue
322322
}
323323

324324
if (expectedType != null && constant != null) {
@@ -328,7 +328,7 @@ class ConstantInjectionPoint : InjectionPoint<PsiElement>() {
328328
expectedType.className != CommonClassNames.JAVA_LANG_STRING
329329
)
330330
) {
331-
return@forEachRemaining
331+
continue
332332
}
333333

334334
// then check if we expect any class literal
@@ -337,14 +337,14 @@ class ConstantInjectionPoint : InjectionPoint<PsiElement>() {
337337
expectedType.className != CommonClassNames.JAVA_LANG_CLASS
338338
)
339339
) {
340-
return@forEachRemaining
340+
continue
341341
}
342342

343343
// otherwise we expect a primitive literal
344344
if (expectedType.sort in Type.BOOLEAN..Type.DOUBLE &&
345345
constant::class.javaPrimitiveType?.let(Type::getType) != expectedType
346346
) {
347-
return@forEachRemaining
347+
continue
348348
}
349349
}
350350

src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,10 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() {
204204
private val selector: MixinSelector,
205205
private val ldc: String?,
206206
) : CollectVisitor<PsiMethod>(mode) {
207-
override fun accept(methodNode: MethodNode) {
208-
val insns = methodNode.instructions ?: return
207+
override fun accept(methodNode: MethodNode) = sequence {
208+
val insns = methodNode.instructions ?: return@sequence
209209
var seenStringConstant: String? = null
210-
insns.iterator().forEachRemaining { insn ->
210+
for (insn in insns) {
211211
if (insn is MethodInsnNode) {
212212
// make sure we're coming from a string constant
213213
if (seenStringConstant != null) {
@@ -225,7 +225,7 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() {
225225
}
226226
}
227227

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

src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,23 +124,24 @@ class CtorHeadInjectionPoint : InjectionPoint<PsiElement>() {
124124
mode: Mode,
125125
private val enforce: EnforceMode,
126126
) : HeadInjectionPoint.MyCollectVisitor(project, clazz, mode) {
127-
override fun accept(methodNode: MethodNode) {
128-
val insns = methodNode.instructions ?: return
127+
override fun accept(methodNode: MethodNode) = sequence {
128+
val insns = methodNode.instructions ?: return@sequence
129129

130130
if (!methodNode.isConstructor) {
131-
super.accept(methodNode)
132-
return
131+
yieldAll(super.accept(methodNode))
132+
return@sequence
133133
}
134134

135-
val delegateCtorCall = methodNode.findDelegateConstructorCall() ?: run {
136-
super.accept(methodNode)
137-
return
135+
val delegateCtorCall = methodNode.findDelegateConstructorCall()
136+
if (delegateCtorCall == null) {
137+
yieldAll(super.accept(methodNode))
138+
return@sequence
138139
}
139140

140141
if (enforce == EnforceMode.POST_DELEGATE) {
141-
val insn = delegateCtorCall.next ?: return
142+
val insn = delegateCtorCall.next ?: return@sequence
142143
addResult(insn, methodNode.findOrConstructSourceMethod(clazz, project))
143-
return
144+
return@sequence
144145
}
145146

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

158-
val lastFieldStoreNext = lastFieldStore.next ?: return
159+
val lastFieldStoreNext = lastFieldStore.next ?: return@sequence
159160
addResult(lastFieldStoreNext, methodNode.findOrConstructSourceMethod(clazz, project))
160161
}
161162
}

src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,23 @@ class FieldInjectionPoint : QualifiedInjectionPoint<PsiField>() {
196196
private val arrayAccess: ArrayAccessType?,
197197
private val fuzz: Int,
198198
) : CollectVisitor<PsiField>(mode) {
199-
override fun accept(methodNode: MethodNode) {
200-
val insns = methodNode.instructions ?: return
201-
insns.iterator().forEachRemaining { insn ->
202-
if (insn !is FieldInsnNode) return@forEachRemaining
199+
override fun accept(methodNode: MethodNode) = sequence {
200+
val insns = methodNode.instructions ?: return@sequence
201+
for (insn in insns) {
202+
if (insn !is FieldInsnNode) continue
203203
if (mode != Mode.COMPLETION) {
204204
if (opcode != -1 && opcode != insn.opcode) {
205-
return@forEachRemaining
205+
continue
206206
}
207207
if (!selector.matchField(insn.owner, insn.name, insn.desc)) {
208-
return@forEachRemaining
208+
continue
209209
}
210210
}
211211
val actualInsn = if (arrayAccess == null) {
212212
insn
213213
} else {
214214
findArrayInsn(insn, arrayAccess)
215-
} ?: return@forEachRemaining
215+
} ?: continue
216216
val fieldNode = insn.fakeResolve()
217217
val psiField = fieldNode.field.findOrConstructSourceField(
218218
fieldNode.clazz,

src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ class HeadInjectionPoint : InjectionPoint<PsiElement>() {
6262
protected val clazz: ClassNode,
6363
mode: Mode,
6464
) : CollectVisitor<PsiElement>(mode) {
65-
override fun accept(methodNode: MethodNode) {
66-
val insns = methodNode.instructions ?: return
67-
val firstInsn = Iterable { insns.iterator() }.firstOrNull { it.opcode >= 0 } ?: return
65+
override fun accept(methodNode: MethodNode) = sequence {
66+
val insns = methodNode.instructions ?: return@sequence
67+
val firstInsn = insns.firstOrNull { it.opcode >= 0 } ?: return@sequence
6868
addResult(firstInsn, methodNode.findOrConstructSourceMethod(clazz, project))
6969
}
7070
}

0 commit comments

Comments
 (0)