How to make a macro modify struct member?

I am trying to write a macro that may generate an assignment statement to modify one of its arguments (more precisely, modify the object the argument binds to). That argument will be a member of a mutable struct; which member will vary from call to call.

It seems that when I pass a struct member reference to a macro, Julia’s macro expansion assumes the argument is a global within the current module, not local to the scope of the calling function, which leads to a crash. What am I doing wrong?

Example:

mutable struct S x end

macro m(arg)
    :( $arg = 1 )
end

function f()
    @show @macroexpand @m(s)
    @show @macroexpand @m(s.x)

    s = S(1)
    @m(s.x)
end

f()

When I run this, I get

#= Untitled-1:8 =# @macroexpand(#= Untitled-1:8 =# @m(s)) = :(var"#18#s" = 1)
#= Untitled-1:9 =# @macroexpand(#= Untitled-1:9 =# @m(s.x)) = :((Main.s).x = 1)
ERROR: LoadError: UndefVarError: s not defined
Stacktrace:
 [1] f()
   @ Main .\Untitled-1:12
 [2] top-level scope
   @ Untitled-1:15
in expression starting at Untitled-1:15

The first @macroexpand output line looks like I would want it to look: it assigns via a temporary alias with no scope qualifications. The second instead refers directly to Main.s, which does not exist. I just want it to refer to s, the calling function’s local variable.

Background: my real purpose is to cleanly and efficiently implement what is in spirit a “!” function that may in some cases produce output exactly equal to one of its inputs. In those cases, I do not want to copy the input matrix into the provided output matrix, but rather change the binding of the caller’s name for the output matrix to the input matrix. A macro seems like the Julianic way to do this, but maybe not?

To make s refer to the local variable, you have to escape arg:

macro m(arg)
    :($(esc(arg)) = 1)
end
julia> f()
#= REPL[6]:2 =# @macroexpand(#= REPL[6]:2 =# @m(s)) = :(s = 1)
#= REPL[6]:3 =# @macroexpand(#= REPL[6]:3 =# @m(s.x)) = :(s.x = 1)
1

However, this also changes the behavior in the first case @m(s), even though you want the old behavior in that case (if I understand you correctly). To achieve that, you would have to return different expressions (with or without esc) depending on whether your argument is a single variable (a Symbol) or not.