Implement highlight and type resolution for ScriptTypeInfo parameterized java class

i.e.:
public class Box<T>{
    private T value;
    public Box(T value) { this.value = value; }
    public T get() { return value; }
}
This commit is contained in:
bigguy345 committed 2026-03-10 15:34:04 +02:00
1 parent dbc276daa7
commit 54d8aa48fb
8 files changed
+296 -46

No files matched your search

@@ -370,8 +370,9 @@ public class FieldChainMarker {
if (!currentType.hasField(segment)) {
// Field not found — check if the segment names an inner class.
// This handles chains like Outer.Inner where Inner is a nested type, not a field.
if (currentType instanceof ScriptTypeInfo) {
ScriptTypeInfo innerClass = ((ScriptTypeInfo) currentType).getInnerClass(segment);
TypeInfo rawType = currentType.getRawType();
if (rawType instanceof ScriptTypeInfo) {
ScriptTypeInfo innerClass = ((ScriptTypeInfo) rawType).getInnerClass(segment);
if (innerClass != null) {
return new MarkResult(
new ScriptLine.Mark(pos[0], pos[1], innerClass.getTokenType(), innerClass),
@@ -11,6 +11,7 @@ import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSScriptAnalyzer;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSMethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeRegistry;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.TypeParamInfo;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocParamTag;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocParser;
@@ -88,7 +89,7 @@ public class ScriptDocument {
// (the first declarator "int x," is captured; continuations are scanned manually).
private static final Pattern FIELD_DECL_PATTERN = Pattern.compile(
"\\b([A-Za-z_][a-zA-Z0-9_.<>,[ \\t]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*(=|;|,)");
private static final Pattern NEW_TYPE_PATTERN = Pattern.compile("\\bnew\\s+([A-Za-z_][a-zA-Z0-9_]*)");
private static final Pattern NEW_TYPE_PATTERN = Pattern.compile("\\bnew\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<([^>]*)>)?");
// Cast expression pattern: captures type name inside cast parentheses
// Matches: (Type), (Type[]), (pkg.Type), (Type<Generic>)
@@ -754,18 +755,20 @@ public class ScriptDocument {
final int bodyStart;
final int bodyEnd;
final int modifiers;
final String typeParamsClause;
final String extendsClause;
final String implementsClause;
final JSDocInfo jsDoc;
RawTypeDeclaration(String name, TypeInfo.Kind kind, int declOffset, int bodyStart, int bodyEnd,
int modifiers, String extendsClause, String implementsClause, JSDocInfo jsDoc) {
int modifiers, String typeParamsClause, String extendsClause, String implementsClause, JSDocInfo jsDoc) {
this.name = name;
this.kind = kind;
this.declOffset = declOffset;
this.bodyStart = bodyStart;
this.bodyEnd = bodyEnd;
this.modifiers = modifiers;
this.typeParamsClause = typeParamsClause;
this.extendsClause = extendsClause;
this.implementsClause = implementsClause;
this.jsDoc = jsDoc;
@@ -783,7 +786,7 @@ public class ScriptDocument {
private void parseScriptTypes() {
// ========== PASS 1: Discover all type declarations (flat list) ==========
Pattern typeDecl = Pattern.compile(
"(?:(?:public|private|protected|static|final|abstract)\\s+)*(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:\\(\\))?\\s*(?:extends\\s+([A-Za-z_][a-zA-Z0-9_.]*))?\\s*(?:implements\\s+([A-Za-z_][a-zA-Z0-9_.,\\s]*))?\\s*\\{");
"(?:(?:public|private|protected|static|final|abstract)\\s+)*(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<([^>]*)>)?\\s*(?:\\(\\))?\\s*(?:extends\\s+([A-Za-z_][a-zA-Z0-9_.]*))?\\s*(?:implements\\s+([A-Za-z_][a-zA-Z0-9_.,\\s]*))?\\s*\\{");
List<RawTypeDeclaration> rawDeclarations = new ArrayList<>();
Matcher m = typeDecl.matcher(text);
@@ -793,8 +796,9 @@ public class ScriptDocument {
String kindStr = m.group(1);
String typeName = m.group(2);
String extendsClause = m.group(3);
String implementsClause = m.group(4);
String typeParamsClause = m.group(3);
String extendsClause = m.group(4);
String implementsClause = m.group(5);
int bodyStart = text.indexOf('{', m.start());
int bodyEnd = findMatchingBrace(bodyStart);
@@ -815,7 +819,7 @@ public class ScriptDocument {
rawDeclarations.add(new RawTypeDeclaration(
typeName, kind, m.start(), bodyStart, bodyEnd,
modifiers, extendsClause, implementsClause, jsDoc));
modifiers, typeParamsClause, extendsClause, implementsClause, jsDoc));
}
// ========== PASS 2: Build hierarchy using stack-based containment ==========
@@ -851,6 +855,11 @@ public class ScriptDocument {
scriptType.setJSDocInfo(raw.jsDoc);
}
// Parse generic type parameters (e.g., <E>, <K, V>, <T extends Entity>)
if (raw.typeParamsClause != null && !raw.typeParamsClause.trim().isEmpty()) {
parseTypeParamsClause(raw.typeParamsClause, scriptType, raw.declOffset);
}
// Register in all lookup maps for O(1) access by any naming convention
scriptTypesByFullName.put(scriptType.getFullName(), scriptType);
scriptTypesByDotName.put(scriptType.getDotSeparatedName(), scriptType);
@@ -888,6 +897,67 @@ public class ScriptDocument {
}
}
/**
* Parse a generic type parameters clause like "E", "K, V", or "T extends Entity"
* into TypeParamInfo objects and add them to the script type.
*/
private void parseTypeParamsClause(String clause, ScriptTypeInfo scriptType, int declOffset) {
String[] params = clause.split(",");
for (String param : params) {
String trimmed = param.trim();
if (trimmed.isEmpty()) continue;
int extendsIdx = trimmed.indexOf(" extends ");
if (extendsIdx >= 0) {
String paramName = trimmed.substring(0, extendsIdx).trim();
String boundName = trimmed.substring(extendsIdx + 9).trim();
TypeParamInfo typeParam = new TypeParamInfo(paramName, boundName, null);
TypeInfo boundTypeInfo = resolveType(boundName, declOffset);
if (boundTypeInfo != null && boundTypeInfo.isResolved()) {
typeParam.setBoundTypeInfo(boundTypeInfo);
}
scriptType.addDeclaredTypeParam(typeParam);
} else {
scriptType.addDeclaredTypeParam(new TypeParamInfo(trimmed, null, null));
}
}
}
private TypeInfo parameterizeWithClause(TypeInfo baseType, String typeArgsClause, int position) {
String[] argNames = typeArgsClause.split(",");
List<TypeInfo> typeArgs = new ArrayList<>();
for (String argName : argNames) {
String trimmed = argName.trim();
if (trimmed.isEmpty()) continue;
TypeInfo argType = resolveType(trimmed, position);
if (argType == null) {
argType = TypeInfo.unresolved(trimmed, trimmed);
}
typeArgs.add(argType);
}
if (!typeArgs.isEmpty()) {
return baseType.parameterize(typeArgs);
}
return baseType;
}
private void markTypeParamDeclarations(List<ScriptLine.Mark> marks, int afterNameEnd, ScriptTypeInfo typeInfo) {
int ltPos = text.indexOf('<', afterNameEnd);
if (ltPos < 0 || ltPos > afterNameEnd + 5) return;
int gtPos = text.indexOf('>', ltPos);
if (gtPos < 0) return;
String clause = text.substring(ltPos + 1, gtPos);
int offset = ltPos + 1;
for (TypeParamInfo param : typeInfo.getDeclaredTypeParams()) {
int paramIdx = clause.indexOf(param.getName(), offset - (ltPos + 1));
if (paramIdx >= 0) {
int paramStart = ltPos + 1 + paramIdx;
int paramEnd = paramStart + param.getName().length();
marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.IMPORTED_CLASS, param));
}
}
}
/**
* Parse fields and methods inside a script-defined type.
*/
@@ -3702,15 +3772,20 @@ public class ScriptDocument {
return resolved;
}
// Positional fallback: only applies to simple names (no dots) that could be
// inner classes visible from the current scope. Dot-separated names like
// "Outer.Inner" are already handled by scriptTypesByDotName in resolveType().
if (typeName != null && !typeName.contains(".")) {
// Walk the enclosing type hierarchy upward from the innermost type at this position.
// At each level, check if there's an inner class matching the name.
// This mirrors Java's scoping rules where inner classes shadow outer-scope types.
ScriptTypeInfo enclosing = findEnclosingScriptType(position);
while (enclosing != null) {
// Check if the name is a declared type parameter on this class (e.g., E in class Box<E>)
TypeParamInfo typeParam = enclosing.getDeclaredTypeParam(typeName);
if (typeParam != null) {
TypeInfo boundType = typeParam.getBoundTypeInfo();
if (boundType != null && boundType.isResolved()) {
return TypeInfo.typeParameter(typeName, boundType);
}
return TypeInfo.typeParameter(typeName);
}
// Check inner classes
ScriptTypeInfo inner = enclosing.getInnerClass(typeName);
if (inner != null) {
return inner;
@@ -4827,7 +4902,7 @@ public class ScriptDocument {
// Extended pattern to capture extends and implements clauses
// Groups: 1=class|interface|enum, 2=TypeName, 3=extends clause, 4=implements clause
Pattern classWithInheritance = Pattern.compile(
"\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:\\(\\))?\\s*" +
"\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<[^>]*>)?\\s*(?:\\(\\))?\\s*" +
"(?:(extends)\\s+([A-Za-z_][a-zA-Z0-9_.]*))?\\s*" +
"(?:(implements)\\s+([A-Za-z_][a-zA-Z0-9_.,\\s]*))?\\s*(?=\\{)");
@@ -4867,6 +4942,11 @@ public class ScriptDocument {
}
marks.add(new ScriptLine.Mark(m.start(2), m.end(2), nameType, typeInfo));
// Mark generic type parameter declarations (e.g., <E>, <K, V>)
if (typeInfo != null && typeInfo.hasDeclaredTypeParams()) {
markTypeParamDeclarations(marks, m.end(2), typeInfo);
}
// Mark extends clause
if (m.group(3) != null) {
//Extends key word
@@ -5537,8 +5617,9 @@ public class ScriptDocument {
}
// Regular type - check for method existence using hierarchy search if it's a ScriptTypeInfo
boolean hasMethod = false;
if (receiverType instanceof ScriptTypeInfo) {
hasMethod = ((ScriptTypeInfo) receiverType).hasMethodInHierarchy(methodName);
TypeInfo rawReceiver = receiverType.getRawType();
if (rawReceiver instanceof ScriptTypeInfo) {
hasMethod = ((ScriptTypeInfo) rawReceiver).hasMethodInHierarchy(methodName);
} else {
hasMethod = receiverType.hasMethod(methodName);
}
@@ -6139,6 +6220,13 @@ public class ScriptDocument {
}
TypeInfo baseType = resolveType(typeName, position);
if (baseType == null) return null;
// Parameterize with generic type arguments (e.g., new Box<String>(...))
String typeArgsClause = newMatcher.group(2);
if (typeArgsClause != null && !typeArgsClause.trim().isEmpty()) {
baseType = parameterizeWithClause(baseType, typeArgsClause, position);
}
String rest = expr.substring(newMatcher.end());
int dims = 0;
for (int i = 0; i < rest.length() && rest.charAt(i) != '('; i++) {
@@ -6600,8 +6688,9 @@ public class ScriptDocument {
// Method call - get return type with argument-based overload resolution
// Check for method existence using hierarchy search if it's a ScriptTypeInfo
boolean hasMethod = false;
if (currentType instanceof ScriptTypeInfo) {
hasMethod = ((ScriptTypeInfo) currentType).hasMethodInHierarchy(segment.name);
TypeInfo rawType = currentType.getRawType();
if (rawType instanceof ScriptTypeInfo) {
hasMethod = ((ScriptTypeInfo) rawType).hasMethodInHierarchy(segment.name);
} else {
hasMethod = currentType.hasMethod(segment.name);
}
@@ -6621,10 +6710,11 @@ public class ScriptDocument {
boolean hasField = false;
FieldInfo fieldInfo = null;
if (currentType instanceof ScriptTypeInfo) {
hasField = ((ScriptTypeInfo) currentType).hasFieldInHierarchy(segment.name);
TypeInfo rawType = currentType.getRawType();
if (rawType instanceof ScriptTypeInfo) {
hasField = ((ScriptTypeInfo) rawType).hasFieldInHierarchy(segment.name);
if (hasField) {
fieldInfo = ((ScriptTypeInfo) currentType).getFieldInfoInHierarchy(segment.name);
fieldInfo = currentType.getFieldInfo(segment.name);
}
} else {
hasField = currentType.hasField(segment.name);
@@ -6639,8 +6729,8 @@ public class ScriptDocument {
// Field not found — check if the segment names an inner class.
// This handles chains like Outer.Inner where Inner is a nested type, not a field.
if (currentType instanceof ScriptTypeInfo) {
ScriptTypeInfo innerClass = ((ScriptTypeInfo) currentType).getInnerClass(segment.name);
if (rawType instanceof ScriptTypeInfo) {
ScriptTypeInfo innerClass = ((ScriptTypeInfo) rawType).getInnerClass(segment.name);
if (innerClass != null) {
return innerClass;
}
@@ -7088,7 +7178,14 @@ public class ScriptDocument {
}
// Otherwise treat it as a type name
return resolveType(typeName);
TypeInfo baseType = resolveType(typeName);
if (baseType != null) {
String typeArgsClause = newMatcher.group(2);
if (typeArgsClause != null && !typeArgsClause.trim().isEmpty()) {
baseType = parameterizeWithClause(baseType, typeArgsClause, position);
}
}
return baseType;
}
}
@@ -519,6 +519,11 @@ public class TokenHoverInfo {
private void extractClassInfo(Token token) {
TypeInfo typeInfo = token.getTypeInfo();
if (typeInfo == null) return;
if (typeInfo.isTypeParameter()) {
extractTypeParameterInfo(typeInfo);
return;
}
packageName = getPackageName(typeInfo);
@@ -718,6 +723,19 @@ public class TokenHoverInfo {
}
}
private void extractTypeParameterInfo(TypeInfo typeInfo) {
iconIndicator = "T";
String paramName = typeInfo.getTypeParameterName();
addSegment("type parameter ", TokenType.MODIFIER.getHexColor());
addSegment(paramName, TokenType.IMPORTED_CLASS.getHexColor());
if (typeInfo.getJavaClass() != null && typeInfo.getJavaClass() != Object.class) {
addSegment(" extends ", TokenType.MODIFIER.getHexColor());
addSegment(typeInfo.getSimpleName(), getColorForTypeInfo(typeInfo));
}
}
private void extractMethodCallInfo(Token token) {
MethodCallInfo callInfo = token.getMethodCallInfo();
MethodInfo methodInfo = callInfo != null ? callInfo.getResolvedMethod() : token.getMethodInfo();
@@ -935,6 +953,9 @@ public class TokenHoverInfo {
if (type == null)
return null;
if (type.isTypeParameter())
return null;
TypeInfo base = type.isArray() ? type.getElementType() : type;
if (base == null) base = type;
@@ -30,6 +30,10 @@ public class TypeParamInfo {
return boundTypeInfo;
}
public void setBoundTypeInfo(TypeInfo boundTypeInfo) {
this.boundTypeInfo = boundTypeInfo;
}
/**
* Resolve the bound type using TypeResolver.
* Called during Phase 2 after all types are loaded.
@@ -150,9 +150,12 @@ public final class GenericContext {
return null;
}
// Direct type variable substitution (unresolved types represent type variables like T).
if (!type.isResolved()) {
TypeInfo substitution = resolveTypeVariable(type.getSimpleName());
// Direct type variable substitution.
// Handles unresolved types (Java reflection path) and TypeInfo.typeParameter() (script-defined path).
// Bounded type parameters have the bound's simpleName, so use getTypeParameterName() for the lookup.
if (!type.isResolved() || type.isTypeParameter()) {
String lookupKey = type.isTypeParameter() ? type.getTypeParameterName() : type.getSimpleName();
TypeInfo substitution = resolveTypeVariable(lookupKey);
if (substitution != null) {
return substitution;
}
@@ -2,6 +2,7 @@ package noppes.npcs.client.gui.util.script.interpreter.type;
import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.TypeParamInfo;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodSignature;
import noppes.npcs.client.gui.util.script.interpreter.token.TokenType;
@@ -37,6 +38,9 @@ public class ScriptTypeInfo extends TypeInfo {
// Enum constants (for enum types only) - name -> EnumConstantInfo
private final Map<String, EnumConstantInfo> enumConstants = new HashMap<>();
// Generic type parameters declared on this type (e.g., <E>, <K, V>, <T extends Entity>)
private final List<TypeParamInfo> declaredTypeParams = new ArrayList<>();
// Parent class reference (for inner class resolution)
private ScriptTypeInfo outerClass;
@@ -101,6 +105,94 @@ public class ScriptTypeInfo extends TypeInfo {
public ScriptTypeInfo getOuterClass() { return outerClass; }
public List<ScriptTypeInfo> getInnerClasses() { return innerClasses; }
// ==================== GENERIC TYPE PARAMETERS ====================
public void addDeclaredTypeParam(TypeParamInfo param) {
declaredTypeParams.add(param);
}
public List<TypeParamInfo> getDeclaredTypeParams() {
return declaredTypeParams;
}
public TypeParamInfo getDeclaredTypeParam(String name) {
for (TypeParamInfo param : declaredTypeParams) {
if (param.getName().equals(name)) {
return param;
}
}
return null;
}
public boolean hasDeclaredTypeParams() {
return !declaredTypeParams.isEmpty();
}
@Override
public List<TypeParamInfo> getTypeParams() {
if (!declaredTypeParams.isEmpty()) {
return declaredTypeParams;
}
return super.getTypeParams();
}
@Override
public TypeParamInfo getTypeParam(String name) {
TypeParamInfo param = getDeclaredTypeParam(name);
if (param != null) {
return param;
}
return super.getTypeParam(name);
}
private static MethodInfo substituteMethod(MethodInfo m, GenericContext ctx) {
TypeInfo retType = m.getReturnType();
TypeInfo subRet = ctx.substitute(retType);
List<FieldInfo> params = new ArrayList<>(m.getParameters());
boolean changed = subRet != retType;
for (int i = 0; i < params.size(); i++) {
FieldInfo p = params.get(i);
TypeInfo subP = ctx.substitute(p.getTypeInfo());
if (subP != p.getTypeInfo()) {
params.set(i, FieldInfo.reflectionParam(p.getName(), subP));
changed = true;
}
}
return changed ? MethodInfo.external(m.getName(), subRet, m.getContainingType(),
params, m.getModifiers(), m.getDocumentation()) : m;
}
@Override
public TypeInfo parameterize(List<TypeInfo> typeArgs) {
if (typeArgs == null || typeArgs.isEmpty()) return this;
ScriptTypeInfo result = new ScriptTypeInfo(getSimpleName(), getFullName(), getKind(),
declarationOffset, bodyStart, bodyEnd, modifiers);
result.setRawType(this);
result.setAppliedTypeArgs(typeArgs);
result.superClass = this.superClass;
result.superClassName = this.superClassName;
result.implementedInterfaces.addAll(this.implementedInterfaces);
result.implementedInterfaceNames.addAll(this.implementedInterfaceNames);
result.declaredTypeParams.addAll(this.declaredTypeParams);
result.outerClass = this.outerClass;
GenericContext ctx = GenericContext.forReceiver(result);
// Parameterize methods and fields with the generic context of this type.
for (Map.Entry<String, List<MethodInfo>> entry : this.methods.entrySet()) {
List<MethodInfo> substituted = new ArrayList<>();
for (MethodInfo m : entry.getValue()) substituted.add(substituteMethod(m, ctx));
result.methods.put(entry.getKey(), substituted);
}
for (Map.Entry<String, FieldInfo> entry : this.fields.entrySet()) {
FieldInfo f = entry.getValue();
TypeInfo subType = ctx.substitute(f.getTypeInfo());
result.fields.put(entry.getKey(), subType != f.getTypeInfo()
? FieldInfo.external(f.getName(), subType, null, f.getModifiers()) : f);
}
return result;
}
// ==================== INHERITANCE ====================
/**
@@ -86,6 +86,7 @@ public class TypeInfo {
private boolean isPrimitive; // Whether this is a primitive type (e.g., int, boolean)
private boolean isObjectLiteral;
private String typeParameterName; // Non-null if this TypeInfo represents a type parameter (e.g., "T")
// JavaScript/TypeScript type info (for types from .d.ts files)
private final JSTypeInfo jsTypeInfo; // The JS type info (null if Java type)
@@ -95,11 +96,11 @@ public class TypeInfo {
// Applied type arguments (concrete types provided for generics, e.g., <String, Integer> in Map<String, Integer>)
// These are TypeInfo instances that can themselves be parameterized for nested generics
private final List<TypeInfo> appliedTypeArgs = new ArrayList<>();
private List<TypeInfo> appliedTypeArgs = new ArrayList<>();
// For parameterized types, the raw/base type (e.g., List for List<String>)
// Null for non-parameterized types
private final TypeInfo rawType;
private TypeInfo rawType;
// For array types, the element type (e.g., IItemStack for IItemStack[])
// Null for non-array types
@@ -178,6 +179,35 @@ public class TypeInfo {
return new TypeInfo(simpleName, fullPath, pkg, Kind.UNKNOWN, null, false, null);
}
/**
* Create a TypeInfo representing an unbounded type parameter (e.g., {@code T} in {@code class Box<T>}).
* Uses Object.class for member lookup (erasure semantics) but displays as the parameter name.
*/
public static TypeInfo typeParameter(String paramName) {
TypeInfo ti = new TypeInfo(paramName, paramName, "", Kind.CLASS, Object.class, true, null);
ti.typeParameterName = paramName;
return ti;
}
/**
* Create a TypeInfo representing a bounded type parameter (e.g., {@code T extends Entity}).
* Delegates member lookup to the bound type but records the original parameter name for display.
*/
public static TypeInfo typeParameter(String paramName, TypeInfo boundType) {
TypeInfo ti = new TypeInfo(boundType.simpleName, boundType.fullName, boundType.packageName,
boundType.kind, boundType.javaClass, true, boundType.enclosingType, boundType.jsTypeInfo);
ti.typeParameterName = paramName;
return ti;
}
public boolean isTypeParameter() {
return typeParameterName != null;
}
public String getTypeParameterName() {
return typeParameterName;
}
public static TypeInfo fromClass(Class<?> clazz) {
if (clazz == null) return null;
@@ -541,6 +571,14 @@ public class TypeInfo {
public List<TypeInfo> getAppliedTypeArgs() {
return appliedTypeArgs;
}
protected void setRawType(TypeInfo rawType) {
this.rawType = rawType;
}
protected void setAppliedTypeArgs(List<TypeInfo> args) {
this.appliedTypeArgs = new ArrayList<>(args);
}
/**
* Create a parameterized version of this type with the given type arguments.
@@ -1215,19 +1253,6 @@ public class TypeInfo {
return null;
}
/**
* Resolves a type parameter to its bound TypeInfo.
* For example, if this type has "T extends EntityPlayerMP", resolveTypeParamToTypeInfo("T") returns the TypeInfo for EntityPlayerMP.
* If no type parameter is found with that name, returns null.
*/
public TypeInfo resolveTypeParamToTypeInfo(String typeName) {
TypeParamInfo param = getTypeParam(typeName);
if (param != null) {
return param.getBoundTypeInfo();
}
return null;
}
@Override
public String toString() {
return "TypeInfo{" + fullName + ", " + kind + ", resolved=" + resolved + "}";
@@ -92,9 +92,16 @@ public class TypeSubstitutor {
String typeName = type.getSimpleName();
// Check if this type is itself a type variable
if (!type.isResolved()) {
TypeInfo substitution = bindings.get(typeName);
// Check if this type is itself a type variable.
// Two cases:
// 1. Unresolved TypeInfo (Java reflection path: TypeInfo.fromGenericType creates these for T, E, K, V)
// 2. TypeInfo.typeParameter() (script-defined path: resolved but tagged as a type parameter)
// For tagged type parameters, use getTypeParameterName() as the lookup key — bounded params have
// the bound's simpleName as their simpleName (e.g., "Entity" for T extends Entity), so using
// simpleName would incorrectly look up "Entity" instead of "T".
if (!type.isResolved() || type.isTypeParameter()) {
String lookupKey = type.isTypeParameter() ? type.getTypeParameterName() : typeName;
TypeInfo substitution = bindings.get(lookupKey);
if (substitution != null) {
return substitution;
}