Skip to content

Commit bd4bba6

Browse files
committed
Add external package support with stub generation
Major features: - Implemented stub mode for external packages (vendored packages) - Added automatic stub generation from vendored Go packages - Created package loader that generates Rust module structure from Go packages - Fixed package function calls to transpile as package::function() instead of method calls - Added goPackageImports initialization for proper import tracking Bug fixes: - Fixed tests.bats line 83: Changed 'if [ ! $? ]' to 'if [ $? -ne 0 ]' to properly check transpilation exit code - This was preventing transpilation from running and .rs/Cargo.toml files from regenerating New files: - go/external_packages.go - External package handling - go/package_loader.go - Package to Rust module conversion - go/stub_generator.go - Generates Rust stubs from Go packages - go/unified_transpiler.go - Unified transpilation interface - tests/XFAIL/external_simple/ - Test case for external packages
1 parent 50bd2b6 commit bd4bba6

File tree

74 files changed

+4252
-309
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+4252
-309
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,24 @@ go build -o go2rust ./go
2424

2525
### External Package Handling
2626

27-
Go2Rust provides three modes for handling external package imports:
27+
Go2Rust provides four modes for handling external package imports:
2828

2929
1. **`transpile` (default)**: Recursively transpiles all dependencies to Rust
3030
- Pure Rust output with no Go runtime dependency
3131
- Currently in development
3232

33-
2. **`ffi`**: Generates FFI bridge to call Go libraries from Rust
33+
2. **`stub`**: Generates stub implementations for external packages
34+
- Creates placeholder Rust modules with helpful TODO comments
35+
- Allows you to manually implement or use Rust equivalents
36+
- Useful when automatic transpilation fails or when you want custom implementations
37+
- Stub files are generated in `vendor/` directory
38+
39+
3. **`ffi`**: Generates FFI bridge to call Go libraries from Rust
3440
- Keeps Go packages as-is and generates bindings
3541
- Useful for packages with cgo or complex dependencies
3642
- Currently in development
3743

38-
3. **`none`**: Fails if external packages are imported
44+
4. **`none`**: Fails if external packages are imported
3945
- Useful for simple, self-contained programs
4046
- Ensures no external dependencies
4147

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
module go2rust
22

33
go 1.24
4+
5+
require (
6+
golang.org/x/mod v0.27.0 // indirect
7+
golang.org/x/sync v0.16.0 // indirect
8+
golang.org/x/tools v0.36.0 // indirect
9+
)

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
2+
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
3+
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
4+
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
5+
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
6+
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=

go/config.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const (
1212
ModeTranspile ExternalPackageMode = iota // Recursively transpile dependencies
1313
ModeFfi // Generate FFI bridge
1414
ModeNone // Error on external imports
15+
ModeStub // Generate stub implementations for user to fill in
1516
)
1617

1718
func (m ExternalPackageMode) String() string {
@@ -22,6 +23,8 @@ func (m ExternalPackageMode) String() string {
2223
return "ffi"
2324
case ModeNone:
2425
return "none"
26+
case ModeStub:
27+
return "stub"
2528
default:
2629
return "unknown"
2730
}
@@ -35,7 +38,9 @@ func ParseExternalPackageMode(s string) (ExternalPackageMode, error) {
3538
return ModeFfi, nil
3639
case "none":
3740
return ModeNone, nil
41+
case "stub":
42+
return ModeStub, nil
3843
default:
39-
return ModeTranspile, fmt.Errorf("invalid external package mode: %s (must be 'transpile', 'ffi', or 'none')", s)
44+
return ModeTranspile, fmt.Errorf("invalid external package mode: %s (must be 'transpile', 'ffi', 'stub', or 'none')", s)
4045
}
4146
}

go/context.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package main
22

33
// TranspileContext holds the context for a single transpilation session
44
type TranspileContext struct {
5-
Imports *ImportTracker
6-
Helpers *HelperTracker
5+
Imports *ImportTracker
6+
Helpers *HelperTracker
7+
PackageMapping map[string]string // Go import path -> Rust crate name
78
}
89

910
// Global context for the current transpilation

go/expr.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func TranspileExpressionContext(out *strings.Builder, expr ast.Expr, ctx ExprCon
147147
TranspileCall(out, e)
148148

149149
case *ast.SelectorExpr:
150-
// Check if this is a package selector or field access using TypeInfo
150+
// Check if this is a type assertion first (e.g., x.(Type))
151151
typeInfo := GetTypeInfo()
152152
isPackageSelector := false
153153

@@ -162,8 +162,48 @@ func TranspileExpressionContext(out *strings.Builder, expr ast.Expr, ctx ExprCon
162162
}
163163
}
164164

165+
// Also check if it's a known package import (fallback)
166+
if !isPackageSelector {
167+
if ident, ok := e.X.(*ast.Ident); ok {
168+
if _, isImport := goPackageImports[ident.Name]; isImport {
169+
isPackageSelector = true
170+
}
171+
}
172+
}
173+
165174
if isPackageSelector {
166175
// Package/type selector
176+
// Check if this is an external package that needs mapping
177+
if ident, ok := e.X.(*ast.Ident); ok {
178+
if pkgPath, exists := goPackageImports[ident.Name]; exists {
179+
// Check if we have a mapping for this package
180+
ctx := GetTranspileContext()
181+
if ctx != nil && ctx.PackageMapping != nil {
182+
if crateName, hasCrate := ctx.PackageMapping[pkgPath]; hasCrate {
183+
// Use the mapped crate name with proper formatting
184+
if !isStdlibPackage(pkgPath) {
185+
// External package - use crate name directly
186+
out.WriteString(crateName)
187+
} else {
188+
// Stdlib package - use normal transpilation
189+
out.WriteString(ident.Name)
190+
}
191+
out.WriteString("::")
192+
out.WriteString(ToSnakeCase(e.Sel.Name))
193+
break
194+
}
195+
}
196+
// If no mapping found but it's a known import, still use package syntax
197+
if !isStdlibPackage(pkgPath) {
198+
// External package without mapping - use sanitized name
199+
out.WriteString(strings.ReplaceAll(strings.ReplaceAll(pkgPath, "/", "_"), ".", "_"))
200+
out.WriteString("::")
201+
out.WriteString(ToSnakeCase(e.Sel.Name))
202+
break
203+
}
204+
}
205+
}
206+
// Default behavior for stdlib or unmapped packages
167207
TranspileExpression(out, e.X)
168208
out.WriteString("::")
169209
out.WriteString(ToSnakeCase(e.Sel.Name))
@@ -1637,6 +1677,32 @@ func TranspileCall(out *strings.Builder, call *ast.CallExpr) {
16371677

16381678
// Check if this is a method call (selector expression)
16391679
if sel, ok := call.Fun.(*ast.SelectorExpr); ok {
1680+
// First check if this is a package function call
1681+
isPackageCall := false
1682+
if ident, ok := sel.X.(*ast.Ident); ok {
1683+
if _, isImport := goPackageImports[ident.Name]; isImport {
1684+
isPackageCall = true
1685+
}
1686+
}
1687+
1688+
if isPackageCall {
1689+
// This is a package function call, not a method call
1690+
// Just transpile the selector expression and add the arguments
1691+
TranspileExpression(out, sel)
1692+
out.WriteString("(")
1693+
for i, arg := range call.Args {
1694+
if i > 0 {
1695+
out.WriteString(", ")
1696+
}
1697+
// Wrap arguments in Rc<RefCell<Option<>>>
1698+
WriteWrapperPrefix(out)
1699+
TranspileExpression(out, arg)
1700+
out.WriteString(")))")
1701+
}
1702+
out.WriteString(")")
1703+
return
1704+
}
1705+
16401706
// This is a method call - handle it specially
16411707
// For method calls, we need to check if the receiver is a wrapped type or not
16421708
// If it's a struct variable, we call the method directly

0 commit comments

Comments
 (0)