User: llamalad7 Date: 09 Oct 26 11:29 Revision: 8539df1c74008cade4a10470748e1794e7dd6277 Summary: 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... TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10643&personal=false Index: gradle/libs.versions.toml =================================================================== --- gradle/libs.versions.toml (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ gradle/libs.versions.toml (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -68,7 +68,7 @@ sentry = "io.sentry:sentry:8.37.1" # Testing -test-mixin = "org.spongepowered:mixin:0.8.5" +test-mixin = "net.fabricmc:sponge-mixin:0.17.4+mixin.0.8.7" test-spigotapi = "org.spigotmc:spigot-api:1.21.11-R0.2-SNAPSHOT" test-bungeecord = "net.md-5:bungeecord-api:1.21-R0.4" test-spongeapi = "org.spongepowered:spongeapi:7.4.0" Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInComplex.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInComplex.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInComplex.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,66 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public class MixedInComplex { + public void method1(String arg) { + int local1 = 0; + CharSequence local2 = null; + char local3 = 'a'; + Integer local4 = 10; + } + + public void method2(int arg) { + int local1 = 0; + } + + public String method3(Integer arg) { + return arg.toString(); + } + + public Integer method4() { + return 1; + } + + public void method5(CharSequence arg) { + int local1 = 0; + String local2 = null; + int local3 = 0; + int local4 = 0; + char local5 = 0; + char local6 = 0; + } + + public void method6(CharSequence arg) { + int local1 = 0; + String local2 = null; + int local3 = 0; + int local4 = 0; + char local5 = 0; + short local6 = 0; + } + + public void method7(char a) { + } + + public void method8(short a) { + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyArg.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyArg.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyArg.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,38 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public class MixedInModifyArg { + public void caller(Object obj) { + callee1("hello", 1); + callee2('a', (short) 3, 4); + callee3(1, 2, 3); + } + + private static void callee1(String x, int y) { + } + + private static void callee2(char w, short x, int y) { + } + + private static void callee3(int a, int b, int c) { + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyConstant.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyConstant.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyConstant.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,37 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public class MixedInModifyConstant { + public void method1(Class arg) { + System.out.println(42); + System.out.println(101); + System.out.println(Long.class); + System.out.println((Object) 5 instanceof Integer); + System.out.println("hello"); + } + + public void method2(String arg) { + System.out.println(Long.class); + System.out.println((Object) 5 instanceof Integer); + System.out.println((Object) null); + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyExpressionValue.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyExpressionValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyExpressionValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,57 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public class MixedInModifyExpressionValue { + public void method1() { + System.out.println(42); + System.out.println(101); + System.out.println(callee1()); + System.out.println(callee2()); + System.out.println(callee5()); + } + + public void method2(String a, long b) { + System.out.println('a'); + System.out.println(callee3()); + System.out.println(callee4()); + } + + private static char callee1() { + return 'a'; + } + + private static int callee2() { + return 1; + } + + private static Long[] callee3() { + return new Long[0]; + } + + private static Double[] callee4() { + return new Double[0]; + } + + private static short callee5() { + return (short) 1; + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyReceiver.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyReceiver.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyReceiver.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,45 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public abstract class MixedInModifyReceiver implements Comparable { + public String test; + public char test2; + + public void caller(char c) { + callee1("hello", 1); + callee2('a', (short) 3, 4); + callee3(1, 2, 3); + this.test = "hello"; + this.test2 = 'a'; + System.out.println(this.test2); + System.out.println(Integer.valueOf(5).longValue()); + } + + private void callee1(String x, int y) { + } + + private void callee2(char w, short x, int y) { + } + + private void callee3(int a, int b, int c) { + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyReturnValue.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyReturnValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyReturnValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public class MixedInModifyReturnValue { + public String method1() { + return "a"; + } + + public Integer method2() { + return 2; + } + + public static Long method3() { + return 3L; + } + + public int method4(String a, int b) { + return b; + } + + public char method5(String a, short b) { + return (char) b; + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyVariable.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyVariable.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInModifyVariable.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public class MixedInModifyVariable { + public void method1(String arg, Object arg2) { + int local1 = 0; + CharSequence local2 = null; + char local3 = 'a'; + Integer local4 = 10; + } + + public void method2(int arg) { + int local1 = 0; + } + + public void method3(CharSequence arg) { + int local1 = 0; + String local2 = null; + int local3 = 0; + int local4 = 0; + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInRedirect.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInRedirect.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInRedirect.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,60 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public abstract class MixedInRedirect implements Comparable { + public String test; + public char test2; + + public void caller(char c) { + callee1("hello", 1); + callee2('a', (short) 3, 4); + callee3(1, 2, 3); + callee4("hello", void.class); + callee5("hello", void.class); + this.test = "hello"; + this.test2 = 'a'; + System.out.println(this.test2); + System.out.println(Integer.valueOf(5).intValue()); + System.out.println((Object) 5 instanceof Integer); + } + + public void caller2(String arg) { + System.out.println((Object) 5 instanceof Integer); + } + + private void callee1(String x, int y) { + } + + private void callee2(char w, short x, int y) { + } + + private void callee3(int a, int b, int c) { + } + + private static Class callee4(String instance, Class clazz) { + return clazz; + } + + private static Class callee5(String instance, Object clazz) { + return null; + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapMethod.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapMethod.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapMethod.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,54 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +import java.util.Collection; +import java.util.List; + +public class MixedInWrapMethod { + public void method1(String arg) { + } + + public Integer method2() { + return 1; + } + + public void method3(CharSequence arg) { + } + + public Long method4() { + return 1L; + } + + public Number method5(List list, int b) { + return null; + } + + public Integer method6(Collection list, char c) { + return null; + } + + public static void method7(String arg) { + } + + public static void method8(int arg) { + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapOperation.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapOperation.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapOperation.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,82 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public abstract class MixedInWrapOperation implements Comparable { + public String test; + public char test2; + + public void caller(char c) { + callee1("hello", 1); + callee2('a', (short) 3, 4); + callee3(1, 2, 3); + this.test = "hello"; + this.test2 = 'a'; + System.out.println(this.test2); + System.out.println(Integer.valueOf(5).intValue()); + System.out.println((Object) 5 instanceof Integer); + } + + public void caller2(int arg) { + System.out.println(arg == 3); + System.out.println(arg == 4); + callee4('a', 'b'); + callee5(1, (short) 2); + callee6((short) 1, 2); + callee7((short) 1, true); + } + + public void caller3(Object arg) { + callee8(arg); + callee9(arg, arg); + } + + private void callee1(String x, int y) { + } + + private void callee2(char w, short x, int y) { + } + + private void callee3(int a, int b, int c) { + } + + private static int callee4(char a, char b) { + return 0; + } + + private static boolean callee5(int a, short b) { + return true; + } + + private static boolean callee6(short a, int b) { + return true; + } + + private static boolean callee7(short a, boolean b) { + return true; + } + + private static void callee8(Object arg) { + } + + private static void callee9(Object arg, Object arg2) { + } +} Index: mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapWithCondition.java =================================================================== --- mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapWithCondition.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ mixin-test-data/src/main/java/com/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInWrapWithCondition.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix; + +public class MixedInWrapWithCondition { + public String test; + public char test2; + + public void caller(char c) { + callee1("hello", 1); + callee2('a', (short) 3, 4); + callee3(1, 2, 3); + this.test = "hello"; + this.test2 = 'a'; + System.out.println(this.test2); + } + + private void callee1(String x, int y) { + } + + private void callee2(char w, short x, int y) { + } + + private void callee3(int a, int b, int c) { + } +} Index: src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt =================================================================== --- src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/expression/MEExpressionMatchUtil.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.expression -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.util.LocalInfo @@ -249,7 +249,7 @@ return ExpressionContext.Type.SLICE } - val handler = MixinAnnotationHandler.forMixinAnnotation(annotationName, project) as? InjectorAnnotationHandler + val handler = MixinAnnotationHandler.forMixinAnnotation(annotationName, project) as? InsnInjectorAnnotationHandler ?: return ExpressionContext.Type.CUSTOM return handler.mixinExtrasExpressionContextType } Index: src/main/kotlin/platform/mixin/expression/gui/MEShowFlowAction.kt =================================================================== --- src/main/kotlin/platform/mixin/expression/gui/MEShowFlowAction.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/expression/gui/MEShowFlowAction.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,10 +20,9 @@ package com.demonwav.mcdev.platform.mixin.expression.gui -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.reference.MethodReference -import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.findClassNodeByPsiClass import com.demonwav.mcdev.platform.mixin.util.isMixin @@ -51,7 +50,6 @@ import com.intellij.psi.PsiNameValuePair import com.intellij.psi.util.isAncestor import com.intellij.psi.util.parentOfType -import com.intellij.psi.util.parents import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.LineNumberNode import org.objectweb.asm.tree.MethodNode @@ -133,12 +131,11 @@ val modifierList = string.parentOfType()?.modifierList ?: return emptySequence() val (injectorAnnotation, injector) = modifierList.annotations.firstNotNullOfOrNull { ann -> - (MixinAnnotationHandler.forMixinAnnotation(ann, project) as? InjectorAnnotationHandler) + (MixinAnnotationHandler.forMixinAnnotation(ann, project) as? InsnInjectorAnnotationHandler) ?.let { ann to it } } ?: return emptySequence() return psiClass.mixinTargets.asSequence() .flatMap { injector.resolveTarget(injectorAnnotation, it) } - .filterIsInstance() .map { target -> Resolved(target.classAndMethod.clazz, target.classAndMethod.method) { it.populateMatchStatuses(module, string, modifierList) Index: src/main/kotlin/platform/mixin/handlers/InjectAnnotationHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/InjectAnnotationHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/InjectAnnotationHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,23 +20,23 @@ package com.demonwav.mcdev.platform.mixin.handlers -import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +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.InjectSignatures +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.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.LocalVariables -import com.demonwav.mcdev.platform.mixin.util.callbackInfoReturnableType -import com.demonwav.mcdev.platform.mixin.util.callbackInfoType import com.demonwav.mcdev.platform.mixin.util.getGenericReturnType import com.demonwav.mcdev.platform.mixin.util.hasAccess import com.demonwav.mcdev.platform.mixin.util.isFabricMixin import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.Parameter import com.demonwav.mcdev.util.findModule -import com.demonwav.mcdev.util.firstIndexOrNull import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiMethod import com.intellij.psi.PsiQualifiedReference -import com.intellij.psi.PsiTypes import com.intellij.psi.util.parentOfType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Opcodes @@ -44,100 +44,67 @@ import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode -class InjectAnnotationHandler : InjectorAnnotationHandler() { +class InjectAnnotationHandler : InsnInjectorAnnotationHandler() { override fun expectedMethodSignature( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): List { + targetInsn: TargetInsn, + ): ExpectedSignatures { val returnType = targetMethod.getGenericReturnType(targetClass, annotation.project) - val result = ArrayList() + // Parameters from injected method + val targetParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod) - // Parameters from injected method (optional) - result.add( - ParameterGroup( - collectTargetMethodParameters(annotation.project, targetClass, targetMethod), - required = ParameterGroup.RequiredLevel.OPTIONAL, - default = true, - ), - ) - - // Callback info (required) - result.add( - ParameterGroup( - listOf( - if (returnType == PsiTypes.voidType()) { - Parameter("ci", callbackInfoType(annotation.project)) - } else { - Parameter( - "cir", - callbackInfoReturnableType(annotation.project, annotation, returnType)!!, - ) - }, - ), - ), - ) - // Captured locals (only if local capture is enabled) + var capturedLocals = emptyList() + val localCapture = (annotation.findDeclaredAttributeValue("locals") as? PsiQualifiedReference) ?.referenceName ?: "NO_CAPTURE" if (localCapture != "NO_CAPTURE") { annotation.findModule()?.let { module -> - var commonLocalsPrefix: MutableList? = null - val resolvedInsns = resolveInstructions(annotation, targetClass, targetMethod).ifEmpty { return@let } - for (insn in resolvedInsns) { - val locals = LocalVariables.getLocals(module, targetClass, targetMethod, insn.insn) + val locals = LocalVariables.getLocals(module, targetClass, targetMethod, targetInsn.insn) - ?.filterNotNull() - ?.drop( - Type.getArgumentTypes(targetMethod.desc).size + - if (targetMethod.hasAccess(Opcodes.ACC_STATIC)) 0 else 1, - ) - ?.filter { it.desc != null } + ?.filterNotNull() + ?.drop( + Type.getArgumentTypes(targetMethod.desc).size + + if (targetMethod.hasAccess(Opcodes.ACC_STATIC)) 0 else 1, + ) + ?.filter { it.desc != null } - ?: continue - if (commonLocalsPrefix == null) { - commonLocalsPrefix = locals.toMutableList() - } else { - val mismatch = commonLocalsPrefix.zip(locals).firstIndexOrNull { (a, b) -> a.desc != b.desc } - if (mismatch != null) { - commonLocalsPrefix.subList(mismatch, commonLocalsPrefix.size).clear() - } - } - } + ?: return@let - if (commonLocalsPrefix != null) { - val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) + val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) - val localParams = commonLocalsPrefix.map { local -> + capturedLocals = locals.map { local -> - val type = - Type.getType(local.desc).toPsiType(elementFactory, annotation.parentOfType()) - sanitizedParameter(type, local.name) - } + val type = + Type.getType(local.desc).toPsiType(elementFactory, annotation.parentOfType()) + sanitizedParameter(type, local.name) + } - val requiredLevel = if (localCapture == "CAPTURE_FAILSOFT") { - ParameterGroup.RequiredLevel.WARN_IF_ABSENT - } else { - ParameterGroup.RequiredLevel.ERROR_IF_ABSENT - } + } - result.add( - ParameterGroup( - localParams, - default = true, - required = requiredLevel, - isVarargs = true, - ), + } + + return ExpectedSignatures.Valid( + InjectSignatures.create( + annotation, + targetParams, + returnType, + capturedLocals, + ) ?: return ExpectedSignatures.Invalid - ) - } + ) + } - } - } - return listOf(MethodSignature(result, PsiTypes.voidType())) + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.inject( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null, + ) } override fun canAlwaysBeStatic(method: PsiMethod): Boolean { return method.isFabricMixin } - override val allowCoerce = true - override val isShiftAlwaysDiscouraged = false override val mixinExtrasExpressionContextType = ExpressionContext.Type.INJECT Index: src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/InjectorAnnotationHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -24,23 +24,20 @@ import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InsnResolutionInfo -import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature +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.SuggestedSignature import com.demonwav.mcdev.platform.mixin.reference.DescSelectorParser import com.demonwav.mcdev.platform.mixin.reference.isMiscDynamicSelector import com.demonwav.mcdev.platform.mixin.reference.parseMixinSelector import com.demonwav.mcdev.platform.mixin.util.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember -import com.demonwav.mcdev.platform.mixin.util.MixinTargetMember import com.demonwav.mcdev.platform.mixin.util.findMethods import com.demonwav.mcdev.platform.mixin.util.getGenericParameterTypes import com.demonwav.mcdev.platform.mixin.util.hasAccess -import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.util.Parameter -import com.demonwav.mcdev.util.cached import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.findAnnotations -import com.demonwav.mcdev.util.findContainingClass -import com.demonwav.mcdev.util.ifNullOrEmpty import com.demonwav.mcdev.util.toJavaIdentifier import com.intellij.openapi.project.Project import com.intellij.psi.PsiAnnotation @@ -49,17 +46,14 @@ import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier import com.intellij.psi.PsiType -import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.findParentOfType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext -import java.util.concurrent.ConcurrentHashMap import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode abstract class InjectorAnnotationHandler : MixinAnnotationHandler { - override fun resolveTarget(annotation: PsiAnnotation, targetClass: ClassNode): List { + override fun resolveTarget(annotation: PsiAnnotation, targetClass: ClassNode): List { val methodAttr = annotation.findAttributeValue("method") val method = methodAttr?.computeStringArray() ?: emptyList() val desc = annotation.findAttributeValue("desc")?.findAnnotations() ?: emptyList() @@ -88,105 +82,48 @@ return null } - return resolveTarget(annotation, targetClass).map { targetMember -> - val targetMethod = targetMember as? MethodTargetMember ?: return@map InsnResolutionInfo.Failure(AtResolver.DEFAULT_UNRESOLVED_MESSAGE) + return resolveTarget(annotation, targetClass).map { targetMethod -> isUnresolved(annotation, targetClass, targetMethod.classAndMethod.method) ?: return@isUnresolved null }.reduceOrNull(InsnResolutionInfo.Failure::combine) ?: InsnResolutionInfo.Failure(AtResolver.DEFAULT_UNRESOLVED_MESSAGE) } - open fun getAtKey(annotation: PsiAnnotation): String = "at" - - protected open fun isUnresolved( + protected abstract fun isUnresolved( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): InsnResolutionInfo.Failure? { - return annotation.findAttributeValue(getAtKey(annotation))?.findAnnotations() - .ifNullOrEmpty { return InsnResolutionInfo.Failure(AtResolver.DEFAULT_UNRESOLVED_MESSAGE) }!! - .firstNotNullOfOrNull { AtResolver(it, targetClass, targetMethod).isUnresolved() } - } + ): InsnResolutionInfo.Failure? override fun resolveForNavigation(annotation: PsiAnnotation, targetClass: ClassNode): List { - return resolveTarget(annotation, targetClass).flatMap { targetMember -> - val targetMethod = targetMember as? MethodTargetMember ?: return@flatMap emptyList() + return resolveTarget(annotation, targetClass).flatMap { targetMethod -> resolveForNavigation(annotation, targetMethod.classAndMethod.clazz, targetMethod.classAndMethod.method) } } - protected open fun resolveForNavigation( + protected abstract fun resolveForNavigation( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): List { - return annotation.findAttributeValue(getAtKey(annotation))?.findAnnotations() - .ifNullOrEmpty { return emptyList() }!! - .flatMap { AtResolver(it, targetClass, targetMethod).resolveNavigationTargets() } - } + ): List - fun resolveInstructions(annotation: PsiAnnotation) = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) { - val containingClass = annotation.findContainingClass() ?: return@cached emptyList() - containingClass.mixinTargets.flatMap { resolveInstructions(annotation, it) } - } - - fun resolveInstructions(annotation: PsiAnnotation, targetClass: ClassNode): List { - return resolveTarget(annotation, targetClass) - .flatMap { targetMember -> - val targetMethod = (targetMember as? MethodTargetMember)?.classAndMethod ?: return@flatMap emptyList() - resolveInstructions(annotation, targetMethod.clazz, targetMethod.method).map { result -> - InsnResult(targetMethod, result) - } - } - } - - open fun resolveInstructions( + abstract fun expectedMethodSignatures( annotation: PsiAnnotation, - targetClass: ClassNode, - targetMethod: MethodNode, - mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION, - ): List> { - val cache = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) { - ConcurrentHashMap, List>>() - } - return cache.computeIfAbsent(ClassAndMethodNode(targetClass, targetMethod) to mode) { - annotation.findAttributeValue(getAtKey(annotation))?.findAnnotations() - .ifNullOrEmpty { return@computeIfAbsent emptyList() }!! - .flatMap { AtResolver(it, targetClass, targetMethod).resolveInstructions(mode) } - } - } + targets: List, + mode: CollectVisitor.Mode = CollectVisitor.Mode.SUGGESTION, + ): List> - /** - * Returns a list of valid method signatures for the injector. - * May return an empty list for no valid signatures, or null for all signatures being valid. - * Null is usually returned when an error is detected, which is better handled by another inspection. - */ - abstract fun expectedMethodSignature( + abstract fun suggestedMethodSignature( annotation: PsiAnnotation, - targetClass: ClassNode, - targetMethod: MethodNode, - ): List? + targets: List, + ): SuggestedSignature? - open fun isInsnAllowed(insn: AbstractInsnNode, decorations: Map): Boolean { - return true - } - - open val allowedInsnDescription = "all instructions" - open fun canAlwaysBeStatic(method: PsiMethod): Boolean { return true } - open val allowCoerce = false - - open val isShiftAlwaysDiscouraged = true - override val isImplicitlyUsed = true override val icon = MixinAssets.MIXIN_INJECTOR_ICON - abstract val mixinExtrasExpressionContextType: ExpressionContext.Type - - data class InsnResult(val method: ClassAndMethodNode, val result: CollectVisitor.Result<*>) - companion object { @JvmStatic protected fun collectTargetMethodParameters( @@ -221,13 +158,19 @@ } } -object DefaultInjectorAnnotationHandler : InjectorAnnotationHandler() { +object DefaultInjectorAnnotationHandler : InsnInjectorAnnotationHandler() { override fun expectedMethodSignature( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ) = null + targetInsn: TargetInsn, + ): ExpectedSignatures = ExpectedSignatures.Unknown + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? = null + override val isSoft = true override val mixinExtrasExpressionContextType = ExpressionContext.Type.CUSTOM Index: src/main/kotlin/platform/mixin/handlers/InsnInjectorAnnotationHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/InsnInjectorAnnotationHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/platform/mixin/handlers/InsnInjectorAnnotationHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,142 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.platform.mixin.handlers + +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InsnResolutionInfo +import com.demonwav.mcdev.platform.mixin.handlers.mixinextras.TargetInsn +import com.demonwav.mcdev.platform.mixin.inspection.injector.ExpectedSignatures +import com.demonwav.mcdev.platform.mixin.util.ClassAndMethodNode +import com.demonwav.mcdev.platform.mixin.util.mixinTargets +import com.demonwav.mcdev.util.cached +import com.demonwav.mcdev.util.findAnnotations +import com.demonwav.mcdev.util.findContainingClass +import com.demonwav.mcdev.util.ifNullOrEmpty +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiModificationTracker +import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext +import java.util.concurrent.ConcurrentHashMap +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.MethodNode + +abstract class InsnInjectorAnnotationHandler : InjectorAnnotationHandler() { + open fun getAtKey(annotation: PsiAnnotation): String = "at" + + final override fun isUnresolved( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode + ): InsnResolutionInfo.Failure? { + val results = annotation.findAttributeValue(getAtKey(annotation))?.findAnnotations() + .ifNullOrEmpty { return InsnResolutionInfo.Failure(AtResolver.DEFAULT_UNRESOLVED_MESSAGE) } + .map { AtResolver(it, targetClass, targetMethod).isUnresolved() } + return if (null in results) null else results.firstNotNullOf { it } + } + + final override fun resolveForNavigation( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode + ): List { + return annotation.findAttributeValue(getAtKey(annotation))?.findAnnotations() + .ifNullOrEmpty { return emptyList() } + .flatMap { AtResolver(it, targetClass, targetMethod).resolveNavigationTargets() } + } + + fun resolveInstructions(annotation: PsiAnnotation) = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) { + val containingClass = annotation.findContainingClass() ?: return@cached emptyList() + containingClass.mixinTargets.flatMap { resolveInstructions(annotation, it) } + } + + fun resolveInstructions(annotation: PsiAnnotation, targetClass: ClassNode): List { + return resolveInstructions(annotation, resolveTarget(annotation, targetClass).map { it.classAndMethod }) + } + + fun resolveInstructions( + annotation: PsiAnnotation, + targets: List, + mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION, + ): List { + return targets.flatMap { (targetClass, targetMethod) -> + resolveInstructions( + annotation, + targetClass, + targetMethod, + mode, + ).map { InsnResult(ClassAndMethodNode(targetClass, targetMethod), it) } + } + } + + open fun resolveInstructions( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode, + mode: CollectVisitor.Mode = CollectVisitor.Mode.RESOLUTION, + ): List> { + val cache = annotation.cached(PsiModificationTracker.MODIFICATION_COUNT) { + ConcurrentHashMap, List>>() + } + return cache.computeIfAbsent(ClassAndMethodNode(targetClass, targetMethod) to mode) { + annotation.findAttributeValue(getAtKey(annotation))?.findAnnotations() + .ifNullOrEmpty { return@computeIfAbsent emptyList() } + .flatMap { AtResolver(it, targetClass, targetMethod).resolveInstructions(mode) } + } + } + + final override fun expectedMethodSignatures( + annotation: PsiAnnotation, + targets: List, + mode: CollectVisitor.Mode, + ): List> { + return resolveInstructions(annotation, targets, mode).map { (target, result) -> + val (targetClass, targetMethod) = target + val targetInsn = TargetInsn(result.insn, result.decorations) + expectedMethodSignature(annotation, targetClass, targetMethod, targetInsn) + } + } + + /** + * Returns a list of valid method signatures for the injector. + * May return an empty list for no valid signatures, or null for all signatures being valid. + * Null is usually returned when an error is detected, which is better handled by another inspection. + */ + abstract fun expectedMethodSignature( + annotation: PsiAnnotation, + targetClass: ClassNode, + targetMethod: MethodNode, + targetInsn: TargetInsn, + ): ExpectedSignatures<*> + + open fun isInsnAllowed(insn: AbstractInsnNode, decorations: Map): Boolean { + return true + } + + open val allowedInsnDescription = "all instructions" + + open val isShiftAlwaysDiscouraged = true + + abstract val mixinExtrasExpressionContextType: ExpressionContext.Type + + data class InsnResult(val method: ClassAndMethodNode, val result: CollectVisitor.Result<*>) +} Index: src/main/kotlin/platform/mixin/handlers/ModifyArgHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/ModifyArgHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/ModifyArgHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,13 +20,17 @@ package com.demonwav.mcdev.platform.mixin.handlers -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.fakeResolve -import com.demonwav.mcdev.platform.mixin.util.getParameter +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.ModifierSignatures +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.ClassAndMethodNode +import com.demonwav.mcdev.platform.mixin.util.getBytecodeParameter import com.demonwav.mcdev.platform.mixin.util.toPsiType +import com.demonwav.mcdev.util.MemberReference +import com.demonwav.mcdev.util.SequencedMap import com.demonwav.mcdev.util.constantValue -import com.demonwav.mcdev.util.descriptor import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiMethod @@ -37,7 +41,7 @@ import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode -class ModifyArgHandler : InjectorAnnotationHandler() { +class ModifyArgHandler : InsnInjectorAnnotationHandler() { override fun isInsnAllowed(insn: AbstractInsnNode, decorations: Map): Boolean { return insn is MethodInsnNode } @@ -48,96 +52,65 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): List? { + targetInsn: TargetInsn, + ): ExpectedSignatures { + val insn = targetInsn.insn + if (insn !is MethodInsnNode) { + return ExpectedSignatures.Invalid + } + val project = annotation.project val index = annotation.findDeclaredAttributeValue("index")?.constantValue as? Int - val validSingleArgTypes = mutableSetOf() - var mayHaveValidFullSignature = true - var validFullSignature: String? = null - val insns = resolveInstructions(annotation, targetClass, targetMethod).ifEmpty { return emptyList() } - for (insn in insns) { - if (insn.insn !is MethodInsnNode) return null - // normalize return type so whole signature matches - val desc = insn.insn.desc.replaceAfterLast(')', "V") + val argTypes = Type.getArgumentTypes(insn.desc) - if (index == null) { - val validArgTypes = Type.getArgumentTypes(desc).mapTo(mutableListOf()) { it.descriptor } - // remove duplicates completely, they are invalid - val toRemove = validArgTypes.filter { e -> validArgTypes.count { it == e } > 1 }.toSet() - validArgTypes.removeIf { toRemove.contains(it) } - if (validArgTypes.isEmpty()) { - return listOf() - } - - if (validSingleArgTypes.isEmpty()) { - validSingleArgTypes.addAll(validArgTypes) + val validTypes = if (index == null) { + argTypes.groupingBy { it }.eachCount().asSequence().filter { it.value == 1 }.map { it.key }.toList() - } else { + } else { - validSingleArgTypes.retainAll(validArgTypes.toSet()) - if (validSingleArgTypes.isEmpty()) { - return listOf() + listOfNotNull(argTypes.getOrNull(index)) - } + } - } - } else { - val singleArgType = Type.getArgumentTypes(desc).getOrNull(index)?.descriptor ?: return listOf() - if (validSingleArgTypes.isEmpty()) { - validSingleArgTypes += singleArgType - } else { - validSingleArgTypes.removeIf { it != singleArgType } - if (validSingleArgTypes.isEmpty()) { - return listOf() - } - } - } - if (mayHaveValidFullSignature) { - if (validFullSignature == null) { - validFullSignature = desc - } else { - if (desc != validFullSignature) { - validFullSignature = null - mayHaveValidFullSignature = false + if (validTypes.isEmpty()) { + return ExpectedSignatures.Invalid - } + } - } - } - } // get the source method for parameter names - val (bytecodeClass, bytecodeMethod) = (insns[0].insn as MethodInsnNode).fakeResolve() - val sourceMethod = insns[0].target as? PsiMethod + val sourceMethod = MemberReference( + insn.name, + insn.desc, + insn.owner.replace('/', '.') + ).resolveMember(project) as PsiMethod? val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) - return validSingleArgTypes.flatMap { type -> - val paramList = sourceMethod?.parameterList - val psiParameter = paramList?.parameters?.firstOrNull { it.type.descriptor == type } - val psiType = psiParameter?.type ?: Type.getType(type).toPsiType(elementFactory, null) - val singleSignature = MethodSignature( - listOf( - ParameterGroup( - listOf( - sanitizedParameter(psiType, psiParameter?.name), - ), - ), - ), - psiType, - ) - if (validFullSignature != null) { - val fullParamGroup = ParameterGroup( - Type.getArgumentTypes(validFullSignature).withIndex().map { (index, argType) -> - val psiParam = paramList?.let { bytecodeMethod.getParameter(bytecodeClass, index, it) } + val psiParams = argTypes.indices.map { index -> sourceMethod?.getBytecodeParameter(index) } + val paramOptions = validTypes.associateWithTo(linkedMapOf()) { type -> + val targetParam = psiParams[index ?: argTypes.indexOf(type)] + val psiType = targetParam?.type ?: type.toPsiType(elementFactory) + sanitizedParameter(psiType, targetParam?.name) + } + val fullParams = if (argTypes.size > 1) { + psiParams.zip(argTypes) { param, argType -> - sanitizedParameter( + sanitizedParameter( - psiParam?.type ?: argType.toPsiType(elementFactory), - psiParam?.name, + param?.type ?: argType.toPsiType(elementFactory), + param?.name, - ) + ) - }, + } + } else null + return ExpectedSignatures.Valid( + ModifierSignatures( + SequencedMap(paramOptions), + fullParams, - ) + ) - listOf( - singleSignature, - MethodSignature(listOf(fullParamGroup), psiType), - ) + ) - } else { - listOf(singleSignature) - } + } + + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.modifier( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null, + ) - } + } - } override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_ARG } Index: src/main/kotlin/platform/mixin/handlers/ModifyArgsHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/ModifyArgsHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/ModifyArgsHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,8 +20,13 @@ package com.demonwav.mcdev.platform.mixin.handlers +import com.demonwav.mcdev.platform.mixin.handlers.mixinextras.TargetInsn +import com.demonwav.mcdev.platform.mixin.inspection.injector.BasicSignatures +import com.demonwav.mcdev.platform.mixin.inspection.injector.ExpectedSignatures import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +import com.demonwav.mcdev.platform.mixin.inspection.injector.SignatureSuggestion +import com.demonwav.mcdev.platform.mixin.inspection.injector.SuggestedSignature +import com.demonwav.mcdev.platform.mixin.util.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.ARGS import com.demonwav.mcdev.util.Parameter import com.intellij.psi.JavaPsiFacade @@ -33,7 +38,7 @@ import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode -class ModifyArgsHandler : InjectorAnnotationHandler() { +class ModifyArgsHandler : InsnInjectorAnnotationHandler() { override fun isInsnAllowed(insn: AbstractInsnNode, decorations: Map): Boolean { return insn is MethodInsnNode } @@ -44,23 +49,38 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): List { + targetInsn: TargetInsn, + ): ExpectedSignatures { val argsType = JavaPsiFacade.getElementFactory(annotation.project) .createTypeByFQClassName(ARGS, annotation.resolveScope) - return listOf( + val shortParams = listOf(Parameter("args", argsType)) + return ExpectedSignatures.Valid( + BasicSignatures( - MethodSignature( + MethodSignature( - listOf( - ParameterGroup(listOf(Parameter("args", argsType))), - ParameterGroup( - collectTargetMethodParameters(annotation.project, targetClass, targetMethod), - required = ParameterGroup.RequiredLevel.OPTIONAL, - isVarargs = true, + shortParams, + PsiTypes.voidType(), + allowCoerceRequired = false, - ), + ), - ), + MethodSignature( + shortParams + collectTargetMethodParameters(annotation.project, targetClass, targetMethod), - PsiTypes.voidType(), + PsiTypes.voidType(), + allowCoerceRequired = false, - ), - ) + ), + ) + ) } + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature { + val argsType = JavaPsiFacade.getElementFactory(annotation.project) + .createTypeByFQClassName(ARGS, annotation.resolveScope) + return SuggestedSignature( + listOf(SignatureSuggestion.Param("args", argsType)), + PsiTypes.voidType(), + ) + } + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_ARGS } Index: src/main/kotlin/platform/mixin/handlers/ModifyConstantHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/ModifyConstantHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/ModifyConstantHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -22,27 +22,34 @@ import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.ConstantInjectionPoint import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InjectionPoint -import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup -import com.demonwav.mcdev.util.findAnnotations -import com.intellij.openapi.project.Project +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.ClassAndMethodNode +import com.demonwav.mcdev.platform.mixin.util.TypeKind +import com.demonwav.mcdev.util.Parameter +import com.demonwav.mcdev.util.sequencedMapOf import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager -import com.intellij.psi.PsiMethod import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes -import com.intellij.psi.util.parentOfType 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.MethodNode +import org.objectweb.asm.tree.TypeInsnNode -class ModifyConstantHandler : InjectorAnnotationHandler() { +class ModifyConstantHandler : InsnInjectorAnnotationHandler() { + private val constantInjectionPoint by lazy { InjectionPoint.byAtCode("CONSTANT") as ConstantInjectionPoint } + private val allowedOpcodes = setOf( + Opcodes.ACONST_NULL, Opcodes.ICONST_M1, Opcodes.ICONST_0, Opcodes.ICONST_1, @@ -64,129 +71,89 @@ Opcodes.IFGE, Opcodes.IFGT, Opcodes.IFLE, - Opcodes.CHECKCAST, Opcodes.INSTANCEOF, ) - private fun getConstantInfos(modifyConstant: PsiAnnotation): List? { - val constants = modifyConstant.findDeclaredAttributeValue("constant") - ?.findAnnotations() - ?.takeIf { it.isNotEmpty() } - ?: return null - return constants.map { constant -> - (InjectionPoint.byAtCode("CONSTANT") as ConstantInjectionPoint).getConstantInfo(constant) ?: return null - } - } - override fun getAtKey(annotation: PsiAnnotation) = "constant" override fun expectedMethodSignature( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): List { - val constantInfos = getConstantInfos(annotation) - if (constantInfos == null) { - val method = annotation.parentOfType() - ?: return emptyList() - val returnType = method.returnType - ?: return emptyList() - val constantParamName = method.parameterList.getParameter(0)?.name ?: "constant" - return listOf( - MethodSignature( - listOf( - ParameterGroup(listOf(sanitizedParameter(returnType, constantParamName))), - ParameterGroup( - collectTargetMethodParameters(annotation.project, targetClass, targetMethod), - isVarargs = true, - required = ParameterGroup.RequiredLevel.OPTIONAL, - ), - ), - returnType, + targetInsn: TargetInsn, + ): ExpectedSignatures { + val targetParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod) + val cst = constantInjectionPoint.getTargetedConstant(targetInsn.insn) ?: return ExpectedSignatures.Invalid + return ExpectedSignatures.Valid(expectedSignatures(annotation, targetInsn.insn, cst, targetParams)) + } + + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.general( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null - ) + ) - ) - } + } + private fun expectedSignatures( + annotation: PsiAnnotation, + targetInsn: AbstractInsnNode, + cst: Any, + trailingParams: List, + ): GeneralSignatures { val psiManager = PsiManager.getInstance(annotation.project) - return constantInfos.asSequence() - .distinctBy { it.constant?.javaClass } - .flatMap { - when (it.constant) { - null -> sequenceOf( - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiType.getJavaLangObject(psiManager, annotation.resolveScope)) + + return if (targetInsn is TypeInsnNode) { + GeneralSignatures( + makeTypeCheckParams(psiManager, annotation), + sequencedMapOf( + TypeKind.INT_LIKE to PsiTypes.booleanType(), + TypeKind.OBJECT to getClassType(psiManager, annotation), + ), + allowCoerce = false, + trailingParams, - ) + ) - is Int -> sequenceOf( - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.intType()), - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.booleanType()), - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.byteType()), - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.charType()), - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.shortType()), + } else { + makeSignatures( + getConstantType(annotation, cst) + ?: throw IllegalStateException("Unknown constant type: ${cst.javaClass.name}"), + trailingParams, - ) + ) - is Long -> sequenceOf( - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.longType()) + } + } + + private fun getConstantType(context: PsiAnnotation, cst: Any) = when (cst) { + is Int -> PsiTypes.intType() + is Long -> PsiTypes.longType() + is Float -> PsiTypes.floatType() + is Double -> PsiTypes.doubleType() + is String -> PsiType.getJavaLangString( + PsiManager.getInstance(context.project), + context.resolveScope, - ) + ) - is Float -> sequenceOf( - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.floatType()) + is Type -> getClassType( + PsiManager.getInstance(context.project), + context, - ) + ) - is Double -> sequenceOf( - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiTypes.doubleType()) - ) - is String -> sequenceOf( - makeMethodSignature(annotation.project, targetClass, targetMethod, PsiType.getJavaLangString(psiManager, annotation.resolveScope)) - ) - is Type -> sequenceOf( - makeTypeCheckMethodSignature(annotation.project, psiManager, annotation, targetClass, targetMethod, getClassType(psiManager, annotation)), - makeTypeCheckMethodSignature(annotation.project, psiManager, annotation, targetClass, targetMethod, PsiTypes.booleanType()), - ) - else -> throw IllegalStateException("Unknown constant type: ${it.constant.javaClass.name}") + + else -> null - } + } - } - .toList() - } - private fun makeMethodSignature( - project: Project, - targetClass: ClassNode, - targetMethod: MethodNode, - type: PsiType, - ): MethodSignature { - return MethodSignature( - listOf( - ParameterGroup(listOf(sanitizedParameter(type, "constant"))), - ParameterGroup( - collectTargetMethodParameters(project, targetClass, targetMethod), - isVarargs = true, - required = ParameterGroup.RequiredLevel.OPTIONAL, - ), - ), + private fun makeSignatures(type: PsiType, trailingParams: List): GeneralSignatures { + return GeneralSignatures( + listOf(sanitizedParameter(type, "constant")), type, + trailingParams, ) } - private fun makeTypeCheckMethodSignature( - project: Project, - psiManager: PsiManager, - context: PsiElement, - targetClass: ClassNode, - targetMethod: MethodNode, - returnType: PsiType, - ): MethodSignature { - return MethodSignature( - listOf( - ParameterGroup( - listOf( + private fun makeTypeCheckParams(psiManager: PsiManager, context: PsiElement): List { + return listOf( - sanitizedParameter(PsiType.getJavaLangObject(psiManager, context.resolveScope), "instance"), - sanitizedParameter(getClassType(psiManager, context), "type"), - ) + sanitizedParameter(PsiType.getJavaLangObject(psiManager, context.resolveScope), "instance"), + sanitizedParameter(getClassType(psiManager, context), "type"), + ) - ), - ParameterGroup( - collectTargetMethodParameters(project, targetClass, targetMethod), - isVarargs = true, - required = ParameterGroup.RequiredLevel.OPTIONAL, - ), - ), - returnType, - ) } private fun getClassType(psiManager: PsiManager, context: PsiElement): PsiType { Index: src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/ModifyVariableHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,14 +20,17 @@ package com.demonwav.mcdev.platform.mixin.handlers -import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AbstractLoadInjectionPoint import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor -import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InjectionPoint -import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +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.ModifierSignatures +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.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.LocalInfo import com.demonwav.mcdev.platform.mixin.util.toPsiType -import com.demonwav.mcdev.util.constantStringValue +import com.demonwav.mcdev.util.Parameter +import com.demonwav.mcdev.util.SequencedMap import com.demonwav.mcdev.util.findContainingMethod import com.demonwav.mcdev.util.findModule import com.intellij.psi.JavaPsiFacade @@ -37,53 +40,49 @@ import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode -class ModifyVariableHandler : InjectorAnnotationHandler() { +class ModifyVariableHandler : InsnInjectorAnnotationHandler() { override fun expectedMethodSignature( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): List? { - val module = annotation.findModule() ?: return null + targetInsn: TargetInsn, + ): ExpectedSignatures { + val module = annotation.findModule() ?: return ExpectedSignatures.Unknown + val targetParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod) - val at = annotation.findAttributeValue("at") as? PsiAnnotation - val atCode = at?.findAttributeValue("value")?.constantStringValue - val isLoadStore = atCode != null && InjectionPoint.byAtCode(atCode) is AbstractLoadInjectionPoint - val mode = if (isLoadStore) CollectVisitor.Mode.COMPLETION else CollectVisitor.Mode.RESOLUTION - val targets = resolveInstructions(annotation, targetClass, targetMethod, mode) - - val targetParamsGroup = ParameterGroup( - collectTargetMethodParameters(annotation.project, targetClass, targetMethod), - required = ParameterGroup.RequiredLevel.OPTIONAL, - isVarargs = true, - ) - - val method = annotation.findContainingMethod() ?: return null + val method = annotation.findContainingMethod() ?: return ExpectedSignatures.Unknown val localType = method.parameterList.getParameter(0)?.type val info = LocalInfo.fromAnnotation(localType, annotation) val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) - val seenParams = mutableSetOf() - val result = mutableListOf() - for (insn in targets) { + val result = linkedMapOf() - val matchedLocals = info.matchLocals( + val matchedLocals = info.matchLocals( - module, targetClass, targetMethod, insn.insn, - CollectVisitor.Mode.COMPLETION, matchType = false - ) ?: continue + module, targetClass, targetMethod, targetInsn.insn, + CollectVisitor.Mode.SUGGESTION, matchType = false + ).orEmpty() - for (local in matchedLocals) { + for (local in matchedLocals) { - if (seenParams.add(local.desc + local.name)) { - val localType = Type.getType(local.desc).toPsiType(elementFactory) - result += MethodSignature( - listOf( - ParameterGroup(listOf(sanitizedParameter(localType, local.name, local.isNamed))), - targetParamsGroup, - ), - localType, - ) + val type = Type.getType(local.desc ?: continue) + result.computeIfAbsent(type) { + sanitizedParameter(type.toPsiType(elementFactory), local.name, local.isNamed) - } - } + } + } + + return ExpectedSignatures.Valid( + ModifierSignatures( + SequencedMap(result), + trailingParams = targetParams, + ) + ) - } + } - return result + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.modifier( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null, + ) } override val isShiftAlwaysDiscouraged = false Index: src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/RedirectInjectorHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -21,16 +21,22 @@ 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.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 @@ -50,7 +56,7 @@ import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.TypeInsnNode -class RedirectInjectorHandler : InjectorAnnotationHandler() { +class RedirectInjectorHandler : InsnInjectorAnnotationHandler() { private fun getRedirectType(insn: AbstractInsnNode): RedirectType? { return when (insn) { is FieldInsnNode -> { @@ -88,37 +94,43 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - ): List? { - val insns = resolveInstructions(annotation, targetClass, targetMethod).ifEmpty { return emptyList() } - return getRedirectType(insns[0].insn)?.expectedMethodSignature( + targetInsn: TargetInsn, + ): ExpectedSignatures { + val extraParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod) + val redirectType = getRedirectType(targetInsn.insn) ?: return ExpectedSignatures.Invalid + return redirectType.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, + targetInsn.insn, + extraParams, + )?.let(ExpectedSignatures<*>::Valid) ?: ExpectedSignatures.Invalid + } + + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.general( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null - ) + ) - MethodSignature(paramGroups + extraGroup, returnType) - } + } - } - override val allowCoerce = true - 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, - insns: List, - ): List? + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures? } private abstract class FieldAccess : RedirectType { @@ -144,31 +156,22 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insns: List, - ): List { - 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() - } + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures { + insn as FieldInsnNode - val (elementFactory, fieldType) = determineFieldType(firstMatch, annotation) + val (elementFactory, fieldType) = determineFieldType(insn, annotation) val parameters = mutableListOf() - if (firstMatch.opcode == Opcodes.GETFIELD) { - parameters += Parameter("instance", Type.getObjectType(firstMatch.owner).toPsiType(elementFactory)) + if (insn.opcode == Opcodes.GETFIELD) { + parameters += Parameter("instance", Type.getObjectType(insn.owner).toPsiType(elementFactory)) } - return listOf( - MethodSignature( - listOf(ParameterGroup(parameters)), + return GeneralSignatures( + parameters, - fieldType, + fieldType, - ), + trailingParams, ) } } @@ -178,32 +181,23 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insns: List, - ): List { - 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() - } + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures { + insn as FieldInsnNode - val (elementFactory, fieldType) = determineFieldType(firstMatch, annotation) + val (elementFactory, fieldType) = determineFieldType(insn, annotation) val parameters = mutableListOf() - if (firstMatch.opcode == Opcodes.PUTFIELD) { - parameters += Parameter("instance", Type.getObjectType(firstMatch.owner).toPsiType(elementFactory)) + if (insn.opcode == Opcodes.PUTFIELD) { + parameters += Parameter("instance", Type.getObjectType(insn.owner).toPsiType(elementFactory)) } parameters += Parameter("value", fieldType) - return listOf( - MethodSignature( - listOf(ParameterGroup(parameters)), + return GeneralSignatures( + parameters, - PsiTypes.voidType(), + PsiTypes.voidType(), - ), + trailingParams, ) } } @@ -217,25 +211,15 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insns: List, - ): List { - 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() - } + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures { + insn as MethodInsnNode val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) val sourceClassAndMethod = ( - MemberReference(firstMatch.name, firstMatch.desc, firstMatch.owner.replace('/', '.')) + MemberReference(insn.name, insn.desc, insn.owner.replace('/', '.')) .resolveAsm(annotation.project) as? MethodTargetMember )?.classAndMethod val signature = sourceClassAndMethod?.method?.getGenericSignature( @@ -244,8 +228,8 @@ ) val parameters = mutableListOf() - if (firstMatch.opcode != Opcodes.INVOKESTATIC) { - parameters += Parameter("instance", Type.getObjectType(firstMatch.owner).toPsiType(elementFactory)) + if (insn.opcode != Opcodes.INVOKESTATIC) { + parameters += Parameter("instance", Type.getObjectType(insn.owner).toPsiType(elementFactory)) } val sortedLocals = sourceClassAndMethod?.method?.localVariables?.sortedBy { it.index } @@ -254,20 +238,24 @@ .asSequence() .withIndex() .mapTo(parameters) { (index, type) -> - val i = if (firstMatch.opcode == Opcodes.INVOKESTATIC) index else index + 1 + 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(firstMatch.desc).withIndex().mapTo(parameters) { (index, type) -> - val i = if (firstMatch.opcode == Opcodes.INVOKESTATIC) index else index + 1 + 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(firstMatch.desc).toPsiType(elementFactory) - return listOf(MethodSignature(listOf(ParameterGroup(parameters)), returnType)) + val returnType = signature?.first ?: Type.getReturnType(insn.desc).toPsiType(elementFactory) + return GeneralSignatures( + parameters, + returnType, + trailingParams, + ) } } @@ -276,13 +264,10 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insns: List, - ): List? { - 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) + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures? { + val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, insn, 0) ?: return null if (arrayType.sort != Type.ARRAY) { return null @@ -290,17 +275,10 @@ val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) - return listOf( - MethodSignature( - listOf( - ParameterGroup( - listOf( - Parameter("array", arrayType.toPsiType(elementFactory)), - ), - ), - ), + return GeneralSignatures( + listOf(Parameter("array", arrayType.toPsiType(elementFactory))), - PsiTypes.intType(), + PsiTypes.intType(), - ), + trailingParams, ) } } @@ -310,13 +288,10 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insns: List, - ): List? { - 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) + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures? { + val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, insn, 1) ?: return null if (arrayType.sort != Type.ARRAY) { return null @@ -325,18 +300,13 @@ val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) val psiArrayType = arrayType.toPsiType(elementFactory) as PsiArrayType - return listOf( - MethodSignature( + return GeneralSignatures( - listOf( + listOf( - ParameterGroup( - listOf( - Parameter("array", psiArrayType), - Parameter("index", PsiTypes.intType()), - ), + Parameter("array", psiArrayType), + Parameter("index", PsiTypes.intType()), + ), - ), - ), - psiArrayType.componentType, + psiArrayType.componentType, - ), + trailingParams, ) } } @@ -346,13 +316,10 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insns: List, - ): List? { - 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) + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures? { + val arrayType = AsmDfaUtil.getStackType(annotation.project, targetClass, targetMethod, insn, 2) ?: return null if (arrayType.sort != Type.ARRAY) { return null @@ -361,19 +328,14 @@ val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) val psiArrayType = arrayType.toPsiType(elementFactory) as PsiArrayType - return listOf( - MethodSignature( + return GeneralSignatures( - listOf( + listOf( - ParameterGroup( - listOf( - Parameter("array", psiArrayType), - Parameter("index", PsiTypes.intType()), - Parameter("value", psiArrayType.componentType), - ), + Parameter("array", psiArrayType), + Parameter("index", PsiTypes.intType()), + Parameter("value", psiArrayType.componentType), + ), - ), - ), - PsiTypes.voidType(), + PsiTypes.voidType(), - ), + trailingParams, ) } } @@ -387,59 +349,57 @@ annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, - insns: List, - ): List { - 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() - } + insn: AbstractInsnNode, + trailingParams: List, + ): GeneralSignatures? { + insn as TypeInsnNode val elementFactory = JavaPsiFacade.getElementFactory(annotation.project) - val constructedType = Type.getObjectType(firstMatch.desc).toPsiType(elementFactory) + val constructedType = Type.getObjectType(insn.desc).toPsiType(elementFactory) return Method.expectedMethodSignature( annotation, targetClass, targetMethod, - insns.mapNotNull { - NewInsnInjectionPoint.Util.findInitCall(it as TypeInsnNode) - }, - ).map { (paramGroups, _) -> + NewInsnInjectionPoint.Util.findInitCall(insn) ?: return null, + trailingParams, + ).let { sig -> // drop the instance parameter, return the constructed type - MethodSignature(listOf(ParameterGroup(paramGroups[0].parameters.drop(1))), constructedType) + 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, - insns: List, - ): List { - val firstMatch = insns.first() - val isValid = insns.all { it.opcode == firstMatch.opcode } - if (!isValid) return emptyList() - + insn: AbstractInsnNode, + trailingParams: List, + ): 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 = ParameterGroup( - listOf( + val parameters = listOf( - Parameter("instance", objectType), - Parameter("type", classType), + Parameter("instance", objectType), + Parameter("type", classType), + ) + return GeneralSignatures( + parameters, + sequencedMapOf( + TypeKind.INT_LIKE to PsiTypes.booleanType(), + TypeKind.OBJECT to classType, ), + allowCoerce = false, + trailingParams, ) - return listOf( - MethodSignature(listOf(parameters), PsiTypes.booleanType()), - MethodSignature(listOf(parameters), classType), - ) } } } Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/AtResolver.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.handlers.injectionPoint -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarContext import com.demonwav.mcdev.platform.mixin.handlers.desugar.DesugarUtil @@ -297,7 +297,7 @@ val targetAttr = at.findAttributeValue("target") val target = targetAttr?.let { parseMixinSelector(it) } val injector = findInjectorAnnotation(at, skipThroughSlice = false)?.let(MixinAnnotationHandler::forMixinAnnotation) - as? InjectorAnnotationHandler + as? InsnInjectorAnnotationHandler // Collect all possible targets fun doCollectVariants(injectionPoint: InjectionPoint): List { Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantInjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -50,6 +50,7 @@ import com.intellij.psi.PsiSwitchLabelStatementBase import com.intellij.psi.util.PsiUtil import com.intellij.util.ArrayUtilRt +import java.util.EnumSet import java.util.Locale import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -195,6 +196,10 @@ return null } + fun getTargetedConstant(insn: AbstractInsnNode) = + insn.computeConstantValue(EnumSet.allOf(ExpandCondition::class.java)) + ?.let { if (it === NullSentinel) null else it } + class ConstantInfo(val constant: Any?, val expandConditions: Set) enum class ExpandCondition(vararg val opcodes: Int) { Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/ConstantStringMethodInjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -148,7 +148,7 @@ targetClass: ClassNode, mode: CollectVisitor.Mode, ): CollectVisitor? { - if (mode == CollectVisitor.Mode.COMPLETION) { + if (!mode.assumeCorrectAt) { return MyCollectVisitor(mode, at.project, MemberInfo(), null) } return target?.let { MyCollectVisitor(mode, at.project, it, AtResolver.getArgs(at)["ldc"]) } @@ -233,7 +233,7 @@ // must take a string and return void if (insn.desc != "(Ljava/lang/String;)V") return - if (mode != Mode.COMPLETION) { + if (mode.assumeCorrectAt) { // ensure we match the target if (!selector.matchMethod(insn.owner, insn.name, insn.desc)) { return Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/FieldInjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -97,7 +97,7 @@ targetClass: ClassNode, mode: CollectVisitor.Mode, ): CollectVisitor? { - if (mode == CollectVisitor.Mode.COMPLETION) { + if (!mode.assumeCorrectAt) { return MyCollectVisitor(mode, at.project, MemberInfo(), -1, null, 8) } val opcode = (at.findDeclaredAttributeValue("opcode")?.constantValue as? Int) @@ -205,7 +205,7 @@ val insns = methodNode.instructions ?: return@sequence for (insn in insns) { if (insn !is FieldInsnNode) continue - if (mode != Mode.COMPLETION) { + if (mode.assumeCorrectAt) { if (opcode != -1 && opcode != insn.opcode) { continue } Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -161,7 +161,7 @@ // filters have passed, and the specifier acts on the result of them. // Separately, these happen to also be the filters that we don't want to apply during completion, so that all // results are shown. - if (mode != CollectVisitor.Mode.COMPLETION) { + if (mode.assumeCorrectAt) { addOrdinalFilter(at, targetClass, collectVisitor) addQuantifierFilter(at, targetClass, collectVisitor) addSpecifierFilter(at, targetClass, collectVisitor, defaultSpecifier) @@ -458,8 +458,12 @@ val index: Int get() = sourceLocationInfo.index } - enum class Mode { RESOLUTION, COMPLETION } + enum class Mode(val assumeCorrectSignature: Boolean, val assumeCorrectAt: Boolean) { + RESOLUTION(true, true), + COMPLETION(false, false), + SUGGESTION(false, true), -} + } +} fun nodeMatchesSelector( insn: MethodInsnNode, @@ -467,7 +471,7 @@ selector: MixinSelector, project: Project, ): PsiMethod? { - if (mode != CollectVisitor.Mode.COMPLETION) { + if (mode.assumeCorrectAt) { if (!selector.matchMethod(insn.owner, insn.name, insn.desc)) { return null } Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeAssignInjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -105,7 +105,7 @@ val fuzz = args["fuzz"]?.toIntOrNull()?.coerceAtLeast(1) ?: 1 val skip = args["skip"]?.let { parseSkip(it) } ?: Const.DEFAULT_SKIP - if (mode == CollectVisitor.Mode.COMPLETION) { + if (!mode.assumeCorrectAt) { return MyCollectVisitor(mode, at.project, MemberInfo(), fuzz, skip) } return target?.let { MyCollectVisitor(mode, at.project, it, fuzz, skip) } Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/InvokeInjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -64,7 +64,7 @@ targetClass: ClassNode, mode: CollectVisitor.Mode, ): CollectVisitor? { - if (mode == CollectVisitor.Mode.COMPLETION) { + if (!mode.assumeCorrectAt) { return MyCollectVisitor(mode, at.project, MemberInfo()) } return target?.let { MyCollectVisitor(mode, at.project, it) } Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/LoadInjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -53,16 +53,17 @@ import org.objectweb.asm.tree.VarInsnNode abstract class AbstractLoadInjectionPoint(private val store: Boolean) : InjectionPoint() { - private fun getModifyVariableInfo(at: PsiAnnotation, mode: CollectVisitor.Mode?): LocalInfo? { + private fun getModifyVariableInfo(at: PsiAnnotation, mode: CollectVisitor.Mode): LocalInfo? { val modifyVariable = at.parentOfType() ?: return null if (!modifyVariable.hasQualifiedName(MODIFY_VARIABLE)) { return null } val method = modifyVariable.findContainingMethod() ?: return null - val localType = method.parameterList.getParameter(0)?.type - if (localType == null && mode != CollectVisitor.Mode.COMPLETION) { - return null + var localType = method.parameterList.getParameter(0)?.type + when { + !mode.assumeCorrectSignature -> localType = null + localType == null -> return null } return LocalInfo.fromAnnotation(localType, modifyVariable) } @@ -90,7 +91,7 @@ target: MixinSelector?, targetClass: PsiClass, ): NavigationVisitor? { - val info = getModifyVariableInfo(at, null) ?: return null + val info = getModifyVariableInfo(at, CollectVisitor.Mode.RESOLUTION) ?: return null return MyNavigationVisitor(info, store) } Index: src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/injectionPoint/NewInsnInjectionPoint.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -27,9 +27,7 @@ import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.AT import com.demonwav.mcdev.platform.mixin.util.findClassNodeByPsiClass -import com.demonwav.mcdev.platform.mixin.util.findMethod import com.demonwav.mcdev.platform.mixin.util.findMethods -import com.demonwav.mcdev.util.MemberReference import com.demonwav.mcdev.util.Quantifier import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.fullQualifiedName @@ -104,7 +102,7 @@ targetClass: ClassNode, mode: CollectVisitor.Mode, ): CollectVisitor? { - if (mode == CollectVisitor.Mode.COMPLETION) { + if (!mode.assumeCorrectAt) { return MyCollectVisitor(mode, at.project, MemberInfo()) } return getTarget(at, target)?.let { MyCollectVisitor(mode, at.project, it) } Index: src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/MixinExtrasInjectorAnnotationHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,10 +20,10 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler 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.inspection.injector.ExpectedSignatures +import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignatures import com.demonwav.mcdev.platform.mixin.util.FieldTargetMember import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.getGenericParameterTypes @@ -49,7 +49,7 @@ import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.TypeInsnNode -abstract class MixinExtrasInjectorAnnotationHandler : InjectorAnnotationHandler() { +abstract class MixinExtrasInjectorAnnotationHandler : InsnInjectorAnnotationHandler() { open val oldSuperBehavior = false enum class InstructionType { @@ -99,101 +99,25 @@ return supportedInstructionTypes.any { it.matches(TargetInsn(insn, decorations)) } } - abstract fun expectedMethodSignature( + abstract fun expectedMethodSignatureImpl( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, target: TargetInsn, - ): Pair? + ): MethodSignatures? - open fun intLikeTypePositions( - target: TargetInsn - ): List = emptyList() - - override val allowCoerce = true - - override fun expectedMethodSignature( + final override fun expectedMethodSignature( annotation: PsiAnnotation, targetClass: ClassNode, - targetMethod: MethodNode - ): List? { - val insns = resolveInstructions(annotation, targetClass, targetMethod) - .ifEmpty { return emptyList() } - .map { TargetInsn(it.insn, it.decorations) } - val signatures = insns.map { insn -> - expectedMethodSignature(annotation, targetClass, targetMethod, insn) - } - val firstMatch = signatures[0] ?: return emptyList() - if (signatures.drop(1).any { it != firstMatch }) return emptyList() - val intLikeTypePositions = insns.map { intLikeTypePositions(it) }.distinct().singleOrNull().orEmpty() - return allPossibleSignatures( - annotation, - targetClass, - targetMethod, - firstMatch.first, - firstMatch.second, - intLikeTypePositions - ) - } - - private fun allPossibleSignatures( - annotation: PsiAnnotation, - targetClass: ClassNode, targetMethod: MethodNode, - params: ParameterGroup, - returnType: PsiType, - intLikeTypePositions: List - ): List { - if (intLikeTypePositions.isEmpty()) { - return listOf( - makeSignature(annotation, targetClass, targetMethod, params, returnType, intLikeTypePositions) + targetInsn: TargetInsn + ): ExpectedSignatures { + return ExpectedSignatures.Valid( + expectedMethodSignatureImpl(annotation, targetClass, targetMethod, targetInsn) + ?: return ExpectedSignatures.Invalid - ) - } + ) + } - return buildList { - for (actualType in intLikePsiTypes) { - val newParams = params.parameters.toMutableList() - var newReturnType = returnType - for (pos in intLikeTypePositions) { - when (pos) { - is MethodSignature.TypePosition.Return -> newReturnType = actualType - is MethodSignature.TypePosition.Param -> - newParams[pos.index] = newParams[pos.index].copy(type = actualType) - } - } - add( - makeSignature( - annotation, - targetClass, - targetMethod, - ParameterGroup(newParams), - newReturnType, - intLikeTypePositions - ) - ) - } - } - } - private fun makeSignature( - annotation: PsiAnnotation, - targetClass: ClassNode, - targetMethod: MethodNode, - params: ParameterGroup, - returnType: PsiType, - intLikeTypePositions: List - ) = MethodSignature( - listOf( - params, - ParameterGroup( - collectTargetMethodParameters(annotation.project, targetClass, targetMethod), - required = ParameterGroup.RequiredLevel.OPTIONAL, - isVarargs = true, - ), - ), - returnType, - intLikeTypePositions - ) - protected fun getInsnReturnType(insn: AbstractInsnNode): Type? { return when { insn is MethodInsnNode -> Type.getReturnType(insn.desc) @@ -432,7 +356,3 @@ } } } - -private val intLikePsiTypes = listOf( - PsiTypes.intType(), PsiTypes.booleanType(), PsiTypes.charType(), PsiTypes.byteType(), PsiTypes.shortType() -) Index: src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyExpressionValueHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,10 +20,16 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras +import com.demonwav.mcdev.platform.mixin.inspection.injector.GeneralSignatures import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +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.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.Parameter +import com.demonwav.mcdev.util.SequencedSet +import com.demonwav.mcdev.util.emptySequencedSet +import com.demonwav.mcdev.util.sequencedSetOf import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType @@ -56,22 +62,37 @@ override val allowedInsnDescription = "instructions that return a value" - override fun expectedMethodSignature( + override fun expectedMethodSignatureImpl( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, target: TargetInsn - ): Pair? { + ): GeneralSignatures? { val psiType = getReturnType(target, annotation) ?: return null - return ParameterGroup(listOf(Parameter("original", psiType))) to psiType + return GeneralSignatures( + listOf(Parameter("original", psiType)), + psiType, + trailingParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod), + intLikePositions = intLikeTypePositions(target), + ) } - override fun intLikeTypePositions(target: TargetInsn): List { + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.general( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null + ) + } + + private fun intLikeTypePositions(target: TargetInsn): SequencedSet { val expressionType = target.getDecoration(ExpressionDecorations.SIMPLE_EXPRESSION_TYPE) if (expressionType == ExpressionASMUtils.INTLIKE_TYPE) { - return listOf(MethodSignature.TypePosition.Return, MethodSignature.TypePosition.Param(0)) + return sequencedSetOf(MethodSignature.TypePosition.Return, MethodSignature.TypePosition.Param(0)) } - return emptyList() + return emptySequencedSet() } private fun getReturnType( Index: src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReceiverHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,9 +20,11 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +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.ClassAndMethodNode import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode @@ -43,15 +45,29 @@ override val allowedInsnDescription = "non-static method invocations and field references" - override fun expectedMethodSignature( + override fun expectedMethodSignatureImpl( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, target: TargetInsn - ): Pair? { + ): GeneralSignatures? { val params = getPsiParameters(target.insn, targetClass, annotation) ?: return null - return ParameterGroup(params) to params[0].type + return GeneralSignatures( + params, + params[0].type, + trailingParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod), + ) } + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.general( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null + ) + } + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_RECEIVER } Index: src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/ModifyReturnValueHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,11 +20,13 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +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.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.getGenericReturnType import com.demonwav.mcdev.util.Parameter import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiType import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode @@ -34,15 +36,29 @@ override val allowedInsnDescription = "return instructions" - override fun expectedMethodSignature( + override fun expectedMethodSignatureImpl( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, target: TargetInsn - ): Pair { + ): GeneralSignatures { val returnType = targetMethod.getGenericReturnType(targetClass, annotation.project) - return ParameterGroup(listOf(Parameter("original", returnType))) to returnType + return GeneralSignatures( + listOf(Parameter("original", returnType)), + returnType, + trailingParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod), + ) } + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.general( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null + ) + } + override val mixinExtrasExpressionContextType = ExpressionContext.Type.MODIFY_RETURN_VALUE } Index: src/main/kotlin/platform/mixin/handlers/mixinextras/ShareUtil.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/ShareUtil.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/ShareUtil.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -22,7 +22,6 @@ import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler -import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.util.constantStringValue import com.demonwav.mcdev.util.findContainingClass @@ -109,11 +108,7 @@ val id = this.value ?: return emptyList() return mixinTargets.flatMap { targetClass -> - injector.resolveTarget(injectorAnnotation, targetClass).mapNotNull { target -> - if (target !is MethodTargetMember) { - return@mapNotNull null - } - + injector.resolveTarget(injectorAnnotation, targetClass).map { target -> ShareKey( namespace, id, Index: src/main/kotlin/platform/mixin/handlers/mixinextras/WrapMethodHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/WrapMethodHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/WrapMethodHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -21,44 +21,49 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InsnResolutionInfo -import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +import com.demonwav.mcdev.platform.mixin.inspection.injector.ExpectedSignatures +import com.demonwav.mcdev.platform.mixin.inspection.injector.OperationWrapperSignatures +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.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.findSourceElement import com.demonwav.mcdev.platform.mixin.util.getGenericReturnType -import com.demonwav.mcdev.platform.mixin.util.mixinExtrasOperationType -import com.demonwav.mcdev.util.Parameter import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod import com.intellij.psi.search.GlobalSearchScope -import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodNode class WrapMethodHandler : InjectorAnnotationHandler() { - override val allowCoerce get() = true - - override fun expectedMethodSignature( + override fun expectedMethodSignatures( annotation: PsiAnnotation, - targetClass: ClassNode, - targetMethod: MethodNode, - ): List { + targets: List, + mode: CollectVisitor.Mode, + ): List> { + return targets.map { (targetClass, targetMethod) -> - val returnType = targetMethod.getGenericReturnType(targetClass, annotation.project) + val returnType = targetMethod.getGenericReturnType(targetClass, annotation.project) - return listOf( - MethodSignature( - listOf( - ParameterGroup( - collectTargetMethodParameters(annotation.project, targetClass, targetMethod) + - Parameter( - "original", - mixinExtrasOperationType(annotation, returnType) ?: return emptyList() - ), + ExpectedSignatures.Valid( + OperationWrapperSignatures.create( + annotation, + collectTargetMethodParameters(annotation.project, targetClass, targetMethod), + returnType, + ) ?: return@map ExpectedSignatures.Invalid - ) + ) - ), - returnType + } + } + + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.operationWrapper( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null - ) + ) - ) } override fun isUnresolved( @@ -84,5 +89,5 @@ )?.let(::listOf).orEmpty() } - override val mixinExtrasExpressionContextType = ExpressionContext.Type.CUSTOM + override fun canAlwaysBeStatic(method: PsiMethod) = false } Index: src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/WrapOperationHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -21,10 +21,13 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras 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.mixinExtrasOperationType +import com.demonwav.mcdev.platform.mixin.inspection.injector.OperationWrapperSignatures +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.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.toPsiType import com.demonwav.mcdev.util.Parameter +import com.demonwav.mcdev.util.buildSequencedSet import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiType @@ -47,33 +50,46 @@ return if (annotation.hasAttribute("constant")) "constant" else "at" } - override fun expectedMethodSignature( + override fun expectedMethodSignatureImpl( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, target: TargetInsn - ): Pair? { + ): OperationWrapperSignatures? { val params = getParameterTypes(target, targetClass, annotation) ?: return null val returnType = getReturnType(target, annotation) ?: return null - val operationType = mixinExtrasOperationType(annotation, returnType) ?: return null - return ParameterGroup( - params + Parameter("original", operationType) - ) to returnType + return OperationWrapperSignatures.create( + annotation, + params, + returnType, + intLikeTypePositions(target), + trailingParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod), + ) } - override fun intLikeTypePositions(target: TargetInsn) = buildList { - if ( - target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) - == ExpressionASMUtils.INTLIKE_TYPE - ) { - add(MethodSignature.TypePosition.Return) + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.operationWrapper( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null + ) - } + } + + private fun intLikeTypePositions(target: TargetInsn) = buildSequencedSet { target.getDecoration>(ExpressionDecorations.SIMPLE_OPERATION_ARGS)?.forEachIndexed { i, it -> if (it == ExpressionASMUtils.INTLIKE_TYPE) { add(MethodSignature.TypePosition.Param(i)) } } + if ( + target.getDecoration(ExpressionDecorations.SIMPLE_OPERATION_RETURN_TYPE) + == ExpressionASMUtils.INTLIKE_TYPE + ) { + add(MethodSignature.TypePosition.Return) - } + } + } private fun getParameterTypes( target: TargetInsn, Index: src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt =================================================================== --- src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/handlers/mixinextras/WrapWithConditionHandler.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,10 +20,12 @@ package com.demonwav.mcdev.platform.mixin.handlers.mixinextras -import com.demonwav.mcdev.platform.mixin.inspection.injector.ParameterGroup +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.ClassAndMethodNode import com.demonwav.mcdev.platform.mixin.util.nextRealInsn import com.intellij.psi.PsiAnnotation -import com.intellij.psi.PsiType import com.intellij.psi.PsiTypes import com.llamalad7.mixinextras.expression.impl.point.ExpressionContext import org.objectweb.asm.Opcodes @@ -66,15 +68,29 @@ return getPsiParameters(insn, targetClass, annotation)?.size ?: 0 } - override fun expectedMethodSignature( + override fun expectedMethodSignatureImpl( annotation: PsiAnnotation, targetClass: ClassNode, targetMethod: MethodNode, target: TargetInsn - ): Pair? { + ): GeneralSignatures? { val params = getPsiParameters(target.insn, targetClass, annotation) ?: return null - return ParameterGroup(params) to PsiTypes.booleanType() + return GeneralSignatures( + params, + PsiTypes.booleanType(), + trailingParams = collectTargetMethodParameters(annotation.project, targetClass, targetMethod), + ) } + override fun suggestedMethodSignature( + annotation: PsiAnnotation, + targets: List + ): SuggestedSignature? { + return SuggestedSignature.general( + annotation, + expectedMethodSignatures(annotation, targets).collectSignatures() ?: return null + ) + } + override val mixinExtrasExpressionContextType = ExpressionContext.Type.WRAP_WITH_CONDITION } Index: src/main/kotlin/platform/mixin/inspection/injector/CancellableBeforeSuperCallInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/CancellableBeforeSuperCallInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/CancellableBeforeSuperCallInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember @@ -61,7 +61,7 @@ object Util { fun doesInjectBeforeSuperConstructorCall(annotation: PsiAnnotation): Boolean { val handler = MixinAnnotationHandler.forMixinAnnotation(MixinConstants.Annotations.INJECT)!! - as InjectorAnnotationHandler + as InsnInjectorAnnotationHandler for (target in MixinAnnotationHandler.resolveTarget(annotation)) { if (target !is MethodTargetMember) { Index: src/main/kotlin/platform/mixin/inspection/injector/DisallowedTargetInsnInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/DisallowedTargetInsnInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/DisallowedTargetInsnInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,11 +20,10 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection -import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.mixinTargets import com.demonwav.mcdev.util.findContainingClass @@ -37,20 +36,18 @@ override fun buildVisitor(holder: ProblemsHolder) = object : JavaElementVisitor() { override fun visitAnnotation(annotation: PsiAnnotation) { - if (!annotation.hasQualifiedName(MixinConstants.Annotations.AT)) { + if (!annotation.hasQualifiedName(MixinConstants.Annotations.AT) && + !annotation.hasQualifiedName(MixinConstants.Annotations.CONSTANT) + ) { return } val injectorAnnotation = AtResolver.findInjectorAnnotation(annotation, skipThroughSlice = false) ?: return val injector = MixinAnnotationHandler.forMixinAnnotation(injectorAnnotation, annotation.project) - as? InjectorAnnotationHandler ?: return + as? InsnInjectorAnnotationHandler ?: return val containingClass = injectorAnnotation.findContainingClass() ?: return val hasInvalidInstructions = containingClass.mixinTargets.any { targetClass -> injector.resolveTarget(injectorAnnotation, targetClass).any { targetMember -> - if (targetMember !is MethodTargetMember) { - return@any false - } - AtResolver(annotation, targetMember.classAndMethod.clazz, targetMember.classAndMethod.method) .resolveInstructions() .any { Index: src/main/kotlin/platform/mixin/inspection/injector/DiscouragedShiftInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/DiscouragedShiftInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/DiscouragedShiftInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.AtResolver import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.InjectionPoint @@ -41,7 +41,7 @@ return } val injectorAnnotation = AtResolver.findInjectorAnnotation(annotation) ?: return - val injector = MixinAnnotationHandler.forMixinAnnotation(injectorAnnotation, holder.project) as? InjectorAnnotationHandler + val injector = MixinAnnotationHandler.forMixinAnnotation(injectorAnnotation, holder.project) as? InsnInjectorAnnotationHandler ?: return val atValue = annotation.findDeclaredAttributeValue("value") ?: return val atCode = atValue.constantStringValue ?: return @@ -53,7 +53,7 @@ } } - private fun isShiftDiscouraged(shift: Int, at: PsiAnnotation, injector: InjectorAnnotationHandler, atCode: String): Boolean { + private fun isShiftDiscouraged(shift: Int, at: PsiAnnotation, injector: InsnInjectorAnnotationHandler, atCode: String): Boolean { if (injector.isShiftAlwaysDiscouraged) { return shift != 0 } Index: src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/InvalidInjectorMethodSignatureInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -21,20 +21,22 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection -import com.demonwav.mcdev.platform.mixin.reference.MethodReference import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.COERCE +import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Classes.CALLBACK_INFO import com.demonwav.mcdev.platform.mixin.util.findDelegateConstructorCall import com.demonwav.mcdev.platform.mixin.util.hasAccess -import com.demonwav.mcdev.platform.mixin.util.isAssignable import com.demonwav.mcdev.platform.mixin.util.isConstructor import com.demonwav.mcdev.platform.mixin.util.isMixinExtrasSugar -import com.demonwav.mcdev.util.Parameter +import com.demonwav.mcdev.platform.mixin.util.mixinTargets +import com.demonwav.mcdev.util.SequencedSet +import com.demonwav.mcdev.util.findContainingClass import com.demonwav.mcdev.util.findKeyword import com.demonwav.mcdev.util.fullQualifiedName -import com.demonwav.mcdev.util.invokeLater import com.demonwav.mcdev.util.synchronize import com.intellij.codeInsight.FileModificationService import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview @@ -53,30 +55,26 @@ import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.application.runWriteAction -import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.JavaElementVisitor import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClassType +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor -import com.intellij.psi.PsiEllipsisType import com.intellij.psi.PsiFile import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier import com.intellij.psi.PsiNameHelper import com.intellij.psi.PsiParameterList -import com.intellij.psi.PsiPrimitiveType import com.intellij.psi.PsiType -import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.codeStyle.VariableKind import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtil -import com.intellij.psi.util.TypeConversionUtil -import com.intellij.psi.util.parentOfType import com.intellij.psi.util.startOffset +import com.siyeh.ig.psiutils.VariableNameGenerator import org.objectweb.asm.Opcodes class InvalidInjectorMethodSignatureInspection : MixinInspection() { @@ -90,239 +88,137 @@ override fun visitMethod(method: PsiMethod) { val identifier = method.nameIdentifier ?: return val modifiers = method.modifierList + val parameters = method.parameterList - var reportedStatic = false - var reportedSignature = false + val (annotation, handler) = modifiers.annotations.firstNotNullOfOrNull { annotation -> + (MixinAnnotationHandler.forMixinAnnotation(annotation, annotation.project) + as? InjectorAnnotationHandler)?.let { annotation to it } + } ?: return - for (annotation in modifiers.annotations) { - val handler = MixinAnnotationHandler.forMixinAnnotation(annotation, annotation.project) - as? InjectorAnnotationHandler ?: continue - val methodAttribute = annotation.findDeclaredAttributeValue("method") ?: continue - val targetMethods = MethodReference.resolve(methodAttribute) ?: continue + val targetMethods = annotation.findContainingClass()?.mixinTargets?.flatMap { targetClass -> + handler.resolveTarget(annotation, targetClass).map { it.classAndMethod } + } ?: return - val matchesByMethod = targetMethods.associateWith { classAndMethod -> - handler.resolveInstructions( - annotation, - classAndMethod.clazz, - classAndMethod.method - ) + val matchesByMethod = targetMethods.asSequence() + .mapNotNull { classAndMethod -> + if (handler is InsnInjectorAnnotationHandler) { + handler.resolveInstructions(annotation, classAndMethod.clazz, classAndMethod.method) + .takeUnless { it.isEmpty() } + ?.let { classAndMethod to it } + } else { + classAndMethod to emptyList() - } + } - val hasDisallowedInsns = matchesByMethod.values.asSequence() + } + .toMap() + .ifEmpty { return } + + val hasDisallowedInsns = handler is InsnInjectorAnnotationHandler && matchesByMethod.values.asSequence() - .flatten().any { !handler.isInsnAllowed(it.insn, it.decorations) } - if (hasDisallowedInsns) { + .flatten().any { !handler.isInsnAllowed(it.insn, it.decorations) } + if (hasDisallowedInsns) { - continue + return - } + } - for ((targetMethod, matches) in matchesByMethod) { - if (matches.isEmpty()) { - // We will never inject - continue - } - if (!reportedStatic) { + val requiredStaticness = matchesByMethod.asSequence() + .mapNotNull { (targetMethod, matches) -> - var shouldBeStatic = targetMethod.method.hasAccess(Opcodes.ACC_STATIC) + var shouldBeStatic = targetMethod.method.hasAccess(Opcodes.ACC_STATIC) - if (!shouldBeStatic && targetMethod.method.isConstructor) { - // before the superclass constructor call, everything must be static - val methodInsns = targetMethod.method.instructions - val delegateCtorCall = targetMethod.method.findDelegateConstructorCall() - if (methodInsns != null && delegateCtorCall != null) { - shouldBeStatic = matches.any { - methodInsns.indexOf(it.insn) <= methodInsns.indexOf(delegateCtorCall) - } - } - } + if (!shouldBeStatic && targetMethod.method.isConstructor) { + // before the superclass constructor call, everything must be static + val methodInsns = targetMethod.method.instructions + val delegateCtorCall = targetMethod.method.findDelegateConstructorCall() + if (methodInsns != null && delegateCtorCall != null) { + shouldBeStatic = matches.any { + methodInsns.indexOf(it.insn) <= methodInsns.indexOf(delegateCtorCall) + } + } + } - if (shouldBeStatic && !modifiers.hasModifierProperty(PsiModifier.STATIC)) { - reportedStatic = true + when { + shouldBeStatic -> true + handler.canAlwaysBeStatic(method) -> null + else -> false + } + }.toSet() + + when { + requiredStaticness.size == 2 -> holder.registerProblem( + identifier, + "Impossible combination of targets: some require a static handler and others a non-static handler", + ) + + true in requiredStaticness -> if (!method.hasModifierProperty(PsiModifier.STATIC)) { - holder.registerProblem( - identifier, - "Method must be static", - QuickFixFactory.getInstance().createModifierListFix( - modifiers, - PsiModifier.STATIC, - true, - false, - ), - ) + holder.registerProblem( + identifier, + "Method must be static", + QuickFixFactory.getInstance().createModifierListFix( + modifiers, + PsiModifier.STATIC, + true, + false, + ), + ) - } else if (!shouldBeStatic && modifiers.hasModifierProperty(PsiModifier.STATIC)) { - if (!handler.canAlwaysBeStatic(method)) { - reportedStatic = true + } + + false in requiredStaticness -> if (method.hasModifierProperty(PsiModifier.STATIC)) { - holder.registerProblem( - modifiers.findKeyword(PsiModifier.STATIC) ?: identifier, - "Method must not be static", - QuickFixFactory.getInstance().createModifierListFix( - modifiers, - PsiModifier.STATIC, - false, - false, - ), - ) - } - } + holder.registerProblem( + modifiers.findKeyword(PsiModifier.STATIC) ?: identifier, + "Method must not be static", + QuickFixFactory.getInstance().createModifierListFix( + modifiers, + PsiModifier.STATIC, + false, + false, + ), + ) + } + } - } - if (!reportedSignature) { - // Check method parameters - val parameters = method.parameterList - val possibleSignatures = handler.expectedMethodSignature( + val isAlreadyValid = handler.expectedMethodSignatures( - annotation, + annotation, - targetMethod.clazz, - targetMethod.method, - ) ?: continue + targetMethods, + CollectVisitor.Mode.RESOLUTION, + ).all { it.matches(method) } - val annotationName = annotation.nameReferenceElement?.referenceName - - if (possibleSignatures.isEmpty()) { - reportedSignature = true - if (handler.isUnresolved(annotation) != null) { - holder.registerProblem( - parameters, - "There are no possible signatures for this injector", - ) + if (isAlreadyValid) { + return - } + } - continue - } - var isValid = false - for ((expectedParameters, expectedReturnType) in possibleSignatures) { - val paramsMatch = - Util.checkParameters(parameters, expectedParameters, handler.allowCoerce) == CheckResult.OK - if (paramsMatch) { - val methodReturnType = method.returnType - if (methodReturnType != null && - checkReturnType(expectedReturnType, methodReturnType, method, handler.allowCoerce) - ) { - isValid = true - break - } - } - } + val signatureOptions = + handler.expectedMethodSignatures(annotation, targetMethods).collectSignatures() + val signatureSuggestion = signatureOptions?.let { SuggestedReturnType.forParams(parameters, it) } + ?: handler.suggestedMethodSignature(annotation, targetMethods) - if (!isValid) { - val (expectedParameters, expectedReturnType, intLikeTypePositions) = possibleSignatures[0] - val normalizedReturnType = when (expectedReturnType) { - is PsiEllipsisType -> expectedReturnType.toArrayType() - else -> expectedReturnType - } + val declarationStart = (method.returnTypeElement ?: identifier).startOffsetInParent + val declarationEnd = method.parameterList.textRangeInParent.endOffset - val paramsCheck = Util.checkParameters(parameters, expectedParameters, handler.allowCoerce) - val isWarning = paramsCheck == CheckResult.WARNING - val methodReturnType = method.returnType - val returnTypeOk = methodReturnType != null && - checkReturnType(normalizedReturnType, methodReturnType, method, handler.allowCoerce) - val isError = paramsCheck == CheckResult.ERROR || !returnTypeOk - if (isWarning || isError) { - reportedSignature = true - - val description = - "Method signature does not match expected signature for $annotationName" - val quickFix = SignatureQuickFix( + if (signatureSuggestion == null) { + holder.registerProblem( - method, + method, - expectedParameters.takeUnless { paramsCheck == CheckResult.OK }, - normalizedReturnType.takeUnless { returnTypeOk }, - intLikeTypePositions + "There are no possible signatures for this injector", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + TextRange.create(declarationStart, declarationEnd), - ) + ) - val highlightType = - if (isError) - ProblemHighlightType.GENERIC_ERROR_OR_WARNING - else - ProblemHighlightType.WARNING - val declarationStart = (method.returnTypeElement ?: identifier).startOffsetInParent - val declarationEnd = method.parameterList.textRangeInParent.endOffset + } else { + val annotationName = annotation.nameReferenceElement?.referenceName + val description = + "Method signature does not match expected signature for $annotationName" + val quickFix = SignatureQuickFix(method, signatureSuggestion) + - holder.registerProblem( - method, - description, + holder.registerProblem( + method, + description, - highlightType, + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - TextRange.create(declarationStart, declarationEnd), + TextRange.create(declarationStart, declarationEnd), - quickFix + quickFix, - ) - } - } - } + ) + } + } + } - } - } - } - private fun checkReturnType( - expectedReturnType: PsiType, - methodReturnType: PsiType, - method: PsiMethod, - allowCoerce: Boolean, - ): Boolean { - val expectedErasure = TypeConversionUtil.erasure(expectedReturnType) - val returnErasure = TypeConversionUtil.erasure(methodReturnType) - if (expectedErasure == returnErasure) { - return true - } - if (!allowCoerce || !method.hasAnnotation(COERCE)) { - return false - } - if (expectedReturnType is PsiPrimitiveType || methodReturnType is PsiPrimitiveType) { - return false - } - return isAssignable(methodReturnType, expectedReturnType) - } - } - - object Util { - fun checkParameters( - parameterList: PsiParameterList, - expected: List, - allowCoerce: Boolean, - ): CheckResult { - val parameters = parameterList.parameters - val parametersWithoutSugar = parameters.dropLastWhile { it.isMixinExtrasSugar }.toTypedArray() - var pos = 0 - - for (group in expected) { - // Check if parameter group matches - if (group.match(parametersWithoutSugar, pos, allowCoerce)) { - pos += group.size - } else if (group.required != ParameterGroup.RequiredLevel.OPTIONAL) { - return if (group.required == ParameterGroup.RequiredLevel.ERROR_IF_ABSENT) { - CheckResult.ERROR - } else { - CheckResult.WARNING - } - } - } - - // Sugars are valid on any injector and should be ignored, as long as they're at the end. - while (pos < parameters.size) { - if (parameters[pos].isMixinExtrasSugar) { - pos++ - } else { - break - } - } - - // check we have consumed all the parameters - if (pos < parameters.size) { - return if ( - expected.lastOrNull()?.isVarargs == true && - expected.last().required == ParameterGroup.RequiredLevel.WARN_IF_ABSENT - ) { - CheckResult.WARNING - } else { - CheckResult.ERROR - } - } - - return CheckResult.OK - } - } - - enum class CheckResult { - OK, WARNING, ERROR - } - private class SignatureQuickFix( method: PsiMethod, @SafeFieldForPreview - private val expectedParams: List?, - @SafeFieldForPreview - private val expectedReturnType: PsiType?, - private val intLikeTypePositions: List + private val signatureSuggestion: SignatureSuggestion, ) : LocalQuickFixAndIntentionActionOnPsiElement(method) { private val fixName = "Fix method signature" @@ -346,7 +242,8 @@ val method = startElement as PsiMethod fixParameters(project, method.parameterList, false) fixReturnType(method, editor ?: return, file, false) - fixIntLikeTypes(method, editor, false) + fixCoerce(project, method, false) + fixIntLikeTypes(project, method, editor, false) } override fun generatePreview(project: Project, editor: Editor, file: PsiFile): IntentionPreviewInfo { @@ -355,14 +252,13 @@ fixParameters(project, method.parameterList, true) // Pass the original startElement because the underlying fix gets the preview element itself fixReturnType(startElement as PsiMethod, editor, file, true) - fixIntLikeTypes(method, editor, true) + fixCoerce(project, method, true) + fixIntLikeTypes(project, method, editor, true) return IntentionPreviewInfo.DIFF } private fun fixParameters(project: Project, parameters: PsiParameterList, preview: Boolean) { - if (expectedParams == null) { - return - } + val suggestedParams = signatureSuggestion.params ?: return // We want to preserve captured locals val locals = parameters.parameters.dropWhile { val fqname = (it.type as? PsiClassType)?.fullQualifiedName ?: return@dropWhile true @@ -374,22 +270,24 @@ // We want to preserve sugars, and while we're at it, we might as well move them all to the end val sugars = parameters.parameters.filter { it.isMixinExtrasSugar } - val newParams = expectedParams.flatMapTo(mutableListOf()) { - if (it.default) { - val nameHelper = PsiNameHelper.getInstance(project) - val languageLevel = PsiUtil.getLanguageLevel(parameters) + val nameHelper = PsiNameHelper.getInstance(project) + val languageLevel = PsiUtil.getLanguageLevel(parameters) - it.parameters.mapIndexed { i: Int, p: Parameter -> + + val usedNames = mutableSetOf() + val newParams = suggestedParams.mapTo(mutableListOf()) { p -> - val paramName = p.name?.takeIf { name -> nameHelper.isIdentifier(name, languageLevel) } + val paramName = p.name?.takeIf { name -> nameHelper.isIdentifier(name, languageLevel) } - ?: JavaCodeStyleManager.getInstance(project) - .suggestVariableName(VariableKind.PARAMETER, null, null, p.type).names - .firstOrNull() - ?: "var$i" - JavaPsiFacade.getElementFactory(project).createParameter(paramName, p.type) + ?: suggestedParamNames(p.type).firstOrNull { it !in usedNames } + ?: VariableNameGenerator(parameters, VariableKind.PARAMETER) + .byType(p.type) + .skipNames(usedNames) + .generate(false) + usedNames.add(paramName) + val newParam = JavaPsiFacade.getElementFactory(project).createParameter(paramName, p.type) + if (p.coerce) { + newParam.modifierList!!.addAnnotation(COERCE) - } + } - } else { - emptyList() + newParam - } + } - } // Restore the captured locals and sugars before applying the fix newParams.addAll(locals) newParams.addAll(sugars) @@ -403,10 +301,7 @@ } private fun fixReturnType(method: PsiMethod, editor: Editor, file: PsiFile, preview: Boolean) { - if (expectedReturnType == null) { - return - } - val fix = QuickFixFactory.getInstance().createMethodReturnFix(method, expectedReturnType, false) + val fix = QuickFixFactory.getInstance().createMethodReturnFix(method, signatureSuggestion.returnType, false) if (preview) { fix.generatePreview(file.project, editor, file) } else { @@ -414,37 +309,51 @@ } } - private fun fixIntLikeTypes(method: PsiMethod, editor: Editor, preview: Boolean) { - if (intLikeTypePositions.isEmpty()) { - return + private fun fixCoerce(project: Project, method: PsiMethod, preview: Boolean) { + val existingCoerce = method.modifierList.findAnnotation(COERCE) + val needsCoerce = signatureSuggestion.coerceReturnType + val returnTypeElement = method.returnTypeElement!! + + val fixCoerce: () -> Unit = when { + existingCoerce != null && !needsCoerce -> { + { existingCoerce.delete() } - } + } - val runnable = { - val template = makeIntLikeTypeTemplate(method, intLikeTypePositions) - if (template != null) { - editor.caretModel.moveToOffset(method.startOffset) - TemplateManager.getInstance(method.project) - .startTemplate(editor, template) + + existingCoerce == null && needsCoerce -> { + val annotation = JavaPsiFacade.getElementFactory(project) + .createAnnotationFromText("@$COERCE", returnTypeElement); + { method.modifierList.add(annotation) } } + + else -> return } if (preview) { - runnable() + fixCoerce() } else { - invokeLater { - WriteCommandAction.runWriteCommandAction( - method.project, - "Choose Int-Like Type", - null, - runnable, - method.parentOfType()!! - ) + runWriteAction(fixCoerce) - } - } + } + } + + private fun fixIntLikeTypes(project: Project, method: PsiMethod, editor: Editor, preview: Boolean) { + if (preview || signatureSuggestion.intLikeTypes.isEmpty()) { + return - } + } + runWriteAction { + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) + val template = makeIntLikeTypeTemplate(method, signatureSuggestion.intLikeTypes) + if (template != null) { + editor.caretModel.moveToOffset(method.startOffset) + TemplateManager.getInstance(method.project) + .startTemplate(editor, template) + } + } + } + private fun makeIntLikeTypeTemplate( method: PsiMethod, - positions: List + positions: SequencedSet ): Template? { val builder = TemplateBuilderImpl(method) builder.replaceElement( @@ -462,8 +371,15 @@ } return builder.buildInlineTemplate() } + + private companion object { + fun suggestedParamNames(type: PsiType): List = when { + type.equalsToText(CALLBACK_INFO) -> listOf("ci") + else -> emptyList() - } -} + } + } + } +} private class ChooseIntLikeTypeExpression : Expression() { private val lookupItems: Array = intLikeTypes.map(LookupElementBuilder::create).toTypedArray() Index: src/main/kotlin/platform/mixin/inspection/injector/MethodSignature.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/MethodSignature.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/MethodSignature.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -20,24 +20,178 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector +import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.COERCE +import com.demonwav.mcdev.platform.mixin.util.checkCoerce +import com.demonwav.mcdev.platform.mixin.util.isMixinExtrasSugar +import com.demonwav.mcdev.util.Parameter +import com.demonwav.mcdev.util.SequencedSet +import com.demonwav.mcdev.util.allEqual +import com.demonwav.mcdev.util.emptySequencedSet import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiParameter +import com.intellij.psi.PsiParameterList import com.intellij.psi.PsiType import com.intellij.psi.PsiTypeElement +import com.intellij.psi.PsiTypes data class MethodSignature( - val parameters: List, + val requiredParams: List, val returnType: PsiType, - val intLikeTypes: List = emptyList() + val allowCoerceRequired: Boolean, + val trailingParams: List = emptyList(), + val intLikePositions: SequencedSet = emptySequencedSet(), ) { sealed interface TypePosition { fun getElement(method: PsiMethod): PsiTypeElement? + fun getType(signature: MethodSignature): PsiType + + fun getParam(params: Array): PsiParameter? + data object Return : TypePosition { override fun getElement(method: PsiMethod) = method.returnTypeElement + + override fun getType(signature: MethodSignature) = signature.returnType + + override fun getParam(params: Array) = null } data class Param(val index: Int) : TypePosition { override fun getElement(method: PsiMethod) = method.parameterList.parameters[index].typeElement + + override fun getType(signature: MethodSignature) = signature.requiredParams[index].type + + override fun getParam(params: Array) = params[index] } } + + fun allPositions(numParams: Int): Sequence { + require(numParams >= requiredParams.size) + return sequenceOf(TypePosition.Return) + (0.. matches( + params: List, + returnType: PsiType, + returnCoerce: Boolean, + paramType: (ParamT) -> PsiType, + paramCoerce: (ParamT) -> Boolean, + ): Boolean { + val intLikeAssignment = when (val anchor = intLikePositions.firstOrNull()) { + null -> null + is TypePosition.Param -> paramType(params.getOrNull(anchor.index) ?: return false) + TypePosition.Return -> returnType + } + val transformedReturnType = if (TypePosition.Return in intLikePositions) intLikeAssignment!! else returnType + return matchesReturnType(transformedReturnType, returnCoerce) + && matchesParams( + params, + paramType, + paramCoerce, + knownIntLikeAssignment = intLikeAssignment, + ) + } + + private fun matchesParams( + params: List, + paramType: (ParamT) -> PsiType, + paramCoerce: (ParamT) -> Boolean, + knownIntLikeAssignment: PsiType?, + ): Boolean { + if (params.size !in requiredParams.size..requiredParams.size + trailingParams.size) { + return false + } + + val intLikeAssignment = knownIntLikeAssignment + ?: (intLikePositions.firstOrNull() as? TypePosition.Param)?.let { paramType(params[it.index]) } + + if (intLikeAssignment == null && intLikePositions.isNotEmpty()) { + // We don't know the return type, but we should make sure the combination is feasible for some return type + val intLikeIndices = intLikePositions.mapNotNull { (it as? TypePosition.Param)?.index } + val isFeasible = intLikeIndices.asSequence().map { paramType(params[it]) }.allEqual() + || intLikeIndices.all { index -> + val param = params[index] + checkCoerce( + PsiTypes.intType(), + paramType(param), + coerce = paramCoerce(param), + expectedIntLike = false, + ) + } + if (!isFeasible) { + return false + } + } + + fun matchParams(expected: List, allowCoerce: Boolean, startIndex: Int): Boolean { + return expected.asSequence() + .zip(params.asSequence().withIndex().drop(startIndex)) + .all { (expected, indexAndActual) -> + val (index, actual) = indexAndActual + matchType( + expected.type, + paramType(actual), + isIntLike = intLikeAssignment == null && TypePosition.Param(index) in intLikePositions, + coerce = allowCoerce && paramCoerce(actual), + ) + } + } + + val transformedRequiredParams = if (intLikeAssignment == null) { + requiredParams + } else { + requiredParams.mapIndexed { i, param -> + if (TypePosition.Param(i) in intLikePositions) { + param.copy(type = intLikeAssignment) + } else { + param + } + } + } + + return matchParams(transformedRequiredParams, allowCoerceRequired, 0) + && matchParams(trailingParams, true, requiredParams.size) + } + + private companion object { + private fun matchType(expected: PsiType, actual: PsiType, isIntLike: Boolean, coerce: Boolean) = + checkCoerce(expected, actual, coerce, isIntLike) + } +} Index: src/main/kotlin/platform/mixin/inspection/injector/MethodSignatures.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/MethodSignatures.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/platform/mixin/inspection/injector/MethodSignatures.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,205 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.platform.mixin.inspection.injector + +import com.demonwav.mcdev.platform.mixin.util.TypeKind +import com.demonwav.mcdev.platform.mixin.util.callbackInfoReturnableType +import com.demonwav.mcdev.platform.mixin.util.callbackInfoType +import com.demonwav.mcdev.platform.mixin.util.mixinExtrasOperationType +import com.demonwav.mcdev.util.Parameter +import com.demonwav.mcdev.util.SequencedMap +import com.demonwav.mcdev.util.SequencedSet +import com.demonwav.mcdev.util.emptySequencedSet +import com.demonwav.mcdev.util.minus +import com.demonwav.mcdev.util.sequencedMapOf +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypes +import org.objectweb.asm.Type + +sealed interface ExpectedSignatures { + fun matches(method: PsiMethod): Boolean + + data object Unknown : ExpectedSignatures { + override fun matches(method: PsiMethod) = true + } + + data object Invalid : ExpectedSignatures { + override fun matches(method: PsiMethod) = false + } + + data class Valid(val expected: T) : ExpectedSignatures { + override fun matches(method: PsiMethod) = expected.options.any { it.matches(method) } + } +} + +inline fun List>.collectSignatures(): List? = + mapNotNull { + when (it) { + ExpectedSignatures.Unknown -> null + ExpectedSignatures.Invalid -> return null + is ExpectedSignatures.Valid -> it.expected as R + } + } + +interface MethodSignatures { + val options: List +} + +class ModifierSignatures( + val paramOptions: SequencedMap, + val fullParams: List? = null, + val trailingParams: List = emptyList(), +) : MethodSignatures { + override val options = paramOptions.values.flatMap { param -> + listOfNotNull( + MethodSignature( + listOf(param), + param.type, + allowCoerceRequired = false, + trailingParams = trailingParams, + ), + fullParams?.let { + MethodSignature( + it, + param.type, + allowCoerceRequired = false, + trailingParams = trailingParams, + ) + }, + ) + } +} + +class BasicSignatures(override val options: List) : MethodSignatures { + constructor(vararg options: MethodSignature) : this(options.asList()) +} + +class OperationWrapperSignatures private constructor( + val params: List, + val returnType: PsiType, + val intLikePositions: SequencedSet, + val trailingParams: List, + operationType: PsiType, +) : MethodSignatures { + val signature = MethodSignature( + params + Parameter("original", operationType), + returnType, + allowCoerceRequired = true, + trailingParams = trailingParams, + intLikePositions = intLikePositions, + ) + + override val options = listOf(signature) + + companion object { + fun create( + context: PsiElement, + params: List, + returnType: PsiType, + intLikePositions: SequencedSet = emptySequencedSet(), + trailingParams: List = emptyList(), + ) = mixinExtrasOperationType(context, returnType)?.let { + OperationWrapperSignatures(params, returnType, intLikePositions, trailingParams, it) + } + } +} + +data class GeneralSignatures( + val params: List, + val returnTypeOptions: SequencedMap, + val allowCoerce: Boolean, + val trailingParams: List, + val intLikePositions: SequencedSet = emptySequencedSet(), +) : MethodSignatures { + constructor( + params: List, + returnType: PsiType, + trailingParams: List, + allowCoerce: Boolean = true, + intLikePositions: SequencedSet = emptySequencedSet(), + ) : this( + params, + sequencedMapOf(TypeKind.of(returnType) to returnType), + allowCoerce, + trailingParams, + intLikePositions, + ) + + override val options = returnTypeOptions.keys.map(::specificSignature) + + fun specificSignature(returnKind: TypeKind) = MethodSignature( + params, + returnTypeOptions.getValue(returnKind), + allowCoerceRequired = allowCoerce, + trailingParams = trailingParams, + intLikePositions = if (returnKind == TypeKind.INT_LIKE) { + intLikePositions + } else { + intLikePositions - MethodSignature.TypePosition.Return + }, + ) +} + +class InjectSignatures private constructor( + val params: List, + val locals: List, + ciParam: Parameter, +) : MethodSignatures { + val shortSignature = if (params.isNotEmpty()) { + MethodSignature( + listOf(ciParam), + PsiTypes.voidType(), + allowCoerceRequired = true, + ) + } else { + null + } + + val longSignature = MethodSignature( + params + ciParam, + PsiTypes.voidType(), + allowCoerceRequired = true, + trailingParams = locals, + ) + + override val options = listOfNotNull(shortSignature, longSignature) + + companion object { + fun create( + context: PsiElement, + params: List, + returnType: PsiType, + locals: List + ): InjectSignatures? { + val ciParam = if (returnType == PsiTypes.voidType()) { + Parameter("ci", callbackInfoType(context.project)) + } else { + Parameter( + "cir", + callbackInfoReturnableType(context.project, context, returnType) ?: return null, + ) + } + return InjectSignatures(params, locals, ciParam) + } + } +} Index: src/main/kotlin/platform/mixin/inspection/injector/MixinParameterNameInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/MixinParameterNameInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/MixinParameterNameInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection @@ -30,7 +31,9 @@ import com.demonwav.mcdev.platform.mixin.util.MixinTargetMember import com.demonwav.mcdev.platform.mixin.util.hasNamedLocalVariables import com.demonwav.mcdev.platform.mixin.util.isMixinExtrasSugar +import com.demonwav.mcdev.util.Parameter import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.ifNullOrEmpty import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemDescriptor @@ -63,7 +66,7 @@ override fun visitMethod(method: PsiMethod) { val module = method.findModule() ?: return val parameters = method.parameterList.parameters - val parametersWithoutSugar = parameters.dropLastWhile { it.isMixinExtrasSugar }.toTypedArray() + val parametersWithoutSugar = parameters.dropLastWhile { it.isMixinExtrasSugar } val validNames = arrayOfNulls>(parameters.size) @@ -78,10 +81,7 @@ } for (index in validNames.indices) { - val names = validNames[index] ?: continue - if (names.isEmpty()) { - continue - } + val names = validNames[index].ifNullOrEmpty { continue } if (parameters[index].name !in names) { val fixes = mutableListOf() @@ -110,7 +110,7 @@ validNames: Array?>, module: Module, method: PsiMethod, parameters: Array, - parametersWithoutSugar: Array, + parametersWithoutSugar: List, annotation: PsiAnnotation, handler: InjectorAnnotationHandler, target: MixinTargetMember @@ -123,43 +123,51 @@ return true } - val validNamesForThisTarget = arrayOfNulls>(parameters.size) - if (reportForMainSignature) { - val expectedSignatures = - handler.expectedMethodSignature(annotation, target.classAndMethod.clazz, target.classAndMethod.method) - ?: return false + val expectedSignatures = handler.expectedMethodSignatures(annotation, listOf(target.classAndMethod)) + + for (expected in expectedSignatures) { + val signatures = when (expected) { + ExpectedSignatures.Unknown -> continue + ExpectedSignatures.Invalid -> return false + is ExpectedSignatures.Valid -> expected.expected + } + val validNamesForThisTarget = Array?>(parameters.size) { mutableSetOf() } - var anyValidSignatures = false + var anyValidSignatures = false - for ((expectedParams, _) in expectedSignatures) { - if (InvalidInjectorMethodSignatureInspection.Util.checkParameters( - method.parameterList, - expectedParams, - handler.allowCoerce - ) != InvalidInjectorMethodSignatureInspection.CheckResult.OK - ) { + for (expectedSignature in signatures.options) { + if (!expectedSignature.matches(method)) { - continue - } + continue + } - - anyValidSignatures = true + anyValidSignatures = true - checkExpectedSignatureForKnownNames( - validNamesForThisTarget, + checkExpectedSignatureForKnownNames( + validNamesForThisTarget, - handler, - expectedParams, - parametersWithoutSugar + expectedSignature.requiredParams + expectedSignature.trailingParams, + parametersWithoutSugar, - ) - } + ) + } - if (!anyValidSignatures) { - return false - } + if (!anyValidSignatures) { + return false + } + + for ((pos, names) in validNamesForThisTarget.withIndex()) { + val suggestions = names ?: mutableSetOf() + val existingNames = validNames[pos] + if (existingNames == null) { + validNames[pos] = suggestions + } else { + existingNames.retainAll(suggestions) - } + } + } + } + } if (reportForLocal) { for (pos in parametersWithoutSugar.size until parameters.size) { if (!checkSugarForKnownNames( - validNamesForThisTarget, + validNames, module, handler, annotation, @@ -173,51 +181,30 @@ } } - for (index in validNamesForThisTarget.indices) { - val names = validNamesForThisTarget[index] ?: continue - if (validNames[index] == null) { - validNames[index] = names - } else { - validNames[index]!!.retainAll(names) - } - } - return true } private fun checkExpectedSignatureForKnownNames( validNamesForThisTarget: Array?>, - handler: InjectorAnnotationHandler, - expectedParams: List, - parametersWithoutSugar: Array + expectedParams: List, + parametersWithoutSugar: List ) { if (parametersWithoutSugar.isEmpty()) { return } - var pos = 0 - for (group in expectedParams) { - if (!group.match(parametersWithoutSugar, pos, handler.allowCoerce)) { - continue - } - for (expectedParam in group.parameters) { - if (expectedParam.knownName && expectedParam.name != null) { - if (validNamesForThisTarget[pos] == null) { - validNamesForThisTarget[pos] = mutableSetOf(expectedParam.name) + for (pos in parametersWithoutSugar.indices) { + val expectedParam = expectedParams[pos] + if (!expectedParam.knownName || expectedParam.name == null) { + validNamesForThisTarget[pos] = null - } else { + } else { - validNamesForThisTarget[pos]!!.add(expectedParam.name) + validNamesForThisTarget[pos]?.add(expectedParam.name) - } - } + } + } - pos++ - if (pos >= parametersWithoutSugar.size) { - return - } + } - } - } - } private fun checkSugarForKnownNames( - validNamesForThisTarget: Array?>, + validNames: Array?>, module: Module, handler: InjectorAnnotationHandler, annotation: PsiAnnotation, @@ -225,6 +212,10 @@ parameter: PsiParameter, pos: Int ): Boolean { + if (handler !is InsnInjectorAnnotationHandler) { + // Can't have locals + return true + } val localAnnotation = parameter.getAnnotation(MixinConstants.MixinExtras.LOCAL) ?: return true val localInfo = LocalInfo.fromAnnotation(parameter.type, localAnnotation) @@ -239,14 +230,15 @@ target.classAndMethod.method, insn.insn, CollectVisitor.Mode.RESOLUTION )?.singleOrNull() ?: return false - if (matchedLocal.isNamed) { - if (validNamesForThisTarget[pos] == null) { - validNamesForThisTarget[pos] = mutableSetOf(matchedLocal.name) + val suggestions = if (matchedLocal.isNamed) mutableSetOf(matchedLocal.name) else mutableSetOf() + + val existingNames = validNames[pos] + if (existingNames == null) { + validNames[pos] = suggestions - } else { + } else { - validNamesForThisTarget[pos]!!.add(matchedLocal.name) + existingNames.retainAll(suggestions) - } - } + } + } - } return true } Index: src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableArgsOnlyInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableArgsOnlyInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableArgsOnlyInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection @@ -51,7 +51,7 @@ val problemElement = modifyVariable.nameReferenceElement ?: return val injector = - MixinAnnotationHandler.forMixinAnnotation(MODIFY_VARIABLE) as? InjectorAnnotationHandler ?: return + MixinAnnotationHandler.forMixinAnnotation(MODIFY_VARIABLE) as? InsnInjectorAnnotationHandler ?: return val localInfo = LocalInfo.fromAnnotation(localType, modifyVariable) if (Util.shouldReport(localInfo, injector, modifyVariable)) { @@ -69,7 +69,7 @@ object Util { fun shouldReport( localInfo: LocalInfo, - injector: InjectorAnnotationHandler, + injector: InsnInjectorAnnotationHandler, injectorAnnotation: PsiAnnotation, ): Boolean { if (localInfo.argsOnly) { Index: src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableMayUseNameInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableMayUseNameInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/ModifyVariableMayUseNameInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,13 +20,12 @@ package com.demonwav.mcdev.platform.mixin.inspection.injector -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix import com.demonwav.mcdev.platform.mixin.util.LocalInfo -import com.demonwav.mcdev.platform.mixin.util.MethodTargetMember import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.MODIFY_VARIABLE import com.demonwav.mcdev.platform.mixin.util.hasNamedLocalVariables import com.demonwav.mcdev.platform.mixin.util.mixinTargets @@ -62,7 +61,7 @@ val problemElement = modifyVariable.nameReferenceElement ?: return val injector = - MixinAnnotationHandler.forMixinAnnotation(MODIFY_VARIABLE) as? InjectorAnnotationHandler ?: return + MixinAnnotationHandler.forMixinAnnotation(MODIFY_VARIABLE) as? InsnInjectorAnnotationHandler ?: return val localInfo = LocalInfo.fromAnnotation(localType, modifyVariable) if (ignoreForImplicitLocals && localInfo.isImplicit) { @@ -106,7 +105,7 @@ object Util { fun getVariableNameToIntroduce( localInfo: LocalInfo, - injector: InjectorAnnotationHandler, + injector: InsnInjectorAnnotationHandler, injectorAnnotation: PsiAnnotation, ): String? { if (localInfo.index == null && localInfo.ordinal == null && localInfo.names.isNotEmpty()) { @@ -124,7 +123,7 @@ } for (target in injector.resolveTarget(injectorAnnotation, targetClass)) { - val (clazz, method) = (target as? MethodTargetMember)?.classAndMethod ?: continue + val (clazz, method) = target.classAndMethod for (insn in injector.resolveInstructions(injectorAnnotation, clazz, method)) { val matchedLocals = localInfo.matchLocals( module, Index: src/main/kotlin/platform/mixin/inspection/injector/ParameterGroup.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/ParameterGroup.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/injector/ParameterGroup.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,96 +0,0 @@ -/* - * 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.inspection.injector - -import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.COERCE -import com.demonwav.mcdev.platform.mixin.util.isAssignable -import com.demonwav.mcdev.util.Parameter -import com.demonwav.mcdev.util.normalize -import com.intellij.psi.PsiParameter -import com.intellij.psi.PsiPrimitiveType -import com.intellij.psi.PsiType -import com.intellij.psi.PsiTypes - -data class ParameterGroup( - val parameters: List, - val required: RequiredLevel = RequiredLevel.ERROR_IF_ABSENT, - val default: Boolean = required != RequiredLevel.OPTIONAL, - val isVarargs: Boolean = false, -) { - - val size - get() = this.parameters.size - - fun match(parameters: Array, currentPosition: Int, allowCoerce: Boolean): Boolean { - // Check if remaining parameter count is enough - if (!isVarargs && currentPosition + size > parameters.size) { - return false - } - - var pos = currentPosition - - // Check parameter types - for ((_, expectedType) in this.parameters) { - if (isVarargs && pos == parameters.size) { - break - } - val parameter = parameters[pos++] - if (!matchParameter(expectedType, parameter, allowCoerce)) { - return false - } - } - - return !isVarargs || pos == parameters.size - } - - enum class RequiredLevel { - OPTIONAL, WARN_IF_ABSENT, ERROR_IF_ABSENT - } - - companion object { - private val INT_TYPES = setOf( - PsiTypes.intType(), - PsiTypes.shortType(), - PsiTypes.charType(), - PsiTypes.byteType(), - PsiTypes.booleanType() - ) - - private fun matchParameter(expectedType: PsiType, parameter: PsiParameter, allowCoerce: Boolean): Boolean { - val normalizedExpected = expectedType.normalize() - val normalizedParameter = parameter.type.normalize() - if (normalizedExpected == normalizedParameter) { - return true - } - if (!allowCoerce || !parameter.hasAnnotation(COERCE)) { - return false - } - - if (normalizedExpected is PsiPrimitiveType) { - if (normalizedParameter !is PsiPrimitiveType) { - return false - } - return normalizedExpected in INT_TYPES && normalizedParameter in INT_TYPES - } - return isAssignable(normalizedParameter, normalizedExpected) - } - } -} Index: src/main/kotlin/platform/mixin/inspection/injector/SuggestedSignature.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/injector/SuggestedSignature.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/platform/mixin/inspection/injector/SuggestedSignature.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,419 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.platform.mixin.inspection.injector + +import com.demonwav.mcdev.platform.mixin.util.IntLikeAnchorSolver +import com.demonwav.mcdev.platform.mixin.util.ReturnTypeSolver +import com.demonwav.mcdev.platform.mixin.util.TypeKind +import com.demonwav.mcdev.util.SequencedSet +import com.demonwav.mcdev.util.descriptor +import com.demonwav.mcdev.util.emptySequencedSet +import com.demonwav.mcdev.util.normalize +import com.demonwav.mcdev.util.reduceFallible +import com.demonwav.mcdev.util.sequencedSetOf +import com.demonwav.mcdev.util.singleDistinct +import com.demonwav.mcdev.util.singleDistinctOrNull +import com.intellij.psi.GenericsUtil +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiParameterList +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypes +import com.intellij.psi.util.parentOfType +import org.objectweb.asm.Type + +sealed interface SignatureSuggestion { + val params: List? + val returnType: PsiType + val intLikeTypes: SequencedSet + val coerceReturnType: Boolean + + data class Param(val name: String?, val type: PsiType, val coerce: Boolean = false) +} + +data class SuggestedReturnType( + override val returnType: PsiType, + val returnTypeIsIntLike: Boolean = false, + override val coerceReturnType: Boolean = false, +) : SignatureSuggestion { + override val intLikeTypes = + if (returnTypeIsIntLike) sequencedSetOf(MethodSignature.TypePosition.Return) else emptySequencedSet() + override val params get() = null + + companion object { + /** + * Returns a valid suggested return type iff one is possible while making no changes to the existing parameters. + */ + fun forParams(params: PsiParameterList, signatures: List): SuggestedReturnType? { + val validSignatures = signatures.map { expected -> + expected.options.filter { it.matchesParams(params) }.ifEmpty { return null } + } + + val solver = ReturnTypeSolver(params) + + for (expected in validSignatures) { + solver.addExpected(expected) + } + + return solver.solve() + } + } +} + +data class SuggestedSignature( + override val params: List, + override val returnType: PsiType, + override val intLikeTypes: SequencedSet = emptySequencedSet(), + override val coerceReturnType: Boolean = false, +) : SignatureSuggestion { + private fun intersectCoerce(other: SuggestedSignature, manager: PsiManager): SuggestedSignature? { + val intLikeTypes = linkedSetOf() + + val (returnType, returnTypeIsIntLike, coerceReturnType) = mergeTypes( + manager, + this.returnType, + other.returnType, + MethodSignature.TypePosition.Return in this.intLikeTypes, + MethodSignature.TypePosition.Return in other.intLikeTypes, + ) ?: return null + + if (returnTypeIsIntLike) { + intLikeTypes.add(MethodSignature.TypePosition.Return) + } + + return SuggestedSignature( + params.withIndex().zip(other.params) { (index, a), b -> + val pos = MethodSignature.TypePosition.Param(index) + val (type, isIntLike, coerce) = mergeTypes( + manager, + a.type, + b.type, + pos in this.intLikeTypes, + pos in other.intLikeTypes, + ) ?: return null + if (isIntLike) { + intLikeTypes.add(pos) + } + val name = if (a.name == b.name) a.name else null + SignatureSuggestion.Param(name, type, a.coerce || b.coerce || coerce) + }, + returnType, + SequencedSet(intLikeTypes), + this.coerceReturnType || other.coerceReturnType || coerceReturnType, + ) + } + + companion object { + private fun exact( + signature: MethodSignature, + takeTrailing: Int = 0, + intLikeAssignment: PsiType? = null, + ): SuggestedSignature { + var returnType = signature.returnType + val params = + (signature.requiredParams.asSequence() + signature.trailingParams.asSequence().take(takeTrailing)).map { + SignatureSuggestion.Param( + it.name, + it.type, + ) + }.toMutableList() + if (intLikeAssignment != null) { + for (pos in signature.intLikePositions) { + when (pos) { + MethodSignature.TypePosition.Return -> returnType = intLikeAssignment + is MethodSignature.TypePosition.Param -> { + params[pos.index] = params[pos.index].copy(type = intLikeAssignment) + } + } + } + } + return SuggestedSignature( + params, + returnType, + if (intLikeAssignment == null) signature.intLikePositions else emptySequencedSet(), + ) + } + + /** + * Returns a suggested signature for the given `@Modify`-style signatures. The resulting signature, if any, will + * always have 1 parameter. Preference is given to the existing return type, if any, since this is likely to be + * typed before the parameters. + */ + fun modifier(annotation: PsiAnnotation, signatures: List): SuggestedSignature? { + val parameterOptions = signatures.map { it.paramOptions } + val psiManager = PsiManager.getInstance(annotation.project) + + val optionsByType = parameterOptions.asSequence() + .flatMap { it.entries } + .groupBy({ it.key }, { it.value }) + + val existingReturnType = annotation.parentOfType()?.returnType + val forExistingReturnType = existingReturnType?.let { optionsByType[Type.getType(it.descriptor)] } + + val chosenParams = forExistingReturnType?.takeIf { it.size == parameterOptions.size } + ?: optionsByType.values.firstOrNull { it.size == parameterOptions.size } ?: return null + + val name = chosenParams.asSequence().map { it.name }.singleDistinctOrNull() ?: "original" + val type = chosenParams.asSequence().map { it.type } + .reduce { a, b -> GenericsUtil.getLeastUpperBound(a, b, psiManager) ?: a } + + return SuggestedSignature(listOf(SignatureSuggestion.Param(name, type)), type) + } + + /** + * Returns a suggested signature for the given `Operation`-wrapper signatures. Trailing parameters are never + * used, and the Operation type is never `@Coerce`d. Doing so would make it possible to reconcile differing + * parameter counts in some cases, but would be confusing and impractical to use. + */ + fun operationWrapper( + annotation: PsiAnnotation, + signatures: List, + ): SuggestedSignature? { + return intersect( + annotation, + signatures.map { it.signature }, + numParams = { it.requiredParams.size }, + ) + } + + /** + * Returns a suggested signature for the given `@Inject` signatures. The target method's parameters are captured + * if possible, and we take the longest possible prefix of the available locals from each signature. `@Coerce` + * is used to rectify any differences in these cases, where possible. The `CallbackInfo(Returnable)` parameter + * may be `@Coerce`d to `CallbackInfo` itself, but never to any other type. Doing so would make it possible to + * reconcile differing parameter counts in some cases, but would be confusing and impractical to use. + */ + fun inject(annotation: PsiAnnotation, signatures: List): SuggestedSignature? { + val localsToUse = + coerciblePrefixLength(signatures.asSequence().map { sig -> sig.locals.asSequence().map { it.type } }) + + val longForm = intersect( + annotation, + signatures.map { it.longSignature }, + numParams = { it.requiredParams.size + localsToUse }, + ) + + return longForm ?: intersect( + annotation, + signatures.map { it.shortSignature ?: it.longSignature }, + numParams = { it.requiredParams.size }, + ) + } + + /** + * Returns a suggested signature for the given signatures. The signature shapes are relatively flexible with + * constraints as per the structure of [GeneralSignatures]. The resulting signature, if any, will have as many + * parameters as the longest input signature mandates, with trailing parameters being taken from any shorter + * signatures to fill the gaps. `@Coerce` is used to reconcile differences in parameter and return types, where + * the input signatures allow it. + */ + fun general(annotation: PsiAnnotation, signatures: List): SuggestedSignature? { + val numParams = signatures.maxOf { it.params.size } + + val candidatesByReturnKind = signatures.asSequence() + .flatMap { sig -> + sig.returnTypeOptions.keys.asSequence().map { it to sig.specificSignature(it) } + } + .groupBy({ it.first }, { it.second }) + .values + + for (candidates in candidatesByReturnKind) { + if (candidates.size < signatures.size) { + // Not viable + continue + } + + val intersected = intersect( + annotation, + candidates, + numParams = { numParams }, + ) ?: continue + + return intersected + } + + return null + } + + /** + * Returns the most specific possible intersection of the given signatures, iff any exists. The returned + * signature, if any, is guaranteed to satisfy all the given signatures, including when some parts do not permit + * coercion. Parameters are taken from each signature as per [numParams]. + */ + private fun intersect( + context: PsiElement, + signatures: List, + numParams: (MethodSignature) -> Int, + ): SuggestedSignature? { + val paramsToUse = signatures + .map { sig -> + numParams(sig).also { + if (it !in sig.requiredParams.size..sig.requiredParams.size + sig.trailingParams.size) { + return null + } + } + } + .asSequence() + .singleDistinctOrNull() + ?: return null + + val intLikeAnchor = run { + signatures.mapNotNull { it.intLikePositions.firstOrNull() }.toList() + .ifEmpty { return@run null } + .asSequence() + // With the currently available signature shapes, there can only ever be 1 anchor. + // This logic will need revisiting if that changes. + .singleDistinct() + } + val intLikeAssignment = intLikeAnchor?.let { + val solver = IntLikeAnchorSolver() + + val allIntLikePositions = signatures.asSequence() + .flatMap { it.intLikePositions } + .toHashSet() + for (signature in signatures) { + for (pos in signature.allPositions(paramsToUse)) { + if (pos in signature.intLikePositions || pos !in allIntLikePositions) { + continue + } + val isTrailingParam = pos is MethodSignature.TypePosition.Param && + pos.index > signature.requiredParams.lastIndex + val allowCoerce = isTrailingParam || signature.allowCoerceRequired + val isValid = solver.constrain( + pos.getType(signature), + isAnchor = pos == intLikeAnchor, + isHard = !allowCoerce, + ) + if (!isValid) { + return null + } + } + } + + solver.solve() + } + + val suggestedSignatures = signatures.map { + exact(it, takeTrailing = paramsToUse - it.requiredParams.size, intLikeAssignment = intLikeAssignment) + } + + if (suggestedSignatures.asSequence().zipWithNext { a, b -> !kindsMatch(a, b) }.any { it }) { + return null + } + + val manager = PsiManager.getInstance(context.project) + val intersected = suggestedSignatures.asSequence() + .reduceFallible { acc, it -> acc.intersectCoerce(it, manager) } ?: return null + + // It is fine simply to intersect and then check hard constraints, because if an input signature contains a + // subtype Y of X at a position where X is a hard constraint, then the intersection will coerce Y upwards + // to X. If it contains instead a *supertype* Z of X, then reconciliation is not possible and the following + // check will fail: + return intersected.takeIf { + signatures.all { + it.allowCoerceRequired || it.matches(intersected) + } + } + } + } +} + +private fun kindsMatch(a: SuggestedSignature, b: SuggestedSignature) = + TypeKind.of(a.returnType) == TypeKind.of(b.returnType) + && a.params.size == b.params.size + && a.params.indices.all { TypeKind.of(a.params[it].type) == TypeKind.of(b.params[it].type) } + +/** + * Returns the largest N such that the sequences' first N types can be merged element-wise. + */ +private fun coerciblePrefixLength(types: Sequence>): Int { + val iterators = types.map { it.iterator() }.toList() + var i = 0 + + while (true) { + var kind: TypeKind? = null + var forcedIntType: PsiType? = null + + for (iterator in iterators) { + val candidate = if (iterator.hasNext()) iterator.next() else return i + val candidateKind = TypeKind.of(candidate) + + when (kind) { + null -> kind = candidateKind + candidateKind -> {} + else -> return i + } + + if (candidateKind == TypeKind.INT_LIKE && candidate != PsiTypes.intType()) { + when (forcedIntType) { + null -> forcedIntType = candidate + candidate -> {} + else -> return i + } + } + } + + i++ + } +} + +/** + * Merges the two types with regards to `@Coerce` behavior. Returns the merged type, whether the merged type is a + * free int-like type, and whether the merge **newly** requires `@Coerce`. + * + * **Preconditions:** The types must be of the same kind, and if either type is a free int-like type, the other must be + * too. (Fully solving int-like types is left to the caller.) + */ +private fun mergeTypes( + manager: PsiManager, + a: PsiType, + b: PsiType, + aIsIntLike: Boolean, + bIsIntLike: Boolean, +): TypeMergeResult? = when (TypeKind.of(a)) { + TypeKind.OBJECT -> { + TypeMergeResult( + GenericsUtil.getLeastUpperBound(a, b, manager)!!, + isIntLike = false, + coerce = a.normalize() != b.normalize(), + ) + } + + TypeKind.INT_LIKE -> { + when { + aIsIntLike && bIsIntLike -> TypeMergeResult(a, isIntLike = true, coerce = false) + aIsIntLike || bIsIntLike -> error("Int-like type should have been forced") + a == b -> TypeMergeResult(a, isIntLike = false, coerce = false) + a == PsiTypes.intType() -> TypeMergeResult(b, isIntLike = false, coerce = true) + b == PsiTypes.intType() -> TypeMergeResult(a, isIntLike = false, coerce = true) + else -> null + } + } + + else -> { + require(a == b) + TypeMergeResult(a, isIntLike = false, coerce = false) + } +} + +private data class TypeMergeResult(val type: PsiType, val isIntLike: Boolean, val coerce: Boolean) Index: src/main/kotlin/platform/mixin/inspection/mixinextras/InjectLocalCaptureReplaceWithLocalInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/mixinextras/InjectLocalCaptureReplaceWithLocalInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/mixinextras/InjectLocalCaptureReplaceWithLocalInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.mixinextras -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix @@ -101,7 +101,7 @@ // resolve the local variables at the targets val handler = MixinAnnotationHandler.forMixinAnnotation(MixinConstants.Annotations.INJECT) - as InjectorAnnotationHandler + as InsnInjectorAnnotationHandler val module = annotation.findModule() ?: return val localsAndParamCountsAtTargets = handler.resolveInstructions(annotation).map { result -> val locals = LocalVariables.getLocals( Index: src/main/kotlin/platform/mixin/inspection/mixinextras/LocalArgsOnlyInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/mixinextras/LocalArgsOnlyInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/mixinextras/LocalArgsOnlyInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.mixinextras -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection import com.demonwav.mcdev.platform.mixin.inspection.fix.AnnotationAttributeFix @@ -54,7 +54,7 @@ val method = parameter.declarationScope as? PsiMethod ?: return val (injector, injectorAnnotation) = method.annotations.mapFirstNotNull { annotation -> - (MixinAnnotationHandler.forMixinAnnotation(annotation, holder.project) as? InjectorAnnotationHandler)?.let { it to annotation } + (MixinAnnotationHandler.forMixinAnnotation(annotation, holder.project) as? InsnInjectorAnnotationHandler)?.let { it to annotation } } ?: return val localType = parameter.type.unwrapLocalRef() Index: src/main/kotlin/platform/mixin/inspection/mixinextras/LocalMayUseNameInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/mixinextras/LocalMayUseNameInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/mixinextras/LocalMayUseNameInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,11 +20,11 @@ package com.demonwav.mcdev.platform.mixin.inspection.mixinextras -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection -import com.demonwav.mcdev.platform.mixin.inspection.injector.ModifyVariableMayUseNameInspection.Util.getVariableNameToIntroduce import com.demonwav.mcdev.platform.mixin.inspection.injector.ModifyVariableMayUseNameInspection.ReplaceWithNameFix +import com.demonwav.mcdev.platform.mixin.inspection.injector.ModifyVariableMayUseNameInspection.Util.getVariableNameToIntroduce import com.demonwav.mcdev.platform.mixin.util.LocalInfo import com.demonwav.mcdev.platform.mixin.util.MixinConstants import com.demonwav.mcdev.platform.mixin.util.unwrapLocalRef @@ -72,7 +72,7 @@ } val (injector, injectorAnnotation) = method.annotations.mapFirstNotNull { annotation -> - (MixinAnnotationHandler.forMixinAnnotation(annotation, holder.project) as? InjectorAnnotationHandler)?.let { it to annotation } + (MixinAnnotationHandler.forMixinAnnotation(annotation, holder.project) as? InsnInjectorAnnotationHandler)?.let { it to annotation } } ?: return val variableName = getVariableNameToIntroduce(localInfo, injector, injectorAnnotation) ?: return Index: src/main/kotlin/platform/mixin/inspection/mixinextras/UnnecessaryMutableLocalInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/mixinextras/UnnecessaryMutableLocalInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/mixinextras/UnnecessaryMutableLocalInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.mixinextras -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.mixinextras.WrapOperationHandler import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection @@ -58,7 +58,7 @@ val hasValidMixinAnnotation = method.annotations.any { ann -> MixinAnnotationHandler.forMixinAnnotation(ann, project) // Mutable Local references do have different semantics inside a WrapOperation. - ?.let { it is InjectorAnnotationHandler && it !is WrapOperationHandler } == true + ?.let { it is InsnInjectorAnnotationHandler && it !is WrapOperationHandler } == true } if (!hasValidMixinAnnotation) { return Index: src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt =================================================================== --- src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/inspection/mixinextras/UnresolvedLocalCaptureInspection.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -20,7 +20,7 @@ package com.demonwav.mcdev.platform.mixin.inspection.mixinextras -import com.demonwav.mcdev.platform.mixin.handlers.InjectorAnnotationHandler +import com.demonwav.mcdev.platform.mixin.handlers.InsnInjectorAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.MixinAnnotationHandler import com.demonwav.mcdev.platform.mixin.handlers.injectionPoint.CollectVisitor import com.demonwav.mcdev.platform.mixin.inspection.MixinInspection @@ -50,7 +50,7 @@ val method = parameter.findContainingMethod() ?: return val targets = method.annotations.mapFirstNotNull { annotation -> val handler = - MixinAnnotationHandler.forMixinAnnotation(annotation, holder.project) as? InjectorAnnotationHandler + MixinAnnotationHandler.forMixinAnnotation(annotation, holder.project) as? InsnInjectorAnnotationHandler ?: return@mapFirstNotNull null handler.resolveInstructions(annotation) } ?: return Index: src/main/kotlin/platform/mixin/util/AsmUtil.kt =================================================================== --- src/main/kotlin/platform/mixin/util/AsmUtil.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/util/AsmUtil.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -75,7 +75,6 @@ import com.intellij.psi.PsiModifier 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 @@ -830,10 +829,23 @@ } } -fun MethodNode.getParameter(clazz: ClassNode, index: Int, parameterList: PsiParameterList): PsiParameter? { - return parameterList.parameters.getOrNull(index - getOffset(clazz)) +private fun PsiMethod.getOffset(): Int { + val clazz = containingClass ?: return 0 + return if (this.isConstructor) { + when { + clazz.isEnum -> 2 + clazz.containingClass != null && !clazz.hasModifierProperty(PsiModifier.STATIC) -> 1 + else -> 0 -} + } + } else { + 0 + } +} +fun PsiMethod.getBytecodeParameter(index: Int): PsiParameter? { + return parameterList.parameters.getOrNull(index - getOffset()) +} + /** * Attempts to find the source method using [findSourceElement]. If this fails, or if the result is not a `PsiMethod`, * then a new source method is constructed, possibly copying the body of the found source element. Index: src/main/kotlin/platform/mixin/util/IntLikeAnchorSolver.kt =================================================================== --- src/main/kotlin/platform/mixin/util/IntLikeAnchorSolver.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/platform/mixin/util/IntLikeAnchorSolver.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,97 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.platform.mixin.util + +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypes +import java.util.EnumSet + +/** + * Represents a constraint solver for the anchor type of a signature containing free int-like types. + */ +class IntLikeAnchorSolver { + private val candidates = EnumSet.allOf(IntType::class.java) + private var hasIntPreference = false + private var hasLeafPreference = false + + /** + * Adds the given constraint to the solver and returns whether a solution is still possible. + */ + fun constrain(desiredType: PsiType, isAnchor: Boolean, isHard: Boolean): Boolean { + val type = IntType.of(desiredType) ?: return false + when { + isHard && (isAnchor || type == IntType.INT) -> candidates.retainAll(EnumSet.of(type)) + type == IntType.INT -> hasIntPreference = true + isAnchor -> candidates.retainAll(EnumSet.of(type)) + else -> { + candidates.retainAll(EnumSet.of(type, IntType.INT)) + hasLeafPreference = true + } + } + return candidates.isNotEmpty() + } + + /** + * Returns either a chosen anchor type or `null` if any anchor type will suffice. + * + * **Precondition:** A solution is possible. + */ + fun solve(): PsiType? { + require(candidates.isNotEmpty()) + + candidates.singleOrNull()?.let { return it.type } + + return when { + // int preference is the strongest, since choosing a leaf type instead would require a @Coerce *and* the + // use of an undesired leaf type + hasIntPreference -> { + // Cannot have 2 leaves without int + check(IntType.INT in candidates) + PsiTypes.intType() + } + // Leaf preference is the next strongest, since choosing int instead would require a @Coerce + // NB leaf preferences can only come from non-anchor positions + hasLeafPreference -> { + // Cannot have 2 leaves and a leaf preference + candidates.single { it != IntType.INT }.type + } + else -> { + // We have multiple options and no preference at all + check(candidates.size == IntType.entries.size) + null + } + } + } +} + +private enum class IntType(val type: PsiType) { + INT(PsiTypes.intType()), + BYTE(PsiTypes.byteType()), + SHORT(PsiTypes.shortType()), + CHAR(PsiTypes.charType()), + BOOLEAN(PsiTypes.booleanType()); + + companion object { + private val lookup = entries.associateBy { it.type } + + fun of(type: PsiType) = lookup[type] + } +} Index: src/main/kotlin/platform/mixin/util/LocalInfo.kt =================================================================== --- src/main/kotlin/platform/mixin/util/LocalInfo.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/util/LocalInfo.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -88,15 +88,13 @@ ): List? { val locals = getLocals(module, targetClass, methodNode, insn) ?.drop(if (methodNode.hasAccess(Opcodes.ACC_STATIC)) 0 else 1) + ?.filterNotNull() ?: return null val typeDesc = type?.descriptor if (ordinal != null) { val ordinals = mutableMapOf() val result = mutableListOf() for (local in locals) { - if (local == null) { - continue - } val ordinal = ordinals[local.desc] ?: 0 ordinals[local.desc!!] = ordinal + 1 if (ordinal == this.ordinal && (!matchType || typeDesc == null || local.desc == typeDesc)) { @@ -107,7 +105,7 @@ } if (index != null) { - val local = locals.firstOrNull { it?.index == index } + val local = locals.firstOrNull { it.index == index } if (local != null) { if (!matchType || typeDesc == null || local.desc == typeDesc) { return listOf(local) @@ -119,9 +117,6 @@ if (names.isNotEmpty()) { val result = mutableListOf() for (local in locals) { - if (local == null) { - continue - } if (names.contains(local.name)) { if (!matchType || typeDesc == null || local.desc == typeDesc) { result += local @@ -132,15 +127,13 @@ } // implicit mode - if (mode == CollectVisitor.Mode.COMPLETION) { - return locals.asSequence() - .filterNotNull() - .filter { local -> locals.count { it?.desc == local.desc } == 1 } - .toList() + if (!mode.assumeCorrectSignature) { + val byType = locals.asSequence().filter { it.desc != null }.groupBy { it.desc!! } + return byType.values.mapNotNull { it.singleOrNull() } } return if (matchType && typeDesc != null) { - locals.singleOrNull { it?.desc == typeDesc }?.let { listOf(it) } ?: emptyList() + locals.singleOrNull { it.desc == typeDesc }?.let { listOf(it) } ?: emptyList() } else { emptyList() } Index: src/main/kotlin/platform/mixin/util/Mixin.kt =================================================================== --- src/main/kotlin/platform/mixin/util/Mixin.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/platform/mixin/util/Mixin.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -33,6 +33,7 @@ import com.demonwav.mcdev.util.computeStringArray import com.demonwav.mcdev.util.constantValue import com.demonwav.mcdev.util.findModule +import com.demonwav.mcdev.util.normalize import com.demonwav.mcdev.util.resolveClassArray import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project @@ -329,3 +330,19 @@ return true } + +private val INT_TYPES = setOf( + PsiTypes.intType(), + PsiTypes.shortType(), + PsiTypes.charType(), + PsiTypes.byteType(), + PsiTypes.booleanType(), +) + +fun checkCoerce(expected: PsiType, actual: PsiType, coerce: Boolean, expectedIntLike: Boolean): Boolean = when { + expectedIntLike -> actual in INT_TYPES + actual.normalize() == expected.normalize() -> true + !coerce -> false + actual in INT_TYPES -> expected == PsiTypes.intType() + else -> isAssignable(actual.normalize(), expected.normalize()) +} Index: src/main/kotlin/platform/mixin/util/ReturnTypeSolver.kt =================================================================== --- src/main/kotlin/platform/mixin/util/ReturnTypeSolver.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/platform/mixin/util/ReturnTypeSolver.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,261 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.platform.mixin.util + +import com.demonwav.mcdev.platform.mixin.inspection.injector.MethodSignature +import com.demonwav.mcdev.platform.mixin.inspection.injector.SuggestedReturnType +import com.demonwav.mcdev.platform.mixin.util.MixinConstants.Annotations.COERCE +import com.demonwav.mcdev.util.MutableSequencedMap +import com.demonwav.mcdev.util.PrioritySet +import com.demonwav.mcdev.util.normalize +import com.demonwav.mcdev.util.singleDistinct +import com.intellij.psi.CommonClassNames +import com.intellij.psi.GenericsUtil +import com.intellij.psi.PsiArrayType +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiParameterList +import com.intellij.psi.PsiPrimitiveType +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypes + +/** + * Represents a constraint solver for the return type of a signature given a fixed list of parameters. + */ +class ReturnTypeSolver(private val parameterList: PsiParameterList) { + private val manager = PsiManager.getInstance(parameterList.project) + private val objectReturnOptionsCache = hashMapOf>>() + private val suggestionsByType = hashMapOf>() + private val typeOptions = PrioritySet() + private var numExpected = 0 + + /** + * Adds a new constraint that the chosen return type must match at least one of the given signatures. + */ + fun addExpected(signatures: List) { + val expectedIndex = numExpected++ + for (signature in signatures) { + for ((option, priority) in signature.returnTypeOptions()) { + val key = TypeKey.of(option) + typeOptions.add(key, priority) + suggestionsByType.getOrPut(key, ::linkedMapOf).putIfAbsent(expectedIndex, option) + } + } + } + + /** + * Returns a suggested return type, or null if it is not possible to satisfy all the constraints. + */ + fun solve(): SuggestedReturnType? { + val suggestions = typeOptions.asSequence() + .map { suggestionsByType.getValue(it) }.firstOrNull { it.size == numExpected }?.values + return suggestions?.let { intersect(it) } + } + + /** + * Returns the possible return types that this signature can support, along with their priority. + * + * **Precondition:** The parameter list is valid according to the signature. + */ + private fun MethodSignature.returnTypeOptions(): List> { + if (MethodSignature.TypePosition.Return in intLikePositions) { + val params = parameterList.parameters + val anchor = (intLikePositions.first() as? MethodSignature.TypePosition.Param) + ?.let { params[it.index].type } + return when (anchor) { + PsiTypes.intType() -> { + intReturnOptions(allowCoerceRequired) + } + null -> { + // The return type itself is the anchor + val intLikeParams = intLikePositions.asSequence() + .mapNotNull { it.getParam(params) } + .groupBy { it.type } + when { + intLikeParams.isEmpty() -> { + // Only the return type is int-like, free choice + val results = mutableListOf( + SuggestedReturnType(PsiTypes.intType(), returnTypeIsIntLike = true) withPriority 0 + ) + for (specific in intLikeTypes) { + // We don't know what the return type should be, so we avoid influencing the preference + // order. + results.add(SuggestedReturnType(specific) withPriority Int.MAX_VALUE) + } + results + } + PsiTypes.intType() in intLikeParams -> { + // Only int can be coerced to int (can also be coerced to anything else we found) + listOf(SuggestedReturnType(PsiTypes.intType()) withPriority 0) + } + intLikeParams.size == 1 -> { + // Take the specific leaf type we found, plus int iff all the params have @Coerce. + // int is less preferable since it doesn't match exactly. + val leaf = intLikeParams.keys.single() + val results = mutableListOf( + SuggestedReturnType(leaf) withPriority 0 + ) + if (allowCoerceRequired && intLikeParams.values.single().all { it.hasAnnotation(COERCE) }) { + results.add( + SuggestedReturnType(PsiTypes.intType()) withPriority 1 + ) + } + results + } + else -> { + // Only int can be coerced to multiple types + check(allowCoerceRequired) + for (params in intLikeParams.values) { + // We double-check that the parameters are valid as the caller promised. + // NB We know there are no int params, so all must be coerced. + check(params.all { it.hasAnnotation(COERCE) }) + } + listOf(SuggestedReturnType(PsiTypes.intType()) withPriority 0) + } + } + } + else -> { + // Anchor is a leaf type which can only be coerced to itself + listOf(SuggestedReturnType(anchor) withPriority 0) + } + } + } + return when { + returnType == PsiTypes.intType() -> intReturnOptions(allowCoerceRequired) + TypeKind.of(returnType) == TypeKind.OBJECT -> objectReturnOptions(returnType, allowCoerceRequired) + else -> listOf(SuggestedReturnType(returnType) withPriority 0) + } + } + + /** + * Returns a suggested return type which is the LUB of all the given types. + * + * **Precondition:** [suggestions] must be non-empty and every suggestion in [suggestions] must have the same type + * as considered by [TypeKey]. + */ + private fun intersect(suggestions: Iterable): SuggestedReturnType { + val types = suggestions.asSequence().map { it.returnType } + val kind = types.map { TypeKind.of(it) }.singleDistinct() + val mergedType = when (kind) { + TypeKind.OBJECT -> types.reduce { acc, it -> GenericsUtil.getLeastUpperBound(acc, it, manager)!! } + else -> types.singleDistinct() + } + return SuggestedReturnType( + mergedType, + returnTypeIsIntLike = suggestions.asSequence().map { it.returnTypeIsIntLike }.singleDistinct(), + coerceReturnType = suggestions.any { it.coerceReturnType }, + ) + } + + /** + * Yields all valid supertypes (inclusive) of the given type, at most once per raw type, with priority equal to the + * number of traversal steps required to reach the supertype. + */ + private fun objectReturnOptions(type: PsiType, allowCoerce: Boolean): List> = + if (!allowCoerce) { + listOf(SuggestedReturnType(type) withPriority 0) + } else { + objectReturnOptionsCache.getOrPut(type) { + val result = mutableListOf>() + + val queue = ArrayDeque(listOf(SuggestedReturnType(type) withPriority 0)) + val visited = hashSetOf(type.normalize()) + + while (queue.isNotEmpty()) { + val (next, priority) = queue.removeFirst().also { result.add(it) } + for (directSuper in next.returnType.directSupertypes()) { + if (visited.add(directSuper.normalize())) { + queue.addLast( + SuggestedReturnType( + directSuper, + coerceReturnType = true, + ) withPriority priority + 1 + ) + } + } + } + + result + } + } + + private fun PsiType.directSupertypes(): List = when (this) { + is PsiArrayType -> { + if (componentType is PsiPrimitiveType || componentType.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) { + arraySuperTypes.map { PsiType.getTypeByName(it, manager.project, parameterList.resolveScope) } + } else { + componentType.directSupertypes().map { it.createArrayType() } + } + } + else -> { + val superTypeArray = superTypes + if (superTypeArray.size <= 1) { + superTypeArray.toList() + } else { + // Delay java.lang.Object since it is necessarily less specific than any other type + superTypeArray.filterNot { it.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) } + } + } + } + + private companion object { + private val intLikeTypes = listOf( + PsiTypes.intType(), + PsiTypes.booleanType(), + PsiTypes.charType(), + PsiTypes.byteType(), + PsiTypes.shortType(), + ) + + private val intReturnOptions = intLikeTypes.map { + val needsCoerce = it != PsiTypes.intType() + SuggestedReturnType( + it, + coerceReturnType = needsCoerce, + ) withPriority (if (needsCoerce) 1 else 0) + } + + private val arraySuperTypes = listOf( + CommonClassNames.JAVA_IO_SERIALIZABLE, + CommonClassNames.JAVA_LANG_CLONEABLE, + ) + + private fun intReturnOptions(allowCoerce: Boolean): List> = + if (allowCoerce) intReturnOptions else listOf(SuggestedReturnType(PsiTypes.intType()) withPriority 0) + } +} + +private data class Prioritized(val element: T, val priority: Int) + +private infix fun T.withPriority(priority: Int) = Prioritized(this, priority) + +private sealed interface TypeKey { + data class Type(val type: PsiType) : TypeKey + + data object IntLike : TypeKey + + companion object { + fun of(suggestion: SuggestedReturnType) = if (suggestion.returnTypeIsIntLike) { + IntLike + } else { + Type(suggestion.returnType.normalize()) + } + } +} Index: src/main/kotlin/platform/mixin/util/TypeKind.kt =================================================================== --- src/main/kotlin/platform/mixin/util/TypeKind.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/platform/mixin/util/TypeKind.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.platform.mixin.util + +import com.intellij.psi.PsiType +import com.intellij.psi.PsiTypes + +enum class TypeKind { + OBJECT, + INT_LIKE, + FLOAT, + DOUBLE, + LONG, + VOID; + + companion object { + private val typeMap = mapOf( + PsiTypes.byteType() to INT_LIKE, + PsiTypes.charType() to INT_LIKE, + PsiTypes.intType() to INT_LIKE, + PsiTypes.shortType() to INT_LIKE, + PsiTypes.booleanType() to INT_LIKE, + PsiTypes.doubleType() to DOUBLE, + PsiTypes.floatType() to FLOAT, + PsiTypes.longType() to LONG, + PsiTypes.voidType() to VOID, + ) + + fun of(type: PsiType) = typeMap[type] ?: OBJECT + } +} Index: src/main/kotlin/util/PrioritySet.kt =================================================================== --- src/main/kotlin/util/PrioritySet.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/util/PrioritySet.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,45 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.util + +/** + * Represents a prioritised set of elements. Elements can only be present once and assume the lowest priority value with + * which they were added. Lower priority values indicate more important elements. Iteration yields the elements in + * priority order, with ties broken by insertion order. + */ +class PrioritySet : Iterable { + private val buckets = sortedMapOf>() + private val priorities = hashMapOf() + + fun add(element: T, priority: Int) { + val existing = priorities[element] + when { + existing == null -> {} + priority < existing -> buckets.getValue(existing).remove(element) + priority >= existing -> return + } + priorities[element] = priority + buckets.getOrPut(priority, ::linkedSetOf).add(element) + } + + override fun iterator(): Iterator = + buckets.values.asSequence().flatten().iterator() +} Index: src/main/kotlin/util/sequenced-collections.kt =================================================================== --- src/main/kotlin/util/sequenced-collections.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/main/kotlin/util/sequenced-collections.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,59 @@ +/* + * 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 . + */ + +package com.demonwav.mcdev.util + +@Suppress("JavaDefaultMethodsNotOverriddenByDelegation") +@JvmInline +value class SequencedSet(private val set: MutableSequencedSet) : Set by set + +typealias MutableSequencedSet = java.util.SequencedSet + +private val EMPTY_SEQUENCED_SET: SequencedSet = SequencedSet(linkedSetOf()) + +fun emptySequencedSet(): SequencedSet = EMPTY_SEQUENCED_SET + +fun sequencedSetOf(vararg elements: T): SequencedSet = SequencedSet(linkedSetOf(*elements)) + +inline fun buildSequencedSet(action: MutableSequencedSet.() -> Unit): SequencedSet = + SequencedSet(linkedSetOf().apply(action)) + +operator fun SequencedSet.minus(element: T): SequencedSet { + val result = linkedSetOf() + var removed = false + this.filterTo(result) { + if (!removed && it == element) { + removed = true; false + } else true + } + return SequencedSet(result) +} + +@JvmInline +value class SequencedMap(private val map: MutableSequencedMap) : Map by map + +typealias MutableSequencedMap = java.util.SequencedMap + +private val EMPTY_SEQUENCED_MAP: SequencedMap = SequencedMap(linkedMapOf()) + +@Suppress("UNCHECKED_CAST") +fun emptySequencedMap(): SequencedMap = EMPTY_SEQUENCED_MAP as SequencedMap + +fun sequencedMapOf(vararg pairs: Pair): SequencedMap = SequencedMap(linkedMapOf(*pairs)) Index: src/main/kotlin/util/sequences.kt =================================================================== --- src/main/kotlin/util/sequences.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/util/sequences.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -54,3 +54,36 @@ fun Sequence<*>.countIsAtLeast(n: Int) = n <= 0 || drop(n - 1).any() fun Sequence<*>.countIsLessThan(n: Int) = n > 0 && drop(n - 1).none() + +fun Sequence<*>.allEqual() = zipWithNext().all { (a, b) -> a == b } + +inline fun Sequence.reduceFallible(operation: (acc: S, T) -> S?): S? = + reduceOrNull { acc, it -> operation(acc, it) ?: return null } + +fun Sequence.singleDistinct(): T { + val iter = iterator() + if (!iter.hasNext()) { + throw NoSuchElementException("Sequence is empty.") + } + val first = iter.next() + for (element in iter) { + if (first != element) { + throw IllegalArgumentException("Sequence has more than one distinct element.") + } + } + return first +} + +fun Sequence.singleDistinctOrNull(): T? { + val iter = iterator() + if (!iter.hasNext()) { + return null + } + val first = iter.next() + for (element in iter) { + if (first != element) { + return null + } + } + return first +} Index: src/main/kotlin/util/utils.kt =================================================================== --- src/main/kotlin/util/utils.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/main/kotlin/util/utils.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -47,9 +47,10 @@ import com.intellij.psi.util.PsiUtil import java.lang.invoke.MethodHandles import java.util.Locale -import java.util.Locale.getDefault import java.util.concurrent.CancellationException import java.util.regex.PatternSyntaxException +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.math.min import kotlin.reflect.KClass import org.intellij.lang.annotations.Language @@ -153,9 +154,12 @@ return this } -inline fun ?> T.ifNullOrEmpty(func: () -> Unit): T { +inline fun > T?.ifNullOrEmpty(func: () -> R): R where T : R { + contract { + callsInPlace(func, InvocationKind.AT_MOST_ONCE) + } if (isNullOrEmpty()) { - func() + return func() } return this } Index: src/test/kotlin/framework/test-util.kt =================================================================== --- src/test/kotlin/framework/test-util.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/kotlin/framework/test-util.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -22,12 +22,16 @@ package com.demonwav.mcdev.framework +import com.intellij.codeInsight.template.TemplateManager +import com.intellij.codeInsight.template.impl.TemplateManagerImpl import com.intellij.ide.highlighter.JavaFileType +import com.intellij.lang.annotation.HighlightSeverity import com.intellij.lexer.Lexer import com.intellij.openapi.project.Project import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.roots.libraries.Library import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar +import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.JarFileSystem import com.intellij.openapi.vfs.StandardFileSystems @@ -128,3 +132,52 @@ fixture.launchAction(intention) fixture.checkResult(expected) } + +fun testAllInspectionFixes(fixture: JavaCodeInsightTestFixture, basePath: String, fixName: String) { + val caller = ReflectionUtil.getCallerClass(4)!! + val original = + caller.getResource("$basePath.java")?.readText()?.trim()?.lineSequence()?.joinToString("\n") + ?: Assertions.fail("no test data") + val expected = caller.getResource("$basePath.after.java")?.readText()?.trim()?.lineSequence() + ?.joinToString("\n") ?: Assertions.fail("no expected data") + + fixture.configureByText(JavaFileType.INSTANCE, original) + TemplateManagerImpl.setTemplateTesting(fixture.testRootDisposable) + + var lastFixedRange: TextRange? = null + while (true) { + val (fix, range) = fixture.doHighlighting().firstNotNullOfOrNull { info -> + info.findRegisteredQuickFix { descriptor, range -> + descriptor.action.takeIf { it.familyName == fixName }?.let { it to range } + } + } ?: break + + Assertions.assertNotEquals(lastFixedRange, range, "Fix loop") + lastFixedRange = range + + val document = fixture.file.fileDocument + val lineNumber = document.getLineNumber(range.endOffset) + val expectTemplate = document.getText( + TextRange( + document.getLineStartOffset(lineNumber), + document.getLineEndOffset(lineNumber), + ) + ).trimEnd().endsWith("expect-template") + + fixture.launchAction(fix) + + val hadTemplate = TemplateManager.getInstance(fixture.project).finishTemplate(fixture.editor) + if (expectTemplate) { + Assertions.assertTrue(hadTemplate, "No live template but one was expected") + } else { + Assertions.assertFalse(hadTemplate, "Had live template but one was not expected") + } + } + + val remainingIssues = fixture.doHighlighting().filter { it.severity >= HighlightSeverity.WARNING } + Assertions.assertEquals(emptyList(), remainingIssues) + + Assertions.assertTrue(lastFixedRange != null, "No fixes applied") + + fixture.checkResult(expected) +} Index: src/test/kotlin/platform/mixin/BaseMixinTest.kt =================================================================== --- src/test/kotlin/platform/mixin/BaseMixinTest.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/kotlin/platform/mixin/BaseMixinTest.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -40,7 +40,7 @@ @BeforeEach fun initMixin() { runWriteTask { - mixinLibrary = createLibrary(project, "mixin") + mixinLibrary = createLibrary(project, "sponge-mixin") mixinExtrasLibrary = createLibrary(project, "mixinextras-common") testDataLibrary = createLibrary(project, "mixin-test-data") } Index: src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureFixTest.kt =================================================================== --- src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureFixTest.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureFixTest.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -21,6 +21,7 @@ package com.demonwav.mcdev.platform.mixin import com.demonwav.mcdev.framework.EdtInterceptor +import com.demonwav.mcdev.framework.testAllInspectionFixes import com.demonwav.mcdev.framework.testInspectionFix import com.demonwav.mcdev.platform.mixin.inspection.injector.InvalidInjectorMethodSignatureInspection import org.junit.jupiter.api.DisplayName @@ -31,48 +32,101 @@ @DisplayName("Invalid Injector Method Signature Inspection Test") class InvalidInjectorMethodSignatureFixTest : BaseMixinTest() { - private fun doTest(testName: String) { + private fun doSingleTest(testName: String) { fixture.enableInspections(InvalidInjectorMethodSignatureInspection::class) testInspectionFix(fixture, "invalidInjectorMethodSignature/$testName", "Fix method signature") } + private fun doMultiTest(testName: String) { + fixture.enableInspections(InvalidInjectorMethodSignatureInspection::class) + testAllInspectionFixes(fixture, "invalidInjectorMethodSignature/$testName", "Fix method signature") + } + @Test - @DisplayName("Simple case") - fun simpleCase() = doTest("simpleCase") + @DisplayName("Inject simple case") + fun injectSimpleCase() = doSingleTest("inject/simpleCase") @Test - @DisplayName("Simple case with MixinExtras Sugar") - fun simpleCaseWithMixinExtrasSugar() = doTest("simpleCaseWithMixinExtrasSugar") + @DisplayName("Inject simple case with MixinExtras Sugar") + fun injectSimpleCaseWithMixinExtrasSugar() = doSingleTest("inject/simpleCaseWithMixinExtrasSugar") @Test - @DisplayName("With captured locals") - fun withCapturedLocals() = doTest("withCapturedLocals") + @DisplayName("Inject with captured locals") + fun injectWithCapturedLocals() = doSingleTest("inject/withCapturedLocals") @Test - @DisplayName("Simple inner ctor") - fun simpleInnerCtor() = doTest("simpleInnerCtor") + @DisplayName("Inject simple inner ctor") + fun injectSimpleInnerCtor() = doSingleTest("inject/simpleInnerCtor") @Test - @DisplayName("Inner ctor with locals") - fun innerCtorWithLocals() = doTest("innerCtorWithLocals") + @DisplayName("Inject inner ctor with locals") + fun injectInnerCtorWithLocals() = doSingleTest("inject/innerCtorWithLocals") @Test @DisplayName("Inject without CallbackInfo") - fun injectWithoutCI() = doTest("injectWithoutCI") + fun injectWithoutCI() = doSingleTest("inject/withoutCI") @Test - @DisplayName("ModifyArgs") - fun modifyArgs() = doTest("modifyArgs") + @DisplayName("Inject generic method") + fun injectGenericCase() = doSingleTest("inject/genericCase") @Test - @DisplayName("Generic method") - fun genericCase() = doTest("genericCase") + @DisplayName("Inject generic method complex return type") + fun injectGenericCaseComplexReturnType() = doSingleTest("inject/genericCaseComplexReturnType") @Test - @DisplayName("Generic method complex return type") - fun genericCaseComplexReturnType() = doTest("genericCaseComplexReturnType") + @DisplayName("Inject simple method with inner type") + fun injectSimpleMethodWithInnerType() = doSingleTest("inject/simpleMethodWithInnerType") @Test - @DisplayName("Simple method with inner type") - fun simpleMethodWithInnerType() = doTest("simpleMethodWithInnerType") + @DisplayName("ModifyArgs simple") + fun modifyArgsSimple() = doSingleTest("modifyargs/simple") + + @Test + @DisplayName("ModifyArgs return type only") + fun modifyArgsReturnTypeOnly() = doSingleTest("modifyargs/returnTypeOnly") + + @Test + @DisplayName("Inject multi-target") + fun injectMultiTarget() = doMultiTest("inject/multiTarget") + + @Test + @DisplayName("ModifyArg") + fun modifyArg() = doMultiTest("modifyArg") + + @Test + @DisplayName("ModifyVariable") + fun modifyVariable() = doMultiTest("modifyVariable") + + @Test + @DisplayName("WrapMethod") + fun wrapMethod() = doMultiTest("wrapMethod") + + @Test + @DisplayName("ModifyReturnValue") + fun modifyReturnValue() = doMultiTest("modifyReturnValue") + + @Test + @DisplayName("ModifyExpressionValue") + fun modifyExpressionValue() = doMultiTest("modifyExpressionValue") + + @Test + @DisplayName("ModifyConstant") + fun modifyConstant() = doMultiTest("modifyConstant") + + @Test + @DisplayName("ModifyReceiver") + fun modifyReceiver() = doMultiTest("modifyReceiver") + + @Test + @DisplayName("WrapWithCondition") + fun wrapWithCondition() = doMultiTest("wrapWithCondition") + + @Test + @DisplayName("Redirect") + fun redirect() = doMultiTest("redirect") + + @Test + @DisplayName("WrapOperation") + fun wrapOperation() = doMultiTest("wrapOperation") } Index: src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureInspectionTest.kt =================================================================== --- src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureInspectionTest.kt (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/kotlin/platform/mixin/InvalidInjectorMethodSignatureInspectionTest.kt (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -3,7 +3,7 @@ * * https://mcdev.io/ * - * Copyright (C) 2025 minecraft-dev + * 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 @@ -163,4 +163,417 @@ """, ) } + + @Test + @DisplayName("ModifyArgs") + fun modifyArgs() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Coerce; + import org.spongepowered.asm.mixin.injection.ModifyArgs; + import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + + @Mixin(MixedInSimple.class) + public class TestMixin { + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "parseInt")) + private void correct(Args args) { -} + } + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "parseInt")) + private void correctWithTrailing(Args args, String string, int i) { + } + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "parseInt")) + private void prefixOfTrailing(Args args, String string) { + } + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "parseInt")) + private void coerceTrailing(Args args, @Coerce Object string, int i) { + } + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "parseInt")) + private void coerceArgs(@Coerce Object args) { + } + } + """, + ) + } + + @Test + @DisplayName("ModifyArg") + fun modifyArg() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyArg; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Coerce; + import org.spongepowered.asm.mixin.injection.ModifyArg; + + @Mixin(MixedInModifyArg.class) + public class TestMixin { + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String correct(String original) { + return original; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private int correct(int original) { + return original; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String correctFull(String x, int y) { + return x; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee3"), index = 2) + private int correctIndex(int original) { + return original; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee3"), index = 2) + private int captureOuter(int original, Object obj) { + return original; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee3"), index = 2) + private int captureOuter(int a, int b, int c, Object obj) { + return c; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee3")) + private void implicit() { + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1"), index = 1) + private @Coerce Object coerce(@Coerce Object x) { + return x; + } + } + """, + ) + } + + @Test + @DisplayName("ModifyVariable") + fun modifyVariable() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyVariable; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Coerce; + import org.spongepowered.asm.mixin.injection.ModifyVariable; + + @Mixin(MixedInModifyVariable.class) + public class TestMixin { + @ModifyVariable(method = "method1", at = @At("RETURN")) + private Integer correct(Integer original) { + return original; + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private char correct(char original) { + return original; + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private char correctPartialCapture(char original, String arg) { + return original; + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private char correctFullCapture(char original, String arg, Object arg2) { + return original; + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private char correctCoerceTrailing(char original, @Coerce Object arg) { + return original; + } + + @ModifyVariable(method = "method3", at = @At("RETURN"), ordinal = 2) + private int correctOrdinal(int original) { + return original; + } + + @ModifyVariable(method = "method2", at = @At("RETURN")) + private void implicit() { + } + + @ModifyVariable(method = "method1", at = @At("RETURN"), index = 1) + private @Coerce Object coerce(@Coerce Object x) { + return x; + } + } + """, + ) + } + + @Test + @DisplayName("WrapMethod") + fun wrapMethod() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapMethod; + import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; + import com.llamalad7.mixinextras.injector.wrapoperation.Operation; + import org.spongepowered.asm.mixin.Mixin; + + @Mixin(MixedInWrapMethod.class) + class TestMixin { + @WrapMethod(method = {"method1", "method7"}) + private void mustBeStaticAndNonStatic(String arg, Operation original) { + } + + @WrapMethod(method = {"method7", "method8"}) + private static void incompatibleShapes() { + } + } + """, + ) + } + + @Test + @DisplayName("ModifyReturnValue") + fun modifyReturnValue() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyReturnValue; + import com.llamalad7.mixinextras.injector.ModifyReturnValue; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + + @Mixin(MixedInModifyReturnValue.class) + class TestMixin { + @ModifyReturnValue(method = {"method1", "method4"}, at = @At("RETURN")) + private void incompatibleShapes() { + } + } + """, + ) + } + + @Test + @DisplayName("ModifyExpressionValue") + fun modifyExpressionValue() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyExpressionValue; + import com.llamalad7.mixinextras.expression.Expression; + import com.llamalad7.mixinextras.injector.ModifyExpressionValue; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + + @Mixin(MixedInModifyExpressionValue.class) + class TestMixin { + @ModifyExpressionValue( + method = "method1", at = {@At(value = "INVOKE", target = "callee1"), @At(value = "INVOKE", target = "callee5")} + ) + private void incompatibleShapes() { + } + + @Expression("42") + @ModifyExpressionValue( + method = "method1", at = {@At("MIXINEXTRAS:EXPRESSION"), @At(value = "INVOKE", target = "callee1"), @At(value = "INVOKE", target = "callee5")} + ) + private void incompatibleShapesWithIntLike() { + } + } + """, + ) + } + + @Test + @DisplayName("ModifyConstant") + fun modifyConstant() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyConstant; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.Coerce; + import org.spongepowered.asm.mixin.injection.Constant; + import org.spongepowered.asm.mixin.injection.ModifyConstant; + + import java.io.Serializable; + + @Mixin(MixedInModifyConstant.class) + class TestMixin { + @ModifyConstant(method = "method1", constant = @Constant(classValue = Integer.class)) + private Class wrongShape(Class original) { + return null; + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Long.class)) + private boolean wrongShape(Object obj, Class clazz) { + return true; + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Integer.class)) + private boolean coerceInstanceof(Object obj, @Coerce Object clazz) { + return true; + } + + @ModifyConstant(method = "method1", constant = {@Constant(intValue = 42), @Constant(stringValue = "hello")}) + private void incompatibleShapes() { + } + + @ModifyConstant( + method = "method2", + constant = {@Constant(classValue = Integer.class), @Constant(classValue = Long.class)} + ) + private void differentShapesAndTrailingParamIsUnhelpful() { + } + + @ModifyConstant(method = "method2", constant = @Constant(nullValue = true)) + private void cannotModifyNull() { + } + } + """, + ) + } + + @Test + @DisplayName("ModifyReceiver") + fun modifyReceiver() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyReceiver; + import com.llamalad7.mixinextras.injector.ModifyReceiver; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + + @Mixin(MixedInModifyReceiver.class) + class TestMixin { + @ModifyReceiver( + method = "caller", at = {@At(value = "INVOKE", target = "callee1"), @At(value = "INVOKE", target = "callee2")} + ) + private void incompatibleShapes() { + } + } + """, + ) + } + + @Test + @DisplayName("WrapWithCondition") + fun wrapWithCondition() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapWithCondition; + import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + + @Mixin(MixedInWrapWithCondition.class) + class TestMixin { + @WrapWithCondition( + method = "caller", at = {@At(value = "INVOKE", target = "callee1"), @At(value = "INVOKE", target = "callee2")} + ) + private void incompatibleShapes() { + } + } + """, + ) + } + + @Test + @DisplayName("Redirect") + fun redirect() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInRedirect; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Redirect; + + @Mixin(MixedInRedirect.class) + class TestMixin { + @Redirect( + method = "caller", at = {@At(value = "INVOKE", target = "callee1"), @At(value = "INVOKE", target = "callee2")} + ) + private void incompatibleShapes() { + } + + @Redirect( + method = "caller", at = {@At(value = "CONSTANT", args = "classValue=java/lang/Integer"), @At(value = "INVOKE", target = "callee5")} + ) + private void compatibleShapesButCannotCoerce() { + } + } + """, + ) + } + + @Test + @DisplayName("WrapOperation") + fun wrapOperation() { + doTest( + """ + package test; + + import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapOperation; + import com.llamalad7.mixinextras.expression.Definition; + import com.llamalad7.mixinextras.expression.Expression; + import com.llamalad7.mixinextras.injector.wrapoperation.Operation; + import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + import org.spongepowered.asm.mixin.Mixin; + import org.spongepowered.asm.mixin.injection.At; + import org.spongepowered.asm.mixin.injection.Coerce; + + @Mixin(MixedInWrapOperation.class) + class TestMixin { + @WrapOperation( + method = "caller", at = {@At(value = "INVOKE", target = "callee1"), @At(value = "INVOKE", target = "callee2")} + ) + private void incompatibleShapes() { + } + + @Definition(id = "callee7", method = "callee7") + @Expression({"? == 3", "callee7(?, ?)"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void noWorkingIntLikeAnchor() { + } + + @WrapOperation( + method = "caller3", + at = {@At(value = "INVOKE", target = "callee8"), @At(value = "INVOKE", target = "callee9")} + ) + private void doesNotTryToCoerceOperationToReconcileDifferingParamCounts() { + } + + @WrapOperation( + method = "caller3", + at = {@At(value = "INVOKE", target = "callee8"), @At(value = "INVOKE", target = "callee9")} + ) + private void coercingTheOperationIsAllowedIfTheUserReallyWantsTo(Object arg, @Coerce Object arg2, @Coerce Object arg3) { + } + } + """, + ) + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCase.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCase.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCase.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,20 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.GenericOneParam; -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.GenericTwoParams; -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.List; -import java.util.Map; - -@Mixin(MixedInGeneric.class) -public class TestMixin { - - @Inject(method = "genericMethod", at = @At("RETURN")) - private void injectGeneric(String noGenerics, GenericOneParam oneParam, GenericTwoParams twoParams, GenericOneParam> nestedParam, Map>>[]> pleaseJava, CallbackInfoReturnable> cir) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCase.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCase.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCase.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,14 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; - -@Mixin(MixedInGeneric.class) -public class TestMixin { - - @Inject(method = "genericMethod", at = @At("RETURN")) - private void injectGeneric() { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCaseComplexReturnType.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCaseComplexReturnType.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCaseComplexReturnType.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,18 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.List; -import java.util.Map; - -@Mixin(MixedInGeneric.class) -public class TestMixin { - - @Inject(method = "returnComplex", at = @At("RETURN")) - private void injectGeneric(CallbackInfoReturnable>>[]>> cir) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCaseComplexReturnType.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCaseComplexReturnType.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/genericCaseComplexReturnType.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,14 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; - -@Mixin(MixedInGeneric.class) -public class TestMixin { - - @Inject(method = "returnComplex", at = @At("RETURN")) - private void injectGeneric() { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCase.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCase.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCase.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,20 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.GenericOneParam; +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.GenericTwoParams; +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; +import java.util.Map; + +@Mixin(MixedInGeneric.class) +public class TestMixin { + + @Inject(method = "genericMethod", at = @At("RETURN")) + private void injectGeneric(String noGenerics, GenericOneParam oneParam, GenericTwoParams twoParams, GenericOneParam> nestedParam, Map>>[]> pleaseJava, CallbackInfoReturnable> cir) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCase.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCase.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCase.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,14 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; + +@Mixin(MixedInGeneric.class) +public class TestMixin { + + @Inject(method = "genericMethod", at = @At("RETURN")) + private void injectGeneric() { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCaseComplexReturnType.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCaseComplexReturnType.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCaseComplexReturnType.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,18 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; +import java.util.Map; + +@Mixin(MixedInGeneric.class) +public class TestMixin { + + @Inject(method = "returnComplex", at = @At("RETURN")) + private void injectGeneric(CallbackInfoReturnable>>[]>> cir) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCaseComplexReturnType.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCaseComplexReturnType.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/genericCaseComplexReturnType.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,14 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInGeneric; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; + +@Mixin(MixedInGeneric.class) +public class TestMixin { + + @Inject(method = "returnComplex", at = @At("RETURN")) + private void injectGeneric() { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/innerCtorWithLocals.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/innerCtorWithLocals.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/innerCtorWithLocals.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInOuter.MixedInInner.class) +public class TestMixin { + + @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) + private void injectCtor(MixedInOuter this$0, String string, CallbackInfo ci, String local1, float local2, int local3) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/innerCtorWithLocals.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/innerCtorWithLocals.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/innerCtorWithLocals.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInOuter.MixedInInner.class) +public class TestMixin { + + @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) + private void injectCtor(String string, CallbackInfo ci, String local1, float local2, int local3) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/multiTarget.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/multiTarget.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/multiTarget.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,51 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInComplex; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.io.Serializable; + +@Mixin(MixedInComplex.class) +class TestMixin { + @Inject(method = {"method1", "method2"}, at = @At("RETURN")) + private void inject1(CallbackInfo ci) { + } + + @Inject(method = {"method2", "method3"}, at = @At("RETURN")) + private void inject2(@Coerce CallbackInfo ci) { + } + + @Inject(method = {"method1", "method3"}, at = @At("RETURN")) + private void inject3(@Coerce Serializable arg, @Coerce CallbackInfo ci) { + } + + @Inject(method = {"method3", "method4"}, at = @At("RETURN")) + private void inject4(CallbackInfoReturnable cir) { + } + + @Inject(method = {"method1", "method2"}, at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + private void inject5(CallbackInfo ci) { + } + + @Inject(method = {"method1", "method5"}, at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + private void inject6(@Coerce CharSequence arg, CallbackInfo ci, int local1, @Coerce CharSequence local2, @Coerce char local3) { + } + + @Inject(method = {"method5", "method6"}, at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + private void inject7(CharSequence arg, CallbackInfo ci, int local1, String local2, int local3, int local4, char local5) { + } + + @Inject(method = {"method1", "method3"}, at = @At("RETURN")) + private void inject8(@Coerce Object arg, @Coerce CallbackInfo ci) { + } + + @Inject(method = {"method7", "method8"}, at = @At("HEAD")) + private void inject9(CallbackInfo ci) { + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/multiTarget.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/multiTarget.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/multiTarget.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,47 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInComplex; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(MixedInComplex.class) +class TestMixin { + @Inject(method = {"method1", "method2"}, at = @At("RETURN")) + private void inject1() { + } + + @Inject(method = {"method2", "method3"}, at = @At("RETURN")) + private void inject2() { + } + + @Inject(method = {"method1", "method3"}, at = @At("RETURN")) + private void inject3() { + } + + @Inject(method = {"method3", "method4"}, at = @At("RETURN")) + private void inject4() { + } + + @Inject(method = {"method1", "method2"}, at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + private void inject5() { + } + + @Inject(method = {"method1", "method5"}, at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + private void inject6() { + } + + @Inject(method = {"method5", "method6"}, at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD) + private void inject7() { + } + + @Inject(method = {"method1", "method3"}, at = @At("RETURN")) + private String inject8(@Coerce Object arg, @Coerce CallbackInfo ci) { + } + + @Inject(method = {"method7", "method8"}, at = @At("HEAD")) + private void inject9(@Coerce int a, CallbackInfo ci) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCase.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCase.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCase.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, int i, CallbackInfo ci) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCase.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCase.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCase.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, CallbackInfo ci) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCaseWithMixinExtrasSugar.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCaseWithMixinExtrasSugar.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCaseWithMixinExtrasSugar.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,16 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, int i, CallbackInfo ci, @Local String str) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCaseWithMixinExtrasSugar.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCaseWithMixinExtrasSugar.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleCaseWithMixinExtrasSugar.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,16 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, CallbackInfo ci, @Local String str) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleInnerCtor.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleInnerCtor.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleInnerCtor.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInOuter.MixedInInner.class) +public class TestMixin { + + @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) + private void injectCtor(MixedInOuter this$0, String string, CallbackInfo ci) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleInnerCtor.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleInnerCtor.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleInnerCtor.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInOuter.MixedInInner.class) +public class TestMixin { + + @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) + private void injectCtor(String string, CallbackInfo ci) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleMethodWithInnerType.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleMethodWithInnerType.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleMethodWithInnerType.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(MixedInOuter.class) +public class TestMixin { + + @Inject(method = "methodWithInnerType", at = @At("RETURN")) + private void injectCtor(MixedInOuter.MixedInInner param, CallbackInfoReturnable cir) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleMethodWithInnerType.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleMethodWithInnerType.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/simpleMethodWithInnerType.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,14 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; + +@Mixin(MixedInOuter.class) +public class TestMixin { + + @Inject(method = "methodWithInnerType", at = @At("RETURN")) + private void injectCtor() { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withCapturedLocals.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withCapturedLocals.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withCapturedLocals.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, int i, CallbackInfo ci, String local1, float local2, int local3) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withCapturedLocals.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withCapturedLocals.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withCapturedLocals.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, CallbackInfo ci, String local1, float local2, int local3) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withoutCI.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withoutCI.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withoutCI.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, int i, CallbackInfo ci) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withoutCI.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withoutCI.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/inject/withoutCI.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,14 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @Inject(method = "simpleMethod", at = @At("RETURN")) + private void injectCtor(String string, String invalidLocal) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/injectWithoutCI.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/injectWithoutCI.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/injectWithoutCI.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, int i, CallbackInfo ci) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/injectWithoutCI.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/injectWithoutCI.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/injectWithoutCI.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, String invalidLocal) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/innerCtorWithLocals.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/innerCtorWithLocals.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/innerCtorWithLocals.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInOuter.MixedInInner.class) -public class TestMixin { - - @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) - private void injectCtor(MixedInOuter this$0, String string, CallbackInfo ci, String local1, float local2, int local3) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/innerCtorWithLocals.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/innerCtorWithLocals.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/innerCtorWithLocals.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInOuter.MixedInInner.class) -public class TestMixin { - - @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) - private void injectCtor(String string, CallbackInfo ci, String local1, float local2, int local3) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArg.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArg.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArg.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,42 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyArg; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(MixedInModifyArg.class) +class TestMixin { + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String test1(String x) { + return x; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String test2(String x) { + return x; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private int test3(int y) { + return y; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1"), index = 1) + private int test4(int y) { + return y; + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String test5(String x, int y) { + return x; + } + + @ModifyArg( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee1")} + ) + private int test6(int y) { + return y; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArg.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArg.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArg.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,36 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyArg; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(MixedInModifyArg.class) +class TestMixin { + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test1() { + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String test2() { + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private int test3() { + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1"), index = 1) + private String test4() { + } + + @ModifyArg(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test5(String x, int y) { + } + + @ModifyArg( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee1")} + ) + private void test6() { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArgs.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArgs.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArgs.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,16 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArgs; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.invoke.arg.Args; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "Ljava/lang/Integer;parseInt(Ljava/lang/String;I)I")) - private void inject(Args args) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArgs.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArgs.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyArgs.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,16 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArgs; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.invoke.arg.Args; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "Ljava/lang/Integer;parseInt(Ljava/lang/String;I)I")) - private void inject(String stringToParse, int base) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyConstant.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyConstant.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyConstant.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,53 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyConstant; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import java.io.Serializable; + +@Mixin(MixedInModifyConstant.class) +class TestMixin { + @ModifyConstant(method = "method1", constant = @Constant(intValue = 42)) + private int test1(int constant) { + return constant; + } + + @ModifyConstant(method = "method1", constant = @Constant(intValue = 42)) + private int test2(@Coerce short shouldBePreserved) { + return 0; + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Long.class)) + private static Class test3(Class constant) { + return constant; + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Integer.class)) + private boolean test4(Object instance, Class type) { + return false; + } + + @ModifyConstant( + method = "method1", + constant = {@Constant(classValue = Long.class), @Constant(stringValue = "hello")} + ) + private @Coerce Serializable test5(@Coerce Serializable constant) { + return constant; + } + + @ModifyConstant( + method = "method1", + constant = {@Constant(classValue = Integer.class), @Constant(classValue = Long.class)} + ) + private static Class test6(@Coerce Object o, Class aClass) { + return aClass; + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Integer.class)) + private boolean test7(Object obj, Class clazz, @Coerce Serializable shouldBePreserved) { + return false; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyConstant.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyConstant.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyConstant.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,46 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyConstant; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import java.io.Serializable; + +@Mixin(MixedInModifyConstant.class) +class TestMixin { + @ModifyConstant(method = "method1", constant = @Constant(intValue = 42)) + private void test1() { + } + + @ModifyConstant(method = "method1", constant = @Constant(intValue = 42)) + private void test2(@Coerce short shouldBePreserved) { + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Long.class)) + private static void test3() { + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Integer.class)) + private void test4() { + } + + @ModifyConstant( + method = "method1", + constant = {@Constant(classValue = Long.class), @Constant(stringValue = "hello")} + ) + private void test5() { + } + + @ModifyConstant( + method = "method1", + constant = {@Constant(classValue = Integer.class), @Constant(classValue = Long.class)} + ) + private static void test6() { + } + + @ModifyConstant(method = "method1", constant = @Constant(classValue = Integer.class)) + private void test7(Object obj, Class clazz, @Coerce Serializable shouldBePreserved) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyExpressionValue.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyExpressionValue.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyExpressionValue.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,84 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyExpressionValue; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +import java.io.Serializable; + +@Mixin(MixedInModifyExpressionValue.class) +class TestMixin { + @Expression("42") + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private int test1(int original) { // expect-template + return original; + } + + @Expression("42") + @ModifyExpressionValue( + method = "method1", + at = {@At("MIXINEXTRAS:EXPRESSION"), @At(value = "CONSTANT", args = "intValue=101")} + ) + private int test2(int original) { + return original; + } + + @Definition(id = "callee1", method = "callee1") + @Expression({"42", "callee1()"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private char test3(char original) { + return original; + } + + @Definition(id = "callee2", method = "callee2") + @Expression({"42", "callee2()"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private @Coerce char test4(@Coerce char original) { + return original; + } + + @Definition(id = "callee2", method = "callee2") + @Expression({"42", "callee2()"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private int test5(int shouldBePreserved) { + return shouldBePreserved; + } + + @Expression("'a'") + @ModifyExpressionValue(method = "method2", at = @At("MIXINEXTRAS:EXPRESSION")) + private char test6(char shouldBePreserved, String a) { + return shouldBePreserved; + } + + @Expression("'a'") + @ModifyExpressionValue(method = "method2", at = @At("MIXINEXTRAS:EXPRESSION")) + private byte test7(byte shouldBePreserved, @Coerce Serializable a, long b) { + return shouldBePreserved; + } + + @ModifyExpressionValue( + method = "method2", + at = {@At(value = "INVOKE", target = "callee3"), @At(value = "INVOKE", target = "callee4")} + ) + private @Coerce Number[] test8(@Coerce Number[] original) { + return null; + } + + @ModifyExpressionValue( + method = "method2", + at = {@At(value = "INVOKE", target = "callee3"), @At(value = "INVOKE", target = "callee4")} + ) + private @Coerce Number[] test9(@Coerce Comparable[] original) { + return null; + } + + @Expression({"42", "101"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private int test10(int original) { // expect-template + return original; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyExpressionValue.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyExpressionValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyExpressionValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,74 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyExpressionValue; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +import java.io.Serializable; + +@Mixin(MixedInModifyExpressionValue.class) +class TestMixin { + @Expression("42") + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test1() { // expect-template + } + + @Expression("42") + @ModifyExpressionValue( + method = "method1", + at = {@At("MIXINEXTRAS:EXPRESSION"), @At(value = "CONSTANT", args = "intValue=101")} + ) + private void test2() { + } + + @Definition(id = "callee1", method = "callee1") + @Expression({"42", "callee1()"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test3() { + } + + @Definition(id = "callee2", method = "callee2") + @Expression({"42", "callee2()"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test4(@Coerce char original) { + } + + @Definition(id = "callee2", method = "callee2") + @Expression({"42", "callee2()"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test5(int shouldBePreserved) { + } + + @Expression("'a'") + @ModifyExpressionValue(method = "method2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test6(char shouldBePreserved, String a) { + } + + @Expression("'a'") + @ModifyExpressionValue(method = "method2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test7(byte shouldBePreserved, @Coerce Serializable a, long b) { + } + + @ModifyExpressionValue( + method = "method2", + at = {@At(value = "INVOKE", target = "callee3"), @At(value = "INVOKE", target = "callee4")} + ) + private void test8() { + } + + @ModifyExpressionValue( + method = "method2", + at = {@At(value = "INVOKE", target = "callee3"), @At(value = "INVOKE", target = "callee4")} + ) + private void test9(@Coerce Comparable[] original) { + } + + @Expression({"42", "101"}) + @ModifyExpressionValue(method = "method1", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test10() { // expect-template + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReceiver.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReceiver.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReceiver.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,54 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyReceiver; +import com.llamalad7.mixinextras.injector.ModifyReceiver; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +@Mixin(MixedInModifyReceiver.class) +class TestMixin { + @ModifyReceiver(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private MixedInModifyReceiver test1(MixedInModifyReceiver instance, String x, int y) { + return instance; + } + + @ModifyReceiver( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static MixedInModifyReceiver test2(MixedInModifyReceiver instance, @Coerce char c, @Coerce short i, int j) { + return instance; + } + + @ModifyReceiver(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private MixedInModifyReceiver test3(@Coerce Object instance, @Coerce Object x, @Coerce char y) { + return null; + } + + @ModifyReceiver(method = "caller", at = @At(value = "FIELD", target = "test")) + private MixedInModifyReceiver test4(MixedInModifyReceiver instance, String value) { + return instance; + } + + @ModifyReceiver( + method = "caller", + at = {@At(value = "INVOKE", target = "callee1"), @At(value = "FIELD", target = "test")} + ) + private static MixedInModifyReceiver test5(MixedInModifyReceiver instance, String s, @Coerce char c) { + return instance; + } + + @ModifyReceiver(method = "caller", at = @At(value = "FIELD", target = "test2")) + private MixedInModifyReceiver test6(MixedInModifyReceiver instance, char c) { + return instance; + } + + @ModifyReceiver( + method = "caller", + at = {@At(value = "INVOKE", target = "longValue"), @At(value = "FIELD", target = "test2", opcode = 180)} + ) + private @Coerce Comparable> test7(@Coerce Comparable> instance) { + return instance; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReceiver.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReceiver.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReceiver.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,46 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyReceiver; +import com.llamalad7.mixinextras.injector.ModifyReceiver; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(MixedInModifyReceiver.class) +class TestMixin { + @ModifyReceiver(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test1() { + } + + @ModifyReceiver( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static void test2() { + } + + @ModifyReceiver(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test3(@Coerce Object instance, @Coerce Object x, @Coerce char y) { + } + + @ModifyReceiver(method = "caller", at = @At(value = "FIELD", target = "test")) + private void test4() { + } + + @ModifyReceiver( + method = "caller", + at = {@At(value = "INVOKE", target = "callee1"), @At(value = "FIELD", target = "test")} + ) + private static void test5() { + } + + @ModifyReceiver(method = "caller", at = @At(value = "FIELD", target = "test2")) + private void test6() { + } + + @ModifyReceiver( + method = "caller", + at = {@At(value = "INVOKE", target = "longValue"), @At(value = "FIELD", target = "test2", opcode = 180)} + ) + private void test7() { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReturnValue.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReturnValue.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReturnValue.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,42 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyReturnValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +import java.io.Serializable; + +@Mixin(MixedInModifyReturnValue.class) +class TestMixin { + @ModifyReturnValue(method = "method1", at = @At("RETURN")) + private String test1(String original) { + return original; + } + + @ModifyReturnValue(method = {"method1", "method2"}, at = @At("RETURN")) + private @Coerce Serializable test2(@Coerce Serializable original) { + return original; + } + + @ModifyReturnValue(method = "method1", at = @At("RETURN")) + private String test3(@Coerce CharSequence original) { + return null; + } + + @ModifyReturnValue(method = {"method2", "method3"}, at = @At("RETURN")) + private static @Coerce Number test4(@Coerce Number original) { + return original; + } + + @ModifyReturnValue(method = {"method4", "method5"}, at = @At("RETURN")) + private @Coerce char test5(@Coerce char original, String a) { + return original; + } + + @ModifyReturnValue(method = {"method4", "method5"}, at = @At("RETURN")) + private @Coerce char test6(@Coerce char original, String a, @Coerce short b) { + return original; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReturnValue.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReturnValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyReturnValue.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,34 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyReturnValue; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +@Mixin(MixedInModifyReturnValue.class) +class TestMixin { + @ModifyReturnValue(method = "method1", at = @At("RETURN")) + private void test1() { + } + + @ModifyReturnValue(method = {"method1", "method2"}, at = @At("RETURN")) + private void test2() { + } + + @ModifyReturnValue(method = "method1", at = @At("RETURN")) + private void test3(@Coerce CharSequence original) { + } + + @ModifyReturnValue(method = {"method2", "method3"}, at = @At("RETURN")) + private static void test4() { + } + + @ModifyReturnValue(method = {"method4", "method5"}, at = @At("RETURN")) + private void test5(@Coerce char original, String a) { + } + + @ModifyReturnValue(method = {"method4", "method5"}, at = @At("RETURN")) + private void test6(@Coerce char original, String a, @Coerce short b) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyVariable.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyVariable.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyVariable.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,39 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyVariable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(MixedInModifyVariable.class) +class TestMixin { + @ModifyVariable(method = "method1", at = @At("RETURN")) + private String test1(String arg) { + return arg; + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private int test2(int local1) { + return local1; + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private CharSequence test3(CharSequence local2) { + return local2; + } + + @ModifyVariable(method = "method3", at = @At("RETURN"), index = 4) + private int test4(int local3) { + return local3; + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private char test5(char original, String arg) { + return original; + } + + @ModifyVariable(method = {"method1", "method3"}, at = @At("RETURN")) + private String test6(String original) { + return original; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyVariable.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyVariable.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyVariable.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,33 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInModifyVariable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(MixedInModifyVariable.class) +class TestMixin { + @ModifyVariable(method = "method1", at = @At("RETURN")) + private void test1() { + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private int test2() { + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private CharSequence test3() { + } + + @ModifyVariable(method = "method3", at = @At("RETURN"), index = 4) + private String test4() { + } + + @ModifyVariable(method = "method1", at = @At("RETURN")) + private void test5(char original, String arg) { + } + + @ModifyVariable(method = {"method1", "method3"}, at = @At("RETURN")) + private void test6() { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/returnTypeOnly.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/returnTypeOnly.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/returnTypeOnly.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "parseInt")) + private void inject(Args args, String string, int i) { + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/returnTypeOnly.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/returnTypeOnly.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/returnTypeOnly.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "parseInt")) + private int inject(Args args, String string, int i) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/simple.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/simple.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/simple.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,15 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "Ljava/lang/Integer;parseInt(Ljava/lang/String;I)I")) + private void inject(Args args) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/simple.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/simple.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/modifyargs/simple.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,14 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArgs; + +@Mixin(MixedInSimple.class) +public class TestMixin { + + @ModifyArgs(method = "simpleMethod", at = @At(value = "INVOKE", target = "Ljava/lang/Integer;parseInt(Ljava/lang/String;I)I")) + private void inject(String stringToParse, int base) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/redirect.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/redirect.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/redirect.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,64 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInRedirect; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.io.Serializable; + +@Mixin(MixedInRedirect.class) +class TestMixin { + @Redirect(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test1(MixedInRedirect instance, String x, int y) { + } + + @Redirect( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static void test2(MixedInRedirect instance, @Coerce char c, @Coerce short i, int j) { + } + + @Redirect(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test3(@Coerce Object instance, @Coerce Object x, @Coerce char y) { + } + + @Redirect(method = "caller", at = @At(value = "FIELD", target = "test")) + private void test4(MixedInRedirect instance, String value) { + } + + @Redirect( + method = "caller", + at = {@At(value = "INVOKE", target = "callee1"), @At(value = "FIELD", target = "test")} + ) + private static void test5(MixedInRedirect instance, String s, @Coerce char c) { + } + + @Redirect( + method = "caller", + at = {@At(value = "INVOKE", target = "intValue"), @At(value = "FIELD", target = "test2", opcode = 180)} + ) + private @Coerce char test6(@Coerce Comparable> instance) { + return 0; + } + + @Redirect(method = "caller", at = @At(value = "CONSTANT", args = "classValue=java/lang/Integer")) + private boolean test7(Object instance, Class type) { + return false; + } + + @Redirect( + method = "caller", + at = {@At(value = "CONSTANT", args = "classValue=java/lang/Integer"), @At(value = "INVOKE", target = "callee4")} + ) + private Class test8(@Coerce Object instance, Class aClass) { + return aClass; + } + + @Redirect(method = "caller2", at = @At(value = "CONSTANT", args = "classValue=java/lang/Integer")) + private boolean test7(Object obj, Class clazz, @Coerce Serializable shouldBePreserved) { + return false; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/redirect.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/redirect.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/redirect.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,59 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInRedirect; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.io.Serializable; + +@Mixin(MixedInRedirect.class) +class TestMixin { + @Redirect(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test1() { + } + + @Redirect( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static void test2() { + } + + @Redirect(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String test3(@Coerce Object instance, @Coerce Object x, @Coerce char y) { + } + + @Redirect(method = "caller", at = @At(value = "FIELD", target = "test")) + private void test4() { + } + + @Redirect( + method = "caller", + at = {@At(value = "INVOKE", target = "callee1"), @At(value = "FIELD", target = "test")} + ) + private static void test5() { + } + + @Redirect( + method = "caller", + at = {@At(value = "INVOKE", target = "intValue"), @At(value = "FIELD", target = "test2", opcode = 180)} + ) + private void test6() { + } + + @Redirect(method = "caller", at = @At(value = "CONSTANT", args = "classValue=java/lang/Integer")) + private void test7() { + } + + @Redirect( + method = "caller", + at = {@At(value = "CONSTANT", args = "classValue=java/lang/Integer"), @At(value = "INVOKE", target = "callee4")} + ) + private void test8() { + } + + @Redirect(method = "caller2", at = @At(value = "CONSTANT", args = "classValue=java/lang/Integer")) + private void test7(Object obj, Class clazz, @Coerce Serializable shouldBePreserved) { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCase.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCase.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCase.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, int i, CallbackInfo ci) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCase.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCase.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCase.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, CallbackInfo ci) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCaseWithMixinExtrasSugar.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCaseWithMixinExtrasSugar.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCaseWithMixinExtrasSugar.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,16 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import com.llamalad7.mixinextras.sugar.Local; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, int i, CallbackInfo ci, @Local String str) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCaseWithMixinExtrasSugar.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCaseWithMixinExtrasSugar.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleCaseWithMixinExtrasSugar.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,16 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import com.llamalad7.mixinextras.sugar.Local; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, CallbackInfo ci, @Local String str) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleInnerCtor.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleInnerCtor.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleInnerCtor.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInOuter.MixedInInner.class) -public class TestMixin { - - @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) - private void injectCtor(MixedInOuter this$0, String string, CallbackInfo ci) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleInnerCtor.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleInnerCtor.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleInnerCtor.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInOuter.MixedInInner.class) -public class TestMixin { - - @Inject(method = "(Lcom/demonwav/mcdev/mixintestdata/invalidInjectorMethodSignatureFix/MixedInOuter;Ljava/lang/String;)V", at = @At("RETURN")) - private void injectCtor(String string, CallbackInfo ci) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleMethodWithInnerType.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleMethodWithInnerType.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleMethodWithInnerType.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,16 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(MixedInOuter.class) -public class TestMixin { - - @Inject(method = "methodWithInnerType", at = @At("RETURN")) - private void injectCtor(MixedInOuter.MixedInInner param, CallbackInfoReturnable cir) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleMethodWithInnerType.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleMethodWithInnerType.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/simpleMethodWithInnerType.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInOuter; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInOuter.class) -public class TestMixin { - - @Inject(method = "methodWithInnerType", at = @At("RETURN")) - private void injectCtor() { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/withCapturedLocals.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/withCapturedLocals.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/withCapturedLocals.after.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, int i, CallbackInfo ci, String local1, float local2, int local3) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/withCapturedLocals.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/withCapturedLocals.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/withCapturedLocals.java (revision 739fc17f869706c3e5da886f5258c0e52f9f2d0d) @@ -1,15 +0,0 @@ -package test; - -import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInSimple; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(MixedInSimple.class) -public class TestMixin { - - @Inject(method = "simpleMethod", at = @At("RETURN")) - private void injectCtor(String string, CallbackInfo ci, String local1, float local2, int local3) { - } -} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapMethod.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapMethod.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapMethod.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,34 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapMethod; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Coerce; + +import java.util.Collection; + +@Mixin(MixedInWrapMethod.class) +class TestMixin { + @WrapMethod(method = "method1") + private void test1(String arg, Operation original) { + } + + @WrapMethod(method = "method1") + private void test2(@Coerce CharSequence argHopefullyPreserved, Operation original) { + } + + @WrapMethod(method = {"method1", "method3"}) + private void test3(@Coerce CharSequence arg, Operation original) { + } + + @WrapMethod(method = {"method2", "method4"}) + private @Coerce Number test4(Operation original) { + return null; + } + + @WrapMethod(method = {"method5", "method6"}) + private @Coerce Number test5(@Coerce Collection list, @Coerce char c, Operation original) { + return null; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapMethod.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapMethod.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapMethod.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,30 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapMethod; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Coerce; + +@Mixin(MixedInWrapMethod.class) +class TestMixin { + @WrapMethod(method = "method1") + private @Coerce String test1() { + } + + @WrapMethod(method = "method1") + private String test2(@Coerce CharSequence argHopefullyPreserved, Operation original) { + } + + @WrapMethod(method = {"method1", "method3"}) + private void test3() { + } + + @WrapMethod(method = {"method2", "method4"}) + private void test4() { + } + + @WrapMethod(method = {"method5", "method6"}) + private void test5() { + } +} Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapOperation.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapOperation.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapOperation.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,86 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapOperation; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +import java.io.Serializable; + +@Mixin(MixedInWrapOperation.class) +class TestMixin { + @WrapOperation(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test1(MixedInWrapOperation instance, String x, int y, Operation original) { + } + + @WrapOperation( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static void test2(MixedInWrapOperation instance, @Coerce char c, @Coerce short i, int j, Operation original) { + } + + @WrapOperation(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test3(@Coerce Object instance, @Coerce Object x, @Coerce char y, Operation original) { + } + + @WrapOperation(method = "caller", at = @At(value = "FIELD", target = "test")) + private void test4(MixedInWrapOperation instance, String value, Operation original) { + } + + @WrapOperation( + method = "caller", + at = {@At(value = "INVOKE", target = "intValue"), @At(value = "FIELD", target = "test2", opcode = 180)} + ) + private @Coerce char test5(@Coerce Comparable> instance, Operation original) { + return 0; + } + + @WrapOperation(method = "caller", at = @At(value = "CONSTANT", args = "classValue=java/lang/Integer")) + private boolean test6(Object object, Operation original) { + return false; + } + + @Expression("? == 3") + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean test7(int left, int right, Operation original) { // expect-template + return false; + } + + @Expression({"? == 3", "? == 4"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean test8(int left, int right, Operation original) { // expect-template + return false; + } + + @Definition(id = "callee4", method = "callee4") + @Expression({"? == 3", "callee4(?, ?)"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private @Coerce boolean test9(char c, char c1, Operation original) { + return false; + } + + @Definition(id = "callee5", method = "callee5") + @Expression({"? == 3", "callee5(?, ?)"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean test10(int i, @Coerce short i1, Operation original) { + return false; + } + + @Definition(id = "callee6", method = "callee6") + @Expression({"? == 3", "callee6(?, ?)"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean test11(short i, @Coerce short i1, Operation original) { + return false; + } + + @Expression("? == 3") + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private boolean test12(int a, @Coerce boolean b, Operation original) { + return b; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapOperation.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapOperation.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapOperation.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,76 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapOperation; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +@Mixin(MixedInWrapOperation.class) +class TestMixin { + @WrapOperation(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test1() { + } + + @WrapOperation( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static void test2() { + } + + @WrapOperation(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private String test3(@Coerce Object instance, @Coerce Object x, @Coerce char y, Operation original) { + } + + @WrapOperation(method = "caller", at = @At(value = "FIELD", target = "test")) + private void test4() { + } + + @WrapOperation( + method = "caller", + at = {@At(value = "INVOKE", target = "intValue"), @At(value = "FIELD", target = "test2", opcode = 180)} + ) + private void test5() { + } + + @WrapOperation(method = "caller", at = @At(value = "CONSTANT", args = "classValue=java/lang/Integer")) + private void test6() { + } + + @Expression("? == 3") + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test7() { // expect-template + } + + @Expression({"? == 3", "? == 4"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test8() { // expect-template + } + + @Definition(id = "callee4", method = "callee4") + @Expression({"? == 3", "callee4(?, ?)"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test9() { + } + + @Definition(id = "callee5", method = "callee5") + @Expression({"? == 3", "callee5(?, ?)"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test10() { + } + + @Definition(id = "callee6", method = "callee6") + @Expression({"? == 3", "callee6(?, ?)"}) + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test11() { + } + + @Expression("? == 3") + @WrapOperation(method = "caller2", at = @At("MIXINEXTRAS:EXPRESSION")) + private void test12(int a, @Coerce boolean b, Operation original) { + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapWithCondition.after.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapWithCondition.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapWithCondition.after.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,46 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapWithCondition; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; + +@Mixin(MixedInWrapWithCondition.class) +class TestMixin { + @WrapWithCondition(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private boolean test1(MixedInWrapWithCondition instance, String x, int y) { + return false; + } + + @WrapWithCondition( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static boolean test2(MixedInWrapWithCondition instance, @Coerce char c, @Coerce short i, int j) { + return false; + } + + @WrapWithCondition(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private boolean test3(@Coerce Object instance, @Coerce Object x, @Coerce char y) { + return false; + } + + @WrapWithCondition(method = "caller", at = @At(value = "FIELD", target = "test")) + private boolean test4(MixedInWrapWithCondition instance, String value) { + return false; + } + + @WrapWithCondition( + method = "caller", + at = {@At(value = "INVOKE", target = "callee1"), @At(value = "FIELD", target = "test")} + ) + private static boolean test5(MixedInWrapWithCondition instance, String s, @Coerce char c) { + return false; + } + + @WrapWithCondition(method = "caller", at = @At(value = "FIELD", target = "test2", opcode = 181)) + private boolean test6(MixedInWrapWithCondition instance, char value) { + return false; + } +} \ No newline at end of file Index: src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapWithCondition.java =================================================================== --- src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapWithCondition.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) +++ src/test/resources/com/demonwav/mcdev/platform/mixin/invalidInjectorMethodSignature/wrapWithCondition.java (revision 8539df1c74008cade4a10470748e1794e7dd6277) @@ -0,0 +1,39 @@ +package test; + +import com.demonwav.mcdev.mixintestdata.invalidInjectorMethodSignatureFix.MixedInWrapWithCondition; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(MixedInWrapWithCondition.class) +class TestMixin { + @WrapWithCondition(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test1() { + } + + @WrapWithCondition( + method = "caller", + at = {@At(value = "INVOKE", target = "callee2"), @At(value = "INVOKE", target = "callee3")} + ) + private static void test2() { + } + + @WrapWithCondition(method = "caller", at = @At(value = "INVOKE", target = "callee1")) + private void test3(@Coerce Object instance, @Coerce Object x, @Coerce char y) { + } + + @WrapWithCondition(method = "caller", at = @At(value = "FIELD", target = "test")) + private void test4() { + } + + @WrapWithCondition( + method = "caller", + at = {@At(value = "INVOKE", target = "callee1"), @At(value = "FIELD", target = "test")} + ) + private static void test5() { + } + + @WrapWithCondition(method = "caller", at = @At(value = "FIELD", target = "test2", opcode = 181)) + private void test6() { + } +}