I would say that this falls into a more general known problem https://github.com/JuliaLang/julia/issues/15602.
In your case consider a simpler function:
function f()
p() = "before"
println(p())
p() = "after"
nothing
end
Calling f() will print "after".
In your case you can inspect what is going on with foo in the following way:
julia> @code_typed foo()
CodeInfo(
4 1 ─ invoke Main.error("Wrong"::String)::Union{} │
│ $(Expr(:unreachable))::Union{} │
└── $(Expr(:unreachable))::Union{} │
) => Union{}
and you see that Julia optimizes out all internal logic and just calls error.
If you inspect it one step earlier you can see:
julia> @code_lowered foo()
CodeInfo(
2 1 ─ p = %new(Main.:(#p#7)) │
3 │ %2 = (p)(1) │
│ %3 = %2 != 2 │
└── goto #3 if not %3 │
4 2 ─ (Main.error)("Wrong") │
6 3 ─ return p │
)
any you see that p in top line is assigned only once. Actually the second definition is used (which is not visible here, but could be seen above).
To solve your problem use anonymous functions like this:
function foo2()
p = t -> t + 1
if p(1) != 2
error("Wrong")
end
p = t -> 1
end
and all will work as expected. The limitation of this approach is that you do not get multiple dispatch on name p (it is bound to a concrete anonymous function, but I guess you do not need multiple dispatch in your example).