mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 20:44:08 +00:00
Fixed paramterize() -> substitute() cyclic StackOverFlow
This commit is contained in:
1 parent
0af3bdfd04
commit
9207cc3f43
2 files changed
+94
-26
No files matched your search
+48
-1
@@ -39,6 +39,47 @@ public final class GenericContext {
|
||||
this.boundFallbacks = boundFallbacks != null ? boundFallbacks : new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GenericContext directly from declared type parameters and applied type arguments.
|
||||
*
|
||||
* Unlike {@link #forReceiver(TypeInfo)}, this does NOT introspect a TypeInfo, so it cannot
|
||||
* trigger recursive parameterization. Use this inside {@code parameterize()} implementations
|
||||
* to avoid the cycle: parameterize → forReceiver → substitute → parameterize.
|
||||
*
|
||||
* @param declaredParams the declared type parameters (e.g., [T, E])
|
||||
* @param appliedArgs the applied type arguments (e.g., [String, Integer])
|
||||
* @return a context mapping param names to applied args (with bound fallbacks for unmatched params)
|
||||
*/
|
||||
public static GenericContext fromBindings(List<TypeParamInfo> declaredParams, List<TypeInfo> appliedArgs) {
|
||||
if (declaredParams == null || declaredParams.isEmpty()) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
Map<String, TypeInfo> applied = new HashMap<>();
|
||||
if (appliedArgs != null) {
|
||||
int count = Math.min(declaredParams.size(), appliedArgs.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
TypeParamInfo declared = declaredParams.get(i);
|
||||
TypeInfo arg = appliedArgs.get(i);
|
||||
if (declared == null || declared.getName() == null || declared.getName().isEmpty() || arg == null) {
|
||||
continue;
|
||||
}
|
||||
applied.put(declared.getName(), arg);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, TypeInfo> bounds = new HashMap<>();
|
||||
for (TypeParamInfo declared : declaredParams) {
|
||||
if (declared == null) continue;
|
||||
String name = declared.getName();
|
||||
if (name == null || name.isEmpty()) continue;
|
||||
TypeInfo bound = declared.getBoundTypeInfo();
|
||||
bounds.put(name, (bound != null && bound.isResolved()) ? bound : TypeInfo.OBJECT);
|
||||
}
|
||||
|
||||
return new GenericContext(applied, bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GenericContext for a receiver type.
|
||||
*
|
||||
@@ -85,7 +126,13 @@ public final class GenericContext {
|
||||
String name = declared.getName();
|
||||
if (name == null || name.isEmpty()) continue;
|
||||
TypeInfo bound = declared.getBoundTypeInfo();
|
||||
bounds.put(name, (bound != null && bound.isResolved()) ? bound : TypeInfo.OBJECT);
|
||||
TypeInfo effectiveBound = (bound != null && bound.isResolved()) ? bound : TypeInfo.OBJECT;
|
||||
|
||||
List<TypeInfo> additionalBounds = declared.getAdditionalBoundTypes();
|
||||
if (additionalBounds != null && !additionalBounds.isEmpty()) {
|
||||
effectiveBound = IntersectionTypeInfo.of(effectiveBound, additionalBounds);
|
||||
}
|
||||
bounds.put(name, effectiveBound);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+46
-25
@@ -11,8 +11,10 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a type defined within the script itself (not from Java classpath).
|
||||
@@ -40,6 +42,11 @@ public class ScriptTypeInfo extends TypeInfo {
|
||||
|
||||
// Generic type parameters declared on this type (e.g., <E>, <K, V>, <T extends Entity>)
|
||||
private final List<TypeParamInfo> declaredTypeParams = new ArrayList<>();
|
||||
|
||||
// Recursion guard: tracks ScriptTypeInfo instances currently being parameterized on this thread
|
||||
// to break the cycle: parameterize → substitute → parameterize (self-referential types)
|
||||
private static final ThreadLocal<Set<ScriptTypeInfo>> PARAMETERIZING =
|
||||
ThreadLocal.withInitial(HashSet::new);
|
||||
|
||||
// Parent class reference (for inner class resolution)
|
||||
private ScriptTypeInfo outerClass;
|
||||
@@ -166,34 +173,48 @@ public class ScriptTypeInfo extends TypeInfo {
|
||||
@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, 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));
|
||||
result.methods.put(entry.getKey(), substituted);
|
||||
Set<ScriptTypeInfo> active = PARAMETERIZING.get();
|
||||
if (!active.add(this)) {
|
||||
// Re-entrant call on the same raw type — fall back to shallow parameterization
|
||||
// (no method/field substitution) to break the infinite recursion.
|
||||
return super.parameterize(typeArgs);
|
||||
}
|
||||
for (MethodInfo ctor : this.constructors) {
|
||||
result.constructors.add(substituteMethod(ctor, ctx));
|
||||
|
||||
try {
|
||||
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;
|
||||
// Use fromBindings instead of forReceiver to avoid infinite recursion:
|
||||
// forReceiver(result) → substitute() → parameterize() → forReceiver() → ...
|
||||
GenericContext ctx = GenericContext.fromBindings(this.declaredTypeParams, typeArgs);
|
||||
|
||||
// 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));
|
||||
result.methods.put(entry.getKey(), substituted);
|
||||
}
|
||||
for (MethodInfo ctor : this.constructors) {
|
||||
result.constructors.add(substituteMethod(ctor, 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);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
active.remove(this);
|
||||
}
|
||||
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 ====================
|
||||
|
||||
Reference in new issue
Block a user