From bc05164209c22254bb03e342e444f3f45f3204d5 Mon Sep 17 00:00:00 2001 From: bigguy345 Date: Mon, 29 Dec 2025 16:12:44 +0200 Subject: [PATCH] Proper numeric promotion between java types for fetching the correct overload. i.ie Math.max(20f,20) checks if the second int can be promoted to the nearest matching overload max(float, float), and fetches it --- .../script/interpreter/type/TypeChecker.java | 61 +++++++++++++++++ .../script/interpreter/type/TypeInfo.java | 66 ++++++++++++++++++- 2 files changed, 124 insertions(+), 3 deletions(-) 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); }