From 5364f41ccd329ae6c534b44cae43070e829acb13 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Wed, 23 Jun 2021 13:52:41 +0200 Subject: [PATCH 01/10] wip --- base/error.jl | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/base/error.jl b/base/error.jl index a1a7d1817d4c6..0203462b938ed 100644 --- a/base/error.jl +++ b/base/error.jl @@ -214,6 +214,11 @@ julia> @assert isodd(3) "What even are numbers?" ``` """ macro assert(ex, msgs...) + msg = prepare_error(msgs...) + return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) +end + +function prepare_error(msgs...) msg = isempty(msgs) ? ex : msgs[1] if isa(msg, AbstractString) msg = msg # pass-through @@ -230,7 +235,27 @@ macro assert(ex, msgs...) (Core.println(msg); "Error during bootstrap. See stdout.") end end - return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) + return msg +end + + +""" + @throw_unless cond [text] + +Throw an [`ErrorException`](@ref) if `cond` is `false`. +Message `text` is optionally displayed upon assertion failure. + +# Examples +```jldoctest +julia> @throw_unless iseven(3) "3 is an odd number!" +ERROR: ErrorException: 3 is an odd number! + +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))) end struct ExponentialBackOff From cf9e31f5164dd44d46289fcc70fe3637826233e0 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Thu, 24 Jun 2021 04:24:12 +0200 Subject: [PATCH 02/10] wip: capture and outline variables --- base/error.jl | 131 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 4 deletions(-) diff --git a/base/error.jl b/base/error.jl index 0203462b938ed..1300f287f49f6 100644 --- a/base/error.jl +++ b/base/error.jl @@ -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 From f9b296277d25daad3bab783877fe0c1dada95357 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Thu, 24 Jun 2021 04:31:02 +0200 Subject: [PATCH 03/10] only outline the string creation; rename to `audit` also outline string for assert; fix name move AuditError out of boot add export --- base/error.jl | 136 ++++++++---------------------------------------- base/exports.jl | 1 + 2 files changed, 24 insertions(+), 113 deletions(-) diff --git a/base/error.jl b/base/error.jl index 1300f287f49f6..fb1ed96f07076 100644 --- a/base/error.jl +++ b/base/error.jl @@ -230,7 +230,14 @@ macro assert(ex, msgs...) (Core.println(msg); "Error during bootstrap. See stdout.") end end - return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) + + fn = gensym("assert") + + @eval @noinline function $(fn)() + throw(AssertionError($msg)) + end + + return :($(esc(ex)) ? $(nothing) : $(fn)()) end function prepare_error(ex, msgs...) @@ -253,131 +260,34 @@ function prepare_error(ex, msgs...) return msg 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 +struct AuditError <: Exception + msg::AbstractString end +AuditError() = AuditError("") -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] + @audit cond [text] -Throw an [`ErrorException`](@ref) if `cond` is `false`. -Message `text` is optionally displayed upon assertion failure. +Throw an [`AuditError`](@ref) if `cond` is `false`. +Message `text` is optionally displayed upon audit failure. # Examples ```jldoctest -julia> @throw_unless iseven(3) "3 is an odd number!" -ERROR: ErrorException: 3 is an odd number! +julia> @audit iseven(3) "3 is an odd number!" +ERROR: AuditError: 3 is an odd number! -julia> @throw_unless isodd(3) "What even are numbers?" +julia> @audit isodd(3) "What even are numbers?" ``` """ -macro throw_unless(ex, msgs...) +macro audit(ex, msgs...) 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 + fn = gensym("audit") - return quote - $(fn)($(map(esc, argstup)...)) + @eval @noinline function $(fn)() + throw(AuditError($msg)) end + + return :($(esc(ex)) ? $(nothing) : $(fn)()) end diff --git a/base/exports.jl b/base/exports.jl index 8174488897cb5..afed759e43296 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1025,6 +1025,7 @@ export @polly, @assert, + @audit, @atomic, @atomicswap, @atomicreplace, From 5b87c8c9ce65dd98cecdfe60b49d0e3586f7690e Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Thu, 24 Jun 2021 04:53:23 +0200 Subject: [PATCH 04/10] bootstrap issue; don't outline errors in `@assert` --- base/error.jl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/base/error.jl b/base/error.jl index fb1ed96f07076..d4e0fb3e03d73 100644 --- a/base/error.jl +++ b/base/error.jl @@ -231,13 +231,7 @@ macro assert(ex, msgs...) end end - fn = gensym("assert") - - @eval @noinline function $(fn)() - throw(AssertionError($msg)) - end - - return :($(esc(ex)) ? $(nothing) : $(fn)()) + return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) end function prepare_error(ex, msgs...) From b22f7a2a39b357a8b5cb333310f866f56981dcd5 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Thu, 24 Jun 2021 20:52:31 +0200 Subject: [PATCH 05/10] rename from at-audit to at-check --- base/error.jl | 22 +++++++++++----------- base/exports.jl | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/base/error.jl b/base/error.jl index d4e0fb3e03d73..7f23ab8c5fbde 100644 --- a/base/error.jl +++ b/base/error.jl @@ -254,31 +254,31 @@ function prepare_error(ex, msgs...) return msg end -struct AuditError <: Exception +struct CheckError <: Exception msg::AbstractString end -AuditError() = AuditError("") +CheckError() = CheckError("") """ - @audit cond [text] + @check cond [text] -Throw an [`AuditError`](@ref) if `cond` is `false`. -Message `text` is optionally displayed upon audit failure. +Throw an [`CheckError`](@ref) if `cond` is `false`. +Message `text` is optionally displayed upon check failure. # Examples ```jldoctest -julia> @audit iseven(3) "3 is an odd number!" -ERROR: AuditError: 3 is an odd number! +julia> @check iseven(3) "3 is an odd number!" +ERROR: CheckError: 3 is an odd number! -julia> @audit isodd(3) "What even are numbers?" +julia> @check isodd(3) "What even are numbers?" ``` """ -macro audit(ex, msgs...) +macro check(ex, msgs...) msg = prepare_error(ex, msgs...) - fn = gensym("audit") + fn = gensym("check") @eval @noinline function $(fn)() - throw(AuditError($msg)) + throw(CheckError($msg)) end return :($(esc(ex)) ? $(nothing) : $(fn)()) diff --git a/base/exports.jl b/base/exports.jl index afed759e43296..ff65cca84bf28 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1025,7 +1025,7 @@ export @polly, @assert, - @audit, + @check, @atomic, @atomicswap, @atomicreplace, From 3442cb6a64f855c8b64676522ddee95d3d1886d7 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:23:00 -0500 Subject: [PATCH 06/10] copy tests, docs from `@assert`; add exports --- base/error.jl | 30 ++++++++++++++++------ base/exports.jl | 1 + doc/src/base/base.md | 2 ++ test/misc.jl | 59 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/base/error.jl b/base/error.jl index 7f23ab8c5fbde..bacd3f0731dd6 100644 --- a/base/error.jl +++ b/base/error.jl @@ -234,6 +234,7 @@ macro assert(ex, msgs...) return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) end +# Copied from `macro assert` above for bootstrapping reasons function prepare_error(ex, msgs...) msg = isempty(msgs) ? ex : msgs[1] if isa(msg, AbstractString) @@ -254,6 +255,20 @@ function prepare_error(ex, msgs...) return msg end +""" + CheckError([msg]) + +The checked condition did not evaluate to `true`. +Optional argument `msg` is a descriptive error string. + +# Examples +```jldoctest +julia> @check false "this is not true" +ERROR: CheckError: this is not true +``` + +`CheckError` is usually thrown from [`@check`](@ref). +""" struct CheckError <: Exception msg::AbstractString end @@ -265,6 +280,8 @@ CheckError() = CheckError("") Throw an [`CheckError`](@ref) if `cond` is `false`. Message `text` is optionally displayed upon check failure. +Similar to [`@assert`](@ref), except `@check` is never disabled. + # Examples ```jldoctest julia> @check iseven(3) "3 is an odd number!" @@ -272,19 +289,16 @@ ERROR: CheckError: 3 is an odd number! julia> @check isodd(3) "What even are numbers?" ``` + +!!! compat "Julia 1.8" + This macro was added in Julia 1.8. + """ macro check(ex, msgs...) msg = prepare_error(ex, msgs...) - fn = gensym("check") - - @eval @noinline function $(fn)() - throw(CheckError($msg)) - end - - return :($(esc(ex)) ? $(nothing) : $(fn)()) + return :($(esc(ex)) ? $(nothing) : throw(CheckError($msg))) end - struct ExponentialBackOff n::Int first_delay::Float64 diff --git a/base/exports.jl b/base/exports.jl index ff65cca84bf28..452eddf8e6a64 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -122,6 +122,7 @@ export # Exceptions CapturedException, + CheckError, CompositeException, DimensionMismatch, EOFError, diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 101669f0588a5..1eb5f3a186728 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -362,10 +362,12 @@ Base.backtrace Base.catch_backtrace Base.current_exceptions Base.@assert +Base.@check Base.Experimental.register_error_hint Base.Experimental.show_error_hints Base.ArgumentError Base.AssertionError +Base.CheckError Core.BoundsError Base.CompositeException Base.DimensionMismatch diff --git a/test/misc.jl b/test/misc.jl index 9a7c20d880ff5..be17da956f1a8 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -63,6 +63,65 @@ let deepthought(x, y) = 42 end end +# Test the `@check` macro +@test_throws CheckError (@check 1 == 2) +@test_throws CheckError (@check false) +@test_throws CheckError (@check false "this is a test") +@test_throws CheckError (@check false "this is a test" "another test") +@test_throws CheckError (@check false :a) +let + try + @check 1 == 2 + error("unexpected") + catch ex + @test isa(ex, CheckError) + @test occursin("1 == 2", ex.msg) + end +end +# test @check message +let + try + @check 1 == 2 "this is a test" + error("unexpected") + catch ex + @test isa(ex, CheckError) + @test ex.msg == "this is a test" + end +end +# @check only uses the first message string +let + try + @check 1 == 2 "this is a test" "this is another test" + error("unexpected") + catch ex + @test isa(ex, CheckError) + @test ex.msg == "this is a test" + end +end +# @check calls string() on second argument +let + try + @check 1 == 2 :random_object + error("unexpected") + catch ex + @test isa(ex, CheckError) + @test !occursin("1 == 2", ex.msg) + @test occursin("random_object", ex.msg) + end +end +# if the second argument is an expression, c +let deepthought(x, y) = 42 + try + @check 1 == 2 string("the answer to the ultimate question: ", + deepthought(6, 9)) + error("unexpected") + catch ex + @test isa(ex, CheckError) + @test ex.msg == "the answer to the ultimate question: 42" + end +end + + let # test the process title functions, issue #9957 oldtitle = Sys.get_process_title() Sys.set_process_title("julia0x1") From cfcd3408260ab97aa288ac424661955c4f5c7f6f Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:42:22 -0500 Subject: [PATCH 07/10] reduce redundancy in tests --- test/misc.jl | 181 ++++++++++++++++++++------------------------------- 1 file changed, 69 insertions(+), 112 deletions(-) diff --git a/test/misc.jl b/test/misc.jl index be17da956f1a8..26e9d94b833bd 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -5,123 +5,80 @@ include("testhelpers/withlocales.jl") # Tests that do not really go anywhere else -# test @assert macro -@test_throws AssertionError (@assert 1 == 2) -@test_throws AssertionError (@assert false) -@test_throws AssertionError (@assert false "this is a test") -@test_throws AssertionError (@assert false "this is a test" "another test") -@test_throws AssertionError (@assert false :a) -let - try - @assert 1 == 2 - error("unexpected") - catch ex - @test isa(ex, AssertionError) - @test occursin("1 == 2", ex.msg) - end -end -# test @assert message -let - try - @assert 1 == 2 "this is a test" - error("unexpected") - catch ex - @test isa(ex, AssertionError) - @test ex.msg == "this is a test" - end -end -# @assert only uses the first message string -let - try - @assert 1 == 2 "this is a test" "this is another test" - error("unexpected") - catch ex - @test isa(ex, AssertionError) - @test ex.msg == "this is a test" - end -end -# @assert calls string() on second argument -let - try - @assert 1 == 2 :random_object - error("unexpected") - catch ex - @test isa(ex, AssertionError) - @test !occursin("1 == 2", ex.msg) - @test occursin("random_object", ex.msg) - end -end -# if the second argument is an expression, c -let deepthought(x, y) = 42 - try - @assert 1 == 2 string("the answer to the ultimate question: ", - deepthought(6, 9)) - error("unexpected") - catch ex - @test isa(ex, AssertionError) - @test ex.msg == "the answer to the ultimate question: 42" - end -end - -# Test the `@check` macro -@test_throws CheckError (@check 1 == 2) -@test_throws CheckError (@check false) -@test_throws CheckError (@check false "this is a test") -@test_throws CheckError (@check false "this is a test" "another test") -@test_throws CheckError (@check false :a) -let - try - @check 1 == 2 - error("unexpected") - catch ex - @test isa(ex, CheckError) - @test occursin("1 == 2", ex.msg) - end -end -# test @check message -let - try - @check 1 == 2 "this is a test" - error("unexpected") - catch ex - @test isa(ex, CheckError) - @test ex.msg == "this is a test" - end -end -# @check only uses the first message string -let - try - @check 1 == 2 "this is a test" "this is another test" - error("unexpected") - catch ex - @test isa(ex, CheckError) - @test ex.msg == "this is a test" - end -end -# @check calls string() on second argument -let - try - @check 1 == 2 :random_object - error("unexpected") - catch ex - @test isa(ex, CheckError) - @test !occursin("1 == 2", ex.msg) - @test occursin("random_object", ex.msg) +# test @assert and `@check` macro +# These have the same semantics (except `@assert` is allowed +# to be disabled in the future), so we test them with the same tests. +ASSERT_OR_CHECK = Ref(:assert) +macro assert_or_check(expr...) + if ASSERT_OR_CHECK[] == :assert + var"@assert"(__source__, __module__, expr...) + else + var"@check"(__source__, __module__, expr...) end end -# if the second argument is an expression, c -let deepthought(x, y) = 42 - try - @check 1 == 2 string("the answer to the ultimate question: ", - deepthought(6, 9)) - error("unexpected") - catch ex - @test isa(ex, CheckError) - @test ex.msg == "the answer to the ultimate question: 42" +@testset "@$(val)" for val in (:assert, :check) + ASSERT_OR_CHECK[] = val + @eval begin # eval to delay macro expansion until after we've assigned `val` + E = ASSERT_OR_CHECK[] == :assert ? AssertionError : CheckError + @test_throws E (@assert_or_check 1 == 2) + @test_throws E (@assert_or_check false) + @test_throws E (@assert_or_check false "this is a test") + @test_throws E (@assert_or_check false "this is a test" "another test") + @test_throws E (@assert_or_check false :a) + let + try + @assert_or_check 1 == 2 + error("unexpected") + catch ex + @test isa(ex, E) + @test occursin("1 == 2", ex.msg) + end + end + # test the macro's message + let + try + @assert_or_check 1 == 2 "this is a test" + error("unexpected") + catch ex + @test isa(ex, E) + @test ex.msg == "this is a test" + end + end + # the macro only uses the first message string + let + try + @assert_or_check 1 == 2 "this is a test" "this is another test" + error("unexpected") + catch ex + @test isa(ex, E) + @test ex.msg == "this is a test" + end + end + # the macro calls string() on second argument + let + try + @assert_or_check 1 == 2 :random_object + error("unexpected") + catch ex + @test isa(ex, E) + @test !occursin("1 == 2", ex.msg) + @test occursin("random_object", ex.msg) + end + end + # if the second argument is an expression, c + let deepthought(x, y) = 42 + try + @assert_or_check 1 == 2 string("the answer to the ultimate question: ", + deepthought(6, 9)) + error("unexpected") + catch ex + @test isa(ex, E) + @test ex.msg == "the answer to the ultimate question: 42" + end + end end end - let # test the process title functions, issue #9957 oldtitle = Sys.get_process_title() Sys.set_process_title("julia0x1") From 22a5ef9df0c2e6728f3e7149130895dc8048b130 Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:43:57 -0500 Subject: [PATCH 08/10] add news --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 1dfdb716a997f..8e6b230f41b4a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -73,6 +73,7 @@ New library functions * `hardlink(src, dst)` can be used to create hard links. ([#41639]) * `diskstat(path=pwd())` can be used to return statistics about the disk. ([#42248]) +* `@check cond [msg]` can be used to check if a condition holds and to error otherwise ([#41342]) New library features -------------------- From 94adea6bdc230dc635837873bdb99432b4c2dd3e Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:46:49 -0500 Subject: [PATCH 09/10] rm extra space --- base/error.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/error.jl b/base/error.jl index bacd3f0731dd6..308ee08c8677a 100644 --- a/base/error.jl +++ b/base/error.jl @@ -230,7 +230,6 @@ macro assert(ex, msgs...) (Core.println(msg); "Error during bootstrap. See stdout.") end end - return :($(esc(ex)) ? $(nothing) : throw(AssertionError($msg))) end From 9a6e781ccd9b8e5cc888ae28a2538ddef1ce798b Mon Sep 17 00:00:00 2001 From: Eric Hanson <5846501+ericphanson@users.noreply.github.com> Date: Tue, 28 Dec 2021 22:55:03 -0500 Subject: [PATCH 10/10] add `showerror` method --- base/errorshow.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/errorshow.jl b/base/errorshow.jl index 121fb50db91c1..a9a6d6ef952f1 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -153,6 +153,7 @@ showerror(io::IO, ex::KeyError) = (print(io, "KeyError: key "); showerror(io::IO, ex::InterruptException) = print(io, "InterruptException:") showerror(io::IO, ex::ArgumentError) = print(io, "ArgumentError: ", ex.msg) showerror(io::IO, ex::AssertionError) = print(io, "AssertionError: ", ex.msg) +showerror(io::IO, ex::CheckError) = print(io, "CheckError: ", ex.msg) showerror(io::IO, ex::OverflowError) = print(io, "OverflowError: ", ex.msg) showerror(io::IO, ex::UndefKeywordError) =