Add generics support for Java to Go transpilation#4
Conversation
This adds comprehensive support for Java generics in the transpiler: - Add TypeParameters field to ClassScope for tracking type parameters - Update ParseType to handle generic types with type arguments - Generate Go generic structs with type parameters (e.g., `type List[T any] struct`) - Generate generic constructors and methods with proper type parameter syntax - Handle diamond operator (<>) by inferring types from variable declarations - Support inner class constructors that inherit parent type parameters - Add expectedType context for type inference during object creation The implementation properly handles: - Generic class declarations with type parameters - Generic method receivers with type parameters - Constructor type arguments in generic struct instantiation - Diamond operator type inference from declaration context - Nested classes inheriting parent type parameters
- Added `tree_sitter_test.go` with unit tests for `CapitalizeIdent`, `LowercaseIdent`, and `Ctx.Clone`. - Added integration tests for `ParseNode` covering Programs, Interface Methods, Field Declarations, Try Statements, Parameters, Switch Labels, and Imports. - Fixed a bug in `tree_sitter.go` where `ParseNode` used `ParseExpr` instead of `astutil.ParseType` for field types, causing panics on primitive types. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive support for Java generics in the Java-to-Go transpiler, enabling the transpilation of generic classes, methods, and constructors with proper Go 1.18+ generics syntax. The implementation handles type parameter declarations, generic type instantiation, diamond operator inference, and nested class type parameter inheritance.
Key Changes
- Added type parameter tracking in
ClassScopewith inheritance for nested classes - Implemented generic-aware type parsing that distinguishes between type parameters and reference types
- Extended code generation to produce Go generic structs, constructors, and methods with proper type parameter syntax
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| symbol/class_scope.go | Adds TypeParameters field to track generic type parameters and provides IsTypeParameter helper method |
| symbol/parsing.go | Updates type parsing to use ParseTypeWithTypeParams and implements nested class type parameter inheritance via parseClassScopeWithParentTypeParams |
| astutil/type_parsing.go | Implements ParseTypeWithTypeParams for handling generic types with type arguments and adds ExtractTypeArguments helper |
| expression.go | Adds diamond operator support and type argument inference for object creation expressions, including extractTypeArgsFromString helper |
| statement.go | Sets expectedType in context for diamond operator type inference during variable declarations |
| declaration.go | Generates generic structs and functions with type parameters, including proper receiver types for generic methods |
| generate.go | Adds GenStructWithTypeParams and GenFuncDeclWithTypeParams utilities for creating generic declarations |
| tree_sitter.go | Adds expectedType field to Ctx and updates Clone method to copy it |
| tree_sitter_test.go | Adds unit tests for helper functions and integration tests for ParseNode (though lacking generics-specific tests) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // If we have type arguments, create an IndexExpr or IndexListExpr | ||
| if len(typeArgs) > 0 { | ||
| baseExpr := &ast.StarExpr{X: &ast.Ident{Name: baseName}} | ||
| if len(typeArgs) == 1 { | ||
| return &ast.IndexExpr{ | ||
| X: baseExpr, | ||
| Index: typeArgs[0], | ||
| } | ||
| } | ||
| // Multiple type arguments use IndexListExpr | ||
| return &ast.IndexListExpr{ | ||
| X: baseExpr, | ||
| Indices: typeArgs, | ||
| } |
There was a problem hiding this comment.
Incorrect Go generics syntax for pointer types. When a generic type has type arguments (e.g., List<Integer> in Java), the code creates (*List)[Integer] instead of the correct Go syntax *List[Integer]. The pointer should wrap the entire indexed expression, not just the base identifier.
The correct structure should be:
var baseWithArgs ast.Expr
if len(typeArgs) == 1 {
baseWithArgs = &ast.IndexExpr{
X: &ast.Ident{Name: baseName},
Index: typeArgs[0],
}
} else {
baseWithArgs = &ast.IndexListExpr{
X: &ast.Ident{Name: baseName},
Indices: typeArgs,
}
}
return &ast.StarExpr{X: baseWithArgs}| // If we have type arguments, create an IndexExpr or IndexListExpr | |
| if len(typeArgs) > 0 { | |
| baseExpr := &ast.StarExpr{X: &ast.Ident{Name: baseName}} | |
| if len(typeArgs) == 1 { | |
| return &ast.IndexExpr{ | |
| X: baseExpr, | |
| Index: typeArgs[0], | |
| } | |
| } | |
| // Multiple type arguments use IndexListExpr | |
| return &ast.IndexListExpr{ | |
| X: baseExpr, | |
| Indices: typeArgs, | |
| } | |
| // If we have type arguments, create an IndexExpr or IndexListExpr and wrap in a StarExpr | |
| if len(typeArgs) > 0 { | |
| var baseWithArgs ast.Expr | |
| if len(typeArgs) == 1 { | |
| baseWithArgs = &ast.IndexExpr{ | |
| X: &ast.Ident{Name: baseName}, | |
| Index: typeArgs[0], | |
| } | |
| } else { | |
| baseWithArgs = &ast.IndexListExpr{ | |
| X: &ast.Ident{Name: baseName}, | |
| Indices: typeArgs, | |
| } | |
| } | |
| return &ast.StarExpr{X: baseWithArgs} |
This adds comprehensive support for Java generics in the transpiler: - Add TypeParameters field to ClassScope for tracking type parameters - Update ParseType to handle generic types with type arguments - Generate Go generic structs with type parameters (e.g., `type List[T any] struct`) - Generate generic constructors and methods with proper type parameter syntax - Handle diamond operator (<>) by inferring types from variable declarations - Support inner class constructors that inherit parent type parameters - Add expectedType context for type inference during object creation The implementation properly handles: - Generic class declarations with type parameters - Generic method receivers with type parameters - Constructor type arguments in generic struct instantiation - Diamond operator type inference from declaration context - Nested classes inheriting parent type parameters - Nested generic types like Map<String, List<Integer>>
bfaaf92 to
8934eb6
Compare
Simplify the logic that determines type arguments for inner class constructors. The previous code had redundant checks and contradictory comments. Now the logic is clear: 1. Use explicit type arguments if provided 2. For diamond operator, infer from expectedType 3. For inner class constructors (non-diamond), use parent class type params
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 11 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ctx.lastType = variableType | ||
| // Set expected type for diamond operator inference | ||
| ctx.expectedType = node.ChildByFieldName("type").Content(source) | ||
|
|
||
| declaration := ParseStmt(variableDeclarator, source, ctx).(*ast.AssignStmt) |
There was a problem hiding this comment.
[nitpick] The expectedType field is set on the ctx object (line 53), but ctx is passed by value to TryParseStmt. When the context is then passed to ParseStmt on line 55, this modified context with the expectedType set will be propagated. However, this relies on the fact that the context is not cloned between these calls.
This could be fragile if the code is refactored. The issue is that when the declarator is parsed, it will call ParseExpr which needs access to expectedType. Since Go passes structs by value, any cloning or copying of the context between setting expectedType and using it in ParseExpr could lose this information. Consider documenting this dependency or restructuring to make the data flow more explicit.
| // Helper function to add type arguments to a function expression | ||
| addTypeArgs := func(funExpr ast.Expr, args []string) ast.Expr { | ||
| if len(args) == 0 { | ||
| return funExpr | ||
| } | ||
| typeArgExprs := make([]ast.Expr, len(args)) | ||
| for i, ta := range args { | ||
| typeArgExprs[i] = &ast.Ident{Name: ta} | ||
| } | ||
| if len(typeArgExprs) == 1 { | ||
| return &ast.IndexExpr{ | ||
| X: funExpr, | ||
| Index: typeArgExprs[0], | ||
| } | ||
| } | ||
| return &ast.IndexListExpr{ | ||
| X: funExpr, | ||
| Indices: typeArgExprs, | ||
| } | ||
| } |
There was a problem hiding this comment.
[nitpick] The addTypeArgs helper function (lines 283-301) creates IndexExpr for single type arguments and IndexListExpr for multiple type arguments. However, this distinction is necessary only for Go 1.18-1.19 syntax compatibility.
In Go 1.20+, IndexListExpr can be used for both single and multiple type arguments, simplifying the code. If the project targets Go 1.20+, consider removing the special case for single type arguments to reduce complexity.
If backward compatibility with Go 1.18-1.19 is required, consider documenting this constraint.
| // If we have type arguments, create an IndexExpr or IndexListExpr | ||
| if len(typeArgs) > 0 { | ||
| baseExpr := &ast.StarExpr{X: &ast.Ident{Name: baseName}} | ||
| if len(typeArgs) == 1 { | ||
| return &ast.IndexExpr{ | ||
| X: baseExpr, | ||
| Index: typeArgs[0], | ||
| } | ||
| } | ||
| // Multiple type arguments use IndexListExpr | ||
| return &ast.IndexListExpr{ | ||
| X: baseExpr, | ||
| Indices: typeArgs, | ||
| } | ||
| } | ||
|
|
||
| // No type arguments, just return the base type as a pointer | ||
| return &ast.StarExpr{X: &ast.Ident{Name: baseName}} |
There was a problem hiding this comment.
The handling of generic types with type arguments has an issue with pointer wrapping. On line 79, baseExpr is created as &ast.StarExpr{X: &ast.Ident{Name: baseName}}, wrapping the base type name in a pointer. This is then used as the base for IndexExpr or IndexListExpr.
This produces incorrect syntax like (*List)[string] instead of the correct Go generic syntax *List[string]. The pointer should wrap the entire indexed expression, not just the base identifier.
The fix should be:
baseExpr := &ast.Ident{Name: baseName}
var result ast.Expr
if len(typeArgs) == 1 {
result = &ast.IndexExpr{X: baseExpr, Index: typeArgs[0]}
} else {
result = &ast.IndexListExpr{X: baseExpr, Indices: typeArgs}
}
return &ast.StarExpr{X: result}| // For inner class constructors (not diamond), use parent class type parameters | ||
| // This handles cases like `new Node(element)` inside a generic class | ||
| if len(effectiveTypeArgs) == 0 && !isDiamond && len(ctx.currentClass.TypeParameters) > 0 { | ||
| // Check if className is a nested class of the current class | ||
| for _, sub := range ctx.currentClass.Subclasses { | ||
| if sub.Class.OriginalName == className { | ||
| effectiveTypeArgs = ctx.currentClass.TypeParameters | ||
| break | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic for inferring type arguments for inner class constructors (lines 316-324) may not work correctly. The comparison sub.Class.OriginalName == className checks the nested class name, but nested classes are renamed with the pattern ParentClassName + NestedClassName (see line 211 in symbol/parsing.go). This means the check will likely fail because className would be just "Node" but sub.Class.OriginalName would be something like "ParentNode" after renaming.
Consider using a different approach to identify nested classes, such as checking if the class exists in the subclasses list by comparing against the original unnested name or using a suffix match.
…f github.com:Suhaibinator/java2go into claude/add-generics-support-01H4U5oShbbK8o5kdnW4oRfA
When a nested class declares a type parameter with the same name as
its parent's type parameter (e.g., class Outer<T> { class Inner<T> }),
Java shadows the parent's parameter. Previously, both would appear in
the TypeParameters slice causing duplicates.
Now we properly handle shadowing: parent type parameters are only
included if they aren't shadowed by the nested class's own parameters.
…ssions for generic types
… argument extraction validation
- Bump Go version to 1.24.0 and update dependencies: - logrus from v1.9.0 to v1.9.3 - go-tree-sitter from v0.0.0-20230113054119 to v0.0.0-20240827094217 - golang.org/x/exp to v0.0.0-20251125195548 - golang.org/x/sys to v0.38.0 - Add TypeParameters field to Definition struct for better representation of type parameters in method and constructor definitions. - Refactor type parameter extraction into a dedicated function for cleaner code and improved readability. - Update parsing logic to correctly handle type parameters in method declarations and field declarations, ensuring that generic types are preserved. - Enhance test coverage for generic type parameters in method and field declarations, including tests for inner classes and variadic parameters.
…thods and add tests for type argument inference
…ument handling in existing tests
…d type parameters in parsing functions
This adds comprehensive support for Java generics in the transpiler:
type List[T any] struct)The implementation properly handles: