Skip to content

Add generics support for Java to Go transpilation#4

Merged
Suhaibinator merged 18 commits into
masterfrom
claude/add-generics-support-01H4U5oShbbK8o5kdnW4oRfA
Dec 17, 2025
Merged

Add generics support for Java to Go transpilation#4
Suhaibinator merged 18 commits into
masterfrom
claude/add-generics-support-01H4U5oShbbK8o5kdnW4oRfA

Conversation

@Suhaibinator

Copy link
Copy Markdown
Owner

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

claude and others added 2 commits November 30, 2025 17:10
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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ClassScope with 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.

Comment thread expression.go Outdated
Comment thread tree_sitter_test.go Outdated
Comment thread tree_sitter_test.go
Comment thread declaration.go
Comment thread astutil/type_parsing.go Outdated
Comment on lines +77 to +90
// 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,
}

Copilot AI Dec 1, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}
Suggested change
// 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}

Copilot uses AI. Check for mistakes.
Comment thread expression.go Outdated
Suhaibinator and others added 2 commits November 30, 2025 20:16
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>>
@Suhaibinator
Suhaibinator force-pushed the claude/add-generics-support-01H4U5oShbbK8o5kdnW4oRfA branch from bfaaf92 to 8934eb6 Compare December 1, 2025 04:18
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

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread statement.go
Comment on lines 51 to 55
ctx.lastType = variableType
// Set expected type for diamond operator inference
ctx.expectedType = node.ChildByFieldName("type").Content(source)

declaration := ParseStmt(variableDeclarator, source, ctx).(*ast.AssignStmt)

Copilot AI Dec 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copilot uses AI. Check for mistakes.
Comment thread symbol/parsing.go Outdated
Comment thread declaration.go Outdated
Comment thread symbol/parsing.go Outdated
Comment thread expression.go Outdated
Comment thread expression.go Outdated
Comment thread expression.go
Comment on lines +282 to +301
// 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,
}
}

Copilot AI Dec 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Copilot uses AI. Check for mistakes.
Comment thread astutil/type_parsing.go
Comment on lines +77 to +94
// 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}}

Copilot AI Dec 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}

Copilot uses AI. Check for mistakes.
Comment thread expression.go
Comment on lines +314 to +324
// 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
}
}
}

Copilot AI Dec 2, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread expression.go
Suhaibinator and others added 13 commits December 3, 2025 00:12
…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.
- 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
@Suhaibinator
Suhaibinator merged commit 0c5d977 into master Dec 17, 2025
4 checks passed
@Suhaibinator
Suhaibinator deleted the claude/add-generics-support-01H4U5oShbbK8o5kdnW4oRfA branch December 17, 2025 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants