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 errorhint for nonexisting fields and properties #55165

Merged
merged 14 commits into from
Sep 20, 2024
2 changes: 1 addition & 1 deletion base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1661,7 +1661,7 @@ julia> ab = AB(1, 3)
AB(1.0f0, 3.0)

julia> ab.c # field `c` doesn't exist
ERROR: FieldError: type AB has no field c
ERROR: FieldError: type AB has no field `c`, available fields: `a`, `b`
Stacktrace:
[...]
```
Expand Down
26 changes: 23 additions & 3 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ end

function showerror(io::IO, exc::FieldError)
@nospecialize
print(io, "FieldError: type $(exc.type |> nameof) has no field $(exc.field)")
print(io, "FieldError: type $(exc.type |> nameof) has no field `$(exc.field)`")
Base.Experimental.show_error_hints(io, exc)
end

Expand Down Expand Up @@ -1093,7 +1093,7 @@ end
Experimental.register_error_hint(methods_on_iterable, MethodError)

# Display a hint in case the user tries to access non-member fields of container type datastructures
function fielderror_hint_handler(io, exc)
function fielderror_dict_hint_handler(io, exc)
@nospecialize
field = exc.field
type = exc.type
Expand All @@ -1104,7 +1104,27 @@ function fielderror_hint_handler(io, exc)
end
end

Experimental.register_error_hint(fielderror_hint_handler, FieldError)
Experimental.register_error_hint(fielderror_dict_hint_handler, FieldError)

function fielderror_listfields_hint_handler(io, exc)
fields = fieldnames(exc.type)
if isempty(fields)
print(io, "; $(nameof(exc.type)) has no fields at all.")
else
print(io, ", available fields: $(join(map(k -> "`$k`", fields), ", "))")
end
props = _propertynames_bytype(exc.type)
aplavin marked this conversation as resolved.
Show resolved Hide resolved
isnothing(props) && return
props = setdiff(props, fields)
isempty(props) && return
print(io, "\nAvailable properties: $(join(map(k -> "`$k`", props), ", "))")
end

_extract_val(::Type{Val{V}}) where {V} = V
_extract_val(::Type{Val}) = nothing
_propertynames_bytype(::Type{T}) where {T} = promote_op(Val∘propertynames, T) |> _extract_val
aplavin marked this conversation as resolved.
Show resolved Hide resolved
aplavin marked this conversation as resolved.
Show resolved Hide resolved

Experimental.register_error_hint(fielderror_listfields_hint_handler, FieldError)
aplavin marked this conversation as resolved.
Show resolved Hide resolved

# ExceptionStack implementation
size(s::ExceptionStack) = size(s.stack)
Expand Down
2 changes: 1 addition & 1 deletion base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ julia> struct Foo
end

julia> Base.fieldindex(Foo, :z)
ERROR: FieldError: type Foo has no field z
ERROR: FieldError: type Foo has no field `z`, available fields: `x`, `y`
Stacktrace:
[...]

Expand Down
14 changes: 9 additions & 5 deletions test/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Base.Experimental.register_error_hint(Base.noncallable_number_hint_handler, Meth
Base.Experimental.register_error_hint(Base.string_concatenation_hint_handler, MethodError)
Base.Experimental.register_error_hint(Base.methods_on_iterable, MethodError)
Base.Experimental.register_error_hint(Base.nonsetable_type_hint_handler, MethodError)
Base.Experimental.register_error_hint(Base.fielderror_hint_handler, FieldError)
Base.Experimental.register_error_hint(Base.fielderror_dict_hint_handler, FieldError)
Base.Experimental.register_error_hint(Base.fielderror_listfields_hint_handler, FieldError)
aplavin marked this conversation as resolved.
Show resolved Hide resolved

@testset "SystemError" begin
err = try; systemerror("reason", Cint(0)); false; catch ex; ex; end::SystemError
Expand Down Expand Up @@ -808,12 +809,13 @@ end
@test_throws ArgumentError("invalid index: \"foo\" of type String") [1]["foo"]
@test_throws ArgumentError("invalid index: nothing of type Nothing") [1][nothing]

# issue #53618
@testset "FieldErrorHint" begin
# issue #53618, pr #55165
@testset "FieldErrorHints" begin
struct FieldFoo
a::Float32
b::Int
end
Base.propertynames(foo::FieldFoo) = (:a, :x, :y)

s = FieldFoo(1, 2)

Expand All @@ -823,7 +825,9 @@ end

# Check error message first
errorMsg = sprint(Base.showerror, ex)
@test occursin("FieldError: type FieldFoo has no field c", errorMsg)
@test occursin("FieldError: type FieldFoo has no field `c`", errorMsg)
@test occursin("available fields: `a`, `b`", errorMsg)
@test occursin("Available properties: `x`, `y`", errorMsg)

d = Dict(s => 1)

Expand All @@ -840,7 +844,7 @@ end
ex = test.value::FieldError

errorMsg = sprint(Base.showerror, ex)
@test occursin("FieldError: type Dict has no field c", errorMsg)
@test occursin("FieldError: type Dict has no field `c`", errorMsg)
# Check hint message
hintExpected = "Did you mean to access dict values using key: `:c` ? Consider using indexing syntax dict[:c]\n"
@test occursin(hintExpected, errorMsg)
Expand Down
Loading