Skip to content

Commit 253930a

Browse files
committed
Desugar varargs
1 parent 0b58959 commit 253930a

File tree

5 files changed

+175
-59
lines changed

5 files changed

+175
-59
lines changed

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ import com.intellij.openapi.util.Key
2626
import com.intellij.psi.JavaRecursiveElementWalkingVisitor
2727
import com.intellij.psi.PsiClass
2828
import com.intellij.psi.PsiElement
29-
import com.intellij.psi.PsiFile
29+
import com.intellij.psi.PsiJavaFile
3030
import com.intellij.psi.util.PsiTreeUtil
3131
import com.intellij.psi.util.parents
3232

3333
object DesugarUtil {
3434
private val ORIGINAL_ELEMENT_KEY = Key.create<PsiElement>("mcdev.desugar.originalElement")
3535

36-
private val DESUGARERS = arrayOf<Desugarer>(
36+
private val DESUGARERS = arrayOf(
37+
RemoveVarArgsDesugarer,
3738
FieldAssignmentDesugarer,
3839
)
3940

@@ -45,13 +46,13 @@ object DesugarUtil {
4546
desugared.putCopyableUserData(ORIGINAL_ELEMENT_KEY, original)
4647
}
4748

48-
fun desugar(project: Project, clazz: PsiClass): PsiClass {
49+
fun desugar(project: Project, clazz: PsiClass): PsiClass? {
4950
return clazz.cached {
50-
val desugaredFile = clazz.containingFile.copy() as PsiFile
51-
val desugaredClass = PsiTreeUtil.findSameElementInCopy(clazz, desugaredFile)
51+
val desugaredFile = clazz.containingFile.copy() as? PsiJavaFile ?: return@cached null
52+
var desugaredClass = PsiTreeUtil.findSameElementInCopy(clazz, desugaredFile)
5253
setOriginalRecursive(desugaredClass, clazz)
5354
for (desugarer in DESUGARERS) {
54-
desugarer.desugar(project, desugaredClass)
55+
desugaredClass = desugarer.desugar(project, desugaredFile, desugaredClass)
5556
}
5657
desugaredClass
5758
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@ package com.demonwav.mcdev.platform.mixin.handlers.desugar
2222

2323
import com.intellij.openapi.project.Project
2424
import com.intellij.psi.PsiClass
25+
import com.intellij.psi.PsiJavaFile
26+
import com.intellij.psi.util.PsiTreeUtil
2527

26-
interface Desugarer {
27-
fun desugar(project: Project, clazz: PsiClass)
28+
abstract class Desugarer {
29+
abstract fun desugar(project: Project, file: PsiJavaFile, clazz: PsiClass): PsiClass
30+
31+
companion object {
32+
@JvmStatic
33+
protected val PsiJavaFile.allClasses: Collection<PsiClass>
34+
get() = PsiTreeUtil.findChildrenOfType(this, PsiClass::class.java)
35+
}
2836
}

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

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,82 +28,88 @@ import com.intellij.psi.PsiClass
2828
import com.intellij.psi.PsiClassInitializer
2929
import com.intellij.psi.PsiExpressionStatement
3030
import com.intellij.psi.PsiField
31+
import com.intellij.psi.PsiJavaFile
3132
import com.intellij.psi.PsiMethod
3233
import com.intellij.psi.PsiModifier
3334
import com.intellij.psi.PsiStatement
3435
import com.intellij.psi.PsiType
3536
import com.intellij.util.JavaPsiConstructorUtil
3637

37-
object FieldAssignmentDesugarer : Desugarer {
38-
override fun desugar(project: Project, clazz: PsiClass) {
38+
object FieldAssignmentDesugarer : Desugarer() {
39+
override fun desugar(project: Project, file: PsiJavaFile, clazz: PsiClass): PsiClass {
3940
val staticStatementsToInsertPre = mutableListOf<PsiStatement>()
4041
val staticStatementsToInsertPost = mutableListOf<PsiStatement>()
4142
val nonStaticStatementsToInsert = mutableListOf<PsiStatement>()
4243
var seenStaticInitializer = false
4344

44-
for (child in clazz.children) {
45-
when (child) {
46-
is PsiField -> {
47-
val initializer = child.initializer ?: continue
45+
for (aClass in file.allClasses) {
46+
for (child in aClass.children) {
47+
when (child) {
48+
is PsiField -> {
49+
val initializer = child.initializer ?: continue
50+
51+
if (child.hasModifierProperty(PsiModifier.STATIC)) {
52+
// check if the field is a ConstantValue with no initializer in the bytecode
53+
val constantValue = initializer.constantValue
54+
if (constantValue != null && constantValue !is PsiType) {
55+
continue
56+
}
57+
58+
val fieldInitializer = JavaPsiFacade.getElementFactory(project)
59+
.createStatementFromText("${child.name} = null;", child) as PsiExpressionStatement
60+
(fieldInitializer.expression as PsiAssignmentExpression).rExpression!!.replace(initializer)
61+
DesugarUtil.setOriginalElement(fieldInitializer, DesugarUtil.getOriginalElement(child))
62+
63+
if (seenStaticInitializer) {
64+
staticStatementsToInsertPost += fieldInitializer
65+
} else {
66+
staticStatementsToInsertPre += fieldInitializer
67+
}
68+
} else {
69+
val fieldInitializer = JavaPsiFacade.getElementFactory(project)
70+
.createStatementFromText("this.${child.name} = null;", child) as PsiExpressionStatement
71+
(fieldInitializer.expression as PsiAssignmentExpression).rExpression!!.replace(initializer)
72+
DesugarUtil.setOriginalElement(fieldInitializer, DesugarUtil.getOriginalElement(child))
4873

49-
if (child.hasModifierProperty(PsiModifier.STATIC)) {
50-
// check if the field is a ConstantValue with no initializer in the bytecode
51-
val constantValue = initializer.constantValue
52-
if (constantValue != null && constantValue !is PsiType) {
53-
continue
74+
nonStaticStatementsToInsert += fieldInitializer
5475
}
5576

56-
val fieldInitializer = JavaPsiFacade.getElementFactory(project)
57-
.createStatementFromText("${child.name} = null;", child) as PsiExpressionStatement
58-
(fieldInitializer.expression as PsiAssignmentExpression).rExpression!!.replace(initializer)
59-
DesugarUtil.setOriginalElement(fieldInitializer, DesugarUtil.getOriginalElement(child))
77+
initializer.delete()
78+
}
6079

61-
if (seenStaticInitializer) {
62-
staticStatementsToInsertPost += fieldInitializer
80+
is PsiClassInitializer -> {
81+
if (child.hasModifierProperty(PsiModifier.STATIC)) {
82+
seenStaticInitializer = true
6383
} else {
64-
staticStatementsToInsertPre += fieldInitializer
84+
nonStaticStatementsToInsert += child.body.statements
85+
child.delete()
6586
}
66-
} else {
67-
val fieldInitializer = JavaPsiFacade.getElementFactory(project)
68-
.createStatementFromText("this.${child.name} = null;", child) as PsiExpressionStatement
69-
(fieldInitializer.expression as PsiAssignmentExpression).rExpression!!.replace(initializer)
70-
DesugarUtil.setOriginalElement(fieldInitializer, DesugarUtil.getOriginalElement(child))
71-
72-
nonStaticStatementsToInsert += fieldInitializer
73-
}
74-
75-
initializer.delete()
76-
}
77-
is PsiClassInitializer -> {
78-
if (child.hasModifierProperty(PsiModifier.STATIC)) {
79-
seenStaticInitializer = true
80-
} else {
81-
nonStaticStatementsToInsert += child.body.statements
82-
child.delete()
8387
}
8488
}
8589
}
86-
}
8790

88-
if (staticStatementsToInsertPre.isNotEmpty() || staticStatementsToInsertPost.isNotEmpty()) {
89-
val staticBlock = findStaticBlock(project, clazz)
90-
for (statement in staticStatementsToInsertPre) {
91-
staticBlock.body.addAfter(statement, staticBlock.body.lBrace)
92-
}
93-
for (statement in staticStatementsToInsertPost) {
94-
staticBlock.body.addBefore(statement, staticBlock.body.rBrace)
91+
if (staticStatementsToInsertPre.isNotEmpty() || staticStatementsToInsertPost.isNotEmpty()) {
92+
val staticBlock = findStaticBlock(project, aClass)
93+
for (statement in staticStatementsToInsertPre) {
94+
staticBlock.body.addAfter(statement, staticBlock.body.lBrace)
95+
}
96+
for (statement in staticStatementsToInsertPost) {
97+
staticBlock.body.addBefore(statement, staticBlock.body.rBrace)
98+
}
9599
}
96-
}
97100

98-
if (nonStaticStatementsToInsert.isNotEmpty()) {
99-
for (constructor in findConstructorsCallingSuper(project, clazz)) {
100-
val body = constructor.body ?: continue
101-
val delegateCtorCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor)
102-
for (statement in nonStaticStatementsToInsert.asReversed()) {
103-
body.addAfter(statement, delegateCtorCall?.parent ?: body.lBrace)
101+
if (nonStaticStatementsToInsert.isNotEmpty()) {
102+
for (constructor in findConstructorsCallingSuper(project, aClass)) {
103+
val body = constructor.body ?: continue
104+
val delegateCtorCall = JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor)
105+
for (statement in nonStaticStatementsToInsert.asReversed()) {
106+
body.addAfter(statement, delegateCtorCall?.parent ?: body.lBrace)
107+
}
104108
}
105109
}
106110
}
111+
112+
return clazz
107113
}
108114

109115
private fun findStaticBlock(project: Project, clazz: PsiClass): PsiClassInitializer {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil
24+
import com.intellij.openapi.project.Project
25+
import com.intellij.psi.JavaPsiFacade
26+
import com.intellij.psi.PsiCall
27+
import com.intellij.psi.PsiCapturedWildcardType
28+
import com.intellij.psi.PsiClass
29+
import com.intellij.psi.PsiJavaFile
30+
import com.intellij.psi.PsiMethod
31+
import com.intellij.psi.PsiNewExpression
32+
import com.intellij.psi.PsiTypeCastExpression
33+
import com.intellij.psi.util.PsiTreeUtil
34+
import com.intellij.psi.util.PsiTypesUtil
35+
import com.intellij.psi.util.TypeConversionUtil
36+
import com.siyeh.ig.psiutils.MethodCallUtils
37+
38+
object RemoveVarArgsDesugarer : Desugarer() {
39+
override fun desugar(project: Project, file: PsiJavaFile, clazz: PsiClass): PsiClass {
40+
val varArgsStarts = mutableListOf<Pair<PsiCall, Int>>()
41+
PsiTreeUtil.processElements(file) { element ->
42+
if (element is PsiCall && MethodCallUtils.isVarArgCall(element)) {
43+
val argumentCount = element.resolveMethod()?.parameterList?.parametersCount ?: return@processElements true
44+
varArgsStarts += element to argumentCount - 1
45+
}
46+
true
47+
}
48+
49+
val elementFactory = JavaPsiFacade.getElementFactory(project)
50+
51+
for ((call, varArgsStart) in varArgsStarts.asReversed()) {
52+
val argumentList = call.argumentList ?: continue
53+
val arguments = argumentList.expressions
54+
55+
val result = call.resolveMethodGenerics()
56+
val method = result.element as? PsiMethod ?: continue
57+
val parameters = method.parameterList.parameters
58+
val componentType = PsiTypesUtil.getParameterType(
59+
parameters,
60+
parameters.lastIndex,
61+
true
62+
)
63+
64+
var type = result.substitutor.substitute(componentType)
65+
if (type is PsiCapturedWildcardType) {
66+
type = type.lowerBound
67+
}
68+
69+
val (replacementExpr, newArrayExpr) = if (JavaGenericsUtil.isReifiableType(type)) {
70+
val newExpr = elementFactory.createExpressionFromText("new ${type.canonicalText}[] {}", call)
71+
as PsiNewExpression
72+
newExpr to newExpr
73+
} else {
74+
val erasure = TypeConversionUtil.erasure(type)
75+
val castExpr = elementFactory.createExpressionFromText("(${type.canonicalText}) new ${erasure.canonicalText}[] {}", call)
76+
as PsiTypeCastExpression
77+
castExpr to castExpr.operand as PsiNewExpression
78+
}
79+
80+
if (arguments.size > varArgsStart) {
81+
DesugarUtil.setOriginalElement(
82+
replacementExpr,
83+
DesugarUtil.getOriginalElement(arguments[varArgsStart])
84+
)
85+
newArrayExpr.arrayInitializer!!.addRange(arguments[varArgsStart], arguments.last())
86+
for (i in varArgsStart until arguments.size) {
87+
arguments[i].delete()
88+
}
89+
} else {
90+
DesugarUtil.setOriginalElement(
91+
replacementExpr,
92+
argumentList.lastChild?.let(DesugarUtil::getOriginalElement)
93+
)
94+
}
95+
96+
argumentList.add(replacementExpr)
97+
}
98+
99+
return clazz
100+
}
101+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ class AtResolver(
236236
val targetPsiFile = targetPsiClass.containingFile ?: return emptyList()
237237

238238
// Desugar the target class
239-
val desugaredTargetClass = DesugarUtil.desugar(project, targetPsiClass)
239+
val desugaredTargetClass = DesugarUtil.desugar(project, targetPsiClass) ?: return emptyList()
240240

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

0 commit comments

Comments
 (0)