Skip to content

go: a local that shadows an imported package name is always resolved as the package (go_lsp.c checks imports before local scope) #2032

Description

@DeusData

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:369go_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.

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

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions