Skip to content

Commit e91444c

Browse files
committed
Resolve <T extends Foo> params to Foo in API dump
1 parent fd2b3e8 commit e91444c

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

src/test/java/org/apposed/appose/DumpApi.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ public class DumpApi {
201201
));
202202

203203
private static PrintWriter currentWriter = null;
204+
private static TypeDeclaration<?> currentType = null;
204205

205206
public static void main(String[] args) throws Exception {
206207
if (args.length < 2) {
@@ -386,6 +387,7 @@ static void collectInnerTypes(TypeDeclaration<?> parent, String parentFullName,
386387
* In Python, these static methods become global functions in the module.
387388
*/
388389
static void dumpStaticUtilityAsModuleFunctions(TypeDeclaration<?> type) {
390+
currentType = type;
389391
PrintWriter out = currentWriter != null ? currentWriter : new PrintWriter(System.out);
390392

391393
// First output any inner enums (like OperatingSystem, CpuArchitecture).
@@ -553,6 +555,7 @@ static void dumpType(TypeDeclaration<?> type, String fullName) {
553555
// Skip non-public types unless INCLUDE_PRIVATE
554556
if (!INCLUDE_PRIVATE && !type.isPublic()) return;
555557

558+
currentType = type;
556559
PrintWriter out = currentWriter != null ? currentWriter : new PrintWriter(System.out);
557560

558561
// Special case: Static utility classes should be dumped as module-level functions.
@@ -896,6 +899,39 @@ static String pythonType(Type type) {
896899
ClassOrInterfaceType classType = type.asClassOrInterfaceType();
897900
String baseName = classType.getNameAsString();
898901

902+
// Check if this is a type parameter (e.g., T, E, K, V) used in a generic class.
903+
// Type parameters are often single uppercase letters or short uppercase names.
904+
if (baseName.matches("^[A-Z]$|^[A-Z][A-Z0-9]*$") && baseName.length() <= 3) {
905+
if (currentType != null) {
906+
String className = currentType.getNameAsString();
907+
// For builder classes with self-referential generics, resolve T to the class name.
908+
if (STRIP_GENERICS.contains(className)) {
909+
return className;
910+
}
911+
// Try to find this type parameter in the class declaration.
912+
if (currentType instanceof ClassOrInterfaceDeclaration) {
913+
ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) currentType;
914+
NodeList<TypeParameter> typeParams = classDecl.getTypeParameters();
915+
for (TypeParameter typeParam : typeParams) {
916+
if (typeParam.getNameAsString().equals(baseName)) {
917+
// Check if there's a bound (e.g., T extends Builder<T>)
918+
NodeList<ClassOrInterfaceType> typeBound = typeParam.getTypeBound();
919+
if (!typeBound.isEmpty()) {
920+
String boundName = typeBound.get(0).getNameAsString();
921+
if (STRIP_GENERICS.contains(boundName)) {
922+
return boundName;
923+
}
924+
}
925+
// No useful bound, return the class name for STRIP_GENERICS classes.
926+
if (STRIP_GENERICS.contains(className)) {
927+
return className;
928+
}
929+
}
930+
}
931+
}
932+
}
933+
}
934+
899935
// Check for simple type mappings first (File, Thread, Process, etc.).
900936
if (baseName.equals("File")) return "Path";
901937
if (baseName.equals("Process")) return "subprocess.Popen";
@@ -1018,6 +1054,36 @@ static String pythonType(Type type) {
10181054

10191055
// Type variables (e.g., T, E).
10201056
if (type.isTypeParameter()) {
1057+
// For builder classes with self-referential generics, resolve T to the class name.
1058+
if (currentType != null) {
1059+
String className = currentType.getNameAsString();
1060+
if (STRIP_GENERICS.contains(className)) {
1061+
return className;
1062+
}
1063+
1064+
// Try to resolve the type parameter from the class declaration.
1065+
if (currentType instanceof ClassOrInterfaceDeclaration) {
1066+
ClassOrInterfaceDeclaration classDecl = (ClassOrInterfaceDeclaration) currentType;
1067+
NodeList<TypeParameter> typeParams = classDecl.getTypeParameters();
1068+
if (!typeParams.isEmpty()) {
1069+
String typeVarName = type.asTypeParameter().getNameAsString();
1070+
for (TypeParameter typeParam : typeParams) {
1071+
if (typeParam.getNameAsString().equals(typeVarName)) {
1072+
// Check if there's a bound (e.g., T extends Builder<T>)
1073+
NodeList<ClassOrInterfaceType> typeBound = typeParam.getTypeBound();
1074+
if (!typeBound.isEmpty()) {
1075+
// Use the simple name of the first bound.
1076+
String boundName = typeBound.get(0).getNameAsString();
1077+
// If the bound is in STRIP_GENERICS, use it directly.
1078+
if (STRIP_GENERICS.contains(boundName)) {
1079+
return boundName;
1080+
}
1081+
}
1082+
}
1083+
}
1084+
}
1085+
}
1086+
}
10211087
return type.asTypeParameter().getNameAsString();
10221088
}
10231089

0 commit comments

Comments
 (0)