Repository navigation
fix(go): a bare init never binds to a package init or an init method - #2862
Merged
Merged
Conversation
Go gives `init` no binding in the package block: init functions cannot
be referred to from anywhere in a program, and a method or field named
init needs a selector. A bare `init` is therefore always a local name -
a closure (`init := func(ctx) {...}; init(ctx)`), a parameter, or the
`package init` clause of a directory named init.
Before #1915 same_module bound such a name to the flat package init.
With per-file init QNs it now falls through to the short-name fallback
and binds unrelated methods named init. On kubernetes that minted 24
CALLS (22 e2e storage DefineTests closures among them), 28 WRITES,
7 USAGE and one derived TESTS edge, all false.
cbm_go_suppress_bare_init_bind refuses, for a bare Go `init`, any
registry binding onto a Function (every Go Function named init is a
package init) and a weak short-name binding onto a Method or Field.
lsp_* answers and selector calls (`w.init()`) are untouched. Wired at
the CALLS, USAGE and READS/WRITES seams of both resolvers.
Kubernetes, full index, main vs branch: only those 60 edges removed,
nodes identical, no resolver edge added; the 14 LSP-typed selector
CALLS into init methods are unchanged.
Tests: pipeline_go_bare_init_binds_nothing (+ parallel twin) are RED
with the predicate disabled (closure CALLS, parameter CALLS, closure
WRITES, package-clause USAGE onto Watcher.init) and GREEN with it; the
selector control keeps Watcher.Start/Boot -> Watcher.init.
go_bare_init_never_binds_init_namesake pins the predicate.
Refs #1915
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Go gives the identifier
initno binding in the package block: it declares init functions and nothing else, and the spec says init functions "cannot be referred to from anywhere in a program". A method or field namedinitis only reachable through a selector (w.init(),e.init). So a bareinitin Go is always a local name: a closure (init := func(ctx context.Context) {...}; init(ctx)), a parameter (func sum(init func() ref.Val)), or thepackage initclause of a directory named init.The resolver did not know this. Before #1915 the flat package-init QN made
same_modulebind a bareinitto its own package's init function. #1915 gave every init its own QN (pkg.init#file.go:N), which correctly stops that, but the bare name now falls through to the short-name fallback and binds unrelated methods namedinitinstead. On kubernetes (8c078bc3, full index) every one of these edges was false:DefineTestsclosures, one test-local closure, and thesumparameter (23 ontopluginwatcher.Watcher.init/lazyObject.init, 1 onto the fieldquotaEvaluator.init)init := func(...)onto those methodspackage initclause oftest/e2e/framework/*/init/init.goThe rule
cbm_go_suppress_bare_init_bind(src/pipeline/registry.c), Go only. A bareinit(a call that is not a selector call, a reference that is not the member half of a selector):lsp_*answers are left alone, because the sequential resolver emits them before its registry guards run and refusing one in only the parallel resolver would split the two. Selector calls are untouched, sow.init()keeps resolving toWatcher.init.It is wired the same way as the neighbouring guards, at all six seams, and mirrored across both resolvers: CALLS (pass_calls.c and pass_parallel.c, beside the Java data-member guard), USAGE and READS/WRITES (pass_usages.c and pass_parallel.c, beside the bare-field guard).
Why Go-only, and why it is safe
The rule follows from the language spec, not from a heuristic. In Go no correct edge can come from a bare
initto a package init, a method or a field. In other languagesinitis an ordinary name: in Java, C#, C++, Kotlin or Ruby a bareinit()inside a method calls the class's own method, and in C, Python or JS aninitfunction is called by its bare name. So the rule is gated on the file language, in line with the per-language suppressor rule.Measured on kubernetes (full index, same corpus, fresh cache per binary)
inite.init)A whole-graph edge diff (type, source QN, target QN) between the two indexes shows that only these 60 edges were removed. Nodes are identical (355,618) and no CALLS/USAGE/WRITES/READS edge was added anywhere. The only other movement is SEMANTICALLY_RELATED, with 52 edges removed and 48 added out of about 255. This is expected: that pass builds corpus-wide TF-IDF / random-indexing vectors from each function's CALLS neighbours, so removing 24 CALLS shifts a few pairs near the threshold. Both binaries produce identical graphs across two runs each, so this movement is caused by the change and is not run-to-run noise.
Tests
pipeline_go_bare_init_binds_nothingandpipeline_go_bare_init_binds_nothing_parallel(tests/test_pipeline.c). The fixture has a packageinitin the caller's package, a local closureinitthat is called, written and passed by value, a parameter namedinit, apackage initdirectory and an unrelatedWatcher.initmethod. The tests assert that no CALLS/USAGE/WRITES/READS/CALL_REFERENCE edge from a bareinitreaches any node named init, and that the decoys exist so the zero is not vacuous. The control asserts that the selector callsw.init()fromWatcher.Startand fromBootstill resolve to the method. The parallel twin uses 52 filler files andCBM_WORKERS=4.go_bare_init_never_binds_init_namesake(tests/test_registry.c) pins the predicate: every strategy for the Function, weak strategies only for the Method/Field, lsp_* kept, selectors kept, other names and other languages untouched.package initUSAGE, all ontoWatcher.init). With it enabled, they pass.Refs #1915.