User: joe
Date: 07 May 25 14:58
Revision: af7e57e5cacfb12afce5059ff777281162865205
Summary:
Implement infrastructure to desugar Java code before passing to the navigation visitor
TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9974&personal=false
Index: src/main/kotlin/platform/mixin/handlers/desugar/DesugarUtil.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/desugar/DesugarUtil.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
+++ src/main/kotlin/platform/mixin/handlers/desugar/DesugarUtil.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
@@ -0,0 +1,90 @@
+/*
+ * 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.handlers.desugar
+
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.Key
+import com.intellij.psi.JavaRecursiveElementWalkingVisitor
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.util.PsiTreeUtil
+
+object DesugarUtil {
+ private val ORIGINAL_ELEMENT_KEY = Key.create("mcdev.desugar.originalElement")
+
+ private val DESUGARERS = arrayOf(
+ FieldAssignmentDesugarer,
+ )
+
+ fun getOriginalElement(desugared: PsiElement): PsiElement? {
+ return desugared.getCopyableUserData(ORIGINAL_ELEMENT_KEY)
+ }
+
+ fun setOriginalElement(desugared: PsiElement, original: PsiElement) {
+ desugared.putCopyableUserData(ORIGINAL_ELEMENT_KEY, original)
+ }
+
+ fun desugar(project: Project, clazz: PsiClass): PsiClass {
+ val desugaredFile = clazz.containingFile.copy() as PsiFile
+ val desugaredClass = PsiTreeUtil.findSameElementInCopy(clazz, desugaredFile)
+ setOriginalRecursive(desugaredClass, clazz)
+ for (desugarer in DESUGARERS) {
+ desugarer.desugar(project, desugaredClass)
+ }
+ return desugaredClass
+ }
+
+ private fun setOriginalRecursive(desugared: PsiElement, original: PsiElement) {
+ val desugaredElements = mutableListOf()
+ desugared.accept(object : JavaRecursiveElementWalkingVisitor() {
+ override fun visitElement(element: PsiElement) {
+ super.visitElement(element)
+ desugaredElements.add(element)
+ }
+ })
+
+ val originalElements = mutableListOf()
+ original.accept(object : JavaRecursiveElementWalkingVisitor() {
+ override fun visitElement(element: PsiElement) {
+ super.visitElement(element)
+ originalElements.add(element)
+ }
+ })
+
+ for ((originalElement, desugaredElement) in originalElements.zip(desugaredElements)) {
+ setOriginalElement(desugaredElement, originalElement)
+ }
+ }
+
+ fun getOriginalToDesugaredMap(desugared: PsiElement): Map> {
+ val result = mutableMapOf>()
+ desugared.accept(object : JavaRecursiveElementWalkingVisitor() {
+ override fun visitElement(element: PsiElement) {
+ super.visitElement(element)
+ getOriginalElement(element)?.let { original ->
+ result.getOrPut(original) { mutableListOf() } += desugared
+ }
+ }
+ })
+ return result
+ }
+}
Index: src/main/kotlin/platform/mixin/handlers/desugar/Desugarer.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/desugar/Desugarer.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
+++ src/main/kotlin/platform/mixin/handlers/desugar/Desugarer.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
@@ -0,0 +1,28 @@
+/*
+ * 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.handlers.desugar
+
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiClass
+
+interface Desugarer {
+ fun desugar(project: Project, clazz: PsiClass)
+}
Index: src/main/kotlin/platform/mixin/handlers/desugar/FieldAssignmentDesugarer.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/desugar/FieldAssignmentDesugarer.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
+++ src/main/kotlin/platform/mixin/handlers/desugar/FieldAssignmentDesugarer.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
@@ -0,0 +1,141 @@
+/*
+ * 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.handlers.desugar
+
+import com.demonwav.mcdev.util.constantValue
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiAssignmentExpression
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassInitializer
+import com.intellij.psi.PsiExpressionStatement
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiStatement
+import com.intellij.psi.PsiType
+import com.intellij.util.JavaPsiConstructorUtil
+
+object FieldAssignmentDesugarer : Desugarer {
+ override fun desugar(project: Project, clazz: PsiClass) {
+ val staticStatementsToInsertPre = mutableListOf()
+ val staticStatementsToInsertPost = mutableListOf()
+ val nonStaticStatementsToInsert = mutableListOf()
+ var seenStaticInitializer = false
+
+ for (child in clazz.children) {
+ when (child) {
+ is PsiField -> {
+ val initializer = child.initializer ?: continue
+
+ if (child.hasModifierProperty(PsiModifier.STATIC)) {
+ // check if the field is a ConstantValue with no initializer in the bytecode
+ val constantValue = initializer.constantValue
+ if (constantValue != null && constantValue !is PsiType) {
+ continue
+ }
+
+ val fieldInitializer = JavaPsiFacade.getElementFactory(project)
+ .createStatementFromText("${child.name} = null;", child) as PsiExpressionStatement
+ (fieldInitializer.expression as PsiAssignmentExpression).rExpression!!.replace(initializer)
+ DesugarUtil.setOriginalElement(fieldInitializer, child)
+
+ if (seenStaticInitializer) {
+ staticStatementsToInsertPost += fieldInitializer
+ } else {
+ staticStatementsToInsertPre += fieldInitializer
+ }
+ } else {
+ val fieldInitializer = JavaPsiFacade.getElementFactory(project)
+ .createStatementFromText("this.${child.name} = null;", child) as PsiExpressionStatement
+ (fieldInitializer.expression as PsiAssignmentExpression).rExpression!!.replace(initializer)
+ DesugarUtil.setOriginalElement(fieldInitializer, child)
+
+ nonStaticStatementsToInsert += fieldInitializer
+ }
+
+ initializer.delete()
+ }
+ is PsiClassInitializer -> {
+ if (child.hasModifierProperty(PsiModifier.STATIC)) {
+ seenStaticInitializer = true
+ } else {
+ nonStaticStatementsToInsert += child.body.statements
+ child.delete()
+ }
+ }
+ }
+ }
+
+ if (staticStatementsToInsertPre.isNotEmpty() || staticStatementsToInsertPost.isNotEmpty()) {
+ val staticBlock = findStaticBlock(project, clazz)
+ for (statement in staticStatementsToInsertPre) {
+ staticBlock.body.addAfter(statement, staticBlock.body.lBrace)
+ }
+ for (statement in staticStatementsToInsertPost) {
+ staticBlock.body.addBefore(statement, staticBlock.body.rBrace)
+ }
+ }
+
+ if (nonStaticStatementsToInsert.isNotEmpty()) {
+ for (constructor in findConstructorsCallingSuper(project, clazz)) {
+ val body = constructor.body ?: continue
+ val delegateCtorCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor)
+ for (statement in nonStaticStatementsToInsert.asReversed()) {
+ body.addAfter(statement, delegateCtorCall?.parent ?: body.lBrace)
+ }
+ }
+ }
+ }
+
+ private fun findStaticBlock(project: Project, clazz: PsiClass): PsiClassInitializer {
+ for (initializer in clazz.initializers) {
+ if (initializer.hasModifierProperty(PsiModifier.STATIC)) {
+ return initializer
+ }
+ }
+
+ val initializer = JavaPsiFacade.getElementFactory(project)
+ .createClass("class _Dummy_ { static {} }")
+ .initializers
+ .first()
+ DesugarUtil.setOriginalElement(initializer, clazz)
+ return clazz.add(initializer) as PsiClassInitializer
+ }
+
+ private fun findConstructorsCallingSuper(project: Project, clazz: PsiClass): List {
+ val className = clazz.name ?: return emptyList()
+
+ val constructors = clazz.constructors.filter {
+ !JavaPsiConstructorUtil.isChainedConstructorCall(
+ JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(it)
+ )
+ }
+
+ if (constructors.isNotEmpty()) {
+ return constructors
+ }
+
+ val constructor = JavaPsiFacade.getElementFactory(project).createConstructor(className)
+ DesugarUtil.setOriginalElement(constructor, clazz)
+ return listOf(clazz.add(constructor) as PsiMethod)
+ }
+}
Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt
===================================================================
--- src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision d1e8b460e193d919889bf7ae16bee9bff5778097)
+++ src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
@@ -20,18 +20,22 @@
package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint
+import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarUtil
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.MixinConstants.Classes.SHIFT
-import com.demonwav.mcdev.platform.mixin.util.findBodyElements
+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.computeStringArray
import com.demonwav.mcdev.util.constantStringValue
import com.demonwav.mcdev.util.constantValue
import com.demonwav.mcdev.util.equivalentTo
+import com.demonwav.mcdev.util.findMethods
import com.demonwav.mcdev.util.fullQualifiedName
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.psi.JavaPsiFacade
@@ -43,7 +47,9 @@
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiEnumConstant
import com.intellij.psi.PsiExpression
+import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiModifierList
+import com.intellij.psi.PsiParameterListOwner
import com.intellij.psi.PsiQualifiedReference
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiReferenceExpression
@@ -221,35 +227,56 @@
val target = targetAttr?.let { parseMixinSelector(it) }
val bytecodeResults = resolveInstructions()
- // Then attempt to find the corresponding source elements using the navigation visitor
- val mainTargetElement = targetMethod.findSourceElement(
- getTargetClass(target),
- at.project,
- GlobalSearchScope.allScope(at.project),
- canDecompile = true,
- )
- val targetElements = targetMethod.findBodyElements(
- getTargetClass(target),
- at.project,
- GlobalSearchScope.allScope(at.project),
- )
- if (mainTargetElement == null && targetElements.isEmpty()) {
- return emptyList()
- }
+ val project = at.project
- val targetPsiClass = (mainTargetElement ?: targetElements.first()).parentOfType()
+ // Resolve the target source class
+ val targetPsiClass = getTargetClass(target)
+ .findSourceClass(project, GlobalSearchScope.allScope(project), canDecompile = true)
?: return emptyList()
val targetPsiFile = targetPsiClass.containingFile ?: return emptyList()
+ // Desugar the target class
+ val desugaredTargetClass = DesugarUtil.desugar(project, targetPsiClass)
+
+ // Find the element in the desugared class, first by directly searching and then by searching in the original
+ // and reverse mapping it into the desugared class.
+ val desugaredTargetElement = when {
+ targetMethod.isClinit -> desugaredTargetClass.initializers.firstOrNull {
+ it.hasModifierProperty(PsiModifier.STATIC)
+ }
+ else -> desugaredTargetClass.findMethods(targetMethod.memberReference).firstOrNull()
+ } ?: run {
+ val originalTargetElement = targetMethod.findSourceElement(
+ getTargetClass(target),
+ project,
+ GlobalSearchScope.allScope(project),
+ canDecompile = true,
+ ) ?: return emptyList()
+ DesugarUtil.getOriginalToDesugaredMap(desugaredTargetClass)[originalTargetElement]
+ ?.firstOrNull { it is PsiParameterListOwner }
+ ?: return listOf(originalTargetElement)
+ }
+
+ // Find the source element in the desugared class
val navigationVisitor = injectionPoint.createNavigationVisitor(at, target, targetPsiClass) ?: return emptyList()
navigationVisitor.configureBytecodeTarget(targetClass, targetMethod)
- navigationVisitor.visitStart(mainTargetElement ?: targetElements.first())
- targetElements.forEach { it.accept(navigationVisitor) }
- navigationVisitor.visitEnd(mainTargetElement ?: targetElements.last())
+ navigationVisitor.visitStart(desugaredTargetElement)
+ if (desugaredTargetElement is PsiParameterListOwner) {
+ desugaredTargetElement.acceptChildren(navigationVisitor)
+ } else {
+ desugaredTargetElement.accept(navigationVisitor)
+ }
+ navigationVisitor.visitEnd(desugaredTargetElement)
+ // Map the desugared results back into the original source class
+ val sourceResults = navigationVisitor.result.mapNotNull { desugaredResult ->
+ desugaredResult.parents(true).firstNotNullOfOrNull(DesugarUtil::getOriginalElement)
+ }
+
+ // Match the bytecode results to the source results
return bytecodeResults.mapNotNull { bytecodeResult ->
val matcher = bytecodeResult.sourceLocationInfo.createMatcher(targetPsiFile)
- navigationVisitor.result.forEach(matcher::accept)
+ sourceResults.forEach(matcher::accept)
matcher.result
}
}
Index: src/main/kotlin/platform/mixin/util/AsmUtil.kt
===================================================================
--- src/main/kotlin/platform/mixin/util/AsmUtil.kt (revision d1e8b460e193d919889bf7ae16bee9bff5778097)
+++ src/main/kotlin/platform/mixin/util/AsmUtil.kt (revision af7e57e5cacfb12afce5059ff777281162865205)
@@ -71,6 +71,7 @@
import com.intellij.psi.PsiModifierList
import com.intellij.psi.PsiParameter
import com.intellij.psi.PsiParameterList
+import com.intellij.psi.PsiParameterListOwner
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypes
import com.intellij.psi.impl.compiled.ClsElementImpl
@@ -1036,8 +1037,7 @@
}
val body = when (sourceMethod) {
- is PsiMethod -> sourceMethod.body
- is PsiLambdaExpression -> sourceMethod.body
+ is PsiParameterListOwner -> sourceMethod.body
else -> null
}