Summary
In go_lsp.c a selector's operand is checked against the import map before local scope, in both places that read a selector operand. A local variable whose name equals an imported package name is therefore always treated as the package. Go's scoping rule is the opposite: a local binding shadows the package name from the point of declaration onward.
Where
internal/cbm/lsp/go_lsp.c:369 — go_eval_expr_type, selector_expression branch (// Check if operand is an import alias (pkg.Symbol)). If resolve_import() succeeds the branch returns unconditionally — including return cbm_type_unknown() when the symbol is not found in that package — so it never falls through to evaluating the operand as a local.
internal/cbm/lsp/go_lsp.c:1458 — call emission (// Check if operand is a package import). Same ordering; on a miss it emits an unresolved call (symbol_not_in_registry) and skips type-based dispatch entirely.
Corpus shape (kubernetes)
A very common idiom:
meta, err := meta.Accessor(info.Object) // RHS: package k8s.io/apimachinery/pkg/api/meta
...
meta.SetNamespace(ns) // `meta` is now a local of type metav1.Object
Three independent sites: staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go (setObjectMeta), staging/src/k8s.io/kubectl/pkg/cmd/expose/expose.go:392, staging/src/k8s.io/kubectl/pkg/cmd/run/run.go:724.
Today the call meta.SetNamespace(ns) is bound to the package k8s.io/apimachinery/pkg/api/meta (lsp_direct, confidence 0.95, target meta/meta.go:465) instead of metav1.Object.SetNamespace. With receiver-qualified method QNs (#1913) the constructed package QN no longer exists, the LSP emits the call unresolved, and the pipeline registry's import_map_suffix fallback (src/pipeline/registry.c:757-777) binds it to meta.MetadataAccessor.SetNamespace — a two-parameter interface member — for a one-argument call. Wrong before, wrong differently after.
What was tried
A shadowing guard at both sites (!cbm_scope_contains(ctx->current_scope, <operand>); cbm_scope_contains is NULL-safe, internal/cbm/lsp/scope.c:107) is regression-free (the RHS keeps its package edge — Go evaluates the RHS in the outer scope) but exactly neutral on a reproduction fixture: the LSP still cannot type the local, because a cross-package function return type (meta.Accessor returns metav1.Object) is not propagated into the binding. So the guard alone produces no observable change and was not shipped.
Proposed fix
Land the scoping guard together with cross-package return-type propagation for :=/= bindings, at which point a fixture with x, err := x.F(...) followed by x.Method() can assert the edge to the local's interface method and go RED without the change.
Summary
In
go_lsp.ca selector's operand is checked against the import map before local scope, in both places that read a selector operand. A local variable whose name equals an imported package name is therefore always treated as the package. Go's scoping rule is the opposite: a local binding shadows the package name from the point of declaration onward.Where
internal/cbm/lsp/go_lsp.c:369—go_eval_expr_type,selector_expressionbranch (// Check if operand is an import alias (pkg.Symbol)). Ifresolve_import()succeeds the branch returns unconditionally — includingreturn cbm_type_unknown()when the symbol is not found in that package — so it never falls through to evaluating the operand as a local.internal/cbm/lsp/go_lsp.c:1458— call emission (// Check if operand is a package import). Same ordering; on a miss it emits an unresolved call (symbol_not_in_registry) and skips type-based dispatch entirely.Corpus shape (kubernetes)
A very common idiom:
Three independent sites:
staging/src/k8s.io/apiserver/pkg/registry/rest/resttest/resttest.go(setObjectMeta),staging/src/k8s.io/kubectl/pkg/cmd/expose/expose.go:392,staging/src/k8s.io/kubectl/pkg/cmd/run/run.go:724.Today the call
meta.SetNamespace(ns)is bound to the packagek8s.io/apimachinery/pkg/api/meta(lsp_direct, confidence 0.95, targetmeta/meta.go:465) instead ofmetav1.Object.SetNamespace. With receiver-qualified method QNs (#1913) the constructed package QN no longer exists, the LSP emits the call unresolved, and the pipeline registry'simport_map_suffixfallback (src/pipeline/registry.c:757-777) binds it tometa.MetadataAccessor.SetNamespace— a two-parameter interface member — for a one-argument call. Wrong before, wrong differently after.What was tried
A shadowing guard at both sites (
!cbm_scope_contains(ctx->current_scope, <operand>);cbm_scope_containsis NULL-safe,internal/cbm/lsp/scope.c:107) is regression-free (the RHS keeps its package edge — Go evaluates the RHS in the outer scope) but exactly neutral on a reproduction fixture: the LSP still cannot type the local, because a cross-package function return type (meta.Accessorreturnsmetav1.Object) is not propagated into the binding. So the guard alone produces no observable change and was not shipped.Proposed fix
Land the scoping guard together with cross-package return-type propagation for
:=/=bindings, at which point a fixture withx, err := x.F(...)followed byx.Method()can assert the edge to the local's interface method and go RED without the change.