diff --git a/src/main/kotlin/platform/mixin/completion/InjectionPointTypedHandlerDelegate.kt b/src/main/kotlin/platform/mixin/completion/InjectionPointTypedHandlerDelegate.kt new file mode 100644 index 000000000..0a93cc2b5 --- /dev/null +++ b/src/main/kotlin/platform/mixin/completion/InjectionPointTypedHandlerDelegate.kt @@ -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 + } +} diff --git a/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt b/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt index 2ec45dd0c..47845ee5c 100644 --- a/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt +++ b/src/main/kotlin/platform/mixin/completion/MixinCompletionContributor.kt @@ -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) } } } diff --git a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt index aa7cba28e..f998c5db1 100644 --- a/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt +++ b/src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt @@ -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` } } diff --git a/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt b/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt index 193386535..71e3070a4 100644 --- a/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt @@ -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> { val cache = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) { ConcurrentHashMap, List>>() diff --git a/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt b/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt index 67dc0bcb9..8bc7a41bf 100644 --- a/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt +++ b/src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt @@ -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( diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt index 50de93e6d..63f997f34 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt @@ -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 @@ -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 @@ -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()?.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 @@ class AtResolver( at, target, getTargetClass(target), - CollectVisitor.Mode.MATCH_FIRST, + CollectVisitor.Mode.RESOLUTION, ) if (collectVisitor == null) { // syntax error in target @@ -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> { - 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 { @@ -277,6 +267,7 @@ class AtResolver( sourceResults.forEach(matcher::accept) matcher.result } + .toList() } fun collectTargetVariants(completionHandler: (LookupElementBuilder) -> LookupElementBuilder): List { @@ -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) } @@ -305,23 +298,19 @@ class AtResolver( } } -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 diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt index 39e53bb3e..6921c4afa 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt @@ -310,15 +310,15 @@ class ConstantInjectionPoint : InjectionPoint() { 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 @@ class ConstantInjectionPoint : InjectionPoint() { expectedType.className != CommonClassNames.JAVA_LANG_STRING ) ) { - return@forEachRemaining + continue } // then check if we expect any class literal @@ -337,14 +337,14 @@ class ConstantInjectionPoint : InjectionPoint() { 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 } } diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt index 1e8726df8..f79a0b59c 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt @@ -204,10 +204,10 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() { 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 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() { } } - 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 diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt index b46fa44d4..7f2c2d612 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/CtorHeadInjectionPoint.kt @@ -124,23 +124,24 @@ class CtorHeadInjectionPoint : InjectionPoint() { 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 @@ class CtorHeadInjectionPoint : InjectionPoint() { (insn as FieldInsnNode).owner == clazz.name } ?: delegateCtorCall - val lastFieldStoreNext = lastFieldStore.next ?: return + val lastFieldStoreNext = lastFieldStore.next ?: return@sequence addResult(lastFieldStoreNext, methodNode.findOrConstructSourceMethod(clazz, project)) } } diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt index 34a651421..abb2dffe2 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt @@ -196,23 +196,23 @@ class FieldInjectionPoint : QualifiedInjectionPoint() { 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, diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt index a829a6c72..72fa61b0c 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/HeadInjectionPoint.kt @@ -62,9 +62,9 @@ class HeadInjectionPoint : InjectionPoint() { 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)) } } diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt index b81a5b593..2530003d3 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt @@ -22,6 +22,8 @@ package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint 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.findAnnotations 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.PsiLambdaExpression 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 @@ abstract class InjectionPoint { 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,52 +183,50 @@ abstract class InjectionPoint { 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 - } - insn?.let { insns.indexOf(it) } + AtResolver(sliceAt, targetClass, method).resolveInstructions() + .singleOrNull() + ?.let { method.instructions.indexOf(it.insn) } } } - // 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 - } - if (toInsnIndex == null) { - toInsnIndex = resolveSliceIndex(to, toSelector, insns, method) ?: insns.size() - } - - insns.indexOf(result.insn) in fromInsnIndex!!..toInsnIndex!! + 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 } } } - protected open fun addOrdinalFilter(at: PsiAnnotation, targetClass: ClassNode, collectVisitor: CollectVisitor<*>) { + 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 + collectVisitor.addResultFilter("ordinal") { results, _ -> + results.drop(ordinal).take(1) + } + } + + 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) } } @@ -334,31 +347,35 @@ abstract class NavigationVisitor : JavaRecursiveElementVisitor() { } 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,22 +412,7 @@ abstract class CollectVisitor(protected val mode: Mode) { 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 - } - break - } - } - if (!isFiltered) { - this.result.add(result) - if (mode == Mode.MATCH_FIRST) { - stopWalking() - } - } + yield(result) } private fun getLineNumber(insn: AbstractInsnNode): Int? { @@ -425,18 +427,7 @@ abstract class CollectVisitor(protected val mode: Mode) { 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 @@ abstract class CollectVisitor(protected val mode: Mode) { 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 @@ fun nodeMatchesSelector( ) } -typealias CollectResultFilter = (CollectVisitor.Result, MethodNode) -> Boolean +typealias CollectResultFilter = + (Sequence>, MethodNode) -> Sequence> diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt index 402a324ee..e1c29a9ca 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt @@ -162,14 +162,14 @@ class InvokeAssignInjectionPoint : AbstractMethodInjectionPoint() { 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()) diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt index c11152134..604b15b0d 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt @@ -153,14 +153,14 @@ class InvokeInjectionPoint : AbstractMethodInjectionPoint() { 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, diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/JumpInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/JumpInjectionPoint.kt index 0fbc1bf4b..ea5e1bb39 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/JumpInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/JumpInjectionPoint.kt @@ -92,9 +92,9 @@ class JumpInjectionPoint : InjectionPoint() { 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)) } diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt index f4a436bc8..eb3cde298 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt @@ -112,29 +112,31 @@ abstract class AbstractLoadInjectionPoint(private val store: Boolean) : Injectio 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. - val project = at.project - val ordinals = mutableMapOf() - collectVisitor.addResultFilter("ordinal") { result, method -> - // 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 - val desc = localType.descriptor - val ord = ordinals[desc] ?: 0 - ordinals[desc] = ord + 1 - ord == ordinal + collectVisitor.addResultFilter("ordinal") { results, method -> + val project = at.project + val ordinals = mutableMapOf() + 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`, + ) ?: return@filter true + val desc = localType.descriptor + val ord = ordinals[desc] ?: 0 + ordinals[desc] = ord + 1 + ord == ordinal + } } } @@ -271,7 +273,7 @@ abstract class AbstractLoadInjectionPoint(private val store: Boolean) : Injectio 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 diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt index bf24685aa..c644baca0 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt @@ -162,14 +162,14 @@ class NewInsnInjectionPoint : InjectionPoint() { 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, diff --git a/src/main/kotlin/platform/mixin/handlers/injectionPoint/ReturnInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/injectionPoint/ReturnInjectionPoint.kt index 49e706f1f..6f5e4d95a 100644 --- a/src/main/kotlin/platform/mixin/handlers/injectionPoint/ReturnInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/injectionPoint/ReturnInjectionPoint.kt @@ -133,10 +133,10 @@ abstract class AbstractReturnInjectionPoint(private val tailOnly: Boolean) : Inj 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,7 +160,9 @@ abstract class AbstractReturnInjectionPoint(private val tailOnly: Boolean) : Inj insn = insn.previous } } else { - insns.iterator().forEach(::insnHandler) + for (insn in insns) { + insnHandler(insn) + } } } } diff --git a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt index c7ad60cb3..0def186de 100644 --- a/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt +++ b/src/main/kotlin/platform/mixin/handlers/mixinextras/ExpressionInjectionPoint.kt @@ -219,11 +219,11 @@ class ExpressionInjectionPoint : InjectionPoint() { 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 @@ class ExpressionInjectionPoint : InjectionPoint() { } if (result.isEmpty()) { - return + return@sequence } for (insn in insns) { diff --git a/src/main/kotlin/platform/mixin/inspection/MixinAnnotationTargetInspection.kt b/src/main/kotlin/platform/mixin/inspection/MixinAnnotationTargetInspection.kt index 24d20951e..dcb3e1995 100644 --- a/src/main/kotlin/platform/mixin/inspection/MixinAnnotationTargetInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/MixinAnnotationTargetInspection.kt @@ -83,12 +83,25 @@ class MixinAnnotationTargetInspection : MixinInspection() { 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(')') + } } } } diff --git a/src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt b/src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt index 0e4de681c..c7492abb8 100644 --- a/src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt +++ b/src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt @@ -61,7 +61,7 @@ class UnresolvedLocalCaptureInspection : MixinInspection() { 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( diff --git a/src/main/kotlin/platform/mixin/reference/InjectionPointReference.kt b/src/main/kotlin/platform/mixin/reference/InjectionPointReference.kt index 833d1e615..6950f258f 100644 --- a/src/main/kotlin/platform/mixin/reference/InjectionPointReference.kt +++ b/src/main/kotlin/platform/mixin/reference/InjectionPointReference.kt @@ -23,23 +23,27 @@ package com.demonwav.mcdev.platform.mixin.reference 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.PsiModificationTracker 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 @@ object InjectionPointReference : ReferenceResolver(), MixinReference { 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 @@ object InjectionPointReference : ReferenceResolver(), MixinReference { } 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 @@ object InjectionPointReference : ReferenceResolver(), MixinReference { ).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 @@ object InjectionPointReference : ReferenceResolver(), MixinReference { } } - 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 { diff --git a/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt b/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt index 55b49837f..bf00a56f2 100644 --- a/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt +++ b/src/main/kotlin/platform/mixin/reference/MixinReferenceContributor.kt @@ -47,8 +47,7 @@ class MixinReferenceContributor : PsiReferenceContributor() { // Injection point types registrar.registerReferenceProvider( - PsiJavaPatterns.psiLiteral(StandardPatterns.string()) - .insideAnnotationAttribute(AT), + InjectionPointReference.ELEMENT_PATTERN, InjectionPointReference, ) diff --git a/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt b/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt index 305d26903..b49b1627b 100644 --- a/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt +++ b/src/main/kotlin/platform/mixin/reference/target/TargetReference.kt @@ -90,7 +90,7 @@ object TargetReference : PolyReferenceResolver(), MixinReference { 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() } } diff --git a/src/main/kotlin/platform/mixin/util/InjectionPointSpecifier.kt b/src/main/kotlin/platform/mixin/util/InjectionPointSpecifier.kt new file mode 100644 index 000000000..4755aaa47 --- /dev/null +++ b/src/main/kotlin/platform/mixin/util/InjectionPointSpecifier.kt @@ -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) + } +} diff --git a/src/main/kotlin/platform/mixin/util/MixinConstants.kt b/src/main/kotlin/platform/mixin/util/MixinConstants.kt index 61f9b21a1..9ab610824 100644 --- a/src/main/kotlin/platform/mixin/util/MixinConstants.kt +++ b/src/main/kotlin/platform/mixin/util/MixinConstants.kt @@ -37,7 +37,7 @@ object MixinConstants { 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" diff --git a/src/main/kotlin/util/reference/ReferenceResolver.kt b/src/main/kotlin/util/reference/ReferenceResolver.kt index 1d7459548..503711f65 100644 --- a/src/main/kotlin/util/reference/ReferenceResolver.kt +++ b/src/main/kotlin/util/reference/ReferenceResolver.kt @@ -105,7 +105,7 @@ abstract class PolyReferenceResolver : PsiReferenceProvider() { } } -private fun PsiElement.findContextElement(): PsiElement { +fun PsiElement.findContextElement(): PsiElement { var current: PsiElement var parent = this diff --git a/src/main/kotlin/util/sequences.kt b/src/main/kotlin/util/sequences.kt index 23475debb..ea109c461 100644 --- a/src/main/kotlin/util/sequences.kt +++ b/src/main/kotlin/util/sequences.kt @@ -29,3 +29,24 @@ fun Sequence<*>.notNullToArray(): Array { } 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++ + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5e0aa1aea..943ae39ea 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -713,6 +713,7 @@ +