fix(emit): rename local vars and params that collide with import aliases#6
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to prevent generated variable and parameter names from colliding with imported package aliases. It adds an assignNames function to resolve name collisions by appending a counter suffix and updates the code generation logic to use these collision-free identifiers. The feedback suggests optimizing this process by directly accessing the unexported im.used map within the same package, which would eliminate the need for the new Reserved() method and avoid unnecessary slice allocations.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| func assignNames(im *Imports, p plan.Plan) names { | ||
| taken := map[string]bool{} | ||
| for _, a := range im.Reserved() { | ||
| taken[a] = true | ||
| } |
There was a problem hiding this comment.
Since assignNames and Imports are in the same package, we can directly access im.used instead of calling im.Reserved(). This avoids allocating an intermediate slice and doing a double iteration, improving both performance and memory usage.
| func assignNames(im *Imports, p plan.Plan) names { | |
| taken := map[string]bool{} | |
| for _, a := range im.Reserved() { | |
| taken[a] = true | |
| } | |
| func assignNames(im *Imports, p plan.Plan) names { | |
| taken := make(map[string]bool, len(im.used)+3) | |
| for a := range im.used { | |
| taken[a] = true | |
| } |
There was a problem hiding this comment.
Good catch — applied in 8ec97c8. assignNames now reads im.used directly and Reserved() is removed entirely (it had no other callers). Saves one allocation per assignNames call and keeps the API surface narrower.
| // Reserved returns every identifier the import tracker is currently | ||
| // holding off-limits: alias names assigned to imported packages plus any | ||
| // pre-claimed reserved names supplied to New. Callers that need to pick a | ||
| // non-colliding identifier for a local variable or parameter should treat | ||
| // this set as taken. | ||
| func (im *Imports) Reserved() []string { | ||
| out := make([]string, 0, len(im.used)) | ||
| for name := range im.used { | ||
| out = append(out, name) | ||
| } | ||
| return out | ||
| } |
There was a problem hiding this comment.
Done in 8ec97c8 — Reserved() removed entirely from imports.go since assignNames is its only caller and now reads im.used directly.
Summary
The generated code could pick a local variable or parameter name that shadows an imported package alias. When a downstream line then used the shadowed package — e.g.
db.Open(...)afterdb, err := config.NewDB()— the call resolved against the local value instead of the package, producing a broken constructor that failed to compile.This change adds a small renaming pass in the emitter. After imports are collected, every input and step name is checked against the set of names already taken by import aliases (and a small set of always-reserved identifiers like
err); collisions are suffixed with a numeric counter (db→db2).Trigger
The atode codebase hit this with the following shape:
The generated constructor imported both
db(fordb.Open) andkv(forkv.New) and then declared local variables also calleddbandkv, which immediately collided. Acontext.Contextarg parameter similarly shadowed thecontextimport.Drive-by
example/returns/injector_gen.goregenerates togreeter2 := greeter.NewGreeter(). The previous output (greeter := greeter.NewGreeter()) compiled by accident — Go evaluates the RHS of:=before binding the LHS — but any later reference to the package would have broken. The renamed variable is the same generation now applied consistently.Test plan
internal/emit: newTestEmit_VarNameDoesNotShadowImportedPackage(usesImports.New(reserved...)to inject collision candidates and asserts the rendered body does not assign to those names)make lintreports 0 issuesmake test-e2eregenerates every example and passesgo vet+go build