# How to properly implement lazy compilation of submodules? Problem with invokelatest for kwarg functions

**URL:** <https://discourse.julialang.org/t/how-to-properly-implement-lazy-compilation-of-submodules-problem-with-invokelatest-for-kwarg-functions/90459>\
**Category:** General Usage\
**Tags:** question, metaprogramming\
**Created:** [November 18, 2022, 3:44pm UTC](https://discourse.julialang.org/t/how-to-properly-implement-lazy-compilation-of-submodules-problem-with-invokelatest-for-kwarg-functions/90459 "2022-11-18T15:44:54Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![vpetukhov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vpetukhov/32/12954_2.png) [@vpetukhov](https://discourse.julialang.org/u/vpetukhov)\
**Post date:** [November 18, 2022, 3:44pm UTC](https://discourse.julialang.org/t/how-to-properly-implement-lazy-compilation-of-submodules-problem-with-invokelatest-for-kwarg-functions/90459/1 "2022-11-18T15:44:54Z")

</div>

Inspired by [LazyModules](https://github.com/johnnychen94/LazyModules.jl) I’m trying to make delayed loading of some of submodules from my package. However, it implies that at some point I need to update a module definition on the fly. It works well with normal functions, but by some reason using keyword arguments breaks `Base.invokelatest`.  
Below is a simplified example (_still long, sorry, but nothing shorter reproduces the desired behavior_).

```julia
module T

# Code for lazy submodules

mutable struct LazySubmodule
    _module_name::Symbol
    _code::Expr
    _module::Union{Module, Nothing}
end

macro lm(ex)
    alias, code = ex.args
    s = LazySubmodule(alias, code, __module__ )
    Core.eval( __module__ , :($(alias) = $s))
end

function Base.getproperty(m::LazySubmodule, s::Symbol)
    if s in (:_module_name, :_code, :_module)
        return getfield(m, s)
    end

    # Compile the sub-module
    Core.eval(m._module, :($(m._code)))
    alias = m._code.args[2]
    # Rewrite it's alias with the compiled module instead of the LazySubmodule
    mod = Core.eval(m._module, :($(m._module_name) = $(alias)))

    f = getfield(mod, s)
    # invokelatest should solve the issue with world age and missing functions
    return (args...; kwargs...) -> Base.invokelatest(f, args...; kwargs...)
end

# Define my submodules

@lm M1 = module Module1
    f1(s::String) = print(s)
end

f(s::String) = M1.f1(s)

end

```

This code doesn’t have any keyword arguments, and if I call `T.f("Hello")`, it prints “Hello” as expected. However, if I add keyword arguments to the module definitions as below, the code breaks.

```julia
@lm M1 = module Module1
    f1(;s::String) = print(s)
end

f(s::String) = M1.f1(s=s)

```

Now, the first call `T.f("Hello")` fails with error

```julia
ERROR: MethodError: no method matching f1(; s="Hello")
Closest candidates are:
  f1(; s)

```

while the second call works fine. It looks exactly like [world age problems](https://discourse.julialang.org/t/world-age-problem-explanation/9714/14), and if I remove `invokelatest`, the same behavior could be observed for the case 1 (no keyword arguments), as well.

Could anyone please explain why this is different with and without kwargs, and how it could be fixed? I can see that the [implementation of `invokelatest`](https://github.com/JuliaLang/julia/blob/d12ac369a4be6ae7833fb95c644cea4531764e31/base/essentials.jl#L813) indeed processes functions with kwargs differently. But the actual differences are in the [core](https://github.com/JuliaLang/julia/blob/b36951160ef0ba8c4641dd768cd7a1f5f570d0a9/src/builtins.c#L742), which I’m unable to comprehend…

If there is a better way to make lazy loading of submodules, that would also solve the problem. 🙂

**UPDATE**  
The problem turns out to be not in `invokelatest`, but somewhere in how julia calls methods. Setting

```julia
function Base.getproperty(m::LazySubmodule, s::Symbol)
    if s in (:_module_name, :_code, :_module)
        return getfield(m, s)
    end

    Core.eval(m._module, :($(m._code)))
    alias = m._code.args[2]
    mod = Core.eval(m._module, :($(m._module_name) = $(alias)))

    return (args...; kwargs...) -> error("This code is never reached")
end

```

shows that the result of the function is never used, and instead it’s substituted with `T.M1.f1` directly.

**UPDATE 2**  
The problem is in how Julia insides convert keyword functions to positional functions. While the lowered code for positional arguments looks fine:

```julia
@code_lowered T.f("A")
CodeInfo(
1 ─ %1 = Base.getproperty(Main.T.M1, :f1)
│ %2 = (%1)(s)
└── return %2
)

```

the code for functions with keywords is more complicated:

```julia
CodeInfo(
1 ─ %1 = (:s,)
│ %2 = Core.apply_type(Core.NamedTuple, %1)
│ %3 = Core.tuple(s)
│ %4 = (%2)(%3)
│ %5 = Base.getproperty(Main.T.M1, :f1)
│ %6 = Core.kwfunc(%5)
│ %7 = Base.getproperty(Main.T.M1, :f1)
│ %8 = (%6)(%4, %7)
└── return %8
)

```

So, essentially it calls `Base.getproperty` twice. And as the module gets replaced between the two calls, only the first call uses `invokelatest`. Still, I’d appreciate any advise on how to deal with that.
