mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-14 00:14:09 +00:00
GenericContext overhaul, removed redundant TypeSubstitutor
This commit is contained in:
1 parent
53446e06c6
commit
f66d2f0193
7 files changed
+155
-222
No files matched your search
@@ -4308,6 +4308,54 @@ public class ScriptDocument {
|
||||
return resolveTypeAndTrackUsage(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively resolve any unresolved type names inside a TypeInfo using position-aware resolution.
|
||||
*
|
||||
* This handles a different concern than {@link GenericContext#substitute(TypeInfo)} —
|
||||
* it does not replace type variables with concrete bindings. Instead, it re-resolves
|
||||
* unresolved simple names (e.g., inner class names like "Node" or script-declared type
|
||||
* parameters like "T") that appear as type arguments in parameterized types.
|
||||
*
|
||||
* For example, if a type resolves to {@code List<Node>} but "Node" is still unresolved,
|
||||
* this will attempt to resolve "Node" using the position-aware
|
||||
* {@link #resolveType(String, int)} which can find inner classes and type parameters
|
||||
* visible at the given position.
|
||||
*
|
||||
* @param type the type whose unresolved components should be re-resolved
|
||||
* @param position the script text offset for scope-aware resolution
|
||||
* @return the type with all resolvable components resolved, or the original if nothing changed
|
||||
*/
|
||||
private TypeInfo substituteTypeParams(TypeInfo type, int position) {
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
if (!type.isResolved() && type.getSimpleName() != null
|
||||
&& !type.getSimpleName().contains(".")) {
|
||||
TypeInfo sub = resolveType(type.getSimpleName(), position);
|
||||
return (sub != null && sub.isResolved()) ? sub : type;
|
||||
}
|
||||
|
||||
if (type.isParameterized()) {
|
||||
List<TypeInfo> args = type.getAppliedTypeArgs();
|
||||
if (args != null && !args.isEmpty()) {
|
||||
List<TypeInfo> substituted = new ArrayList<>(args.size());
|
||||
boolean anyChanged = false;
|
||||
for (TypeInfo arg : args) {
|
||||
TypeInfo sub = substituteTypeParams(arg, position);
|
||||
if (sub != arg)
|
||||
anyChanged = true;
|
||||
substituted.add(sub);
|
||||
}
|
||||
if (anyChanged) {
|
||||
TypeInfo raw = type.getRawType();
|
||||
return raw != null ? raw.parameterize(substituted) : type.parameterize(substituted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position-aware type resolution overload.
|
||||
* Resolves a type name with positional context so that an unqualified inner class name
|
||||
@@ -4330,7 +4378,7 @@ public class ScriptDocument {
|
||||
|
||||
// If already resolved, no need for positional fallback
|
||||
if (resolved != null && resolved.isResolved()) {
|
||||
return resolved;
|
||||
return substituteTypeParams(resolved, position);
|
||||
}
|
||||
|
||||
if (typeName != null && !typeName.contains(".")) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.token.TokenType;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.GenericContext;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeSubstitutor;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.interpreter.method.MethodCallInfo;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
|
||||
|
||||
@@ -18,7 +18,6 @@ import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Metadata for a field (variable) declaration or reference.
|
||||
@@ -165,12 +164,8 @@ public final class FieldInfo {
|
||||
}
|
||||
|
||||
// If the receiver is parameterized, substitute class type variables in the field type
|
||||
if (GenericContext.hasGenerics(containingType)) {
|
||||
Map<String, TypeInfo> receiverBindings = TypeSubstitutor.createBindingsFromReceiver(containingType);
|
||||
if (!receiverBindings.isEmpty()) {
|
||||
type = TypeSubstitutor.substitute(type, receiverBindings);
|
||||
}
|
||||
}
|
||||
if (GenericContext.hasGenerics(containingType))
|
||||
type = GenericContext.forReceiver(containingType).substitute(type);
|
||||
|
||||
// Check if this is an enum constant
|
||||
if (field.isEnumConstant()) {
|
||||
@@ -531,6 +526,38 @@ public final class FieldInfo {
|
||||
public String getDocumentation() { return documentation; }
|
||||
public JSDocInfo getJSDocInfo() { return jsDocInfo; }
|
||||
public void setJSDocInfo(JSDocInfo jsDocInfo) { this.jsDocInfo = jsDocInfo; }
|
||||
|
||||
/**
|
||||
* Create a copy of this field with its declared type substituted using the given context.
|
||||
* Returns {@code this} unchanged if the context is null, the declared type is null,
|
||||
* or substitution produces the same type (identity check).
|
||||
*
|
||||
* Used when a generic type is parameterized (e.g., {@code Box<String>}) and its raw fields
|
||||
* (declared with type variables like T) need to reflect the concrete type arguments.
|
||||
*
|
||||
* Example: for {@code class Box<T> { T value; }}, calling this on the raw field {@code value: T}
|
||||
* with a context binding {@code T → String} produces {@code value: String}.
|
||||
*
|
||||
* @param context the generic context containing type variable bindings; if null, returns {@code this} unchanged
|
||||
* @return a new FieldInfo with the substituted declared type, or {@code this} if no substitution occurred
|
||||
*/
|
||||
public FieldInfo substituteTypeParams(GenericContext context) {
|
||||
if (context == null || declaredType == null) {
|
||||
return this;
|
||||
}
|
||||
TypeInfo subType = context.substitute(declaredType);
|
||||
if (subType == declaredType) {
|
||||
return this;
|
||||
}
|
||||
FieldInfo result = new FieldInfo(name, scope, subType, declarationOffset, resolved,
|
||||
containingMethod, documentation, initStart, initEnd, modifiers, reflectionField);
|
||||
result.jsDocInfo = this.jsDocInfo;
|
||||
result.scopeInfo = this.scopeInfo;
|
||||
result.isVarArg = this.isVarArg;
|
||||
result.inferredType = this.inferredType;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getInitStart() { return initStart; }
|
||||
public int getInitEnd() { return initEnd; }
|
||||
public boolean hasInitializer() { return initStart >= 0 && initEnd > initStart; }
|
||||
|
||||
+38
-9
@@ -11,7 +11,7 @@ import noppes.npcs.client.gui.util.script.interpreter.token.TokenType;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.GenericContext;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.TypeSubstitutor;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.interpreter.type.GenericParameterAdapter;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -173,13 +173,12 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
// If the receiver is parameterized (e.g., List<String>), substitute class type variables (E -> String)
|
||||
Map<String, TypeInfo> receiverBindings = null;
|
||||
GenericContext receiverCtx = null;
|
||||
if (GenericContext.hasGenerics(containingType)) {
|
||||
receiverBindings = TypeSubstitutor.createBindingsFromReceiver(containingType);
|
||||
if (!receiverBindings.isEmpty()) {
|
||||
returnType = TypeSubstitutor.substitute(returnType, receiverBindings);
|
||||
}
|
||||
receiverCtx = GenericContext.forReceiver(containingType);
|
||||
returnType = receiverCtx.substitute(returnType);
|
||||
}
|
||||
|
||||
List<FieldInfo> params = new ArrayList<>();
|
||||
// Use getGenericParameterTypes() to preserve generic information
|
||||
java.lang.reflect.Type[] genericParamTypes = method.getGenericParameterTypes();
|
||||
@@ -190,9 +189,8 @@ public final class MethodInfo {
|
||||
paramType = TypeInfo.fromClass(method.getParameterTypes()[i]);
|
||||
}
|
||||
|
||||
if (receiverBindings != null && !receiverBindings.isEmpty()) {
|
||||
paramType = TypeSubstitutor.substitute(paramType, receiverBindings);
|
||||
}
|
||||
if (receiverCtx != null)
|
||||
paramType = receiverCtx.substitute(paramType);
|
||||
|
||||
// Adapt Object params to receiver generic type args (e.g., Map.get(Object) -> Map.get(K))
|
||||
TypeInfo adapted = GenericParameterAdapter.adaptParameterType(method, containingType, i, paramType);
|
||||
@@ -377,6 +375,37 @@ public final class MethodInfo {
|
||||
*/
|
||||
public boolean hasInheritanceMarker() { return isOverride() || isImplements(); }
|
||||
|
||||
/**
|
||||
* Create a copy of this method with all type variables substituted using the given context.
|
||||
* Applies substitution to both the return type and every parameter type recursively.
|
||||
*
|
||||
* Used when a generic type is parameterized (e.g., {@code Box<String>}) and its raw methods
|
||||
* (declared with type variables like T) need to reflect the concrete type arguments.
|
||||
*
|
||||
* Example: for {@code class Box<T> { T get(); }}, calling this on the raw {@code get() → T}
|
||||
* with a context binding {@code T → String} produces {@code get() → String}.
|
||||
*
|
||||
* @param context the generic context containing type variable bindings; if null, returns {@code this} unchanged
|
||||
* @return a new MethodInfo with substituted types, or {@code this} if context is null
|
||||
*/
|
||||
public MethodInfo substituteTypeParams(GenericContext context) {
|
||||
if (context == null) {
|
||||
return this;
|
||||
}
|
||||
TypeInfo subReturnType = context.substitute(returnType);
|
||||
List<FieldInfo> subParams = new ArrayList<>(parameters.size());
|
||||
for (FieldInfo param : parameters) {
|
||||
subParams.add(param.substituteTypeParams(context));
|
||||
}
|
||||
MethodInfo result = new MethodInfo(name, subReturnType, containingType, subParams,
|
||||
fullDeclarationOffset, typeOffset, nameOffset, bodyStart, bodyEnd,
|
||||
resolved, isDeclaration, modifiers, documentation, javaMethod);
|
||||
result.jsDocInfo = this.jsDocInfo;
|
||||
result.overridesFrom = this.overridesFrom;
|
||||
result.implementsFrom = this.implementsFrom;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a position is inside this method's body.
|
||||
*/
|
||||
|
||||
+9
-5
@@ -8,12 +8,16 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A context for resolving and substituting declared type parameters (T, E, K, V...)
|
||||
* based on a receiver type.
|
||||
* Single source of truth for generic type variable substitution.
|
||||
*
|
||||
* Policy:
|
||||
* - Prefer applied type arguments when present (e.g. List<String> binds E -> String).
|
||||
* - Fall back to declared bounds when no applied argument exists (e.g. T extends Entity -> Entity).
|
||||
* Resolves type parameters (T, E, K, V...) to concrete types using:
|
||||
* 1. Applied type arguments (e.g., List<String> binds E → String) — priority
|
||||
* 2. Declared bounds fallback (e.g., <T extends Entity> binds T → Entity) — fallback
|
||||
*
|
||||
* Usage patterns:
|
||||
* - {@link #forReceiver(TypeInfo)} — build context from a parameterized receiver type
|
||||
* - {@link #fromBindings(List, List)} — build context from explicit params/args (anti-recursion safe)
|
||||
* - {@link #substitute(TypeInfo)} — instance method with two-tier resolution
|
||||
*/
|
||||
public final class GenericContext {
|
||||
|
||||
|
||||
+3
-24
@@ -153,24 +153,6 @@ public class ScriptTypeInfo extends TypeInfo {
|
||||
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;
|
||||
@@ -198,17 +180,14 @@ public class ScriptTypeInfo extends TypeInfo {
|
||||
// Parameterize methods, constructors, 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));
|
||||
for (MethodInfo m : entry.getValue()) substituted.add(m.substituteTypeParams(ctx));
|
||||
result.methods.put(entry.getKey(), substituted);
|
||||
}
|
||||
for (MethodInfo ctor : this.constructors) {
|
||||
result.constructors.add(substituteMethod(ctor, ctx));
|
||||
result.constructors.add(ctor.substituteTypeParams(ctx));
|
||||
}
|
||||
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);
|
||||
result.fields.put(entry.getKey(), entry.getValue().substituteTypeParams(ctx));
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
|
||||
@@ -796,14 +796,12 @@ public class TypeInfo {
|
||||
if (rawType != null && rawType != this) {
|
||||
List<MethodInfo> rawMethods = rawType.getAllMethods();
|
||||
if (!rawMethods.isEmpty() && isParameterized()) {
|
||||
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
|
||||
if (!bindings.isEmpty()) {
|
||||
List<MethodInfo> substituted = new ArrayList<>(rawMethods.size());
|
||||
for (MethodInfo m : rawMethods) {
|
||||
substituted.add(m.substituteTypeParams(bindings));
|
||||
}
|
||||
return substituted;
|
||||
GenericContext ctx = GenericContext.forReceiver(this);
|
||||
List<MethodInfo> substituted = new ArrayList<>(rawMethods.size());
|
||||
for (MethodInfo m : rawMethods) {
|
||||
substituted.add(m.substituteTypeParams(ctx));
|
||||
}
|
||||
return substituted;
|
||||
}
|
||||
return rawMethods;
|
||||
}
|
||||
@@ -840,14 +838,12 @@ public class TypeInfo {
|
||||
if (rawType != null && rawType != this) {
|
||||
List<FieldInfo> rawFields = rawType.getAllFields();
|
||||
if (!rawFields.isEmpty() && isParameterized()) {
|
||||
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
|
||||
if (!bindings.isEmpty()) {
|
||||
List<FieldInfo> substituted = new ArrayList<>(rawFields.size());
|
||||
for (FieldInfo f : rawFields) {
|
||||
substituted.add(f.substituteTypeParams(bindings));
|
||||
}
|
||||
return substituted;
|
||||
GenericContext ctx = GenericContext.forReceiver(this);
|
||||
List<FieldInfo> substituted = new ArrayList<>(rawFields.size());
|
||||
for (FieldInfo f : rawFields) {
|
||||
substituted.add(f.substituteTypeParams(ctx));
|
||||
}
|
||||
return substituted;
|
||||
}
|
||||
return rawFields;
|
||||
}
|
||||
@@ -1170,8 +1166,8 @@ public class TypeInfo {
|
||||
} else if (rawType != null && rawType != this) {
|
||||
MethodInfo rawMethod = rawType.getMethodInfo(methodName);
|
||||
if (rawMethod != null && isParameterized()) {
|
||||
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
|
||||
if (!bindings.isEmpty()) return rawMethod.substituteTypeParams(bindings);
|
||||
GenericContext ctx = GenericContext.forReceiver(this);
|
||||
return rawMethod.substituteTypeParams(ctx);
|
||||
}
|
||||
return rawMethod;
|
||||
}
|
||||
@@ -1213,13 +1209,11 @@ public class TypeInfo {
|
||||
} else if (rawType != null && rawType != this) {
|
||||
java.util.List<MethodInfo> rawOverloads = rawType.getAllMethodOverloads(methodName);
|
||||
if (!rawOverloads.isEmpty() && isParameterized()) {
|
||||
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
|
||||
if (!bindings.isEmpty()) {
|
||||
for (MethodInfo m : rawOverloads) {
|
||||
overloads.add(m.substituteTypeParams(bindings));
|
||||
}
|
||||
return overloads;
|
||||
GenericContext ctx = GenericContext.forReceiver(this);
|
||||
for (MethodInfo m : rawOverloads) {
|
||||
overloads.add(m.substituteTypeParams(ctx));
|
||||
}
|
||||
return overloads;
|
||||
}
|
||||
return rawOverloads;
|
||||
}
|
||||
@@ -1303,8 +1297,8 @@ public class TypeInfo {
|
||||
if (rawType != null && rawType != this) {
|
||||
FieldInfo rawField = rawType.getFieldInfo(fieldName);
|
||||
if (rawField != null && isParameterized()) {
|
||||
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
|
||||
if (!bindings.isEmpty()) return rawField.substituteTypeParams(bindings);
|
||||
GenericContext ctx = GenericContext.forReceiver(this);
|
||||
return rawField.substituteTypeParams(ctx);
|
||||
}
|
||||
return rawField;
|
||||
}
|
||||
@@ -1429,11 +1423,9 @@ public class TypeInfo {
|
||||
if (rawType != null && rawType != this) {
|
||||
MethodInfo rawSAM = rawType.getSingleAbstractMethod();
|
||||
if (rawSAM != null && isParameterized()) {
|
||||
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
|
||||
if (!bindings.isEmpty()) {
|
||||
cachedSAM = rawSAM.substituteTypeParams(bindings);
|
||||
return cachedSAM;
|
||||
}
|
||||
GenericContext ctx = GenericContext.forReceiver(this);
|
||||
cachedSAM = rawSAM.substituteTypeParams(ctx);
|
||||
return cachedSAM;
|
||||
}
|
||||
cachedSAM = rawSAM;
|
||||
return cachedSAM;
|
||||
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
package noppes.npcs.client.gui.util.script.interpreter.type;
|
||||
|
||||
import noppes.npcs.client.gui.util.script.interpreter.js_parser.TypeParamInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility for substituting type variables (like T, E, K, V) with concrete types.
|
||||
*
|
||||
* Used when resolving members on parameterized types:
|
||||
* - List<String>.get(int) returns String (not T/Object)
|
||||
* - Map<String, Integer>.get(Object) returns Integer (not V)
|
||||
*
|
||||
* Handles nested generics:
|
||||
* - List<Map<String, Integer>>.get(0) returns Map<String, Integer>
|
||||
*/
|
||||
public class TypeSubstitutor {
|
||||
|
||||
/**
|
||||
* Create a binding map from declared type parameters to applied type arguments.
|
||||
*
|
||||
* Example: For List<String> where List is declared as interface List<E>:
|
||||
* - declaredParams = [TypeParamInfo("E", ...)]
|
||||
* - appliedArgs = [TypeInfo(String)]
|
||||
* - Result: {"E" -> TypeInfo(String)}
|
||||
*
|
||||
* @param declaredParams The declared type parameters (from the generic type definition)
|
||||
* @param appliedArgs The applied type arguments (from the parameterized usage)
|
||||
* @return A map from type parameter names to their concrete TypeInfo values
|
||||
*/
|
||||
public static Map<String, TypeInfo> createBindings(List<TypeParamInfo> declaredParams, List<TypeInfo> appliedArgs) {
|
||||
Map<String, TypeInfo> bindings = new HashMap<>();
|
||||
|
||||
if (declaredParams == null || appliedArgs == null) {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
int count = Math.min(declaredParams.size(), appliedArgs.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
String paramName = declaredParams.get(i).getName();
|
||||
TypeInfo argType = appliedArgs.get(i);
|
||||
if (paramName != null && argType != null) {
|
||||
bindings.put(paramName, argType);
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create bindings from a parameterized receiver type.
|
||||
*
|
||||
* @param receiverType The parameterized type (e.g., List<String>)
|
||||
* @return Bindings from the type's declared params to its applied args
|
||||
*/
|
||||
public static Map<String, TypeInfo> createBindingsFromReceiver(TypeInfo receiverType) {
|
||||
if (receiverType == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
// Get the raw type to access declared type parameters
|
||||
TypeInfo rawType = receiverType.getRawType();
|
||||
List<TypeParamInfo> declaredParams = null;
|
||||
|
||||
// Get declared params from JSTypeInfo or reflection
|
||||
if (rawType.isJSType() && rawType.getJSTypeInfo() != null) {
|
||||
declaredParams = rawType.getJSTypeInfo().getTypeParams();
|
||||
} else {
|
||||
declaredParams = rawType.getTypeParams();
|
||||
}
|
||||
|
||||
// Get applied args from the parameterized type
|
||||
List<TypeInfo> appliedArgs = receiverType.getAppliedTypeArgs();
|
||||
|
||||
return createBindings(declaredParams, appliedArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitute type variables in a type using the given bindings.
|
||||
*
|
||||
* @param type The type to substitute (may contain type variables like T, E)
|
||||
* @param bindings The map from type variable names to concrete types
|
||||
* @return The substituted type
|
||||
*/
|
||||
public static TypeInfo substitute(TypeInfo type, Map<String, TypeInfo> bindings) {
|
||||
if (type == null || bindings == null || bindings.isEmpty()) {
|
||||
return type;
|
||||
}
|
||||
|
||||
String typeName = type.getSimpleName();
|
||||
|
||||
// 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.
|
||||
if (!type.isResolved() || type.isTypeParameter()) {
|
||||
String lookupKey = type.isTypeParameter() ? type.getTypeParameterName() : typeName;
|
||||
TypeInfo substitution = bindings.get(lookupKey);
|
||||
if (substitution != null) {
|
||||
return substitution;
|
||||
}
|
||||
}
|
||||
|
||||
// If the type has applied type arguments, substitute within them recursively
|
||||
if (type.isParameterized()) {
|
||||
List<TypeInfo> originalArgs = type.getAppliedTypeArgs();
|
||||
List<TypeInfo> substitutedArgs = new ArrayList<>();
|
||||
boolean changed = false;
|
||||
|
||||
for (TypeInfo arg : originalArgs) {
|
||||
TypeInfo substitutedArg = substitute(arg, bindings);
|
||||
substitutedArgs.add(substitutedArg);
|
||||
if (substitutedArg != arg) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
// Create a new parameterized type with substituted args
|
||||
return type.getRawType().parameterize(substitutedArgs);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle array types - substitute the element type
|
||||
if (type.getSimpleName().endsWith("[]")) {
|
||||
TypeStringNormalizer.ArraySplit split = TypeStringNormalizer.splitArraySuffixes(typeName);
|
||||
String elementTypeName = split.base;
|
||||
int dims = split.dimensions;
|
||||
TypeInfo elementSubstitution = elementTypeName != null ? bindings.get(elementTypeName) : null;
|
||||
if (elementSubstitution != null) {
|
||||
TypeInfo result = elementSubstitution;
|
||||
for (int i = 0; i < dims; i++) {
|
||||
result = TypeInfo.arrayOf(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user