Repository navigation
fix(go-lsp): type variables bound by cross-file multi-value and named results - #2703
Merged
Merged
Conversation
…ables The cross-file pass handed every registrar the raw result text of a def (pass_lsp_cross.c: `dst->return_types = src->return_type`). The Go registrar splits that string on '|' (go_lsp.c split_pipe_types), so it got `(*Encoder, error)` as one piece. That piece did not parse, and a cross-file callee with more than one result, or with a named result `(e *Encoder)`, had no usable return type. As a result, `e, err := enc.NewEncoder()`, `h, _ := newHead()` and `_, d := diag.Run()` all left the variable untyped, and calls on it fell to name guesses. Same-file calls were fine, because they read the AST. Go defs now pass the parsed result list (extract_go_multi_return), '|'-joined: `*Encoder|error`, and `*Encoder` for the named result. Other languages keep the single raw string. The persisted LSP surface carries this string, so SURFACE_CODEC_VERSION goes from 2 to 3: existing caches re-extract Go surfaces once. The literal version-2 surfaces in the codec tests move to 3, so that they keep testing the envelope rule they name rather than the version mismatch. Tests: pipeline_go_multi_return_receiver_is_typed (+_parallel). A tuple result, a named result, a tuple-bound local reached through a field, and a named slice type each must bind the right method with an lsp_* strategy and none of its two same-named decoys. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The Python surface-envelope test (pipeline round 0) checks that the persisted row is the versioned codec envelope by looking for the literal "v":2. With SURFACE_CODEC_VERSION at 3, the envelope reads "v":3. The assertion keeps its meaning (the row is the versioned envelope and the def is present) and follows the version. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The first D1 change fed the Go cross-file registrar the extractor's return_types. Those are cleaned names for type-reference edges: clean_type_name strips `[]` and `*`. A cross-file single result `[]T` therefore reached the registrar as `T`. `xs := pkg.Generate(); xs[0].M()` lost its typing and fell back to a name guess. On prometheus the base binds `sample[0].T()` to chunks.Sample.T, but the first D1 change bound it to tsdb.T. pass_lsp_cross.c now leaves a single result's raw text alone, as before D1; the registrar already parses `*T` and `[]T`. Only a result LIST `(...)` is split, at its top-level commas, into raw element types: - `(*T, error)` -> `*T|error`; - the named `(t *T, err error)` -> `*T|error`; - the grouped `(a, b int)` -> `int|int`. `chan`, `func`, `map`, `struct` and `interface` never count as result names. A list that does not split cleanly keeps its raw text. Tests: the D1 fixture gains three call sites, each with two same-named decoys: - a single slice result indexed (`xs[0].TS()`); - a tuple with a slice element; - grouped named results. 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
The Go LSP types a variable from the call that binds it:
e := enc.New()givesethe result type ofenc.New, ande.Encode()then binds the right method. This works when the callee is in the same file, because the resolver reads the AST. For a callee in another file, it reads the result type from the cross-file registry. There,pass_lsp_cross.chanded over the raw result text. The Go registrar splits that text on|and parses each piece as one type (*T,[]T, builtins, named types). A result list such as(*Encoder, error)or the named(e *Encoder)therefore parsed as nothing. As a result, every variable bound by a cross-file multi-value or named-result call was untyped:Calls on those variables fell back to name guesses (
suffix_match,same_module,unique_name). Go code returns(T, error)everywhere, so this is a large share of Go method calls.Fix
Go only, in
pass_lsp_cross.c. A result list(...)is split at its top-level commas into its raw element types:(*T, error)*T|error(t *T, err error)*T|error(a, b int)int|int[]Tstays a slice.chan,func,map,structandinterfaceare never taken for result names.The extractor's
return_typesare deliberately not used. They are cleaned names for type-reference edges (clean_type_namestrips[]and*), and[]Sampleread asSamplemistypesxs[0].T(). The corpus check of a first version of this change caught exactly that, and a fixture case now pins it.The persisted LSP surface carries this string, so
SURFACE_CODEC_VERSIONgoes from 2 to 3: existing caches re-extract Go surfaces once. The codec tests' literal v2 envelopes move to v3, so that they keep testing the rule they name rather than the version mismatch.Tests
pipeline_go_multi_return_receiver_is_typedruns sequentially, and its_paralleltwin runs with 52 filler files andCBM_WORKERS=4. It covers seven cross-file call sites:Every target method has two same-named decoys, so a name guess cannot pass. Each site must bind the right file, never a decoy, with an
lsp_*strategy.pass_lsp_cross.cchange reverted, both tests are RED:UseTuple -> EncodeTuple: right=1 decoy=0 lsp=0, a name guess, not typed.Evidence
All binaries are clean builds (
build/removed first).main658639e.No repo got worse: precision and recall are equal or higher on all 13.
Changed strategies (edges, answerable call sites, correct):
The new typed edges are correct (4,429 of 4,433 answerable sites). The name-guess strategies keep their wrong edges and lose correct ones, because those calls now resolve by type instead.
lsp_interface_dispatchgrows by 1,374 edges: values from cross-file(Iface, error)results now bind the interface method. Examples are prometheusapp.Append->storage.Appender.Appendand vaultinm.Put->physical.Backend.Put. scip-go answers almost none of these call sites. A sample with source was 8 of 8 correct.Other edge types:
USAGE-77 /CALL_REFERENCE+79: method values such asdefer c.Closebecome typed references.SEMANTICALLY_RELATEDmoves (+630/-820) because it is built from CALLS neighbours.Go only. On spring-petclinic-microservices (Java), composer (PHP) and moshi (Kotlin), the node and edge dumps are byte-identical to main.
Together with #1907 and #1913. Those PRs remove weak-guess edges, and this PR wins back many of the true calls among them. Measured on a local merge of this branch into their head:
Not in this PR: embedded-struct promotion, package-qualified types read with the using file's imports, method-return chains, func-typed values, range variables and aliases. Each stays a separate change.