Skip to content

Go: a method call on new(T) links a same-named method of another type #3237

Open

Description

@inth3shadows

What happens

A method call on new(T) links to a same-named method of a different type. Written as (&T{}).M(), or through a local (r := new(T); r.M()), the same call links correctly.

// go.mod
module example.com/p

go 1.22

// a_router.go
package p

type Router struct{}

func (r *Router) Host(h string) *Router { return r }

// b_route.go
package p

type Route struct{}

func (r *Route) Host(h string) *Route { return r }

// use.go
package p

func viaNew() *Route {
	return new(Route).Host("x") // Route.Host
}

func viaLiteral() *Route {
	return (&Route{}).Host("y") // Route.Host
}

func viaVar() *Route {
	r := new(Route)
	return r.Host("z") // Route.Host
}
  • Expected: all three call b_route.go Route::Host.
  • Actual: viaNew calls a_router.go Router::Host (resolvedBy: exact-match). viaLiteral and viaVar link Route::Host (instance-method).

The new call itself is left as an unresolved new reference, and the method is matched by its bare name, so which Host it gets depends on file order: with the two type files renamed (b_router.go, a_route.go), viaNew lands on Route::Host by luck.

To reproduce: put the four files in an empty directory, run codegraph init -y, then codegraph callers Host:

method Router::Host (go) — a_router.go:5 (1):
function    viaNew
  use.go:4

method Route::Host (go) — b_route.go:5 (2):
function    viaLiteral
  use.go:8
function    viaVar
  use.go:13

Real case in gorilla/mux at db9d1d0: mux_test.go:60 new(Route).Host("aaa.bbb.ccc") links Router.Host in mux.go, not Route.Host in route.go:366. The test table uses new(Route).Host / .Path / .Queries / ... throughout, so 117 edges in that repo land on Router's methods.

Found by diffing codegraph's call edges against scip-go.

Environment: main at f72c9d2 (2026-11-01), built from source, Node 22.22.2, Linux x64, WASM engine (CODEGRAPH_KERNEL=0), fresh codegraph init. A 60b0d7a build (2026-10-29) gives the same edges.

Cause (inferred): the receiver new(Route) is not kept on the reference, so resolution has only the name. TS/JS has a shape for the same idea, new C().m (NEW_RECEIVER_SHAPE), which resolves only through the class written at the call. Go's new(T) (and a &T{} receiver, which already works) give the type just as directly.

Related: #2782 fixed a call further along a Go chain; #750 tracks chained factory calls.

Edited 2026-11-04: made the snippet a complete module (go.mod, package p) so it runs as pasted, added the reproduce command with codegraph callers output, and noted that the wrong target depends on file order (checked by renaming the files).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions