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

Improving Printf error messages #45366

Merged
merged 10 commits into from
Jul 8, 2022
29 changes: 18 additions & 11 deletions stdlib/Printf/src/Printf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,18 @@ char(::Type{Val{c}}) where {c} = c

# parse format string
function Format(f::AbstractString)
isempty(f) && throw(ArgumentError("empty format string"))
isempty(f) && throw(ArgumentError("Format string must be non-empty"))
Seelengrab marked this conversation as resolved.
Show resolved Hide resolved
bytes = codeunits(f)
len = length(bytes)
pos = 1
b = 0x00

# skip ahead to first format specifier
while pos <= len
b = bytes[pos]
pos += 1
if b == UInt8('%')
pos > len && throw(ArgumentError("invalid format string: '$f'"))
pos > len && throw(ArgumentError("Format string is invalid - first format specifier incomplete at end of string: '$f'"))
Seelengrab marked this conversation as resolved.
Show resolved Hide resolved
if bytes[pos] == UInt8('%')
# escaped '%'
b = bytes[pos]
Expand Down Expand Up @@ -120,7 +122,7 @@ function Format(f::AbstractString)
else
break
end
pos > len && throw(ArgumentError("incomplete format string: '$f'"))
pos > len && throw(ArgumentError("Format string is invalid - last format specifier is incomplete: '$f'"))
b = bytes[pos]
pos += 1
end
Expand All @@ -139,7 +141,7 @@ function Format(f::AbstractString)
precision = 0
parsedprecdigits = false
if b == UInt8('.')
pos > len && throw(ArgumentError("incomplete format string: '$f'"))
pos > len && throw(ArgumentError("Format string is invalid - precision specifier is missing precision: '$f'"))
parsedprecdigits = true
b = bytes[pos]
pos += 1
Expand All @@ -158,16 +160,16 @@ function Format(f::AbstractString)
b = bytes[pos]
pos += 1
if b == prev
pos > len && throw(ArgumentError("invalid format string: '$f'"))
pos > len && throw(ArgumentError("Format string is invalid - unterminated length modifier at end of string: '$f'"))
b = bytes[pos]
pos += 1
end
elseif b in b"Ljqtz"
elseif b in b"Ljqtz" # what is q? Possibly quad?
Seelengrab marked this conversation as resolved.
Show resolved Hide resolved
b = bytes[pos]
pos += 1
end
# parse type
!(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(ArgumentError("invalid format string: '$f', invalid type specifier: '$(Char(b))'"))
!(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(ArgumentError("Format string is invalid - '$(Char(b))' is not a valid type specifier: '$f'"))
type = Val{Char(b)}
if type <: Ints && precision > 0
zero = false
Expand All @@ -184,7 +186,7 @@ function Format(f::AbstractString)
b = bytes[pos]
pos += 1
if b == UInt8('%')
pos > len && throw(ArgumentError("invalid format string: '$f'"))
pos > len && throw(ArgumentError("Format string is invalid - last format specifier is incomplete at end of string: '$f'"))
if bytes[pos] == UInt8('%')
# escaped '%'
b = bytes[pos]
Expand Down Expand Up @@ -409,11 +411,13 @@ const __BIG_FLOAT_MAX__ = 8192
if len > siz
maxout = max(__BIG_FLOAT_MAX__,
ceil(Int, precision(x) * log(2) / log(10)) + 25)
# TODO: `error` is kind of generic, is there a more appropriate one to throw?
len > maxout &&
error("Over $maxout bytes $len needed to output BigFloat $x")
Seelengrab marked this conversation as resolved.
Show resolved Hide resolved
resize!(buf, len + 1)
len = _snprintf(pointer(buf, pos), len + 1, str, x)
end
# TODO: when will this actually be thrown? What more informative error would be appropriate?
len > 0 || throw(ArgumentError("invalid printf formatting $str for BigFloat"))
Seelengrab marked this conversation as resolved.
Show resolved Hide resolved
return pos + len
end
Expand Down Expand Up @@ -805,7 +809,7 @@ plength(::Spec{PositionCounter}, x) = 0
end

@noinline argmismatch(a, b) =
throw(ArgumentError("mismatch between # of format specifiers and provided args: $a != $b"))
throw(ArgumentError("Number of format specifiers and number of provided args differ: $a != $b"))

"""
Printf.format(f::Printf.Format, args...) => String
Expand Down Expand Up @@ -892,8 +896,10 @@ macro printf(io_or_fmt, args...)
return esc(:($Printf.format(stdout, $fmt, $(args...))))
else
io = io_or_fmt
isempty(args) && throw(ArgumentError("must provide required format string"))
fmt = Format(args[1])
isempty(args) && throw(ArgumentError("No format string provided to `@printf` - use like `@printf [io] <format string> [<args...>]."))
fmt_str = first(args)
fmt_str isa String || throw(ArgumentError("First argument after io has to be a format string"))
Seelengrab marked this conversation as resolved.
Show resolved Hide resolved
fmt = Format(fmt_str)
return esc(:($Printf.format($io, $fmt, $(Base.tail(args)...))))
end
end
Expand All @@ -910,6 +916,7 @@ julia> @sprintf "this is a %s %15.1f" "test" 34.567
```
"""
macro sprintf(fmt, args...)
fmt isa String || throw(ArgumentError("First argument has to be a format string."))
Seelengrab marked this conversation as resolved.
Show resolved Hide resolved
f = Format(fmt)
return esc(:($Printf.format($f, $(args...))))
end
Expand Down