Skip to content
Open
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ Build system changes

New library functions
---------------------
* `@check cond [msg]` can be used to check if a condition holds and to error otherwise ([#41342])


* `in!(x, s::AbstractSet)` will return whether `x` is in `s`, and insert `x` in `s` if not.
* The new `Libc.mkfifo` function wraps the `mkfifo` C function on Unix platforms ([#34587]).
Expand Down
65 changes: 65 additions & 0 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,71 @@ 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)
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 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
CheckError() = CheckError("")

"""
@check cond [text]

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!"
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...)
return :($(esc(ex)) ? $(nothing) : throw(CheckError($msg)))
end

struct ExponentialBackOff
n::Int
first_delay::Float64
Expand Down
1 change: 1 addition & 0 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ showerror(io::IO, ex::InterruptException) = print(io, "InterruptException:")
showerror(io::IO, ex::ArgumentError) = print(io, "ArgumentError: ", ex.msg)
showerror(io::IO, ex::DimensionMismatch) = print(io, "DimensionMismatch: ", 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) =
Expand Down
2 changes: 2 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export
# Exceptions
CanonicalIndexError,
CapturedException,
CheckError,
CompositeException,
DimensionMismatch,
EOFError,
Expand Down Expand Up @@ -1063,6 +1064,7 @@ export
@polly,

@assert,
@check,
@atomic,
@atomicswap,
@atomicreplace,
Expand Down
2 changes: 2 additions & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,10 +395,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
Expand Down
122 changes: 69 additions & 53 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,77 @@ 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)
# 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
@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"
@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

Expand Down