diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java index 0327b3c4f..ac0aa901d 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeChecker.java @@ -119,6 +119,67 @@ public final class TypeChecker { } } + /** + * Check if a type is a numeric primitive (byte, short, int, long, float, double) + */ + public static boolean isNumericPrimitive(TypeInfo type) { + if (type == null || type.getJavaClass() == null) return false; + Class cls = type.getJavaClass(); + return cls == byte.class || cls == Byte.class || + cls == short.class || cls == Short.class || + cls == int.class || cls == Integer.class || + cls == long.class || cls == Long.class || + cls == float.class || cls == Float.class || + cls == double.class || cls == Double.class; + } + + /** + * Check if a numeric type can be promoted to a target numeric type. + * Follows Java's numeric promotion hierarchy: byte -> short -> int -> long -> float -> double + */ + public static boolean canPromoteNumeric(TypeInfo from, TypeInfo to) { + if (from == null || to == null || from.getJavaClass() == null || to.getJavaClass() == null) return false; + + Class fromClass = from.getJavaClass(); + Class toClass = to.getJavaClass(); + + // Unbox if necessary + if (fromClass == Byte.class) fromClass = byte.class; + if (fromClass == Short.class) fromClass = short.class; + if (fromClass == Integer.class) fromClass = int.class; + if (fromClass == Long.class) fromClass = long.class; + if (fromClass == Float.class) fromClass = float.class; + if (fromClass == Double.class) fromClass = double.class; + + if (toClass == Byte.class) toClass = byte.class; + if (toClass == Short.class) toClass = short.class; + if (toClass == Integer.class) toClass = int.class; + if (toClass == Long.class) toClass = long.class; + if (toClass == Float.class) toClass = float.class; + if (toClass == Double.class) toClass = double.class; + + // Get numeric ranks (higher = wider type) + int fromRank = getNumericRank(fromClass); + int toRank = getNumericRank(toClass); + + return fromRank >= 0 && toRank >= 0 && fromRank <= toRank; + } + + /** + * Get the numeric rank for promotion hierarchy. + * byte=0, short=1, int=2, long=3, float=4, double=5 + */ + public static int getNumericRank(Class cls) { + if (cls == byte.class) return 0; + if (cls == short.class) return 1; + if (cls == int.class) return 2; + if (cls == long.class) return 3; + if (cls == float.class) return 4; + if (cls == double.class) return 5; + return -1; + } + + /** * Check if the type name is a primitive or its wrapper type. */ diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java index f20c4e9d6..9fdf8dac4 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/TypeInfo.java @@ -321,7 +321,7 @@ public class TypeInfo { /** * Find the best matching method overload based on argument types. - * Uses Java's method resolution rules: exact match, then widening conversion, then autoboxing. + * Uses Java's method resolution rules: exact match, then numeric promotion, then widening conversion, then autoboxing. * * @param methodName The name of the method * @param argTypes The types of the arguments being passed @@ -359,7 +359,67 @@ public class TypeInfo { } } - // Phase 2: Try compatible match (widening, autoboxing, subtyping) + // Phase 2: Try numeric promotion match + // For methods with all numeric parameters, find the narrowest common type that all args can promote to + MethodInfo bestNumericMatch = null; + int bestNumericRank = Integer.MAX_VALUE; + + for (MethodInfo method : overloads) { + if (method.getParameterCount() == argTypes.length) { + java.util.List params = method.getParameters(); + + // Check if all parameters and arguments are numeric primitives + boolean allNumeric = true; + for (int i = 0; i < argTypes.length; i++) { + TypeInfo paramType = params.get(i).getTypeInfo(); + TypeInfo argType = argTypes[i]; + if (paramType == null || argType == null || + !TypeChecker.isNumericPrimitive(paramType) || !TypeChecker.isNumericPrimitive(argType)) { + allNumeric = false; + break; + } + } + + if (allNumeric) { + // Check if all parameters are the same numeric type + TypeInfo commonParamType = params.get(0).getTypeInfo(); + boolean allParamsSame = true; + for (int i = 1; i < params.size(); i++) { + TypeInfo paramType = params.get(i).getTypeInfo(); + if (!paramType.equals(commonParamType)) { + allParamsSame = false; + break; + } + } + + // If all parameters are the same numeric type, check if args can promote to it + if (allParamsSame) { + boolean canPromote = true; + for (int i = 0; i < argTypes.length; i++) { + if (!TypeChecker.canPromoteNumeric(argTypes[i], commonParamType)) { + canPromote = false; + break; + } + } + + // If all args can promote, check if this is the narrowest match so far + if (canPromote) { + int paramRank = TypeChecker.getNumericRank(commonParamType.getJavaClass()); + if (paramRank < bestNumericRank) { + bestNumericRank = paramRank; + bestNumericMatch = method; + } + } + } + } + } + } + + if (bestNumericMatch != null) { + return bestNumericMatch; + } + + // Phase 3: Try compatible match (widening, autoboxing, subtyping) for (MethodInfo method : overloads) { if (method.getParameterCount() == argTypes.length) { boolean compatible = true; @@ -376,7 +436,7 @@ public class TypeInfo { } } - // Phase 3: Return first overload as fallback + // Phase 4: Return first overload as fallback return overloads.get(0); }