Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* https://mcdev.io/
*
* Copyright (C) 2025 minecraft-dev
* Copyright (C) 2026 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
Expand All @@ -29,4 +29,7 @@ public void method(String string) {

public void uniqueMethod(String string) {
}

public void uniqueDescMethod(int i) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* https://mcdev.io/
*
* Copyright (C) 2025 minecraft-dev
* Copyright (C) 2026 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
Expand All @@ -21,7 +21,6 @@
package com.demonwav.mcdev.platform.mixin.action

import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.QualifiedMember
import com.demonwav.mcdev.platform.mixin.reference.toMixinString
import com.demonwav.mcdev.util.findReferencedMember
import com.demonwav.mcdev.util.getQualifiedMemberReference
import com.demonwav.mcdev.util.qualifiedMemberReference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* https://mcdev.io/
*
* Copyright (C) 2025 minecraft-dev
* Copyright (C) 2026 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
Expand All @@ -24,6 +24,7 @@ import com.demonwav.mcdev.platform.mixin.MixinModuleType
import com.demonwav.mcdev.platform.mixin.folding.MixinFoldingSettings
import com.demonwav.mcdev.platform.mixin.reference.target.FieldDefinitionReference
import com.demonwav.mcdev.platform.mixin.reference.target.MethodDefinitionReference
import com.demonwav.mcdev.platform.mixin.util.MemberInfo
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
import com.demonwav.mcdev.util.MemberReference
import com.intellij.lang.ASTNode
Expand All @@ -49,8 +50,8 @@ class MEDefinitionFoldingBuilder : CustomFoldingBuilder() {
val psi = node.psi
if (psi is PsiLiteralExpression) {
val value = psi.value as? String ?: return "..."
val memberReference = MemberReference.parse(value) ?: return "..."
return memberReference.presentableText
val memberInfo = MemberInfo.parse(value) ?: return "..."
return MemberReference(memberInfo.name ?: "*", memberInfo.descriptor, memberInfo.owner).presentableText
}
return "..."
}
Expand Down Expand Up @@ -108,7 +109,7 @@ class MEDefinitionFoldingBuilder : CustomFoldingBuilder() {
if (FieldDefinitionReference.ELEMENT_PATTERN.accepts(expression) ||
MethodDefinitionReference.ELEMENT_PATTERN.accepts(expression)
) {
if (MemberReference.parse(expression.value as String) != null) {
if (MemberInfo.parse(expression.value as String) != null) {
descriptors.add(FoldingDescriptor(expression.node, expression.textRange))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* https://mcdev.io/
*
* Copyright (C) 2025 minecraft-dev
* Copyright (C) 2026 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
Expand All @@ -24,8 +24,8 @@ import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler
import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor
import com.demonwav.mcdev.platform.mixin.util.LocalInfo
import com.demonwav.mcdev.platform.mixin.util.MemberInfo
import com.demonwav.mcdev.platform.mixin.util.MixinConstants
import com.demonwav.mcdev.util.MemberReference
import com.demonwav.mcdev.util.computeStringArray
import com.demonwav.mcdev.util.constantStringValue
import com.demonwav.mcdev.util.descriptor
Expand Down Expand Up @@ -172,7 +172,7 @@ object MEExpressionMatchUtil {

val fields = annotation.findDeclaredAttributeValue("field")?.computeStringArray() ?: emptyList()
for (field in fields) {
val fieldRef = MemberReference.parse(field) ?: continue
val fieldRef = MemberInfo.parse(field) ?: continue
pool.addMember(
definitionId,
SimpleMemberDefinition {
Expand All @@ -183,7 +183,7 @@ object MEExpressionMatchUtil {

val methods = annotation.findDeclaredAttributeValue("method")?.computeStringArray() ?: emptyList()
for (method in methods) {
val methodRef = MemberReference.parse(method) ?: continue
val methodRef = MemberInfo.parse(method) ?: continue
pool.addMember(
definitionId,
object : SimpleMemberDefinition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* https://mcdev.io/
*
* Copyright (C) 2025 minecraft-dev
* Copyright (C) 2026 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
Expand All @@ -21,7 +21,7 @@
package com.demonwav.mcdev.platform.mixin.expression

import com.demonwav.mcdev.platform.mixin.util.LocalInfo
import com.demonwav.mcdev.util.MemberReference
import com.demonwav.mcdev.platform.mixin.util.MemberInfo
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement

Expand All @@ -32,8 +32,8 @@ class MESourceMatchContext(val project: Project) {
val captures: List<PsiElement> get() = capturesInternal

private val types = mutableMapOf<String, MutableList<String>>()
private val fields = mutableMapOf<String, MutableList<MemberReference>>()
private val methods = mutableMapOf<String, MutableList<MemberReference>>()
private val fields = mutableMapOf<String, MutableList<MemberInfo>>()
private val methods = mutableMapOf<String, MutableList<MemberInfo>>()
private val localInfos = mutableMapOf<String, MutableList<LocalInfo>>()

init {
Expand All @@ -57,15 +57,15 @@ class MESourceMatchContext(val project: Project) {
types.getOrPut(key, ::mutableListOf) += desc
}

fun getFields(key: String): List<MemberReference> = fields[key] ?: emptyList()
fun getFields(key: String): List<MemberInfo> = fields[key] ?: emptyList()

fun addField(key: String, field: MemberReference) {
fun addField(key: String, field: MemberInfo) {
fields.getOrPut(key, ::mutableListOf) += field
}

fun getMethods(key: String): List<MemberReference> = methods[key] ?: emptyList()
fun getMethods(key: String): List<MemberInfo> = methods[key] ?: emptyList()

fun addMethod(key: String, method: MemberReference) {
fun addMethod(key: String, method: MemberInfo) {
methods.getOrPut(key, ::mutableListOf) += method
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* https://mcdev.io/
*
* Copyright (C) 2025 minecraft-dev
* Copyright (C) 2026 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
Expand Down Expand Up @@ -51,7 +51,7 @@ class MixinTargetDescriptorFoldingBuilder : CustomFoldingBuilder() {

override fun getLanguagePlaceholderText(node: ASTNode, range: TextRange): String? {
val element = node.psi
return TargetReference.resolveTarget(element)?.let { formatElement(it) }
return TargetReference.resolveTargets(element).singleOrNull()?.let { formatElement(it) }
}

private fun formatElement(element: PsiElement): String? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler {
(actualTarget to actualTarget.methods)
}

return targetClassMethods.mapNotNull { (selector, pair) ->
return targetClassMethods.flatMap { (selector, pair) ->
val (clazz, methods) = pair
methods.firstNotNullOfOrNull { method ->
methods.mapNotNull { method ->
if (selector.matchMethod(method, clazz)) {
MethodTargetMember(clazz, method)
} else {
Expand All @@ -88,9 +88,9 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler {
}

return resolveTarget(annotation, targetClass).map { targetMember ->
val targetMethod = targetMember as? MethodTargetMember ?: return@map InsnResolutionInfo.Failure()
val targetMethod = targetMember as? MethodTargetMember ?: return@map InsnResolutionInfo.Failure(AtResolver.DEFAULT_UNRESOLVED_MESSAGE)
isUnresolved(annotation, targetClass, targetMethod.classAndMethod.method) ?: return@isUnresolved null
}.reduceOrNull(InsnResolutionInfo.Failure::combine) ?: InsnResolutionInfo.Failure()
}.reduceOrNull(InsnResolutionInfo.Failure::combine) ?: InsnResolutionInfo.Failure(AtResolver.DEFAULT_UNRESOLVED_MESSAGE)
}

open fun getAtKey(annotation: PsiAnnotation): String = "at"
Expand All @@ -101,7 +101,7 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler {
targetMethod: MethodNode,
): InsnResolutionInfo.Failure? {
return annotation.findAttributeValue(getAtKey(annotation))?.findAnnotations()
.ifNullOrEmpty { return InsnResolutionInfo.Failure() }!!
.ifNullOrEmpty { return InsnResolutionInfo.Failure(AtResolver.DEFAULT_UNRESOLVED_MESSAGE) }!!
.firstNotNullOfOrNull { AtResolver(it, targetClass, targetMethod).isUnresolved() }
}

Expand Down Expand Up @@ -170,10 +170,6 @@ abstract class InjectorAnnotationHandler : MixinAnnotationHandler {

open val allowedInsnDescription = "all instructions"

override fun createUnresolvedMessage(annotation: PsiAnnotation): String? {
return "Cannot resolve any target instructions in target class"
}

open fun canAlwaysBeStatic(method: PsiMethod): Boolean {
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface MixinAnnotationHandler {
fun resolveTarget(annotation: PsiAnnotation, targetClass: ClassNode): List<MixinTargetMember>

fun isUnresolved(annotation: PsiAnnotation): InsnResolutionInfo.Failure? {
val containingClass = annotation.findContainingClass() ?: return InsnResolutionInfo.Failure()
val containingClass = annotation.findContainingClass() ?: return null // no containing mixin, don't show unresolved error
return containingClass.mixinTargets
.mapNotNull { isUnresolved(annotation, it) }
.reduceOrNull(InsnResolutionInfo.Failure::combine)
Expand All @@ -67,8 +67,6 @@ interface MixinAnnotationHandler {

fun resolveForNavigation(annotation: PsiAnnotation, targetClass: ClassNode): List<PsiElement>

fun createUnresolvedMessage(annotation: PsiAnnotation): String?

/**
* Returns true if we don't actually know the implementation of the annotation, and we're just making
* a guess. Prevents unresolved errors but still attempts navigation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import org.objectweb.asm.tree.ClassNode
interface MixinMemberAnnotationHandler : MixinAnnotationHandler {
override fun isUnresolved(annotation: PsiAnnotation, targetClass: ClassNode): InsnResolutionInfo.Failure? {
return if (resolveTarget(annotation, targetClass).isEmpty()) {
InsnResolutionInfo.Failure()
createUnresolvedMessage(annotation)?.let(InsnResolutionInfo<Nothing>::Failure)
} else {
null
}
Expand All @@ -39,4 +39,6 @@ interface MixinMemberAnnotationHandler : MixinAnnotationHandler {
val targets = resolveTarget(annotation, targetClass)
return targets.mapNotNull { it.findSourceElement(project, annotation.resolveScope, canDecompile = true) }
}

fun createUnresolvedMessage(annotation: PsiAnnotation): String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ import com.demonwav.mcdev.platform.mixin.util.findSourceClass
import com.demonwav.mcdev.platform.mixin.util.findSourceElement
import com.demonwav.mcdev.platform.mixin.util.isClinit
import com.demonwav.mcdev.platform.mixin.util.memberReference
import com.demonwav.mcdev.util.Quantifier
import com.demonwav.mcdev.util.computeStringArray
import com.demonwav.mcdev.util.constantStringValue
import com.demonwav.mcdev.util.constantValue
import com.demonwav.mcdev.util.countIsAtLeast
import com.demonwav.mcdev.util.descriptor
import com.demonwav.mcdev.util.equivalentTo
import com.demonwav.mcdev.util.findMethods
Expand Down Expand Up @@ -174,6 +176,8 @@ class AtResolver(
else -> 0
}
}

const val DEFAULT_UNRESOLVED_MESSAGE = "Cannot resolve any instructions in target"
}

fun isUnresolved(): InsnResolutionInfo.Failure? {
Expand All @@ -190,14 +194,24 @@ class AtResolver(
)
if (collectVisitor == null) {
// syntax error in target
val stringValue = targetAttr?.constantStringValue ?: return InsnResolutionInfo.Failure()
val stringValue = targetAttr?.constantStringValue ?: return InsnResolutionInfo.Failure(DEFAULT_UNRESOLVED_MESSAGE)
return if (isMiscDynamicSelector(at.project, stringValue)) {
null
} else {
InsnResolutionInfo.Failure()
InsnResolutionInfo.Failure(DEFAULT_UNRESOLVED_MESSAGE)
}
}
return when (val result = collectVisitor.visit(targetMethod)) {
is InsnResolutionInfo.Failure -> result
is InsnResolutionInfo.Success -> {
val minMatches = collectVisitor.quantifier.min(Quantifier.Context.INSTRUCTION).coerceAtLeast(1)
if (result.results.countIsAtLeast(minMatches)) {
null
} else {
InsnResolutionInfo.Failure("Quantifier requires at least $minMatches matches")
}
}
}
return collectVisitor.visit(targetMethod) as? InsnResolutionInfo.Failure
}

fun resolveInstructions(
Expand All @@ -207,12 +221,12 @@ class AtResolver(
}

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

val collectVisitor = injectionPoint.createCollectVisitor(at, target, getTargetClass(target), mode)
?: return InsnResolutionInfo.Failure()
?: return InsnResolutionInfo.Failure(DEFAULT_UNRESOLVED_MESSAGE)

return collectVisitor.visit(targetMethod)
}
Expand Down Expand Up @@ -311,13 +325,19 @@ class AtResolver(

sealed class InsnResolutionInfo<out T : PsiElement>(val results: Sequence<CollectVisitor.Result<T>>) {
class Success<T : PsiElement>(results: Sequence<CollectVisitor.Result<T>>) : InsnResolutionInfo<T>(results)
class Failure(val filterStats: Map<String, Int> = emptyMap()) : InsnResolutionInfo<Nothing>(emptySequence()) {
class Failure(val messages: Set<String>, val filterStats: Map<String, Int>) : InsnResolutionInfo<Nothing>(emptySequence()) {
constructor(message: String, filterStats: Map<String, Int> = emptyMap()) : this(linkedSetOf(message), filterStats)

infix fun combine(other: Failure): Failure {
val messages = linkedSetOf<String>()
messages += this.messages
messages += other.messages

val result = LinkedHashMap(this.filterStats)
for ((key, value) in other.filterStats) {
result[key] = (result[key] ?: 0) + value
}
return Failure(result)
return Failure(messages, result)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint

import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
import com.demonwav.mcdev.platform.mixin.util.MemberInfo
import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
import com.demonwav.mcdev.platform.mixin.util.fakeResolve
import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceMethod
import com.demonwav.mcdev.util.MemberReference
import com.demonwav.mcdev.util.Quantifier
import com.demonwav.mcdev.util.constantStringValue
import com.demonwav.mcdev.util.createLiteralExpression
import com.demonwav.mcdev.util.toTypedArray
Expand Down Expand Up @@ -148,7 +149,7 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() {
mode: CollectVisitor.Mode,
): CollectVisitor<PsiMethod>? {
if (mode == CollectVisitor.Mode.COMPLETION) {
return MyCollectVisitor(mode, at.project, MemberReference(""), null)
return MyCollectVisitor(mode, at.project, MemberInfo(), null)
}
return target?.let { MyCollectVisitor(mode, at.project, it, AtResolver.getArgs(at)["ldc"]) }
}
Expand Down Expand Up @@ -204,6 +205,9 @@ class ConstantStringMethodInjectionPoint : AbstractMethodInjectionPoint() {
private val selector: MixinSelector,
private val ldc: String?,
) : CollectVisitor<PsiMethod>(mode) {
override val quantifier: Quantifier
get() = selector.quantifier

override fun accept(methodNode: MethodNode) = sequence {
val insns = methodNode.instructions ?: return@sequence
var seenStringConstant: String? = null
Expand Down
Loading
Loading