User: llamalad7
Date: 25 May 25 11:26
Revision: a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf
Summary:
New: Support injection point specifiers everywhere.
This includes several related things like:
- Rework `CollectVisitor`s to work with `Sequence`s rather than having a special case for only wanting the first result
- Rework `CollectResultFilter`s to filter based on the whole sequence (this is needed to support `:LAST`)
- Offer proper completion options for specifiers in the right context
- Fix `At#value` completions disappearing after typing `:`
TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=10026&personal=false
Index: src/main/kotlin/platform/mixin/completion/InjectionPointTypedHandlerDelegate.kt
===================================================================
--- src/main/kotlin/platform/mixin/completion/InjectionPointTypedHandlerDelegate.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
+++ src/main/kotlin/platform/mixin/completion/InjectionPointTypedHandlerDelegate.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -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 .
+ */
+
+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
+ }
+}
Index: src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt
===================================================================
--- src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -221,7 +221,7 @@
val filteredLocals = localInfo.matchLocals(
module, targetClass, targetMethod, actualInsn,
- CollectVisitor.Mode.MATCH_ALL
+ CollectVisitor.Mode.RESOLUTION
) ?: return@addMember false
filteredLocals.any { it.index == virtualInsn.`var` }
}
Index: src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -141,7 +141,7 @@
annotation: PsiAnnotation,
targetClass: ClassNode,
targetMethod: MethodNode,
- mode: CollectVisitor.Mode = CollectVisitor.Mode.MATCH_ALL,
+ mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION,
): List> {
val cache = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) {
ConcurrentHashMap, List>>()
Index: src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -48,7 +48,7 @@
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(
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -25,7 +25,7 @@
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
@@ -55,7 +55,6 @@
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
@@ -92,10 +91,9 @@
var atCode = at.qualifiedName?.let { InjectionPointAnnotation.atCodeFor(it) }
?: at.findDeclaredAttributeValue("value")?.constantStringValue ?: return null
- // remove slice selector
- val isInSlice = at.parentOfType()?.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(':')
}
}
@@ -181,7 +179,7 @@
at,
target,
getTargetClass(target),
- CollectVisitor.Mode.MATCH_FIRST,
+ CollectVisitor.Mode.RESOLUTION,
)
if (collectVisitor == null) {
// syntax error in target
@@ -192,33 +190,25 @@
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> {
- return (getInstructionResolutionInfo(mode) as? InsnResolutionInfo.Success)?.results ?: emptyList()
+ fun resolveInstructions(
+ mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION,
+ ): Sequence> {
+ 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 {
// First resolve the actual target in the bytecode using the collect visitor
@@ -277,6 +267,7 @@
sourceResults.forEach(matcher::accept)
matcher.result
}
+ .toList()
}
fun collectTargetVariants(completionHandler: (LookupElementBuilder) -> LookupElementBuilder): List {
@@ -291,11 +282,13 @@
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)
}
@@ -305,23 +298,19 @@
}
}
-sealed class InsnResolutionInfo {
- class Success(val results: List>) : InsnResolutionInfo()
- class Failure(val filterToBlame: String? = null) : InsnResolutionInfo() {
+sealed class InsnResolutionInfo(val results: Sequence>) {
+ class Success(results: Sequence>) : InsnResolutionInfo(results)
+ class Failure(val filterStats: Map = emptyMap()) : InsnResolutionInfo(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
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -310,15 +310,15 @@
private val constantInfo: ConstantInfo?,
private val expectedType: Type? = null,
) : CollectVisitor(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) {
@@ -328,7 +328,7 @@
expectedType.className != CommonClassNames.JAVA_LANG_STRING
)
) {
- return@forEachRemaining
+ continue
}
// then check if we expect any class literal
@@ -337,14 +337,14 @@
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
}
}
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -204,10 +204,10 @@
private val selector: MixinSelector,
private val ldc: String?,
) : CollectVisitor(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) {
@@ -225,7 +225,7 @@
}
}
- private fun processMethodInsn(insn: MethodInsnNode) {
+ private suspend fun SequenceScope>.processMethodInsn(insn: MethodInsnNode) {
// must take a string and return void
if (insn.desc != "(Ljava/lang/String;)V") return
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -124,23 +124,24 @@
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,
@@ -155,7 +156,7 @@
(insn as FieldInsnNode).owner == clazz.name
} ?: delegateCtorCall
- val lastFieldStoreNext = lastFieldStore.next ?: return
+ val lastFieldStoreNext = lastFieldStore.next ?: return@sequence
addResult(lastFieldStoreNext, methodNode.findOrConstructSourceMethod(clazz, project))
}
}
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -196,23 +196,23 @@
private val arrayAccess: ArrayAccessType?,
private val fuzz: Int,
) : CollectVisitor(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,
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -62,9 +62,9 @@
protected val clazz: ClassNode,
mode: Mode,
) : CollectVisitor(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))
}
}
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -22,6 +22,8 @@
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
import com.demonwav.mcdev.platform.mixin.reference.toMixinString
+import com.demonwav.mcdev.platform.mixin.util.InjectionPointSpecifier
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.SLICE
import com.demonwav.mcdev.platform.mixin.util.SourceCodeLocationInfo
import com.demonwav.mcdev.platform.mixin.util.fakeResolve
import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceMethod
@@ -32,6 +34,7 @@
import com.demonwav.mcdev.util.fullQualifiedName
import com.demonwav.mcdev.util.getQualifiedMemberReference
import com.demonwav.mcdev.util.internalName
+import com.demonwav.mcdev.util.memoized
import com.demonwav.mcdev.util.realName
import com.demonwav.mcdev.util.shortName
import com.intellij.codeInsight.completion.JavaLookupElementBuilder
@@ -50,17 +53,16 @@
import com.intellij.psi.PsiLiteral
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
-import com.intellij.psi.PsiMethodReferenceExpression
import com.intellij.psi.PsiSubstitutor
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.parentOfType
import com.intellij.serviceContainer.BaseKeyedLazyInstance
import com.intellij.util.ArrayUtilRt
import com.intellij.util.KeyedLazyInstance
+import com.intellij.util.containers.sequenceOfNotNull
import com.intellij.util.xmlb.annotations.Attribute
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
-import org.objectweb.asm.tree.InsnList
import org.objectweb.asm.tree.LineNumberNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
@@ -128,26 +130,39 @@
mode: CollectVisitor.Mode,
): CollectVisitor? {
return doCreateCollectVisitor(at, target, targetClass, mode)?.also {
- addFilters(at, targetClass, it)
+ val isInsideSlice = at.parentOfType()?.hasQualifiedName(SLICE) == true
+ val defaultSpecifier = if (isInsideSlice) InjectionPointSpecifier.FIRST else InjectionPointSpecifier.ALL
+ addFilters(at, targetClass, it, defaultSpecifier)
}
}
- protected open fun addFilters(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor) {
- addStandardFilters(at, targetClass, collectVisitor)
+ protected open fun addFilters(
+ at: PsiAnnotation,
+ targetClass: ClassNode,
+ collectVisitor: CollectVisitor,
+ defaultSpecifier: InjectionPointSpecifier
+ ) {
+ addStandardFilters(at, targetClass, collectVisitor, defaultSpecifier)
}
- fun addStandardFilters(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor<*>) {
+ fun addStandardFilters(
+ at: PsiAnnotation,
+ targetClass: ClassNode,
+ collectVisitor: CollectVisitor,
+ defaultSpecifier: InjectionPointSpecifier
+ ) {
addShiftSupport(at, targetClass, collectVisitor)
addSliceFilter(at, targetClass, collectVisitor)
// make sure the ordinal filter is last, so that the ordinal only increments once the other filters have passed
addOrdinalFilter(at, targetClass, collectVisitor)
+ addSpecifierFilter(at, targetClass, collectVisitor, defaultSpecifier)
}
protected open fun addShiftSupport(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor<*>) {
collectVisitor.shiftBy = AtResolver.getShift(at)
}
- protected open fun addSliceFilter(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor<*>) {
+ protected open fun addSliceFilter(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor) {
// resolve slice annotation, take into account slice id if present
val sliceId = at.findDeclaredAttributeValue("slice")?.constantStringValue
val parentAnnotation = at.parentOfType() ?: return
@@ -168,54 +183,52 @@
if (from == null && to == null) {
return
}
- val fromSelector = from?.findDeclaredAttributeValue("value")?.constantStringValue?.let { atCode ->
- SliceSelector.values().firstOrNull { atCode.endsWith(":${it.name}") }
- } ?: SliceSelector.FIRST
- val toSelector = to?.findDeclaredAttributeValue("value")?.constantStringValue?.let { atCode ->
- SliceSelector.values().firstOrNull { atCode.endsWith(":${it.name}") }
- } ?: SliceSelector.FIRST
fun resolveSliceIndex(
sliceAt: PsiAnnotation?,
- selector: SliceSelector,
- insns: InsnList,
method: MethodNode,
): Int? {
return sliceAt?.let {
- val results = AtResolver(sliceAt, targetClass, method).resolveInstructions()
- val insn = if (selector == SliceSelector.LAST) {
- results.lastOrNull()?.insn
- } else {
- results.firstOrNull()?.insn
+ AtResolver(sliceAt, targetClass, method).resolveInstructions()
+ .singleOrNull()
+ ?.let { method.instructions.indexOf(it.insn) }
- }
+ }
- insn?.let { insns.indexOf(it) }
- }
+ }
- }
- // allocate lazy indexes so we don't have to re-run the at resolver for the slices each time
- var fromInsnIndex: Int? = null
- var toInsnIndex: Int? = null
-
- collectVisitor.addResultFilter("slice") { result, method ->
- val insns = method.instructions ?: return@addResultFilter true
- if (fromInsnIndex == null) {
- fromInsnIndex = resolveSliceIndex(from, fromSelector, insns, method) ?: 0
+ collectVisitor.addResultFilter("slice") { results, method ->
+ val insns = method.instructions
+ val fromInsnIndex = resolveSliceIndex(from, method) ?: 0
+ val toInsnIndex = resolveSliceIndex(to, method) ?: insns.size()
+ results.filter { insns.indexOf(it.insn) in fromInsnIndex.. toInsnIndex }
- }
+ }
- if (toInsnIndex == null) {
- toInsnIndex = resolveSliceIndex(to, toSelector, insns, method) ?: insns.size()
- }
+ }
- insns.indexOf(result.insn) in fromInsnIndex!!..toInsnIndex!!
+ protected open fun addOrdinalFilter(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor) {
+ val ordinal = at.findDeclaredAttributeValue("ordinal")?.constantValue as? Int ?: return
+ if (ordinal < 0) return
+ collectVisitor.addResultFilter("ordinal") { results, _ ->
+ results.drop(ordinal).take(1)
}
}
- protected open fun addOrdinalFilter(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor<*>) {
- val ordinal = at.findDeclaredAttributeValue("ordinal")?.constantValue as? Int ?: return
- if (ordinal < 0) return
- collectVisitor.addResultFilter("ordinal") { _, _ ->
- collectVisitor.ordinal++ == ordinal
+ protected open fun addSpecifierFilter(
+ at: PsiAnnotation,
+ targetClass: ClassNode,
+ collectVisitor: CollectVisitor,
+ defaultSpecifier: InjectionPointSpecifier
+ ) {
+ val point = at.findDeclaredAttributeValue("value")?.constantStringValue ?: return
+ val specifier = InjectionPointSpecifier.entries.firstOrNull { point.endsWith(":$it") } ?: defaultSpecifier
+ collectVisitor.addResultFilter("specifier") { results, _ ->
+ val single = when (specifier) {
+ InjectionPointSpecifier.FIRST -> results.firstOrNull()
+ InjectionPointSpecifier.LAST -> results.lastOrNull()
+ InjectionPointSpecifier.ONE -> results.singleOrNull()
+ InjectionPointSpecifier.ALL -> return@addResultFilter results
- }
+ }
+ sequenceOfNotNull(single)
- }
+ }
+ }
abstract fun createLookup(targetClass: ClassNode, result: CollectVisitor.Result): LookupElementBuilder?
@@ -334,31 +347,35 @@
}
abstract class CollectVisitor(protected val mode: Mode) {
- fun visit(methodNode: MethodNode) {
- this.method = methodNode
- try {
- accept(methodNode)
- } catch (e: StopWalkingException) {
- // ignore
+ fun visit(methodNode: MethodNode): InsnResolutionInfo {
+ val numRetained = IntArray(resultFilters.size + 1)
+ var results = accept(methodNode).onEach { numRetained[0]++ }
+ for ((i, filter) in resultFilters.asSequence().map { it.second }.withIndex()) {
+ results = filter(results, methodNode).onEach { numRetained[i + 1]++ }
}
+ results = results.memoized()
+ if (results.iterator().hasNext()) {
+ return InsnResolutionInfo.Success(results)
- }
+ }
+ val filterStats = resultFilters.asSequence()
+ .map { it.first }
+ .zip(numRetained.asSequence().zipWithNext(Int::minus))
+ .toMap()
+ return InsnResolutionInfo.Failure(filterStats)
+ }
fun addResultFilter(name: String, filter: CollectResultFilter) {
resultFilters += name to filter
}
- protected abstract fun accept(methodNode: MethodNode)
+ protected abstract fun accept(methodNode: MethodNode): Sequence>
- private lateinit var method: MethodNode
private var nextIndex = 0
private val nextIndexByLine = mutableMapOf()
- val result = mutableListOf>()
private val resultFilters = mutableListOf>>()
- var filterToBlame: String? = null
- internal var ordinal = 0
internal var shiftBy = 0
- protected fun addResult(
+ protected suspend fun SequenceScope>.addResult(
insn: AbstractInsnNode,
element: T,
qualifier: String? = null,
@@ -395,23 +412,8 @@
if (insn === shiftedInsn) decorations else emptyMap()
)
- var isFiltered = false
- for ((name, filter) in resultFilters) {
- if (!filter(result, method)) {
- isFiltered = true
- if (filterToBlame == null) {
- filterToBlame = name
+ yield(result)
- }
+ }
- break
- }
- }
- if (!isFiltered) {
- this.result.add(result)
- if (mode == Mode.MATCH_FIRST) {
- stopWalking()
- }
- }
- }
private fun getLineNumber(insn: AbstractInsnNode): Int? {
var i: AbstractInsnNode? = insn
@@ -425,18 +427,7 @@
return null
}
- @Suppress("MemberVisibilityCanBePrivate")
- protected fun stopWalking() {
- throw StopWalkingException()
- }
-
- private class StopWalkingException : Exception() {
- override fun fillInStackTrace(): Throwable {
- return this
- }
- }
-
- data class Result(
+ data class Result(
val sourceLocationInfo: SourceCodeLocationInfo,
val originalInsn: AbstractInsnNode,
val insn: AbstractInsnNode,
@@ -447,7 +438,7 @@
val index: Int get() = sourceLocationInfo.index
}
- enum class Mode { MATCH_ALL, MATCH_FIRST, COMPLETION }
+ enum class Mode { RESOLUTION, COMPLETION }
}
fun nodeMatchesSelector(
@@ -471,4 +462,5 @@
)
}
-typealias CollectResultFilter = (CollectVisitor.Result, MethodNode) -> Boolean
+typealias CollectResultFilter =
+ (Sequence>, MethodNode) -> Sequence>
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -162,14 +162,14 @@
private val fuzz: Int,
private val skip: Set,
) : CollectVisitor(mode) {
- override fun accept(methodNode: MethodNode) {
- val insns = methodNode.instructions ?: return
- insns.iterator().forEachRemaining { insn ->
+ override fun accept(methodNode: MethodNode) = sequence {
+ val insns = methodNode.instructions ?: return@sequence
+ for (insn in insns) {
if (insn !is MethodInsnNode) {
- return@forEachRemaining
+ continue
}
- val sourceMethod = nodeMatchesSelector(insn, mode, selector, project) ?: return@forEachRemaining
+ val sourceMethod = nodeMatchesSelector(insn, mode, selector, project) ?: continue
val offset = insns.indexOf(insn)
val maxOffset = (offset + fuzz + 1).coerceAtMost(insns.size())
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -153,14 +153,14 @@
private val project: Project,
private val selector: MixinSelector,
) : CollectVisitor(mode) {
- override fun accept(methodNode: MethodNode) {
- val insns = methodNode.instructions ?: return
- insns.iterator().forEachRemaining { insn ->
+ override fun accept(methodNode: MethodNode) = sequence {
+ val insns = methodNode.instructions ?: return@sequence
+ for (insn in insns) {
if (insn !is MethodInsnNode) {
- return@forEachRemaining
+ continue
}
- val sourceMethod = nodeMatchesSelector(insn, mode, selector, project) ?: return@forEachRemaining
+ val sourceMethod = nodeMatchesSelector(insn, mode, selector, project) ?: continue
addResult(
insn,
sourceMethod,
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/JumpInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/JumpInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/JumpInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -92,9 +92,9 @@
mode: Mode,
private val opcode: Int
) : CollectVisitor(mode) {
- override fun accept(methodNode: MethodNode) {
- val insns = methodNode.instructions ?: return
- insns.iterator().forEachRemaining { insn ->
+ override fun accept(methodNode: MethodNode) = sequence {
+ val insns = methodNode.instructions ?: return@sequence
+ for (insn in insns) {
if (insn is JumpInsnNode && (opcode == -1 || insn.opcode == opcode)) {
addResult(insn, methodNode.findOrConstructSourceMethod(clazz, project))
}
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -112,31 +112,33 @@
return null
}
- override fun addOrdinalFilter(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor<*>) {
+ override fun addOrdinalFilter(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor) {
val ordinal = at.findDeclaredAttributeValue("ordinal")?.constantValue as? Int ?: return
if (ordinal < 0) return
// Replace the ordinal filter with one that takes into account the type of the local variable being modified.
// Fixes otherwise incorrect results for completion.
+ collectVisitor.addResultFilter("ordinal") { results, method ->
- val project = at.project
- val ordinals = mutableMapOf()
+ val project = at.project
+ val ordinals = mutableMapOf()
- collectVisitor.addResultFilter("ordinal") { result, method ->
+ results.filter { result ->
- // store returns the instruction after the variable
- val varInsn = (if (store) result.originalInsn.previous ?: result.originalInsn else result.originalInsn)
- as? VarInsnNode ?: throw IllegalStateException("AbstractLoadInjectionPoint returned non-var insn")
- val localType = AsmDfaUtil.getLocalVariableType(
- project,
- targetClass,
- method,
- result.originalInsn,
- varInsn.`var`,
+ // store returns the instruction after the variable
+ val varInsn = (if (store) result.originalInsn.previous ?: result.originalInsn else result.originalInsn)
+ as? VarInsnNode ?: throw IllegalStateException("AbstractLoadInjectionPoint returned non-var insn")
+ val localType = AsmDfaUtil.getLocalVariableType(
+ project,
+ targetClass,
+ method,
+ result.originalInsn,
+ varInsn.`var`,
- ) ?: return@addResultFilter true
+ ) ?: return@filter true
- val desc = localType.descriptor
- val ord = ordinals[desc] ?: 0
- ordinals[desc] = ord + 1
- ord == ordinal
- }
- }
+ val desc = localType.descriptor
+ val ord = ordinals[desc] ?: 0
+ ordinals[desc] = ord + 1
+ ord == ordinal
+ }
+ }
+ }
private class MyNavigationVisitor(
private val info: LocalInfo,
@@ -271,7 +273,7 @@
private val info: LocalInfo,
private val store: Boolean,
) : CollectVisitor(mode) {
- override fun accept(methodNode: MethodNode) {
+ override fun accept(methodNode: MethodNode) = sequence {
var opcode = when (info.type) {
null -> null
!is PsiPrimitiveType -> Opcodes.ALOAD
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -162,14 +162,14 @@
private val project: Project,
private val selector: MixinSelector,
) : CollectVisitor(mode) {
- override fun accept(methodNode: MethodNode) {
- val insns = methodNode.instructions ?: return
- insns.iterator().forEachRemaining { insn ->
- if (insn !is TypeInsnNode) return@forEachRemaining
- if (insn.opcode != Opcodes.NEW) return@forEachRemaining
- val initCall = findInitCall(insn) ?: return@forEachRemaining
+ override fun accept(methodNode: MethodNode) = sequence {
+ val insns = methodNode.instructions ?: return@sequence
+ for (insn in insns) {
+ if (insn !is TypeInsnNode) continue
+ if (insn.opcode != Opcodes.NEW) continue
+ val initCall = findInitCall(insn) ?: continue
- val sourceMethod = nodeMatchesSelector(initCall, mode, selector, project) ?: return@forEachRemaining
+ val sourceMethod = nodeMatchesSelector(initCall, mode, selector, project) ?: continue
addResult(
insn,
sourceMethod,
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/ReturnInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/ReturnInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/ReturnInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -133,10 +133,10 @@
mode: Mode,
private val tailOnly: Boolean,
) : CollectVisitor(mode) {
- override fun accept(methodNode: MethodNode) {
- val insns = methodNode.instructions ?: return
+ override fun accept(methodNode: MethodNode) = sequence {
+ val insns = methodNode.instructions ?: return@sequence
val elementFactory = JavaPsiFacade.getElementFactory(project)
- fun insnHandler(insn: AbstractInsnNode): Boolean {
+ suspend fun SequenceScope>.insnHandler(insn: AbstractInsnNode): Boolean {
if (insn.opcode !in Opcodes.IRETURN..Opcodes.RETURN) {
return false
}
@@ -160,11 +160,13 @@
insn = insn.previous
}
} else {
- insns.iterator().forEach(::insnHandler)
+ for (insn in insns) {
+ insnHandler(insn)
- }
- }
- }
-}
+ }
+ }
+ }
+ }
+}
class ReturnInjectionPoint : AbstractReturnInjectionPoint(false)
class TailInjectionPoint : AbstractReturnInjectionPoint(true)
Index: src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -219,11 +219,11 @@
private val poolFactory: IdentifierPoolFactory,
private val contextType: ExpressionContext.Type,
) : CollectVisitor(mode) {
- override fun accept(methodNode: MethodNode) {
- val insns = methodNode.instructions ?: return
+ override fun accept(methodNode: MethodNode) = sequence {
+ val insns = methodNode.instructions ?: return@sequence
val pool = poolFactory(methodNode)
- val flows = MEExpressionMatchUtil.getFlowMap(project, targetClass, methodNode) ?: return
+ val flows = MEExpressionMatchUtil.getFlowMap(project, targetClass, methodNode) ?: return@sequence
val result = IdentityHashMap>>()
@@ -247,7 +247,7 @@
}
if (result.isEmpty()) {
- return
+ return@sequence
}
for (insn in insns) {
Index: src/main/kotlin/platform/mixin/inspection/MixinAnnotationTargetInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/MixinAnnotationTargetInspection.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/inspection/MixinAnnotationTargetInspection.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -83,13 +83,26 @@
private fun addProblem(annotation: PsiAnnotation, message: String, failure: InsnResolutionInfo.Failure) {
val nameElement = annotation.nameReferenceElement ?: return
- var betterMessage = message
- if (failure.filterToBlame != null) {
- betterMessage += " (filtered out by ${failure.filterToBlame})"
- }
+ val betterMessage = addFilterMessage(message, failure.filterStats)
val quickFix = RemoveAnnotationQuickFix(annotation, annotation.parentOfType())
- holder.registerProblem(nameElement, message, quickFix)
+ holder.registerProblem(nameElement, betterMessage, quickFix)
}
+
+ private fun addFilterMessage(message: String, stats: Map): String {
+ if (stats.isEmpty()) {
+ return message
- }
+ }
+ return buildString {
+ append(message)
+ append(" (")
+ val it = stats.entries.asSequence().filter { it.value != 0 }.iterator()
+ append(it.next().let { (k, v) -> "$v filtered out by $k" })
+ for ((k, v) in it) {
+ append(", $v more by $k")
- }
+ }
+ append(')')
-}
+ }
+ }
+ }
+ }
+}
Index: src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -61,7 +61,7 @@
for (target in targets) {
val matchingLocals = localInfo.matchLocals(
module, target.method.clazz, target.method.method, target.result.insn,
- CollectVisitor.Mode.MATCH_ALL
+ CollectVisitor.Mode.RESOLUTION
) ?: continue
if (matchingLocals.size != 1) {
holder.registerProblem(
Index: src/main/kotlin/platform/mixin/reference/InjectionPointReference.kt
===================================================================
--- src/main/kotlin/platform/mixin/reference/InjectionPointReference.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/reference/InjectionPointReference.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -23,23 +23,27 @@
import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InjectionPoint
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT_CODE
-import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.SLICE
import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.INJECTION_POINT
-import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.SELECTOR
+import com.demonwav.mcdev.platform.mixin.util.InjectionPointSpecifier
import com.demonwav.mcdev.util.cached
import com.demonwav.mcdev.util.constantStringValue
+import com.demonwav.mcdev.util.insideAnnotationAttribute
import com.demonwav.mcdev.util.reference.ReferenceResolver
import com.demonwav.mcdev.util.reference.completeToLiteral
import com.demonwav.mcdev.util.toTypedArray
+import com.intellij.codeInsight.completion.CompletionUtil
import com.intellij.codeInsight.completion.PrioritizedLookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
+import com.intellij.patterns.ElementPattern
+import com.intellij.patterns.PsiJavaPatterns
+import com.intellij.patterns.StandardPatterns
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
-import com.intellij.psi.PsiEnumConstant
+import com.intellij.psi.PsiLiteral
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.AnnotatedElementsSearch
import com.intellij.psi.search.searches.ClassInheritorsSearch
@@ -51,6 +55,8 @@
import com.intellij.psi.util.parentOfType
object InjectionPointReference : ReferenceResolver(), MixinReference {
+ val ELEMENT_PATTERN: ElementPattern = PsiJavaPatterns.psiLiteral(StandardPatterns.string())
+ .insideAnnotationAttribute(AT)
override val description: String
get() = "injection point type '%s'"
@@ -60,11 +66,9 @@
override fun resolveReference(context: PsiElement): PsiElement? {
// Remove slice selectors from the injection point type
var name = context.constantStringValue ?: return null
- val at = context.parentOfType() ?: return null
- val isInsideSlice = at.parentOfType()?.hasQualifiedName(SLICE) == true
- if (isInsideSlice) {
- for (sliceSelector in getSliceSelectors(context.project)) {
- if (name.endsWith(":$sliceSelector")) {
+ if (allowSpecifiers(context)) {
+ for (specifier in InjectionPointSpecifier.entries) {
+ if (name.endsWith(":$specifier")) {
name = name.substringBeforeLast(':')
break
}
@@ -85,8 +89,16 @@
}
override fun collectVariants(context: PsiElement): Array {
+ val atCodes = getAllAtCodes(context.project).keys
+ val text = context.constantStringValue?.removeSuffix(CompletionUtil.DUMMY_IDENTIFIER)
+ val prefix = text?.substringBeforeLast(':', missingDelimiterValue = "")
+ if (prefix != null && prefix in atCodes && allowSpecifiers(context)) {
+ return InjectionPointSpecifier.entries.asSequence()
+ .map { LookupElementBuilder.create("$prefix:$it") }
+ .toTypedArray()
+ }
return (
- getAllAtCodes(context.project).keys.asSequence()
+ atCodes.asSequence()
.filter {
InjectionPoint.byAtCode(it)?.discouragedMessage == null
}
@@ -106,6 +118,11 @@
).toTypedArray()
}
+ private fun allowSpecifiers(context: PsiElement): Boolean {
+ val at = context.parentOfType() ?: return false
+ return InjectionPointSpecifier.isAllowed(at)
+ }
+
private fun LookupElementBuilder.completeInjectionPoint(context: PsiElement): LookupElementBuilder {
val injectionPoint = InjectionPoint.byAtCode(lookupString) ?: return completeToLiteral(context)
@@ -114,26 +131,6 @@
}
}
- private val SLICE_SELECTORS_KEY = Key>>("mcdev.sliceSelectors")
-
- private fun getSliceSelectors(project: Project): List {
- return CachedValuesManager.getManager(project).getCachedValue(
- project,
- SLICE_SELECTORS_KEY,
- {
- val selectorClass = JavaPsiFacade.getInstance(project)
- .findClass(SELECTOR, GlobalSearchScope.allScope(project))
- ?: return@getCachedValue CachedValueProvider.Result(
- emptyList(),
- PsiModificationTracker.MODIFICATION_COUNT,
- )
- val enumConstants = selectorClass.fields.mapNotNull { (it as? PsiEnumConstant)?.name }
- CachedValueProvider.Result(enumConstants, PsiModificationTracker.MODIFICATION_COUNT)
- },
- false,
- )
- }
-
private val INJECTION_POINT_INHERITORS = Key>>("mcdev.injectionPointInheritors")
private fun getCustomInjectionPointInheritors(project: Project): List {
Index: src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt
===================================================================
--- src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -47,8 +47,7 @@
// Injection point types
registrar.registerReferenceProvider(
- PsiJavaPatterns.psiLiteral(StandardPatterns.string())
- .insideAnnotationAttribute(AT),
+ InjectionPointReference.ELEMENT_PATTERN,
InjectionPointReference,
)
Index: src/main/kotlin/platform/mixin/reference/target/TargetReference.kt
===================================================================
--- src/main/kotlin/platform/mixin/reference/target/TargetReference.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/reference/target/TargetReference.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -90,7 +90,7 @@
return targets.all {
val failure = AtResolver(at, it.clazz, it.method).isUnresolved()
// leave it if there is a filter to blame, the target reference was at least resolved
- failure != null && failure.filterToBlame == null
+ failure != null && failure.filterStats.isEmpty()
}
}
Index: src/main/kotlin/platform/mixin/util/InjectionPointSpecifier.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/InjectionPointSpecifier.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
+++ src/main/kotlin/platform/mixin/util/InjectionPointSpecifier.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -0,0 +1,48 @@
+/*
+ * 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 .
+ */
+
+package com.demonwav.mcdev.platform.mixin.util
+
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.SLICE
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.SPECIFIER
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.psi.util.parentOfType
+
+enum class InjectionPointSpecifier {
+ FIRST, LAST, ONE, ALL;
+
+ companion object {
+ fun isAllowed(at: PsiAnnotation): Boolean {
+ if (isAllowedOutsideSlice(at.project)) {
+ return true
+ }
+ val isInsideSlice = at.parentOfType()?.hasQualifiedName(SLICE) == true
+ return isInsideSlice
+ }
+
+ private fun isAllowedOutsideSlice(project: Project): Boolean =
+ (JavaPsiFacade.getInstance(project)
+ .findClass(SPECIFIER, GlobalSearchScope.allScope(project))
+ != null)
+ }
+}
Index: src/main/kotlin/platform/mixin/util/MixinConstants.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/MixinConstants.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/platform/mixin/util/MixinConstants.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -37,7 +37,7 @@
const val COMPATIBILITY_LEVEL = "org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel"
const val CONSTANT_CONDITION = "org.spongepowered.asm.mixin.injection.Constant.Condition"
const val INJECTION_POINT = "org.spongepowered.asm.mixin.injection.InjectionPoint"
- const val SELECTOR = "org.spongepowered.asm.mixin.injection.InjectionPoint.Selector"
+ const val SPECIFIER = "org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier"
const val TARGET_SELECTOR = "org.spongepowered.asm.mixin.injection.selectors.TargetSelector"
const val MIXIN_AGENT = "org.spongepowered.tools.agent.MixinAgent"
const val MIXIN_CONFIG = "org.spongepowered.asm.mixin.transformer.MixinConfig"
Index: src/main/kotlin/util/reference/ReferenceResolver.kt
===================================================================
--- src/main/kotlin/util/reference/ReferenceResolver.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/util/reference/ReferenceResolver.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -105,7 +105,7 @@
}
}
-private fun PsiElement.findContextElement(): PsiElement {
+fun PsiElement.findContextElement(): PsiElement {
var current: PsiElement
var parent = this
Index: src/main/kotlin/util/sequences.kt
===================================================================
--- src/main/kotlin/util/sequences.kt (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/kotlin/util/sequences.kt (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -29,3 +29,24 @@
}
fun Sequence.filterNotNull(transform: (T) -> Any?) = this.filter { transform(it) != null }
+
+fun Sequence.memoized(): Sequence {
+ val cache = mutableListOf()
+ val iterator = this.iterator()
+
+ return sequence {
+ var index = 0
+ while (true) {
+ if (index < cache.size) {
+ yield(cache[index])
+ } else if (iterator.hasNext()) {
+ val next = iterator.next()
+ cache.add(next)
+ yield(next)
+ } else {
+ break
+ }
+ index++
+ }
+ }
+}
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision 55ee6b293763157cfe5a060027a5685b2196fc61)
+++ src/main/resources/META-INF/plugin.xml (revision a9c3fab25901dfabbcfc9b3aab88c31d3aa79cdf)
@@ -713,6 +713,7 @@
+