Module parameters and run-time efficiency

Regarding your @inline workaround. This will work sometimes, because inlining makes constant propagation more potent. But it’s only a hint and may not work in all cases. If the module is not a known constant at the call site, you can still end up in a similar situation to the original (except with the dynamic dispatch happening within the ::Module method rather than the ::Type{Mod} method). The function barrier this introduces may result in different performance characteristics (often an improvement).

A more idiomatic pattern similar to what you’re doing might be this (I use slightly different patterns for A and B, you can choose which you prefer):

module ABCore
export foo
function foo end # function stub we'll add to later
end

module A
using ..ABCore # may need to fiddle with the number of `.` depending on your context
export AA
struct AAType end # singleton type
AA = AAType() # construct an instance
ABCore.foo(::AAType, bar) = bar + bar # add a method
end

module B
using ..ABCore # may need to fiddle with the number of `.` depending on your context
export BB
struct BB end # singleton type
# here I use a slightly different pattern where BB is a type, which will change how this is called
ABCore.foo(::BB, bar) = bar * bar # add a method
end

julia> using .ABCore, .A, .B

julia> foo(AA, 3) # AA is an instance, so call like this
6

julia> foo(BB(), 3) # BB is a type, so use () to construct an instance
9

julia> methods(foo) # all methods of the same function now
# 2 methods for generic function "foo" from Main.ABCore:
 [1] foo(::BB, bar)
     @ Main.B REPL[3]:9
 [2] foo(::Main.A.AAType, bar)
     @ Main.A REPL[2]:10

In this pattern, rather than passing around Modules, you use the singleton type provided by each module to control which method of foo is called. There is only a single foo function, but different modules create different methods to implement it.