# Testing a string macro

**URL:** <https://discourse.julialang.org/t/testing-a-string-macro/112244>\
**Category:** General Usage\
**Tags:** testing, macros\
**Created:** [March 28, 2024, 6:06pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244 "2024-03-28T18:06:15Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![GHTaarn](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ghtaarn/32/216007_2.png) [@GHTaarn](https://discourse.julialang.org/u/GHTaarn)\
**Post date:** [March 28, 2024, 6:06pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/1 "2024-03-28T18:06:15Z")

</div>

The code

```julia
using Test

xx = Dict{String,String}()

macro x_str(str,suf)
    return str * xx[suf]
end

@testset "a" begin
    xx["a"] = "apple"
    @test x"tasty "a == "tasty apple"
end

```

Throws an error:

```plaintext
ERROR: LoadError: KeyError: key "a" not found

```

and so far I have corrected it by replacing the second last line with:

```julia
    @test eval(Meta.parse("""x"tasty "a""")) == "tasty apple"

```

Is there a better way to do this?

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [March 28, 2024, 6:17pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/2 "2024-03-28T18:17:21Z")

</div>

This is basically a confusion about what macros do and when they run, and what they are supposed to return.

The problem is that your string macro body executes when shortly after the code is _parsed_ (technically, when the code is “lowered”), which happens _before_ your `@testset` code _runs_. So, the line `xx["a"] = "apple"` hasn’t executed yet, and the dictionary lookup fails.

A solution is to change your macro definition to return an _expression_ that constructs the desired string, so that it doesn’t actually execute the `xx[suf]` lookup when the macro is _lowered_ (when the macro body runs), but only when the corresponding line is executed:

```julia
macro x_str(str,suf)
    return :($str * $xx[$suf])
end

```

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [March 28, 2024, 6:22pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/3 "2024-03-28T18:22:46Z")

</div>

There are several level of problems here.

A macro, including string macros, are supposed to return expressions, not do the evaluation themselves. Any evaluation done in a macro is done at parse time.

The code you wrote proceeds similar the following order. Because you try to evaluate `xx["a"]` before you set it, you get the error that you get.

```julia
xx = Dict{String,String}()
temp = "tasty " * xx["a"]
ex = :(@testset "a" begin
    xx["a"] = "apple"
    @test $temp == "tasty apple"
end)
eval(ex)

```

Rather your macro should return an expression that will be evaluated in the normal order.

```julia
julia> macro x_str(str, suf)
           return :($str * xx[$suf])
       end
@x_str (macro with 1 method)

julia> @testset "a" begin
           xx["a"] = "apple"
           println(xx["a"])
       end
apple
Test Summary: |Time
a | None 0.0s
Test.DefaultTestSet("a", Any[], 0, false, false, true, 1.711649824769256e9, 1.711649824780527e9, false, "REPL[11]")

julia> @macroexpand x"tasty "a
:("tasty " * Main.xx["a"])

julia> macro x_str(str, suf)
           return :($str * xx[$suf])
       end
@x_str (macro with 1 method)

julia> let xx = Dict("a" => "orange")
           x"tasty "a
       end
"tasty apple"

```

Also note that with the definition I just gave, `xx` is `Main.xx`.

If I want `xx` to refer to something in the context of the local scope, then I would need to the following.

```julia-repl
julia> macro x_str(str, suf)
           return esc(:($str * xx[$suf]))
       end
@x_str (macro with 1 method)

julia> let xx = Dict("a" => "orange")
           x"tasty "a
       end
"tasty orange"

julia> @macroexpand x"tasty "a
:("tasty " * xx["a"])

```

Edit: Added the `esc` in the last example.

---

<div class="post-metadata">

**Author:** ![GHTaarn](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ghtaarn/32/216007_2.png) [@GHTaarn](https://discourse.julialang.org/u/GHTaarn)\
**Post date:** [March 28, 2024, 7:58pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/4 "2024-03-28T19:58:33Z")

</div>

Thank you @stevengj and @mkitti , I learnt some very valuable lessons, especially:

> [@mkitti](#):
>
> A macro, including string macros, are supposed to return expressions, not do the evaluation themselves. Any evaluation done in a macro is done at parse time.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [March 28, 2024, 8:05pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/5 "2024-03-28T20:05:58Z")

</div>

> [@mkitti](#):
>
> A macro, including string macros, are supposed to return expressions, not do the evaluation themselves.

Macros often do the evaluation and return the “final” value instead of an expression. Even in base Julia, see eg `r"..."` and `dateformat"..."`.

---

<div class="post-metadata">

**Author:** ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)\
**Post date:** [March 28, 2024, 9:44pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/6 "2024-03-28T21:44:34Z")

</div>

> [@aplavin](#):
>
> Macros often do the evaluation and return the “final” value instead of an expression. Even in base Julia, see eg `r"..."` and `dateformat"..."`.

I think this is one of those “Do as I say, not as I do” moments. Base can bend the rules, but generally you shouldn’t be using `eval` in a macro.

---

<div class="post-metadata">

**Author:** ![bertschi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bertschi/32/33462_2.png) [@bertschi](https://discourse.julialang.org/u/bertschi)\
**Post date:** [March 28, 2024, 11:09pm UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/7 "2024-03-28T23:09:06Z")

</div>

It’s not about using `eval` in the macro, but computing a concrete value instead of an expression that would do so later. This is also used in regular macros when literal information is available at compile time, e.g., `@printf` will compile the format string and does not allow it to depend on runtime information:

```julia
@printf "%d" 10 # works
let s = "%d"; @printf s 10 end # does not work

```

Guess that using literal compile time information in this fashion is even more widespread in string macros.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [March 29, 2024, 12:47am UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/8 "2024-03-29T00:47:33Z")

</div>

> [@aplavin](#):
>
> Macros often do the evaluation and return the “final” value instead of an expression. Even in base Julia, see eg `r"..."` and `dateformat"..."`.

My statement holds here. In the examples you gave, we are creating literal expressions from literal expressions. Importantly, these are usually not dependent on outside variables.

This cam have some really distinct effects that you cannot otherwise accomplish.

```julia-repl
julia> big"5" === big"5"
false

julia> macro big5()
           return big"5"
       end
@big5 (macro with 1 method)

julia> @big5() === @big5()
true

```

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [March 29, 2024, 4:23am UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/9 "2024-03-29T04:23:26Z")

</div>

> [@mkitti](#):
>
> If I want `xx` to refer to something in the context of the local scope, then I would need to the following.

Did you mean to insert `esc` here? Like this:

```julia
julia> macro x_str(str, suf)
           return esc(:($str * xx[$suf]))
       end
@x_str (macro with 1 method)

julia> let xx = Dict("a" => "orange")
           x"tasty "a
       end
"tasty orange"

julia> @macroexpand x"tasty "a
:("tasty " * xx["a"])

```

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [March 29, 2024, 4:26am UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/10 "2024-03-29T04:26:29Z")

</div>

Yes. I corrected the post.

---

<div class="post-metadata">

**Author:** ![bertschi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bertschi/32/33462_2.png) [@bertschi](https://discourse.julialang.org/u/bertschi)\
**Post date:** [March 29, 2024, 9:12am UTC](https://discourse.julialang.org/t/testing-a-string-macro/112244/11 "2024-03-29T09:12:30Z")

</div>

Except that `esc` is a hammer that should be used with care:

```julia
macro x_str(str, suf)
    return esc(:($str * xx[$suf]))
end

macro y_str(str, suf)
    return :($str * $(esc(:xx))[$suf])
end

```

```julia-repl
julia> let xx = Dict("a" => "orange"), * = tuple
           @show x"tasty "a
           @show y"tasty "a
       end;
x"tasty "a = ("tasty ", "orange")
y"tasty "a = "tasty orange"

```
