Skip to content

Commit 9c94ff3

Browse files
committed
Add AnonymousAndLocalClassDesugarer
1 parent 29e926b commit 9c94ff3

File tree

13 files changed

+1879
-25
lines changed

13 files changed

+1879
-25
lines changed

src/main/kotlin/platform/mixin/handlers/desugar/AnonymousAndLocalClassDesugarer.kt

Lines changed: 619 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Minecraft Development for IntelliJ
3+
*
4+
* https://mcdev.io/
5+
*
6+
* Copyright (C) 2025 minecraft-dev
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the GNU Lesser General Public License as published
10+
* by the Free Software Foundation, version 3.0 only.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
package com.demonwav.mcdev.platform.mixin.handlers.desugar
22+
23+
class DesugarContext(val classVersion: Int)

src/main/kotlin/platform/mixin/handlers/desugar/DesugarUtil.kt

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,42 @@
2121
package com.demonwav.mcdev.platform.mixin.handlers.desugar
2222

2323
import com.demonwav.mcdev.util.cached
24+
import com.demonwav.mcdev.util.childrenOfType
2425
import com.intellij.openapi.project.Project
2526
import com.intellij.openapi.util.Key
27+
import com.intellij.openapi.util.TextRange
28+
import com.intellij.openapi.util.UnfairTextRange
2629
import com.intellij.psi.JavaRecursiveElementWalkingVisitor
30+
import com.intellij.psi.PsiAnonymousClass
2731
import com.intellij.psi.PsiClass
2832
import com.intellij.psi.PsiElement
2933
import com.intellij.psi.PsiJavaFile
34+
import com.intellij.psi.PsiLambdaExpression
35+
import com.intellij.psi.PsiMember
36+
import com.intellij.psi.PsiMethod
37+
import com.intellij.psi.PsiMethodReferenceExpression
38+
import com.intellij.psi.PsiNameIdentifierOwner
39+
import com.intellij.psi.PsiReference
40+
import com.intellij.psi.PsiSubstitutor
41+
import com.intellij.psi.PsiTypeParameter
42+
import com.intellij.psi.PsiVariable
43+
import com.intellij.psi.impl.light.LightMemberReference
44+
import com.intellij.psi.search.LocalSearchScope
45+
import com.intellij.psi.search.searches.ReferencesSearch
3046
import com.intellij.psi.util.PsiTreeUtil
3147
import com.intellij.psi.util.parents
48+
import com.intellij.refactoring.util.LambdaRefactoringUtil
49+
import com.intellij.util.JavaPsiConstructorUtil
50+
import com.intellij.util.Processor
3251
import org.jetbrains.annotations.VisibleForTesting
3352

3453
object DesugarUtil {
3554
private val ORIGINAL_ELEMENT_KEY = Key.create<PsiElement>("mcdev.desugar.originalElement")
55+
private val UNNAMED_VARIABLE_KEY = Key.create<Boolean>("mcdev.desugar.unnamedVariable")
3656

3757
private val DESUGARERS = arrayOf(
3858
RemoveVarArgsDesugarer,
59+
AnonymousAndLocalClassDesugarer,
3960
FieldAssignmentDesugarer,
4061
)
4162

@@ -61,13 +82,21 @@ object DesugarUtil {
6182
}
6283
}
6384

64-
fun desugar(project: Project, clazz: PsiClass): PsiClass? {
85+
fun isUnnamedVariable(variable: PsiVariable): Boolean {
86+
return variable.getCopyableUserData(UNNAMED_VARIABLE_KEY) == true
87+
}
88+
89+
fun setUnnamedVariable(variable: PsiVariable, value: Boolean) {
90+
variable.putCopyableUserData(UNNAMED_VARIABLE_KEY, value)
91+
}
92+
93+
fun desugar(project: Project, clazz: PsiClass, context: DesugarContext): PsiClass? {
6594
val file = clazz.containingFile as? PsiJavaFile ?: return null
6695
return file.cached {
6796
val desugaredFile = file.copy() as PsiJavaFile
6897
setOriginalRecursive(desugaredFile, file)
6998
for (desugarer in DESUGARERS) {
70-
desugarer.desugar(project, desugaredFile)
99+
desugarer.desugar(project, desugaredFile, context)
71100
}
72101
getOriginalToDesugaredMap(desugaredFile)[clazz]?.filterIsInstance<PsiClass>()?.firstOrNull()
73102
}
@@ -95,4 +124,90 @@ object DesugarUtil {
95124
setOriginalElement(desugaredElement, originalElement)
96125
}
97126
}
127+
128+
internal fun allClasses(file: PsiJavaFile): List<PsiClass> {
129+
return file.childrenOfType<PsiClass>().filter { it !is PsiTypeParameter }
130+
}
131+
132+
internal fun allClassesShallow(clazz: PsiClass): List<PsiClass> {
133+
val allClasses = mutableListOf<PsiClass>()
134+
clazz.acceptChildren(object : JavaRecursiveElementWalkingVisitor() {
135+
override fun visitClass(aClass: PsiClass) {
136+
if (aClass !is PsiTypeParameter) {
137+
allClasses += aClass
138+
}
139+
}
140+
})
141+
return allClasses
142+
}
143+
144+
internal fun findReferencesInFile(element: PsiElement): List<PsiReference> {
145+
fun PsiMember.createSyntheticReference(): PsiReference {
146+
return object : LightMemberReference(manager, this, PsiSubstitutor.EMPTY) {
147+
override fun getElement() = this@createSyntheticReference
148+
override fun getRangeInElement(): TextRange {
149+
val identifier = (this@createSyntheticReference as? PsiNameIdentifierOwner)?.nameIdentifier
150+
if (identifier != null) {
151+
val startOffsetInParent = identifier.startOffsetInParent
152+
return if (startOffsetInParent >= 0) {
153+
TextRange.from(startOffsetInParent, identifier.textLength)
154+
} else {
155+
UnfairTextRange(-1, -1)
156+
}
157+
}
158+
159+
return super.getRangeInElement()
160+
}
161+
}
162+
}
163+
164+
val file = element.containingFile as? PsiJavaFile ?: return emptyList()
165+
val results = mutableListOf<PsiReference>()
166+
167+
ReferencesSearch.search(element, LocalSearchScope(file)).forEach(Processor {
168+
results += it
169+
true
170+
})
171+
172+
// subclass constructor references don't work for non-physical files
173+
if (element is PsiMethod && element.isConstructor) {
174+
val clazz = element.containingClass
175+
if (clazz != null) {
176+
for (subClass in allClasses(file)) {
177+
if (subClass !is PsiAnonymousClass && subClass.isInheritor(clazz, false)) {
178+
val countImplicitSuperCalls = element.parameterList.isEmpty
179+
val constructors = subClass.constructors
180+
for (constructor in constructors) {
181+
val thisOrSuperCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor)
182+
if (JavaPsiConstructorUtil.isSuperConstructorCall(thisOrSuperCall)) {
183+
val reference = thisOrSuperCall!!.methodExpression.reference
184+
if (reference != null && reference.isReferenceTo(element)) {
185+
results += reference
186+
}
187+
} else if (thisOrSuperCall == null && countImplicitSuperCalls) {
188+
results += constructor.createSyntheticReference()
189+
}
190+
}
191+
192+
if (constructors.isEmpty() && countImplicitSuperCalls) {
193+
results += subClass.createSyntheticReference()
194+
}
195+
}
196+
}
197+
}
198+
}
199+
200+
return results
201+
}
202+
203+
internal fun desugarMethodReferenceToLambda(methodReference: PsiMethodReferenceExpression): PsiLambdaExpression? {
204+
val originalMethodRef = getOriginalElement(methodReference)
205+
val lambda = LambdaRefactoringUtil.convertMethodReferenceToLambda(methodReference, false, true)
206+
?: return null
207+
setOriginalElement(lambda, originalMethodRef)
208+
for (parameter in lambda.parameterList.parameters) {
209+
setUnnamedVariable(parameter, true)
210+
}
211+
return lambda
212+
}
98213
}

src/main/kotlin/platform/mixin/handlers/desugar/Desugarer.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,8 @@
2121
package com.demonwav.mcdev.platform.mixin.handlers.desugar
2222

2323
import com.intellij.openapi.project.Project
24-
import com.intellij.psi.PsiClass
2524
import com.intellij.psi.PsiJavaFile
26-
import com.intellij.psi.util.childrenOfType
2725

2826
abstract class Desugarer {
29-
abstract fun desugar(project: Project, file: PsiJavaFile)
30-
31-
companion object {
32-
@JvmStatic
33-
protected val PsiJavaFile.allClasses: List<PsiClass>
34-
get() = this.childrenOfType<PsiClass>()
35-
}
27+
abstract fun desugar(project: Project, file: PsiJavaFile, context: DesugarContext)
3628
}

src/main/kotlin/platform/mixin/handlers/desugar/FieldAssignmentDesugarer.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ import com.intellij.psi.PsiType
3636
import com.intellij.util.JavaPsiConstructorUtil
3737

3838
object FieldAssignmentDesugarer : Desugarer() {
39-
override fun desugar(project: Project, file: PsiJavaFile) {
39+
override fun desugar(project: Project, file: PsiJavaFile, context: DesugarContext) {
4040
val staticStatementsToInsertPre = mutableListOf<PsiStatement>()
4141
val staticStatementsToInsertPost = mutableListOf<PsiStatement>()
4242
val nonStaticStatementsToInsert = mutableListOf<PsiStatement>()
4343
var seenStaticInitializer = false
4444

45-
for (aClass in file.allClasses) {
45+
for (aClass in DesugarUtil.allClasses(file)) {
4646
for (child in aClass.children) {
4747
when (child) {
4848
is PsiField -> {

src/main/kotlin/platform/mixin/handlers/desugar/RemoveVarArgsDesugarer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import com.intellij.psi.util.TypeConversionUtil
3838
import com.siyeh.ig.psiutils.MethodCallUtils
3939

4040
object RemoveVarArgsDesugarer : Desugarer() {
41-
override fun desugar(project: Project, file: PsiJavaFile) {
41+
override fun desugar(project: Project, file: PsiJavaFile, context: DesugarContext) {
4242
val varArgsStarts = mutableListOf<Pair<PsiCall, Int>>()
4343
PsiTreeUtil.processElements(file) { element ->
4444
if (element is PsiCall && MethodCallUtils.isVarArgCall(element)) {

src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint
2222

23+
import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarContext
2324
import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarUtil
2425
import com.demonwav.mcdev.platform.mixin.reference.MixinSelector
2526
import com.demonwav.mcdev.platform.mixin.reference.isMiscDynamicSelector
@@ -226,7 +227,8 @@ class AtResolver(
226227
val targetPsiFile = targetPsiClass.containingFile ?: return emptyList()
227228

228229
// Desugar the target class
229-
val desugaredTargetClass = DesugarUtil.desugar(project, targetPsiClass) ?: return emptyList()
230+
val desugaredTargetClass = DesugarUtil.desugar(project, targetPsiClass, DesugarContext(targetClass.version))
231+
?: return emptyList()
230232

231233
// Find the element in the desugared class, first by directly searching and then by searching in the original
232234
// and reverse mapping it into the desugared class.

src/main/kotlin/platform/mixin/util/LocalVariables.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ package com.demonwav.mcdev.platform.mixin.util
4848

4949
import com.demonwav.mcdev.facet.MinecraftFacet
5050
import com.demonwav.mcdev.platform.mixin.MixinModuleType
51+
import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarUtil
5152
import com.demonwav.mcdev.util.SemanticVersion
5253
import com.demonwav.mcdev.util.cached
5354
import com.demonwav.mcdev.util.mapToArray
@@ -118,7 +119,7 @@ object LocalVariables {
118119
}
119120

120121
for (parameter in method.parameterList.parameters) {
121-
val mixinName = if (argsOnly) "var$argsIndex" else parameter.name
122+
val mixinName = if (argsOnly) "arg$argsIndex" else parameter.name
122123
args += SourceLocalVariable(
123124
parameter.name,
124125
parameter.type,
@@ -217,7 +218,8 @@ object LocalVariables {
217218
name,
218219
instruction.variable.type,
219220
localIndex,
220-
variable = instruction.variable
221+
variable = instruction.variable,
222+
mixinName = if (DesugarUtil.isUnnamedVariable(instruction.variable)) "var$localIndex" else name,
221223
)
222224
if (instruction.variable.isDoubleSlot && localIndex + 1 < localsHere.size) {
223225
localsHere[localIndex + 1] = null
@@ -250,7 +252,6 @@ object LocalVariables {
250252
val localsHere = this.locals[offset] ?: emptyArray()
251253
var changed = false
252254
val nextLocals = this.locals[nextOffset]
253-
@Suppress("KotlinConstantConditions") // kotlin is wrong
254255
if (nextLocals == null) {
255256
this.locals[nextOffset] = localsHere.clone()
256257
changed = true
@@ -270,7 +271,6 @@ object LocalVariables {
270271
}
271272
}
272273
}
273-
@Suppress("KotlinConstantConditions") // kotlin is wrong
274274
if (changed) {
275275
instructionQueue.add(nextOffset)
276276
}
@@ -325,8 +325,8 @@ object LocalVariables {
325325
is PsiVariable -> if (element.isDoubleSlot) 2 else 1
326326
// arrays have copy of array, length and index variables, iterables have the iterator variable
327327
is PsiForeachStatement -> {
328-
val param = element.iterationParameter as? PsiParameter
329-
if (param?.type is PsiArrayType) 3 else 1
328+
val param = element.iterationParameter
329+
if (param.type is PsiArrayType) 3 else 1
330330
}
331331
else -> 0
332332
}

src/main/kotlin/util/class-utils.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package com.demonwav.mcdev.util
2222

23+
import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarUtil
2324
import com.intellij.codeInsight.daemon.impl.quickfix.AddMethodFix
2425
import com.intellij.navigation.AnonymousElementProvider
2526
import com.intellij.openapi.project.Project
@@ -56,6 +57,7 @@ val PsiClass.outerQualifiedName
5657

5758
val PsiClass.fullQualifiedName
5859
get(): String? {
60+
(DesugarUtil.getOriginalElement(this) as? PsiClass)?.let { return it.fullQualifiedName }
5961
return try {
6062
outerQualifiedName ?: buildQualifiedName(StringBuilder()).toString()
6163
} catch (e: ClassNameResolutionFailedException) {

src/test/kotlin/platform/mixin/desugar/AbstractDesugarTest.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package com.demonwav.mcdev.platform.mixin.desugar
2222

2323
import com.demonwav.mcdev.framework.BaseMinecraftTest
24+
import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarContext
2425
import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarUtil
2526
import com.demonwav.mcdev.platform.mixin.handlers.desugar.Desugarer
2627
import com.intellij.openapi.command.WriteCommandAction
@@ -35,15 +36,16 @@ import org.intellij.lang.annotations.Language
3536
import org.junit.jupiter.api.Assertions.assertEquals
3637
import org.junit.jupiter.api.Assertions.assertInstanceOf
3738
import org.junit.jupiter.api.Assertions.assertTrue
39+
import org.objectweb.asm.Opcodes
3840

3941
abstract class AbstractDesugarTest : BaseMinecraftTest() {
4042
abstract val desugarer: Desugarer
4143

42-
protected fun doTestNoChange(@Language("JAVA") code: String) {
43-
doTest(code, code)
44+
protected fun doTestNoChange(@Language("JAVA") code: String, classVersion: Int = Opcodes.V21) {
45+
doTest(code, code, classVersion)
4446
}
4547

46-
protected fun doTest(@Language("JAVA") before: String, @Language("JAVA") after: String) {
48+
protected fun doTest(@Language("JAVA") before: String, @Language("JAVA") after: String, classVersion: Int = Opcodes.V21) {
4749
WriteCommandAction.runWriteCommandAction(project) {
4850
val codeStyleManager = CodeStyleManager.getInstance(project)
4951
val javaCodeStyleManager = JavaCodeStyleManager.getInstance(project)
@@ -71,7 +73,7 @@ abstract class AbstractDesugarTest : BaseMinecraftTest() {
7173

7274
val desugaredFile = testFile.copy() as PsiJavaFile
7375
DesugarUtil.setOriginalRecursive(desugaredFile, testFile)
74-
desugarer.desugar(project, desugaredFile)
76+
desugarer.desugar(project, desugaredFile, DesugarContext(classVersion))
7577
assertEquals(
7678
expectedText,
7779
codeStyleManager.reformat(javaCodeStyleManager.shortenClassReferences(desugaredFile.copy())).text

0 commit comments

Comments
 (0)