Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add noscope macro to make try blocks evaluate in the local scope #39217

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,83 @@ macro eval(mod, ex)
return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex)))
end

"""
@noscope try ...

Evaluate a `try` block in the local scope.

```jldoctest
julia> x
ERROR: UndefVarError: x not defined

julia> @noscope try
x = 1
error()
catch err
display(err)
finally
println("finally")
end
ErrorException()
finally

julia> x
1
```
"""
macro noscope(ex)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe

Suggested change
macro noscope(ex)
macro noscope360(ex)

?

!(isa(ex, Expr) && ex.head == :try) && throw(ArgumentError("@noscope only supports try blocks"))
if length(ex.args) == 4 # try that includes finally
if ex.args[2] isa Symbol # try-catch-finally with catch var
return Expr(:tryfinally,
Expr(:trycatch,
:($(esc(ex.args[1]))),
quote
begin
Copy link
Sponsor Member Author

@IanButterworth IanButterworth Jan 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think this and all the finally and catch blocks should still be scoped

Suggested change
begin
let

$(esc(ex.args[2])) = $(esc(Expr(:the_exception)))
$(esc(ex.args[3]))
end
end
),
:($(esc(ex.args[4])))
)
else
if ex.args[3] === false # try-finally
return Expr(:tryfinally,
:($(esc(ex.args[1]))),
:($(esc(ex.args[4])))
)
else # try-catch-finally with no catch var
return Expr(:tryfinally,
Expr(:trycatch,
:($(esc(ex.args[1]))),
:($(esc(ex.args[3])))
),
:($(esc(ex.args[4])))
)
end
end
elseif length(ex.args) == 3 # try-catch
if ex.args[2] isa Symbol # try-catch with catch var
return Expr(:trycatch,
:($(esc(ex.args[1]))),
quote
begin
$(esc(ex.args[2])) = $(esc(Expr(:the_exception)))
$(esc(ex.args[3]))
end
end
)
else # try-catch with no catch var
return Expr(:trycatch,
:($(esc(ex.args[1]))),
:($(esc(ex.args[3])))
)
end
end
error("Unrecognized `try` form")
end

argtail(x, rest...) = rest

"""
Expand Down
3 changes: 3 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,9 @@ export
@specialize,
@polly,

# scope manipulation
@noscope,

@assert,
@__dot__,
@enum,
Expand Down
21 changes: 21 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -928,3 +928,24 @@ end
@testset "issue #28188" begin
@test `$(@__FILE__)` == let file = @__FILE__; `$file` end
end

@testset "`try` scope change via. @noscope" begin
@assert !isdefined(@__MODULE__, :noscopevar1)
try noscopevar1 = 1; catch; end
@assert !isdefined(@__MODULE__, :noscopevar1)

@noscope try noscopevar1 = 1; catch end
@test noscopevar1 == 1

@noscope try noscopevar2 = 1; catch err display(err); end
@test noscopevar2 == 1

@noscope try noscopevar3 = 1; catch err display(err); finally 0; end
@test noscopevar3 == 1

@noscope try noscopevar4 = 1; catch; 0; finally 0; end
@test noscopevar4 == 1

@noscope try noscopevar5 = 1; finally 0; end
@test noscopevar5 == 1
end