Inferred type of 'any' vars upon their first assignment

i.e.:
var npc
npc = event.getNpc()

npc becomes ICustomNpc
This commit is contained in:
bigguy345 committed 2026-01-05 05:25:39 +02:00
1 parent 761b59479e
commit cd942fea30
7 files changed
+80 -15

No files matched your search

@@ -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;
@@ -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()+
@@ -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; }
@@ -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);
@@ -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() +
@@ -362,7 +362,7 @@ public final class MethodInfo {
if (cachedSignature == null) {
List<TypeInfo> 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,
@@ -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