Summary
When two modules export a function with the same leaf name, the inferred
parameter types reach only one of them. The second reports its parameter bare
even though the checker proved it.
Reproduction
module A {
export fn rate(x) = x + 1 ?: 0
}
module B {
export fn rate(s) = "${s}!"
}
let v = A::rate(2)
let w = B::rate("hi")
$ osprey mod2.osp --symbols
scratchpad::A::rate -> fn scratchpad::A::rate(x: int) -> int
scratchpad::B::rate -> fn scratchpad::B::rate(s) -> string
B::rate's return type resolves (string), but its parameter stays s where
it should be s: string. With only one rate in the program, both slots fill
correctly — the loss appears only once a second module reuses the leaf name.
Root cause
fill_inferred (crates/osprey-lsp/src/analysis.rs) looks a symbol up by its
QUALIFIED outline name (A::rate), while ProgramTypes::param_types is keyed
by the leaf the checker recorded (rate). One entry answers for both modules,
so the second symbol's parameter lookup misses.
The obvious patch — falling back to the unqualified source_name — is worse
than the gap: two modules exporting rate would then be handed each other's
parameter types, turning a missing annotation into a wrong one. The lookup has
to stay scope-aware.
Scope
Not a regression. On main symbols_json never consulted inference at all, so
every parameter came back bare; this branch fills them and this is the one
shape it does not reach. B::rate is no worse than main, A::rate is
better.
Suggested direction
Infer and fill each module container against its own local Program, then
qualify the resulting names on the way out, rather than filling qualified
symbols from one global ProgramTypes whose keys have already lost the module
scope. Alternatively, key ProgramTypes function entries by qualified name so
the outline's lookup is exact.
Acceptance
- Both
A::rate and B::rate report their proven parameter and return types.
- A test with two same-leaf module functions of DIFFERENT types asserts neither
is given the other's — the wrong-answer case must be pinned, not just the
missing-answer one.
Summary
When two modules export a function with the same leaf name, the inferred
parameter types reach only one of them. The second reports its parameter bare
even though the checker proved it.
Reproduction
B::rate's return type resolves (string), but its parameter staysswhereit should be
s: string. With only oneratein the program, both slots fillcorrectly — the loss appears only once a second module reuses the leaf name.
Root cause
fill_inferred(crates/osprey-lsp/src/analysis.rs) looks a symbol up by itsQUALIFIED outline name (
A::rate), whileProgramTypes::param_typesis keyedby the leaf the checker recorded (
rate). One entry answers for both modules,so the second symbol's parameter lookup misses.
The obvious patch — falling back to the unqualified
source_name— is worsethan the gap: two modules exporting
ratewould then be handed each other'sparameter types, turning a missing annotation into a wrong one. The lookup has
to stay scope-aware.
Scope
Not a regression. On
mainsymbols_jsonnever consulted inference at all, soevery parameter came back bare; this branch fills them and this is the one
shape it does not reach.
B::rateis no worse thanmain,A::rateisbetter.
Suggested direction
Infer and fill each module container against its own local
Program, thenqualify the resulting names on the way out, rather than filling qualified
symbols from one global
ProgramTypeswhose keys have already lost the modulescope. Alternatively, key
ProgramTypesfunction entries by qualified name sothe outline's lookup is exact.
Acceptance
A::rateandB::ratereport their proven parameter and return types.is given the other's — the wrong-answer case must be pinned, not just the
missing-answer one.