User: joe
Date: 06 Nov 25 11:26
Revision: 4a481131a25e50573ba9e876841ce2310c8a3ed5
Summary:
Add inspection for @ModifyVariable being able to use names instead of ordinals and indexes
TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10229&personal=false
Index: src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableMayUseNameInspection.kt
===================================================================
--- src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableMayUseNameInspection.kt (revision 4a481131a25e50573ba9e876841ce2310c8a3ed5)
+++ src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableMayUseNameInspection.kt (revision 4a481131a25e50573ba9e876841ce2310c8a3ed5)
@@ -0,0 +1,118 @@
+/*
+ * 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.inspection.injector
+
+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.inspection.MixinInspection
+import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix
+import com.demonwav.mcdev.platform.mixin.util.LocalInfo
+import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
+import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MODIFY_VARIABLE
+import com.demonwav.mcdev.platform.mixin.util.hasNamedLocalVariables
+import com.demonwav.mcdev.platform.mixin.util.mixinTargets
+import com.demonwav.mcdev.util.findAnnotation
+import com.demonwav.mcdev.util.findContainingClass
+import com.demonwav.mcdev.util.findModule
+import com.intellij.codeInspection.ProblemsHolder
+import com.intellij.psi.JavaElementVisitor
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiMethod
+
+class ModifyVariableMayUseNameInspection : MixinInspection() {
+ override fun getStaticDescription() = "Reports @ModifyVariable injectors relying on index or ordinal that may use a name instead"
+
+ override fun buildVisitor(holder: ProblemsHolder) = object : JavaElementVisitor() {
+ override fun visitMethod(method: PsiMethod) {
+ val modifyVariable = method.findAnnotation(MODIFY_VARIABLE) ?: return
+ val localType = method.parameterList.getParameter(0)?.type ?: return
+ val problemElement = modifyVariable.nameReferenceElement ?: return
+
+ val injector =
+ MixinAnnotationHandler.forMixinAnnotation(MODIFY_VARIABLE) as? InjectorAnnotationHandler ?: return
+ val localInfo = LocalInfo.fromAnnotation(localType, modifyVariable)
+
+ val variableName = getVariableNameToIntroduce(localInfo, injector, modifyVariable) ?: return
+ holder.registerProblem(
+ problemElement,
+ "@ModifyVariable can use variable name",
+ ReplaceWithNameFix(modifyVariable, variableName),
+ )
+ }
+ }
+
+ class ReplaceWithNameFix(
+ annotation: PsiAnnotation,
+ private val variableName: String,
+ ) : AnnotationAttributeFix(annotation, "name" to variableName, "ordinal" to null, "index" to null) {
+ override fun getText() = "Use variable name '$variableName'"
+ override fun getFamilyName() = "Use variable name '$variableName'"
+ }
+
+ companion object {
+ fun getVariableNameToIntroduce(
+ localInfo: LocalInfo,
+ injector: InjectorAnnotationHandler,
+ injectorAnnotation: PsiAnnotation,
+ ): String? {
+ if (localInfo.index == null && localInfo.ordinal == null && localInfo.names.isNotEmpty()) {
+ // already a named local
+ return null
+ }
+
+ val module = injectorAnnotation.findModule() ?: return null
+ val mixinTargets = injectorAnnotation.findContainingClass()?.mixinTargets ?: return null
+
+ var variableName: String? = null
+ for (targetClass in mixinTargets) {
+ if (!module.hasNamedLocalVariables(targetClass.name.replace('/', '.'))) {
+ return null
+ }
+
+ for (target in injector.resolveTarget(injectorAnnotation, targetClass)) {
+ val (clazz, method) = (target as? MethodTargetMember)?.classAndMethod ?: continue
+ for (insn in injector.resolveInstructions(injectorAnnotation, clazz, method)) {
+ val matchedLocals = localInfo.matchLocals(
+ module,
+ clazz,
+ method,
+ insn.insn,
+ CollectVisitor.Mode.RESOLUTION
+ ) ?: return null
+
+ for (local in matchedLocals) {
+ if (!local.isNamed) {
+ return null
+ }
+ if (variableName != null && local.name != variableName) {
+ return null
+ }
+ variableName = local.name
+ }
+ }
+ }
+ }
+
+ return variableName
+ }
+ }
+}
Index: src/main/kotlin/platform/mixin/util/LocalVariables.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/LocalVariables.kt (revision e10f75298ac8e65d7ce4aa2224dfff4519a34fb1)
+++ src/main/kotlin/platform/mixin/util/LocalVariables.kt (revision 4a481131a25e50573ba9e876841ce2310c8a3ed5)
@@ -805,7 +805,7 @@
localType.descriptor
}
}
- localVars[j] = LocalVariable("var$j", desc, null, i, null, j)
+ localVars[j] = LocalVariable("var$j", desc, null, i, null, j, isNamed = false)
if (desc != null) {
lastKnownType[j] = desc
}
@@ -881,6 +881,7 @@
val start: Int?,
var end: Int?,
val index: Int,
+ val isNamed: Boolean = true,
) {
fun isInRange(index: Int): Boolean {
val end = this.end
@@ -904,6 +905,7 @@
ancestor.start,
ancestor.end,
ancestor.index,
+ ancestor.isNamed,
) {
var lifetime = 0
var frames = 0
Index: src/main/kotlin/platform/mixin/util/Mixin.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/Mixin.kt (revision e10f75298ac8e65d7ce4aa2224dfff4519a34fb1)
+++ src/main/kotlin/platform/mixin/util/Mixin.kt (revision 4a481131a25e50573ba9e876841ce2310c8a3ed5)
@@ -35,6 +35,7 @@
import com.demonwav.mcdev.util.resolveClassArray
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiArrayType
@@ -299,3 +300,12 @@
val version = (versionField.initializer as? PsiLiteralExpression)?.value as? String ?: return null
return SemanticVersion.tryParse(version)
}
+
+fun Module.hasNamedLocalVariables(className: String): Boolean {
+ if (className.startsWith("net.minecraft.") || className.startsWith("com.mojang.blaze3d.")) {
+ // TODO: we'll be able to handle this better when we know which Minecraft version will be unobfuscated
+ return Registry.`is`("mcdev.unobfuscated.minecraft")
+ } else {
+ return true
+ }
+}
Index: src/main/resources/META-INF/plugin.xml
===================================================================
--- src/main/resources/META-INF/plugin.xml (revision e10f75298ac8e65d7ce4aa2224dfff4519a34fb1)
+++ src/main/resources/META-INF/plugin.xml (revision 4a481131a25e50573ba9e876841ce2310c8a3ed5)
@@ -1230,6 +1230,14 @@
level="WARNING"
hasStaticDescription="true"
implementationClass="com.demonwav.mcdev.platform.mixin.inspection.injector.ModifyVariableArgsOnlyInspection"/>
+
+