@@ -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