Skip to content
2 changes: 1 addition & 1 deletion base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,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
31 changes: 28 additions & 3 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,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 @@ -1102,7 +1102,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 @@ -1113,7 +1113,32 @@ 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)
isnothing(props) && return
props = setdiff(props, fields)
isempty(props) && return
print(io, "\nAvailable properties: $(join(map(k -> "`$k`", props), ", "))")
end

function _propertynames_bytype(T::Type)
which(propertynames, (T,)) === which(propertynames, (Any,)) && return nothing
inferred_names = promote_op(Val∘propertynames, T)
inferred_names isa DataType && inferred_names <: Val || return nothing
inferred_names = inferred_names.parameters[1]
inferred_names isa NTuple{<:Any, Symbol} || return nothing
return Symbol[inferred_names[i] for i in 1:length(inferred_names)]
end

Experimental.register_error_hint(fielderror_listfields_hint_handler, FieldError)

# 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 @@ -1017,7 +1017,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_listfields_hint_handler, FieldError)
Base.Experimental.register_error_hint(Base.fielderror_dict_hint_handler, FieldError)

@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