Skip to content

Methods of a single function are missing or scattered across modules #161

@timholy

Description

@timholy

Issue demonstration

In preparing to revise #160 a bit, I was trying to understand the logic a bit more. I created a fresh package depot with these "package" definitions:

A.jl:

module A

foo(x) = 1

end # module

B.jl:

module B

import A

struct Foo end
A.foo(::Foo) = 2

end # module

Each has its own Project.toml file and the standard package directory structure, with B's project file listing A in its [deps] section. Then if I cache symbols, I get this:

julia> env[:A][:foo].methods
1-element Array{SymbolServer.MethodStore,1}:
 SymbolServer.MethodStore(:foo, :A, "/tmp/pkgs/dev/A/src/A.jl", 3, Pair{Any,Any}[:x => SymbolServer.FakeTypeName(SymbolServer.VarRef(SymbolServer.VarRef(nothing, :Core), :Any), Any[])], Symbol[], SymbolServer.FakeTypeName(SymbolServer.VarRef(SymbolServer.VarRef(nothing, :Core), :Any), Any[]))

You'll notice there is only one method listed in the MethodStore, despite the fact that

julia> methods(A.foo)
# 2 methods for generic function "foo":
[1] foo(::B.Foo) in B at /tmp/pkgs/dev/B/src/B.jl:6
[2] foo(x) in A at /tmp/pkgs/dev/A/src/A.jl:3

Is it stored in B? To make sure I don't miss it (in case there's name-mangling to encode A.foo), let's look for "foo" anywhere in the name:

julia> ks = String.(keys(env[:B].vals));

julia> filter(s->occursin("foo", s), ks)
0-element Array{String,1}

Doesn't seem to be there.

If I change the definition of B.jl to

module B

import A: foo

struct Foo end
foo(::Foo) = 2

end # module

and start a fresh session and build the cache again, I get

julia> env[:A][:foo].methods
1-element Array{SymbolServer.MethodStore,1}:
 SymbolServer.MethodStore(:foo, :A, "/tmp/pkgs/dev/A/src/A.jl", 3, Pair{Any,Any}[:x => SymbolServer.FakeTypeName(SymbolServer.VarRef(SymbolServer.VarRef(nothing, :Core), :Any), Any[])], Symbol[], SymbolServer.FakeTypeName(SymbolServer.VarRef(SymbolServer.VarRef(nothing, :Core), :Any), Any[]))

julia> env[:B][:foo].methods
1-element Array{SymbolServer.MethodStore,1}:
 SymbolServer.MethodStore(:foo, :B, "/tmp/pkgs/dev/B/src/B.jl", 6, Pair{Any,Any}[Symbol("#unused#") => SymbolServer.FakeTypeName(SymbolServer.VarRef(SymbolServer.VarRef(nothing, :B), :Foo), Any[])], Symbol[], SymbolServer.FakeTypeName(SymbolServer.VarRef(SymbolServer.VarRef(nothing, :Core), :Any), Any[]))

That's better---it got both methods---but if you see a foo(3) in B then

julia> B.foo(3)
1

which demonstrates that it called the method defined in A, not the one defined in B. Meaning that all methods are available from any module that can access it. Might this cause a problem? To find out, I added

function bar()
    return foo(convert(Int, 3.0))
end

to B. Then I positioned my cursor somewhere in foo and hit F12, which takes me to foo(::Foo) rather than foo(::Any), and it doesn't give me a choice in a popup dialog. In contrast, positioning my cursor inside convert and hitting F12 takes me to a definition (the wrong one, but that's a separate issue) but gives me a dialog allowing me to manually choose other methods.

Proposed fix

Fixing this seems to require a bit of an architecture change. To me it seems that the most consistent representation would be to lump all methods for A.foo together in a single FunctionStore placed in A, rather than parcel them out to the modules in which they are defined. This is consistent with how Julia does it:

julia> B.foo === A.foo
true

julia> ft = typeof(A.foo)
typeof(A.foo)

julia> ftn = ft.name
typeof(A.foo)

julia> ftn.module     # A "owns" foo...
A

julia> m = methods(A.foo).ms[1]
foo(::B.Foo) in B at /tmp/pkgs/dev/B/src/B.jl:6

julia> m.module    # ...even though there are methods for it in B
B

When foo requires module-scoping for use in B (my first definition of B.jl), then I'd propose that foo should not be a recorded symbol in env[:B]. (Ideally, hitting F12 in a call A.foo(x) would grok the explicit module-scoping and find it in A.) In the second definition of B.jl, where B imported A.foo, I'd propose that env[:B][:foo] should return just VarRef(VarRef(A), :foo), and that all methods of A.foo be looked up in env[:A].

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions