diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 0fc253bd73d1c..e28b3a21659a8 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -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: [...] ``` diff --git a/base/errorshow.jl b/base/errorshow.jl index d805cb64fb81e..9c8aad8b6ee2c 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -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 @@ -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 @@ -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) diff --git a/base/reflection.jl b/base/reflection.jl index 2ddd34b0f73c1..5b395efc58190 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -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: [...] diff --git a/test/errorshow.jl b/test/errorshow.jl index a82ab7743dc5a..3ede370553212 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -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 @@ -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) @@ -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) @@ -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)