diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java index 12439e28b..5318490de 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java @@ -4480,7 +4480,7 @@ public class ScriptDocument { // Simple variable targetField = resolveVariable(targetName, lhsStart); if (targetField != null) { - targetType = targetField.getDeclaredType(); + targetType = targetField.getTypeInfo(); reflectionField = targetField.getReflectionField(); } } @@ -4494,6 +4494,16 @@ public class ScriptDocument { ExpressionTypeResolver.CURRENT_EXPECTED_TYPE = null; } + // Type inference: If the target field has "any" type and no inferred type yet, + // set the inferred type from the resolved source type (first assignment wins) + if (targetField != null && targetField.canInferType() && targetField.getInferredType() == null + && sourceType != null && sourceType.isResolved()) { + // Don't infer "any" type - that doesn't refine anything + if (!"any".equals(sourceType.getFullName())) { + targetField.setInferredType(sourceType); + } + } + // Determine if this is a script-defined field or external field FieldInfo finalTargetField = targetField; boolean isScriptField = targetField != null && @@ -4590,7 +4600,7 @@ public class ScriptDocument { return; // Field doesn't exist, can't attach assignment } - TypeInfo targetType = targetField.getDeclaredType(); + TypeInfo targetType = targetField.getTypeInfo(); // Resolve the source type with expected type context TypeInfo sourceType; @@ -4601,6 +4611,15 @@ public class ScriptDocument { ExpressionTypeResolver.CURRENT_EXPECTED_TYPE = null; } + // Type inference: If the target field has "any" type and no inferred type yet, + // set the inferred type from the resolved source type + if (targetField.canInferType() && targetField.getInferredType() == null && sourceType != null && sourceType.isResolved()) { + // Don't infer "any" type - that doesn't refine anything + if (!"any".equals(sourceType.getFullName())) { + targetField.setInferredType(sourceType); + } + } + // Create the assignment info // Declaration assignments should NOT check final status - this is the one place where final fields can be assigned AssignmentInfo info = new AssignmentInfo( @@ -5575,14 +5594,14 @@ public class ScriptDocument { if (locals != null && locals.containsKey(varName)) { FieldInfo field = locals.get(varName); if (field.isVisibleAt(position)) { - return field.getDeclaredType(); + return field.getTypeInfo(); } } } // Check global fields if (globalFields.containsKey(varName)) { - return globalFields.get(varName).getDeclaredType(); + return globalFields.get(varName).getTypeInfo(); } return null; diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldAccessInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldAccessInfo.java index dc29943e3..fc87898f7 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldAccessInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldAccessInfo.java @@ -98,7 +98,7 @@ public class FieldAccessInfo { // Check return type compatibility with expected type (e.g., assignment LHS) if (expectedType != null && resolvedField != null) { - TypeInfo fieldType = resolvedField.getDeclaredType(); + TypeInfo fieldType = resolvedField.getTypeInfo(); if (fieldType != null && !TypeChecker.isTypeCompatible(expectedType, fieldType)) { //extra space is necessary for alignment //setError(ErrorType.TYPE_MISMATCH, "Provided type: " + fieldType.getSimpleName()+ diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java index d10218d83..5d6b6efdd 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/field/FieldInfo.java @@ -57,6 +57,11 @@ public final class FieldInfo { // Enum constant specific info private EnumConstantInfo enumConstantInfo; + // Type inference support (for JavaScript "any" typed variables) + // When a variable is declared without type (var x;), it starts as "any" but can be + // refined through assignments or JSDoc comments + private TypeInfo inferredType; + private FieldInfo(String name, Scope scope, TypeInfo declaredType, int declarationOffset, boolean resolved, MethodInfo containingMethod, String documentation, int initStart, int initEnd, int modifiers, @@ -411,12 +416,53 @@ public final class FieldInfo { return reflectionField; } + // ==================== TYPE INFERENCE ==================== + + /** + * Get the inferred type for this field. + * This is set when a variable declared as "any" has its type refined through + * assignment analysis or JSDoc comments. + */ + public TypeInfo getInferredType() { + return inferredType; + } + + /** + * Set the inferred type for this field. + * Used when type inference determines a more specific type than the declared type. + * @param inferredType The refined type based on assignments or JSDoc + */ + public void setInferredType(TypeInfo inferredType) { + this.inferredType = inferredType; + } + + /** + * Check if this field has a type that can be refined (currently "any" type). + */ + public boolean canInferType() { + return declaredType != null && "any".equals(declaredType.getFullName()); + } + + /** + * Get the effective type for this field, considering inference. + * Returns inferredType if available, otherwise declaredType. + */ + public TypeInfo getEffectiveType() { + if (inferredType != null) { + return inferredType; + } + return declaredType; + } + // ==================== BASIC GETTERS ==================== public String getName() { return name; } public Scope getScope() { return scope; } public TypeInfo getDeclaredType() { return declaredType; } - public TypeInfo getTypeInfo() { return declaredType; } // Alias for getDeclaredType + public TypeInfo getTypeInfo() { + // Return the effective type (inferred if available, otherwise declared) + return getEffectiveType(); + } public int getDeclarationOffset() { return declarationOffset; } public boolean isResolved() { return resolved; } public MethodInfo getContainingMethod() { return containingMethod; } diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java index 12b9099ea..18faeaadf 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/hover/TokenHoverInfo.java @@ -719,7 +719,7 @@ public class TokenHoverInfo { } } - TypeInfo declaredType = fieldInfo.getDeclaredType(); + TypeInfo declaredType = fieldInfo.getTypeInfo(); // Try to get modifiers from Java reflection if this is a chained field boolean foundModifiers = false; @@ -825,7 +825,7 @@ public class TokenHoverInfo { } } - TypeInfo declaredType = fieldInfo.getDeclaredType(); + TypeInfo declaredType = fieldInfo.getTypeInfo(); if (declaredType != null) { int typeColor = getColorForTypeInfo(declaredType); addSegment(getName(declaredType), typeColor); @@ -873,7 +873,7 @@ public class TokenHoverInfo { } } - TypeInfo declaredType = fieldInfo.getDeclaredType(); + TypeInfo declaredType = fieldInfo.getTypeInfo(); if (declaredType != null) { int typeColor = getColorForTypeInfo(declaredType); addSegment(getName(declaredType), typeColor); @@ -928,7 +928,7 @@ public class TokenHoverInfo { for (int i = 0; i < params.size(); i++) { if (i > 0) addSegment(", ", TokenType.DEFAULT.getHexColor()); FieldInfo param = params.get(i); - TypeInfo paramType = param.getDeclaredType(); + TypeInfo paramType = param.getTypeInfo(); if (paramType != null) { int paramTypeColor = getColorForTypeInfo(paramType); addSegment(getName(paramType), paramTypeColor); @@ -1025,7 +1025,7 @@ public class TokenHoverInfo { for (int i = 0; i < params.size(); i++) { if (i > 0) addSegment(", ", TokenType.DEFAULT.getHexColor()); FieldInfo param = params.get(i); - TypeInfo paramType = param.getDeclaredType(); + TypeInfo paramType = param.getTypeInfo(); if (paramType != null) { int paramTypeColor = getColorForTypeInfo(paramType); addSegment(getName(paramType), paramTypeColor); diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodCallInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodCallInfo.java index 86ca3b446..dfafad691 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodCallInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodCallInfo.java @@ -362,7 +362,7 @@ public class MethodCallInfo { FieldInfo para = resolvedMethod.getParameters().get(i); TypeInfo argType = arg.getResolvedType(); - TypeInfo paramType = para.getDeclaredType(); + TypeInfo paramType = para.getTypeInfo(); if (argType != null && paramType != null) { if (!TypeChecker.isTypeCompatible(paramType, argType)) { setArgTypeError(i, "Expected " + paramType.getSimpleName() + diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java index b5b1d9b7a..354417161 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/method/MethodInfo.java @@ -362,7 +362,7 @@ public final class MethodInfo { if (cachedSignature == null) { List paramTypes = new ArrayList<>(); for (FieldInfo param : parameters) - paramTypes.add(param.getDeclaredType()); + paramTypes.add(param.getTypeInfo()); cachedSignature = new MethodSignature(name, paramTypes); } return cachedSignature; @@ -584,7 +584,7 @@ public final class MethodInfo { } // Check for unresolved parameter types - TypeInfo paramType = param.getDeclaredType(); + TypeInfo paramType = param.getTypeInfo(); if (paramType == null || !paramType.isResolved()) { String typeName = paramType != null ? paramType.getSimpleName() : "unknown"; addParameterError(param, i, ErrorType.PARAMETER_UNDEFINED, diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java index d79a360ba..afb66fafd 100644 --- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java +++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/type/ScriptTypeInfo.java @@ -910,7 +910,7 @@ public class ScriptTypeInfo extends TypeInfo { return false; for (int i = 0; i < params.size(); i++) { - TypeInfo paramType = params.get(i).getDeclaredType(); + TypeInfo paramType = params.get(i).getTypeInfo(); if (paramType == null) continue; // Unresolved param, skip check