User: joe Date: 14 Apr 26 11:39 Revision: a358e233c7cf14a981ab246f34665053d1db0403 Summary: Improve MixinInstantiationWithinMixinInspection and provide shadow constructor quick fix when in an enum TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10523&personal=false Index: src/main/kotlin/platform/mixin/inspection/MixinInstantiationWithinMixinInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/MixinInstantiationWithinMixinInspection.kt (revision 4cc13acf50a5b8ea34fd5b792cd60a6b3d68ad10) +++ src/main/kotlin/platform/mixin/inspection/MixinInstantiationWithinMixinInspection.kt (revision a358e233c7cf14a981ab246f34665053d1db0403) @@ -20,18 +20,26 @@ package com.demonwav.mcdev.platform.mixin.inspection +import com.demonwav.mcdev.platform.mixin.action.insertShadows import com.demonwav.mcdev.platform.mixin.util.findMethod +import com.demonwav.mcdev.platform.mixin.util.findOrConstructSourceMethod +import com.demonwav.mcdev.platform.mixin.util.isConstructor import com.demonwav.mcdev.platform.mixin.util.isMixin import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.util.MemberReference import com.demonwav.mcdev.util.descriptor import com.demonwav.mcdev.util.findContainingClass +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.LocalQuickFixOnPsiElement import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project import com.intellij.psi.JavaElementVisitor import com.intellij.psi.PsiClass import com.intellij.psi.PsiConstructorCall import com.intellij.psi.PsiElement import com.intellij.psi.PsiEnumConstant +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier import com.intellij.psi.PsiNewExpression @@ -45,19 +53,51 @@ } val classReference = expression.classReference ?: return val constructedClass = classReference.resolve() as? PsiClass ?: return - visitConstructorCall(expression, constructedClass, classReference) + visitConstructorCall(expression, constructedClass, classReference) { LocalQuickFix.EMPTY_ARRAY } } override fun visitEnumConstant(enumConstant: PsiEnumConstant) { val constructedClass = enumConstant.containingClass ?: return - visitConstructorCall(enumConstant, constructedClass, enumConstant.nameIdentifier) + visitConstructorCall(enumConstant, constructedClass, enumConstant.nameIdentifier) { + var possibleCtors: MutableMap? = null + for (mixinTarget in constructedClass.mixinTargets) { + if (possibleCtors == null) { + possibleCtors = mutableMapOf() + for (method in mixinTarget.methods) { + if (method.isConstructor) { + possibleCtors[method.desc] = method.findOrConstructSourceMethod(mixinTarget, holder.project, canDecompile = false) - } + } + } + } else { + possibleCtors.keys.retainAll(mixinTarget.methods.mapNotNullTo(mutableSetOf()) { method -> method.desc.takeIf { method.isConstructor } }) + } + } + if (possibleCtors.isNullOrEmpty()) { + return@visitConstructorCall LocalQuickFix.EMPTY_ARRAY + } + possibleCtors.map { (desc, sourceMethod) -> + val presentableParams = + sourceMethod.parameterList.parameters.joinToString { it.type.presentableText } + AddShadowConstructorFix(constructedClass, desc, "($presentableParams)") + }.toTypedArray() + } + } - private fun visitConstructorCall(constructorCall: PsiConstructorCall, constructedClass: PsiClass, problemElement: PsiElement) { + private fun visitConstructorCall( + constructorCall: PsiConstructorCall, + constructedClass: PsiClass, + problemElement: PsiElement, + quickFixGenerator: () -> Array, + ) { if (!constructedClass.isMixin || constructedClass != constructorCall.findContainingClass()) { return } - val resolvedConstructorDesc = constructorCall.resolveConstructor()?.descriptor ?: when { + val resolvedConstructor = constructorCall.resolveConstructor() + if (resolvedConstructor == null && (constructorCall.argumentList?.isEmpty == false || constructedClass.constructors.isNotEmpty())) { + // don't generate errors when there is an unresolved constructor compile error + return + } + val resolvedConstructorDesc = resolvedConstructor?.descriptor ?: when { constructedClass.isEnum -> "(Ljava/lang/String;)V" !constructedClass.hasModifierProperty(PsiModifier.STATIC) && constructedClass.containingClass != null -> "(${constructedClass.containingClass!!.descriptor})V" @@ -76,7 +116,20 @@ return } - holder.registerProblem(problemElement, "Constructor does not exist in target class") + holder.registerProblem(problemElement, "Constructor does not exist in target class", *quickFixGenerator()) } } + + private class AddShadowConstructorFix(constructedClass: PsiClass, private val desc: String, private val presentableParams: String) : LocalQuickFixOnPsiElement(constructedClass) { + override fun getFamilyName() = "Add shadow constructor with parameters $presentableParams" + override fun getText() = "Add shadow constructor with parameters $presentableParams" + + override fun invoke(project: Project, psiFile: PsiFile, startElement: PsiElement, endElement: PsiElement) { + val constructedClass = startElement as? PsiClass ?: return + val arbitraryMixinTarget = constructedClass.mixinTargets.firstOrNull() ?: return + val matchingConstructor = arbitraryMixinTarget.findMethod(MemberReference("", desc)) ?: return + val sourceConstructor = matchingConstructor.findOrConstructSourceMethod(arbitraryMixinTarget, project, canDecompile = false) + insertShadows(project, constructedClass, sequenceOf(sourceConstructor)) -} + } + } +}