module MacroMethods
    using ..JuliaLowering
    macro some_macro()
        JuliaLowering.@legacy_quote_to_syntax quote
            some_global
        end
    end

    module ExtraMacroMethods
        using ..MacroMethods
        using ..JuliaLowering
        macro MacroMethods.some_macro(ex)
            JuliaLowering.@legacy_quote_to_syntax quote
                some_global
            end
        end
    end
end

macro strmac_str(ex, suff=nothing)
    s = "$(ex.value) from strmac"
    if !isnothing(suff)
        s = "$s with suffix $(suff.value)"
    end
    s
end

macro cmdmac_cmd(ex, suff=nothing)
    s = "$(ex.value) from cmdmac"
    if !isnothing(suff)
        s = "$s with suffix $(suff.value)"
    end
    s
end

#*******************************************************************************
########################################
# Simple macro
macro add_one(ex)
    JuliaLowering.@legacy_quote_to_syntax quote
        $ex + 1
    end
end
#---------------------
1   (call core.define_method TestMod :@add_one)
2   latestworld
3   TestMod.@add_one
4   (call core.TypeEqOf %₃)
5   (call core.svec %₄ JuliaLowering.MacroContext core.Any)
6   (call core.svec)
7   SourceLocation::1:1
8   (call core.svec %₅ %₆ %₇)
9   (call core.define_method TestMod TestMod.@add_one %₈
    --- code_info
    slots: [slot₁/#self#(!read) slot₂/__context__(!read) slot₃/ex]
    1   (call core.tuple slot₃/ex)
    2   (call JuliaLowering.interpolate_syntax (syntaxinert (block (call + (syntaxunquote ex) 1))) %₁)
    3   (return %₂)
10  latestworld
11  TestMod.@add_one
12  (return %₁₁)

########################################
# Macro using `__context__`
macro foo(ex)
    ctx = __context__
end
#---------------------
1   (call core.define_method TestMod :@foo)
2   latestworld
3   TestMod.@foo
4   (call core.TypeEqOf %₃)
5   (call core.svec %₄ JuliaLowering.MacroContext core.Any)
6   (call core.svec)
7   SourceLocation::1:1
8   (call core.svec %₅ %₆ %₇)
9   (call core.define_method TestMod TestMod.@foo %₈
    --- code_info
    slots: [slot₁/#self#(!read) slot₂/__context__ slot₃/ex(!read) slot₄/ctx(!read,single_assign)]
    1   slot₂/__context__
    2   (= slot₄/ctx %₁)
    3   (return %₁)
10  latestworld
11  TestMod.@foo
12  (return %₁₁)

########################################
# Scope for symbols emitted by macros is the module where the method was
# defined, thus two different modules in this case, even though `@some_macro`
# belongs to the MacroMethods module.
(MacroMethods.@some_macro(), MacroMethods.@some_macro(unused))
#---------------------
1   TestMod.MacroMethods.some_global
2   TestMod.MacroMethods.ExtraMacroMethods.some_global
3   (call core.tuple %₁ %₂)
4   (return %₃)

########################################
# Error: Macro with kw args
macro mmm(a; b=2)
end
#---------------------
LoweringError:
macro mmm(a; b=2)
#         ╙ ── macros cannot accept keyword arguments
end

########################################
# Error: Macro with `where`
macro mmm(a::T) where T
end
#---------------------
LoweringError:
macro mmm(a::T) where T
#     └───────────────┘ ── `where` not allowed in macro signatures
end

########################################
# Error: Bad macro name
macro mmm[](ex)
end
#---------------------
LoweringError:
macro mmm[](ex)
#     └───┘ ── invalid macro name
end

########################################
# Error: Macros not allowed in local scope
let
    macro foo(ex)
    end
end
#---------------------
LoweringError:
let
#   ┌────────────
    macro foo(ex)
    end
#─────┘ ── macro is only allowed in global scope
end

########################################
# Error: Macros not allowed in local scope
function f()
    macro foo()
    end
end
#---------------------
LoweringError:
function f()
#   ┌──────────
    macro foo()
    end
#─────┘ ── this syntax is only allowed at top level
end

########################################
# Error: Macros not found
_never_exist = @m_not_exist 42
#---------------------
MacroExpansionError while expanding @m_not_exist in module Main.TestMod:
_never_exist = @m_not_exist 42
#              └──────────┘ ── Macro not found
Caused by:
UndefVarError: `@m_not_exist` not defined in `Main.TestMod`
Suggestion: check for spelling errors or missing imports.

########################################
# Simple cmdstring
`echo 1`
#---------------------
1   Base.cmd_gen
2   (call core.tuple "echo")
3   (call core.tuple "1")
4   (call core.tuple %₂ %₃)
5   (call %₁ %₄)
6   (return %₅)

########################################
# Simple string macro
strmac"hello"
#---------------------
1   (return "hello from strmac")

########################################
# String macro with suffix
strmac"hello"blah
#---------------------
1   (return "hello from strmac with suffix blah")

########################################
# Simple cmd macro
cmdmac`hello`
#---------------------
1   (return "hello from cmdmac")

########################################
# Cmd macro with suffix
cmdmac`hello`12345
#---------------------
1   (return "hello from cmdmac with suffix 12345")

########################################
# @nospecialize (zero args)
function foo(a)
    @nospecialize
end
#---------------------
1   (call core.define_method TestMod :foo)
2   latestworld
3   TestMod.foo
4   (call core.TypeEqOf %₃)
5   (call core.svec %₄ core.Any)
6   (call core.svec)
7   SourceLocation::1:1
8   (call core.svec %₅ %₆ %₇)
9   (call core.define_method TestMod TestMod.foo %₈
    --- code_info
    slots: [slot₁/#self#(!read) slot₂/a(nospecialize,!read)]
    1   (meta :nospecialize)
    2   (return core.nothing)
10  latestworld
11  TestMod.foo
12  (return %₁₁)

########################################
# @nospecialize (single arg in body)
function foo(a, b)
    @nospecialize a
    a + b
end
#---------------------
1   (call core.define_method TestMod :foo)
2   latestworld
3   TestMod.foo
4   (call core.TypeEqOf %₃)
5   (call core.svec %₄ core.Any core.Any)
6   (call core.svec)
7   SourceLocation::1:1
8   (call core.svec %₅ %₆ %₇)
9   (call core.define_method TestMod TestMod.foo %₈
    --- code_info
    slots: [slot₁/#self#(!read) slot₂/a(nospecialize) slot₃/b]
    1   (meta :nospecialize slot₂/a)
    2   TestMod.+
    3   (call %₂ slot₂/a slot₃/b)
    4   (return %₃)
10  latestworld
11  TestMod.foo
12  (return %₁₁)

########################################
# @nospecialize (multi-arg in body)
function foo(x, y, z)
    @nospecialize x z
    x + y + z
end
#---------------------
1   (call core.define_method TestMod :foo)
2   latestworld
3   TestMod.foo
4   (call core.TypeEqOf %₃)
5   (call core.svec %₄ core.Any core.Any core.Any)
6   (call core.svec)
7   SourceLocation::1:1
8   (call core.svec %₅ %₆ %₇)
9   (call core.define_method TestMod TestMod.foo %₈
    --- code_info
    slots: [slot₁/#self#(!read) slot₂/x(nospecialize) slot₃/y slot₄/z(nospecialize)]
    1   (meta :nospecialize slot₂/x slot₄/z)
    2   TestMod.+
    3   (call %₂ slot₂/x slot₃/y slot₄/z)
    4   (return %₃)
10  latestworld
11  TestMod.foo
12  (return %₁₁)

########################################
# Error: thisfunction disallowed in comprehension/generator
[@__FUNCTION__() for x in 1:2]
#---------------------
LoweringError:
[@__FUNCTION__() for x in 1:2]
#└─────────────┘ ── current function not defined in comprehension or generator

########################################
# Error: thisfunction disallowed in comprehension/generator
f(@__FUNCTION__() for x in 1:2)
#---------------------
LoweringError:
f(@__FUNCTION__() for x in 1:2)
# └─────────────┘ ── current function not defined in comprehension or generator

########################################
# Error: thisfunction disallowed outside of function
let
    @__FUNCTION__()
end
#---------------------
LoweringError:
let
    @__FUNCTION__()
#   └─────────────┘ ── can only be used inside a function
end
