mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 20:07:32 +00:00
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:
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -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;
|
||||
|
||||
|
||||
+4
@@ -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.
|
||||
|
||||
+6
-3
@@ -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 + "}";
|
||||
|
||||
+10
-3
@@ -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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user