⁠
llamalad7: Feature/method signature intersection (#2647 )
* Refactor: Rework expected MethodSignatures.
- ParameterGroups are gone, with every signature now effectively containing a required leading group and an optional vararg trailing group. The prior flexibility made working them easy to get wrong, for example in the matching logic which would have been incorrect for leading vararg groups.
- Inject previously contained 2 optional groups (the target's params and the captured locals) but this was in fact incorrect, since the captured params are required if we want to capture any locals, so it is now (better) represented as 2 separate signature options.
- The distinction between `WARN_IF_ABSENT` and `ERROR_IF_ABSENT` is entirely removed. It was effectively unused since it only applied to captured Inject locals, which are varargs and therefore never "absent" (a separate inspection handles unused LocalCapture).
- ModifyArgs is in fact all-or-nothing wrt capturing the target parameters, and this is now reflected.
* Fix: Disallow non-int int-like types in @ModifyConstant.
Not sure why these are there, Mixin doesn't allow them.
* Fix: Disallow `nullValue` in `@ModifyConstant`.
It does not work and cannot ever work.
* stash
* stash
* finish general intersection
* cleanup
* stash
* stash
* fix general intersection
* stash
* fix tests
* return type only suggestions
* prioritise existing return type in modifier suggestions
* restore BeforeConstant's behaviour of ensuring only 1 discriminator is specified, and supporting wildcard matches
* Fix: Support `.class` constants in `@ModifyConstant`.
* Switch `@ModifyConstant` to a general signature type.
* int coercion kinda fixed ish
* fix int coercion properly
* cleanup
* proper int-like logic
* cleanup
* some docs and fixes
* fix return type solving
* Cache walked supertypes and improve handling of Object as a supertype.
* Cleanup
* Cleanup
* Small fixes
* Small fixes
* Fix: Suggest "ci" for coerced callba...
* Refactor: Rework expected MethodSignatures.
- ParameterGroups are gone, with every signature now effectively containing a required leading group and an optional vararg trailing group. The prior flexibility made working them easy to get wrong, for example in the matching logic which would have been incorrect for leading vararg groups.
- Inject previously contained 2 optional groups (the target's params and the captured locals) but this was in fact incorrect, since the captured params are required if we want to capture any locals, so it is now (better) represented as 2 separate signature options.
- The distinction between `WARN_IF_ABSENT` and `ERROR_IF_ABSENT` is entirely removed. It was effectively unused since it only applied to captured Inject locals, which are varargs and therefore never "absent" (a separate inspection handles unused LocalCapture).
- ModifyArgs is in fact all-or-nothing wrt capturing the target parameters, and this is now reflected.
* Fix: Disallow non-int int-like types in @ModifyConstant.
Not sure why these are there, Mixin doesn't allow them.
* Fix: Disallow `nullValue` in `@ModifyConstant`.
It does not work and cannot ever work.
* stash
* stash
* finish general intersection
* cleanup
* stash
* stash
* fix general intersection
* stash
* fix tests
* return type only suggestions
* prioritise existing return type in modifier suggestions
* restore BeforeConstant's behaviour of ensuring only 1 discriminator is specified, and supporting wildcard matches
* Fix: Support `.class` constants in `@ModifyConstant`.
* Switch `@ModifyConstant` to a general signature type.
* int coercion kinda fixed ish
* fix int coercion properly
* cleanup
* proper int-like logic
* cleanup
* some docs and fixes
* fix return type solving
* Cache walked supertypes and improve handling of Object as a supertype.
* Cleanup
* Cleanup
* Small fixes
* Small fixes
* Fix: Suggest "ci" for coerced callba...
- /*
- * Minecraft Development for IntelliJ
- *
- * https://mcdev.io/
- *
- * Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
- */
-
- package com.demonwav.mcdev.platform.mixin.handlers
-
- import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.NewInsnInjectionPoint
- import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature
- import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup
- import com.demonwav.mcdev.platform.mixin.util.AsmDfaUtil
- import com.demonwav.mcdev.platform.mixin.util.FieldTargetMember
- import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
- import com.demonwav.mcdev.platform.mixin.util.getGenericSignature
- import com.demonwav.mcdev.platform.mixin.util.getGenericType
- import com.demonwav.mcdev.platform.mixin.util.toPsiType
- import com.demonwav.mcdev.util.MemberReference
- import com.demonwav.mcdev.util.Parameter
- import com.demonwav.mcdev.util.toJavaIdentifier
- import com.intellij.psi.JavaPsiFacade
- import com.intellij.psi.PsiAnnotation
- import com.intellij.psi.PsiArrayType
- import com.intellij.psi.PsiElementFactory
- import com.intellij.psi.PsiManager
- import com.intellij.psi.PsiType
- import com.intellij.psi.PsiTypes
- import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext
- import org.objectweb.asm.Opcodes
- import org.objectweb.asm.Type
- import org.objectweb.asm.tree.AbstractInsnNode
- import org.objectweb.asm.tree.ClassNode
- import org.objectweb.asm.tree.FieldInsnNode
- import org.objectweb.asm.tree.InsnNode
- import org.objectweb.asm.tree.MethodInsnNode
- import org.objectweb.asm.tree.MethodNode
- import org.objectweb.asm.tree.TypeInsnNode
-
- class RedirectInjectorHandler : InjectorAnnotationHandler() {
- private fun getRedirectType(insn: AbstractInsnNode): RedirectType? {
- return when (insn) {
- is FieldInsnNode -> {
- if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.GETFIELD) {
- FieldGet
- } else {
- FieldSet
- }
- }
- is MethodInsnNode -> {
- Method
- }
- is InsnNode -> when (insn.opcode) {
- Opcodes.ARRAYLENGTH -> ArrayLength
- in Opcodes.IALOAD..Opcodes.SALOAD -> ArrayGet
- in Opcodes.IASTORE..Opcodes.SASTORE -> ArraySet
- else -> null
- }
- is TypeInsnNode -> when (insn.opcode) {
- Opcodes.NEW -> Constructor
- Opcodes.INSTANCEOF -> InstanceOf
- else -> null
- }
- else -> null
- }
- }
-
- override fun isInsnAllowed(insn: AbstractInsnNode, decorations: Map<String, Any?>): Boolean {
- return getRedirectType(insn)?.isInsnAllowed(insn) ?: false
- }
-
- override val allowedInsnDescription = "redirect targets (see docs)"
-
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- ): List<MethodSignature>? {
- val insns = resolveInstructions(annotation, targetClass, targetMethod).ifEmpty { return emptyList() }
- return getRedirectType(insns[0].insn)?.expectedMethodSignature(
- annotation,
- targetClass,
- targetMethod,
- insns.map { it.insn },
- )?.map { (paramGroups, returnType) ->
- // add a parameter group for capturing the target method parameters
- val extraGroup = ParameterGroup(
- collectTargetMethodParameters(annotation.project, targetClass, targetMethod),
- required = ParameterGroup.RequiredLevel.OPTIONAL,
- isVarargs = true,
- )
- MethodSignature(paramGroups + extraGroup, returnType)
- }
- }
-
- override val allowCoerce = true
-
- override val mixinExtrasExpressionContextType = ExpressionContext.Type.REDIRECT
-
- private interface RedirectType {
- fun isInsnAllowed(node: AbstractInsnNode) = true
-
- fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature>?
- }
-
- private abstract class FieldAccess : RedirectType {
- protected fun determineFieldType(
- firstMatch: FieldInsnNode,
- annotation: PsiAnnotation,
- ): Pair<PsiElementFactory, PsiType> {
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val sourceClassAndField = (
- MemberReference(firstMatch.name, firstMatch.desc, firstMatch.owner.replace('/', '.'))
- .resolveAsm(annotation.project) as? FieldTargetMember
- )?.classAndField
- val fieldType = sourceClassAndField?.field?.getGenericType(sourceClassAndField.clazz, annotation.project)
- ?: Type.getType(firstMatch.desc).toPsiType(elementFactory)
-
- return elementFactory to fieldType
- }
- }
-
- private object FieldGet : FieldAccess() {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature> {
- val firstMatch = insns.first() as FieldInsnNode
- val isValid = insns.all {
- val insn = it as? FieldInsnNode ?: return@all false
- if (insn.opcode != firstMatch.opcode) return@all false
- if (insn.opcode == Opcodes.GETFIELD && insn.owner != firstMatch.owner) return@all false
- insn.name == firstMatch.name && insn.desc == firstMatch.desc
- }
- if (!isValid) {
- return emptyList()
- }
-
- val (elementFactory, fieldType) = determineFieldType(firstMatch, annotation)
- val parameters = mutableListOf<Parameter>()
-
- if (firstMatch.opcode == Opcodes.GETFIELD) {
- parameters += Parameter("instance", Type.getObjectType(firstMatch.owner).toPsiType(elementFactory))
- }
-
- return listOf(
- MethodSignature(
- listOf(ParameterGroup(parameters)),
- fieldType,
- ),
- )
- }
- }
-
- private object FieldSet : FieldAccess() {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature> {
- val firstMatch = insns.first() as FieldInsnNode
- val isValid = insns.all {
- val insn = it as? FieldInsnNode ?: return@all false
- if (insn.opcode != firstMatch.opcode) return@all false
- if (insn.opcode == Opcodes.PUTFIELD && insn.owner != firstMatch.owner) return@all false
- insn.name == firstMatch.name && insn.desc == firstMatch.desc
- }
- if (!isValid) {
- return emptyList()
- }
-
- val (elementFactory, fieldType) = determineFieldType(firstMatch, annotation)
- val parameters = mutableListOf<Parameter>()
-
- if (firstMatch.opcode == Opcodes.PUTFIELD) {
- parameters += Parameter("instance", Type.getObjectType(firstMatch.owner).toPsiType(elementFactory))
- }
- parameters += Parameter("value", fieldType)
-
- return listOf(
- MethodSignature(
- listOf(ParameterGroup(parameters)),
- PsiTypes.voidType(),
- ),
- )
- }
- }
-
- private object Method : RedirectType {
- override fun isInsnAllowed(node: AbstractInsnNode): Boolean {
- return (node as MethodInsnNode).name != "<init>"
- }
-
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature> {
- val firstMatch = insns.first() as MethodInsnNode
- val isValid = insns.all {
- val insn = it as? MethodInsnNode ?: return@all false
- if ((insn.opcode == Opcodes.INVOKESTATIC) != (firstMatch.opcode == Opcodes.INVOKESTATIC)) {
- return@all false
- }
- if (insn.opcode != Opcodes.INVOKESTATIC && insn.owner != firstMatch.owner) return@all false
- insn.name == firstMatch.name && insn.desc == firstMatch.desc
- }
- if (!isValid) {
- return emptyList()
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val sourceClassAndMethod = (
- MemberReference(firstMatch.name, firstMatch.desc, firstMatch.owner.replace('/', '.'))
- .resolveAsm(annotation.project) as? MethodTargetMember
- )?.classAndMethod
- val signature = sourceClassAndMethod?.method?.getGenericSignature(
- sourceClassAndMethod.clazz,
- annotation.project,
- )
-
- val parameters = mutableListOf<Parameter>()
- if (firstMatch.opcode != Opcodes.INVOKESTATIC) {
- parameters += Parameter("instance", Type.getObjectType(firstMatch.owner).toPsiType(elementFactory))
- }
-
- val sortedLocals = sourceClassAndMethod?.method?.localVariables?.sortedBy { it.index }
- if (signature != null) {
- signature.second
- .asSequence()
- .withIndex()
- .mapTo(parameters) { (index, type) ->
- val i = if (firstMatch.opcode == Opcodes.INVOKESTATIC) index else index + 1
- val name = sortedLocals?.getOrNull(i)?.name?.toJavaIdentifier()
- sanitizedParameter(type, name, name != null)
- }
- } else {
- Type.getArgumentTypes(firstMatch.desc).withIndex().mapTo(parameters) { (index, type) ->
- val i = if (firstMatch.opcode == Opcodes.INVOKESTATIC) index else index + 1
- val name = sortedLocals?.getOrNull(i)?.name?.toJavaIdentifier()
- sanitizedParameter(type.toPsiType(elementFactory), name, name != null)
- }
- }
-
- val returnType = signature?.first ?: Type.getReturnType(firstMatch.desc).toPsiType(elementFactory)
- return listOf(MethodSignature(listOf(ParameterGroup(parameters)), returnType))
- }
- }
-
- private object ArrayLength : RedirectType {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature>? {
- val firstMatch = insns.first()
- val isValid = insns.all { it.opcode == firstMatch.opcode }
- if (!isValid) return emptyList()
-
- val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, firstMatch, 0)
- ?: return null
- if (arrayType.sort != Type.ARRAY) {
- return null
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- return listOf(
- MethodSignature(
- listOf(
- ParameterGroup(
- listOf(
- Parameter("array", arrayType.toPsiType(elementFactory)),
- ),
- ),
- ),
- PsiTypes.intType(),
- ),
- )
- }
- }
-
- private object ArrayGet : RedirectType {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature>? {
- val firstMatch = insns.first()
- val isValid = insns.all { it.opcode == firstMatch.opcode }
- if (!isValid) return emptyList()
-
- val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, firstMatch, 1)
- ?: return null
- if (arrayType.sort != Type.ARRAY) {
- return null
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val psiArrayType = arrayType.toPsiType(elementFactory) as PsiArrayType
- return listOf(
- MethodSignature(
- listOf(
- ParameterGroup(
- listOf(
- Parameter("array", psiArrayType),
- Parameter("index", PsiTypes.intType()),
- ),
- ),
- ),
- psiArrayType.componentType,
- ),
- )
- }
- }
-
- private object ArraySet : RedirectType {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature>? {
- val firstMatch = insns.first()
- val isValid = insns.all { it.opcode == firstMatch.opcode }
- if (!isValid) return emptyList()
-
- val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, firstMatch, 2)
- ?: return null
- if (arrayType.sort != Type.ARRAY) {
- return null
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val psiArrayType = arrayType.toPsiType(elementFactory) as PsiArrayType
- return listOf(
- MethodSignature(
- listOf(
- ParameterGroup(
- listOf(
- Parameter("array", psiArrayType),
- Parameter("index", PsiTypes.intType()),
- Parameter("value", psiArrayType.componentType),
- ),
- ),
- ),
- PsiTypes.voidType(),
- ),
- )
- }
- }
-
- private object Constructor : RedirectType {
- override fun isInsnAllowed(node: AbstractInsnNode): Boolean {
- return NewInsnInjectionPoint.Util.findInitCall(node as TypeInsnNode) != null
- }
-
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature> {
- val firstMatch = insns.first() as TypeInsnNode
- val isValid = insns.all {
- val insn = it as? TypeInsnNode ?: return@all false
- insn.opcode == Opcodes.NEW && insn.desc == firstMatch.desc
- }
- if (!isValid) {
- return emptyList()
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
- val constructedType = Type.getObjectType(firstMatch.desc).toPsiType(elementFactory)
-
- return Method.expectedMethodSignature(
- annotation,
- targetClass,
- targetMethod,
- insns.mapNotNull {
- NewInsnInjectionPoint.Util.findInitCall(it as TypeInsnNode)
- },
- ).map { (paramGroups, _) ->
- // drop the instance parameter, return the constructed type
- MethodSignature(listOf(ParameterGroup(paramGroups[0].parameters.drop(1))), constructedType)
- }
- }
- }
-
- private object InstanceOf : RedirectType {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insns: List<AbstractInsnNode>,
- ): List<MethodSignature> {
- val firstMatch = insns.first()
- val isValid = insns.all { it.opcode == firstMatch.opcode }
- if (!isValid) return emptyList()
-
- val psiManager = PsiManager.getInstance(annotation.project)
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
- val objectType = PsiType.getJavaLangObject(psiManager, annotation.resolveScope)
- val classType = elementFactory.createTypeFromText("java.lang.Class<?>", annotation)
- val parameters = ParameterGroup(
- listOf(
- Parameter("instance", objectType),
- Parameter("type", classType),
- ),
- )
- return listOf(
- MethodSignature(listOf(parameters), PsiTypes.booleanType()),
- MethodSignature(listOf(parameters), classType),
- )
- }
- }
- }
- /*
- * Minecraft Development for IntelliJ
- *
- * https://mcdev.io/
- *
- * Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
- */
-
- package com.demonwav.mcdev.platform.mixin.handlers
-
- import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.NewInsnInjectionPoint
- import com.demonwav.mcdev.platform.mixin.handlers.mixinextras.TargetInsn
- import com.demonwav.mcdev.platform.mixin.inspection.injector.ExpectedSignatures
- import com.demonwav.mcdev.platform.mixin.inspection.injector.GeneralSignatures
- import com.demonwav.mcdev.platform.mixin.inspection.injector.SuggestedSignature
- import com.demonwav.mcdev.platform.mixin.inspection.injector.collectSignatures
- import com.demonwav.mcdev.platform.mixin.util.AsmDfaUtil
- import com.demonwav.mcdev.platform.mixin.util.ClassAndMethodNode
- import com.demonwav.mcdev.platform.mixin.util.FieldTargetMember
- import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember
- import com.demonwav.mcdev.platform.mixin.util.TypeKind
- import com.demonwav.mcdev.platform.mixin.util.getGenericSignature
- import com.demonwav.mcdev.platform.mixin.util.getGenericType
- import com.demonwav.mcdev.platform.mixin.util.toPsiType
- import com.demonwav.mcdev.util.MemberReference
- import com.demonwav.mcdev.util.Parameter
- import com.demonwav.mcdev.util.sequencedMapOf
- import com.demonwav.mcdev.util.toJavaIdentifier
- import com.intellij.psi.JavaPsiFacade
- import com.intellij.psi.PsiAnnotation
- import com.intellij.psi.PsiArrayType
- import com.intellij.psi.PsiElementFactory
- import com.intellij.psi.PsiManager
- import com.intellij.psi.PsiType
- import com.intellij.psi.PsiTypes
- import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext
- import org.objectweb.asm.Opcodes
- import org.objectweb.asm.Type
- import org.objectweb.asm.tree.AbstractInsnNode
- import org.objectweb.asm.tree.ClassNode
- import org.objectweb.asm.tree.FieldInsnNode
- import org.objectweb.asm.tree.InsnNode
- import org.objectweb.asm.tree.MethodInsnNode
- import org.objectweb.asm.tree.MethodNode
- import org.objectweb.asm.tree.TypeInsnNode
-
- class RedirectInjectorHandler : InsnInjectorAnnotationHandler() {
- private fun getRedirectType(insn: AbstractInsnNode): RedirectType? {
- return when (insn) {
- is FieldInsnNode -> {
- if (insn.opcode == Opcodes.GETSTATIC || insn.opcode == Opcodes.GETFIELD) {
- FieldGet
- } else {
- FieldSet
- }
- }
- is MethodInsnNode -> {
- Method
- }
- is InsnNode -> when (insn.opcode) {
- Opcodes.ARRAYLENGTH -> ArrayLength
- in Opcodes.IALOAD..Opcodes.SALOAD -> ArrayGet
- in Opcodes.IASTORE..Opcodes.SASTORE -> ArraySet
- else -> null
- }
- is TypeInsnNode -> when (insn.opcode) {
- Opcodes.NEW -> Constructor
- Opcodes.INSTANCEOF -> InstanceOf
- else -> null
- }
- else -> null
- }
- }
-
- override fun isInsnAllowed(insn: AbstractInsnNode, decorations: Map<String, Any?>): Boolean {
- return getRedirectType(insn)?.isInsnAllowed(insn) ?: false
- }
-
- override val allowedInsnDescription = "redirect targets (see docs)"
-
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- targetInsn: TargetInsn,
- ): ExpectedSignatures<GeneralSignatures> {
- val extraParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod)
- val redirectType = getRedirectType(targetInsn.insn) ?: return ExpectedSignatures.Invalid
- return redirectType.expectedMethodSignature(
- annotation,
- targetClass,
- targetMethod,
- targetInsn.insn,
- extraParams,
- )?.let(ExpectedSignatures<*>::Valid) ?: ExpectedSignatures.Invalid
- }
-
- override fun suggestedMethodSignature(
- annotation: PsiAnnotation,
- targets: List<ClassAndMethodNode>
- ): SuggestedSignature? {
- return SuggestedSignature.general(
- annotation,
- expectedMethodSignatures(annotation, targets).collectSignatures<GeneralSignatures>() ?: return null
- )
- }
-
- override val mixinExtrasExpressionContextType = ExpressionContext.Type.REDIRECT
-
- private interface RedirectType {
- val allowCoerce: Boolean get() = true
-
- fun isInsnAllowed(node: AbstractInsnNode) = true
-
- fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures?
- }
-
- private abstract class FieldAccess : RedirectType {
- protected fun determineFieldType(
- firstMatch: FieldInsnNode,
- annotation: PsiAnnotation,
- ): Pair<PsiElementFactory, PsiType> {
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val sourceClassAndField = (
- MemberReference(firstMatch.name, firstMatch.desc, firstMatch.owner.replace('/', '.'))
- .resolveAsm(annotation.project) as? FieldTargetMember
- )?.classAndField
- val fieldType = sourceClassAndField?.field?.getGenericType(sourceClassAndField.clazz, annotation.project)
- ?: Type.getType(firstMatch.desc).toPsiType(elementFactory)
-
- return elementFactory to fieldType
- }
- }
-
- private object FieldGet : FieldAccess() {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures {
- insn as FieldInsnNode
-
- val (elementFactory, fieldType) = determineFieldType(insn, annotation)
- val parameters = mutableListOf<Parameter>()
-
- if (insn.opcode == Opcodes.GETFIELD) {
- parameters += Parameter("instance", Type.getObjectType(insn.owner).toPsiType(elementFactory))
- }
-
- return GeneralSignatures(
- parameters,
- fieldType,
- trailingParams,
- )
- }
- }
-
- private object FieldSet : FieldAccess() {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures {
- insn as FieldInsnNode
-
- val (elementFactory, fieldType) = determineFieldType(insn, annotation)
- val parameters = mutableListOf<Parameter>()
-
- if (insn.opcode == Opcodes.PUTFIELD) {
- parameters += Parameter("instance", Type.getObjectType(insn.owner).toPsiType(elementFactory))
- }
- parameters += Parameter("value", fieldType)
-
- return GeneralSignatures(
- parameters,
- PsiTypes.voidType(),
- trailingParams,
- )
- }
- }
-
- private object Method : RedirectType {
- override fun isInsnAllowed(node: AbstractInsnNode): Boolean {
- return (node as MethodInsnNode).name != "<init>"
- }
-
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures {
- insn as MethodInsnNode
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val sourceClassAndMethod = (
- MemberReference(insn.name, insn.desc, insn.owner.replace('/', '.'))
- .resolveAsm(annotation.project) as? MethodTargetMember
- )?.classAndMethod
- val signature = sourceClassAndMethod?.method?.getGenericSignature(
- sourceClassAndMethod.clazz,
- annotation.project,
- )
-
- val parameters = mutableListOf<Parameter>()
- if (insn.opcode != Opcodes.INVOKESTATIC) {
- parameters += Parameter("instance", Type.getObjectType(insn.owner).toPsiType(elementFactory))
- }
-
- val sortedLocals = sourceClassAndMethod?.method?.localVariables?.sortedBy { it.index }
- if (signature != null) {
- signature.second
- .asSequence()
- .withIndex()
- .mapTo(parameters) { (index, type) ->
- val i = if (insn.opcode == Opcodes.INVOKESTATIC) index else index + 1
- val name = sortedLocals?.getOrNull(i)?.name?.toJavaIdentifier()
- sanitizedParameter(type, name, name != null)
- }
- } else {
- Type.getArgumentTypes(insn.desc).withIndex().mapTo(parameters) { (index, type) ->
- val i = if (insn.opcode == Opcodes.INVOKESTATIC) index else index + 1
- val name = sortedLocals?.getOrNull(i)?.name?.toJavaIdentifier()
- sanitizedParameter(type.toPsiType(elementFactory), name, name != null)
- }
- }
-
- val returnType = signature?.first ?: Type.getReturnType(insn.desc).toPsiType(elementFactory)
- return GeneralSignatures(
- parameters,
- returnType,
- trailingParams,
- )
- }
- }
-
- private object ArrayLength : RedirectType {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures? {
- val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, insn, 0)
- ?: return null
- if (arrayType.sort != Type.ARRAY) {
- return null
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- return GeneralSignatures(
- listOf(Parameter("array", arrayType.toPsiType(elementFactory))),
- PsiTypes.intType(),
- trailingParams,
- )
- }
- }
-
- private object ArrayGet : RedirectType {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures? {
- val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, insn, 1)
- ?: return null
- if (arrayType.sort != Type.ARRAY) {
- return null
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val psiArrayType = arrayType.toPsiType(elementFactory) as PsiArrayType
- return GeneralSignatures(
- listOf(
- Parameter("array", psiArrayType),
- Parameter("index", PsiTypes.intType()),
- ),
- psiArrayType.componentType,
- trailingParams,
- )
- }
- }
-
- private object ArraySet : RedirectType {
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures? {
- val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, insn, 2)
- ?: return null
- if (arrayType.sort != Type.ARRAY) {
- return null
- }
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
-
- val psiArrayType = arrayType.toPsiType(elementFactory) as PsiArrayType
- return GeneralSignatures(
- listOf(
- Parameter("array", psiArrayType),
- Parameter("index", PsiTypes.intType()),
- Parameter("value", psiArrayType.componentType),
- ),
- PsiTypes.voidType(),
- trailingParams,
- )
- }
- }
-
- private object Constructor : RedirectType {
- override fun isInsnAllowed(node: AbstractInsnNode): Boolean {
- return NewInsnInjectionPoint.Util.findInitCall(node as TypeInsnNode) != null
- }
-
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures? {
- insn as TypeInsnNode
-
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
- val constructedType = Type.getObjectType(insn.desc).toPsiType(elementFactory)
-
- return Method.expectedMethodSignature(
- annotation,
- targetClass,
- targetMethod,
- NewInsnInjectionPoint.Util.findInitCall(insn) ?: return null,
- trailingParams,
- ).let { sig ->
- // drop the instance parameter, return the constructed type
- sig.copy(
- params = sig.params.drop(1),
- returnTypeOptions = sequencedMapOf(TypeKind.OBJECT to constructedType),
- )
- }
- }
- }
-
- private object InstanceOf : RedirectType {
- override val allowCoerce: Boolean get() = false
-
- override fun expectedMethodSignature(
- annotation: PsiAnnotation,
- targetClass: ClassNode,
- targetMethod: MethodNode,
- insn: AbstractInsnNode,
- trailingParams: List<Parameter>,
- ): GeneralSignatures {
- val psiManager = PsiManager.getInstance(annotation.project)
- val elementFactory = JavaPsiFacade.getElementFactory(annotation.project)
- val objectType = PsiType.getJavaLangObject(psiManager, annotation.resolveScope)
- val classType = elementFactory.createTypeFromText("java.lang.Class<?>", annotation)
- val parameters = listOf(
- Parameter("instance", objectType),
- Parameter("type", classType),
- )
- return GeneralSignatures(
- parameters,
- sequencedMapOf(
- TypeKind.INT_LIKE to PsiTypes.booleanType(),
- TypeKind.OBJECT to classType,
- ),
- allowCoerce = false,
- trailingParams,
- )
- }
- }
- }