From 8607a84aed2f63024f75ecf7cfc6bc1ce46e294c Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 18 Jul 2024 18:32:07 -0400 Subject: [PATCH 01/13] add errorhint for nonexisting fields and properties --- base/errorshow.jl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index fc9fc5c2aac32..5a5c36e761459 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -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 @@ -1104,7 +1104,23 @@ 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) + println(io, "\nFields of $(nameof(exc.type)):\n$(join(fields, ", "))") + props = _propertynames_bytype(exc.type) + isnothing(props) && return + props = setdiff(props, fields) + isempty(props) && return + println(io, "\nAlso, properties of $(nameof(exc.type)):\n$(join(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 + +Experimental.register_error_hint(fielderror_listfields_hint_handler, FieldError) # ExceptionStack implementation size(s::ExceptionStack) = size(s.stack) From 7ada3a4dc2922fcc240b616a0ab1b1b712b58c4d Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Wed, 24 Jul 2024 06:32:53 -0400 Subject: [PATCH 02/13] more concice FieldError hint, add `` to message --- base/errorshow.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 5a5c36e761459..50825ac80131e 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -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 @@ -1108,12 +1108,12 @@ Experimental.register_error_hint(fielderror_dict_hint_handler, FieldError) function fielderror_listfields_hint_handler(io, exc) fields = fieldnames(exc.type) - println(io, "\nFields of $(nameof(exc.type)):\n$(join(fields, ", "))") + println(io, ", available fields: $(join(map(k -> "`$k`", fields), ", "))") props = _propertynames_bytype(exc.type) isnothing(props) && return props = setdiff(props, fields) isempty(props) && return - println(io, "\nAlso, properties of $(nameof(exc.type)):\n$(join(props, ", "))") + println(io, "\nProperties: $(join(map(k -> "`$k`", props), ", "))") end _extract_val(::Type{Val{V}}) where {V} = V From 98bff6b49c484e2a80e5e24674846ef1c6765025 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Wed, 24 Jul 2024 06:35:24 -0400 Subject: [PATCH 03/13] add test --- test/errorshow.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/errorshow.jl b/test/errorshow.jl index 80352ddeaa9cf..6b5c21aebf82e 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_dict_hint_handler, FieldError) +Base.Experimental.register_error_hint(Base.fielderror_listfields_hint_handler, FieldError) @testset "SystemError" begin err = try; systemerror("reason", Cint(0)); false; catch ex; ex; end::SystemError @@ -808,8 +809,8 @@ 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 @@ -823,7 +824,8 @@ 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) d = Dict(s => 1) @@ -840,7 +842,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) From 42706468abfa13d8bacea7dfc4fdfc717e96ed63 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Wed, 24 Jul 2024 06:37:30 -0400 Subject: [PATCH 04/13] more test --- test/errorshow.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/errorshow.jl b/test/errorshow.jl index 6b5c21aebf82e..24c079add0b68 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -815,6 +815,7 @@ end a::Float32 b::Int end + Base.propertynames(foo::FieldFoo) = (:x, :y, :z) s = FieldFoo(1, 2) @@ -826,6 +827,7 @@ end errorMsg = sprint(Base.showerror, ex) @test occursin("FieldError: type FieldFoo has no field `c`", errorMsg) @test occursin("available fields: `a`, `b`", errorMsg) + @test occursin("Properties: `x`, `y`, `z`", errorMsg) d = Dict(s => 1) From bfa15935bdcb80edc94394a425f9d13698e267e8 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Wed, 24 Jul 2024 11:35:42 -0400 Subject: [PATCH 05/13] update following the review --- base/docs/basedocs.jl | 2 +- base/errorshow.jl | 8 ++++++-- base/reflection.jl | 2 +- test/errorshow.jl | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 19bafbe3de3a4..807f3ca816fed 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -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: [...] ``` diff --git a/base/errorshow.jl b/base/errorshow.jl index 50825ac80131e..f4987134af266 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1108,12 +1108,16 @@ Experimental.register_error_hint(fielderror_dict_hint_handler, FieldError) function fielderror_listfields_hint_handler(io, exc) fields = fieldnames(exc.type) - println(io, ", available fields: $(join(map(k -> "`$k`", fields), ", "))") + 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 - println(io, "\nProperties: $(join(map(k -> "`$k`", props), ", "))") + print(io, "\nAvailable properties: $(join(map(k -> "`$k`", props), ", "))") end _extract_val(::Type{Val{V}}) where {V} = V diff --git a/base/reflection.jl b/base/reflection.jl index 7f9772e5ec976..f250953dc8a1e 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -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: [...] diff --git a/test/errorshow.jl b/test/errorshow.jl index 24c079add0b68..29ff3af475204 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -815,7 +815,7 @@ end a::Float32 b::Int end - Base.propertynames(foo::FieldFoo) = (:x, :y, :z) + Base.propertynames(foo::FieldFoo) = (:a, :x, :y) s = FieldFoo(1, 2) @@ -827,7 +827,7 @@ end errorMsg = sprint(Base.showerror, ex) @test occursin("FieldError: type FieldFoo has no field `c`", errorMsg) @test occursin("available fields: `a`, `b`", errorMsg) - @test occursin("Properties: `x`, `y`, `z`", errorMsg) + @test occursin("Available properties: `x`, `y`", errorMsg) d = Dict(s => 1) From 5da2016542ff3fae51cae41e7d3fba9951e19695 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Mon, 29 Jul 2024 10:20:21 -0400 Subject: [PATCH 06/13] follow review comments --- base/errorshow.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index f4987134af266..b990ed1ef3d8b 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1121,8 +1121,10 @@ function fielderror_listfields_hint_handler(io, exc) 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 +_extract_val(@nospecialize _) = nothing +_propertynames_bytype(::Type{T}) where {T} = + which(propertynames, (T,)) === which(propertynames, (Any,)) ? nothing : + _extract_val(promote_op(Val∘propertynames, T)) Experimental.register_error_hint(fielderror_listfields_hint_handler, FieldError) @@ -1135,4 +1137,4 @@ function show(io::IO, ::MIME"text/plain", stack::ExceptionStack) printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n") show_exception_stack(io, stack) end -show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain"), stack) +show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain") \ No newline at end of file From 86377cdee77680eec0858ad3a3411f3f111ec462 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Mon, 29 Jul 2024 10:23:24 -0400 Subject: [PATCH 07/13] newline --- base/errorshow.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index b990ed1ef3d8b..840ea5f4d4e37 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1137,4 +1137,5 @@ function show(io::IO, ::MIME"text/plain", stack::ExceptionStack) printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n") show_exception_stack(io, stack) end -show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain") \ No newline at end of file +show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain") + From e6f2c3abfbb01c45a3e35eccf5ecd421c03926d1 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Mon, 29 Jul 2024 10:25:03 -0400 Subject: [PATCH 08/13] typo --- base/errorshow.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 840ea5f4d4e37..c08f40c332883 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1137,5 +1137,4 @@ function show(io::IO, ::MIME"text/plain", stack::ExceptionStack) printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n") show_exception_stack(io, stack) end -show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain") - +show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain"), stack) From 4d52030318faf0dfa4d3e93faa4701ceff136c2c Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 29 Aug 2024 21:07:02 -0400 Subject: [PATCH 09/13] Update base/errorshow.jl Co-authored-by: Lilith Orion Hafner --- base/errorshow.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index c08f40c332883..1682ce76ade36 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1123,7 +1123,6 @@ end _extract_val(::Type{Val{V}}) where {V} = V _extract_val(@nospecialize _) = nothing _propertynames_bytype(::Type{T}) where {T} = - which(propertynames, (T,)) === which(propertynames, (Any,)) ? nothing : _extract_val(promote_op(Val∘propertynames, T)) Experimental.register_error_hint(fielderror_listfields_hint_handler, FieldError) From 09b4c4ac332ae11e655126cd173c3bce22dfd3bd Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Fri, 30 Aug 2024 08:40:03 -0400 Subject: [PATCH 10/13] Update test/errorshow.jl Co-authored-by: Lilith Orion Hafner --- test/errorshow.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/errorshow.jl b/test/errorshow.jl index 29ff3af475204..af25bc40c0ffa 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -10,8 +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_dict_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 From 419ab21899bee2342004fb9c7582b8ff83600252 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 19 Sep 2024 09:53:37 -0400 Subject: [PATCH 11/13] Update errorshow.jl --- base/errorshow.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index 1682ce76ade36..855cc7bf427b1 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1120,10 +1120,13 @@ function fielderror_listfields_hint_handler(io, exc) print(io, "\nAvailable properties: $(join(map(k -> "`$k`", props), ", "))") end -_extract_val(::Type{Val{V}}) where {V} = V -_extract_val(@nospecialize _) = nothing -_propertynames_bytype(::Type{T}) where {T} = - _extract_val(promote_op(Val∘propertynames, T)) +function _propertynames_bytype(T::Type) + 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{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) From e10f45726451725208bc992bbb0d14db7770410a Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 19 Sep 2024 09:55:19 -0400 Subject: [PATCH 12/13] Update errorshow.jl --- base/errorshow.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/errorshow.jl b/base/errorshow.jl index 855cc7bf427b1..a79266db20d22 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1121,6 +1121,7 @@ function fielderror_listfields_hint_handler(io, exc) 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] From 443a0076ee8fe9b01317d429de90fec572f1bd79 Mon Sep 17 00:00:00 2001 From: Alexander Plavin Date: Thu, 19 Sep 2024 12:00:01 -0400 Subject: [PATCH 13/13] Update base/errorshow.jl Co-authored-by: Lilith Orion Hafner --- base/errorshow.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/errorshow.jl b/base/errorshow.jl index a79266db20d22..a13d34c093733 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1125,7 +1125,7 @@ function _propertynames_bytype(T::Type) 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{Symbol} || return nothing + inferred_names isa NTuple{<:Any, Symbol} || return nothing return Symbol[inferred_names[i] for i in 1:length(inferred_names)] end