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 @check macro for non-disable-able @assert #41342

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
wip: capture and outline variables
ericphanson committed Dec 29, 2021
commit cf9e31f5164dd44d46289fcc70fe3637826233e0
131 changes: 127 additions & 4 deletions base/error.jl
Original file line number Diff line number Diff line change
@@ -214,11 +214,26 @@ julia> @assert isodd(3) "What even are numbers?"
```
"""
macro assert(ex, msgs...)
msg = prepare_error(msgs...)
msg = isempty(msgs) ? ex : msgs[1]
if isa(msg, AbstractString)
msg = msg # pass-through
elseif !isempty(msgs) && (isa(msg, Expr) || isa(msg, Symbol))
# message is an expression needing evaluating
msg = :(Main.Base.string($(esc(msg))))
elseif isdefined(Main, :Base) && isdefined(Main.Base, :string) && applicable(Main.Base.string, msg)
msg = Main.Base.string(msg)
else
# string() might not be defined during bootstrap
msg = quote
msg = $(Expr(:quote,msg))
isdefined(Main, :Base) ? Main.Base.string(msg) :
(Core.println(msg); "Error during bootstrap. See stdout.")
end
end
return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg)))
end

function prepare_error(msgs...)
function prepare_error(ex, msgs...)
msg = isempty(msgs) ? ex : msgs[1]
if isa(msg, AbstractString)
msg = msg # pass-through
@@ -239,6 +254,100 @@ function prepare_error(msgs...)
end


# https://github.com/c42f/FastClosures.jl/blob/0ffd494814a339d9fb96c247494d3ad72aff8191/src/FastClosures.jl

struct Var
name::Symbol
num_esc::Int
end


# Utility function - fill `varlist` with all accesses to variables inside `ex`
# which are not bound before being accessed. Variables which were bound
# before access are returned in `bound_vars` as a side effect.
#
# With works with the surface syntax so it unfortunately has to reproduce some
# of the lowering logic (and consequently likely has bugs!)
function find_var_uses!(varlist, bound_vars, ex, num_esc)
if isa(ex, Symbol)
var = Var(ex,num_esc)
if !(var in bound_vars)
var ∈ varlist || push!(varlist, var)
end
return varlist
elseif isa(ex, Expr)
if ex.head == :quote || ex.head == :line || ex.head == :inbounds
return varlist
end
if ex.head == :(=)
find_var_uses_lhs!(varlist, bound_vars, ex.args[1], num_esc)
find_var_uses!(varlist, bound_vars, ex.args[2], num_esc)
elseif ex.head == :kw
find_var_uses!(varlist, bound_vars, ex.args[2], num_esc)
elseif ex.head == :for || ex.head == :while || ex.head == :comprehension || ex.head == :let
# New scopes
inner_bindings = copy(bound_vars)
find_var_uses!(varlist, inner_bindings, ex.args, num_esc)
elseif ex.head == :try
# New scope + ex.args[2] is a new binding
find_var_uses!(varlist, copy(bound_vars), ex.args[1], num_esc)
catch_bindings = copy(bound_vars)
!isa(ex.args[2], Symbol) || push!(catch_bindings, Var(ex.args[2],num_esc))
find_var_uses!(varlist,catch_bindings,ex.args[3], num_esc)
if length(ex.args) > 3
finally_bindings = copy(bound_vars)
find_var_uses!(varlist,finally_bindings,ex.args[4], num_esc)
end
elseif ex.head == :call
find_var_uses!(varlist, bound_vars, ex.args[2:end], num_esc)
elseif ex.head == :local
foreach(ex.args) do e
if !isa(e, Symbol)
find_var_uses!(varlist, bound_vars, e, num_esc)
end
end
elseif ex.head == :(::)
find_var_uses_lhs!(varlist, bound_vars, ex, num_esc)
elseif ex.head == :escape
# In the 0.7-DEV churn, escapes persist during recursive macro
# expansion until all macros are expanded. Therefore, we
# need to need to keep track of the number of escapes we're
# currently inside, and to replay these when we construct the let
# expression. See https://github.com/JuliaLang/julia/issues/23221
# for additional pain and gnashing of teeth ;-)
find_var_uses!(varlist, bound_vars, ex.args[1], num_esc+1)
else
find_var_uses!(varlist, bound_vars, ex.args, num_esc)
end
end
varlist
end

find_var_uses!(varlist, bound_vars, exs::Vector, num_esc) =
foreach(e->find_var_uses!(varlist, bound_vars, e, num_esc), exs)

# Find variable uses on the left hand side of an assignment. Some of what may
# be variable uses turn into bindings in this context (cf. tuple unpacking).
function find_var_uses_lhs!(varlist, bound_vars, ex, num_esc)
if isa(ex, Symbol)
var = Var(ex,num_esc)
var ∈ bound_vars || push!(bound_vars, var)
elseif isa(ex, Expr)
if ex.head == :tuple
find_var_uses_lhs!(varlist, bound_vars, ex.args, num_esc)
elseif ex.head == :(::)
find_var_uses!(varlist, bound_vars, ex.args[2], num_esc)
find_var_uses_lhs!(varlist, bound_vars, ex.args[1], num_esc)
else
find_var_uses!(varlist, bound_vars, ex.args, num_esc)
end
end
end

find_var_uses_lhs!(varlist, bound_vars, exs::Vector, num_esc) = foreach(e->find_var_uses_lhs!(varlist, bound_vars, e, num_esc), exs)


export @throw_unless
"""
@throw_unless cond [text]

@@ -254,10 +363,24 @@ julia> @throw_unless isodd(3) "What even are numbers?"
```
"""
macro throw_unless(ex, msgs...)
msg = prepare_error(msgs...)
return :($(esc(ex)) ? $(nothing) : throw(ErrorException($msg)))
msg = prepare_error(ex, msgs...)
fn = gensym("throw_unless")
captured_vars = Var[]
find_var_uses!(captured_vars, Var[], ex, 0)
@show captured_vars
argstup = Tuple(map(x -> x.name, captured_vars))
@show argstup

@eval @noinline function $(fn)($(argstup...))
$(ex) ? $(nothing) : throw(ErrorException($msg))
end

return quote
$(fn)($(map(esc, argstup)...))
end
end


struct ExponentialBackOff
n::Int
first_delay::Float64