Skip to content

Commit

Permalink
Add errorhint for nonexisting fields and properties (#55165)
Browse files Browse the repository at this point in the history
I played a bit with error hints and crafted this:
```julia
julia> (1+2im).real
ERROR: FieldError: type Complex has no field real, available fields: `re`, `im`

julia> nothing.xy
ERROR: FieldError: type Nothing has no field xy; Nothing has no fields at all.

julia> svd(rand(2,2)).VV
ERROR: FieldError: type SVD has no field VV, available fields: `U`, `S`, `Vt`
Available properties: `V`
```

---------

Co-authored-by: Lilith Orion Hafner <[email protected]>
  • Loading branch information
aplavin and LilithHafner authored Sep 20, 2024
1 parent 7f7a472 commit 220742d
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 10 deletions.
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(Valpropertynames, 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

5 comments on commit 220742d

@Zentrik
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nanosoldier runbenchmarks("inference")

@vtjnash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to specify a comparison commit also, or it will just give you absolute performance numbers
@nanosoldier runbenchmarks("inference", vs="@7f7a472168f65043013b6b0692ac6b450ca07ae5")

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - successfully executed benchmarks. A full report can be found here.

@Zentrik
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should be fine with absolute performance numbers but thanks anyways.

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here.

Please sign in to comment.