Skip to content

Commit

Permalink
Add error hint for incorrect stacked indexing (JuliaLang#40934)
Browse files Browse the repository at this point in the history
A common entry level mistake is to try to index a matrix with two sets
of brackets, e.g. `a = [1 2; 3 4]; a[1][2] = 5`

This will lead to an error that `setindex!()` on the element type of `a`
is missing.

This PR adds an error hint for the case where a MethodError is raised
when `setindex!` is called with a `Number` as the first argument.

I considered going broader than numbers, but it seems more likely that
this kind of mistake would happen when working with simple number arrays
vs. something more advanced.

Could also consider if it is possible to do the same for when
`getindex()` is called on a `Number`, which emits a BoundsError.

Co-authored-by: Michael Abbott <[email protected]>
  • Loading branch information
BioTurboNick and mcabbott committed Feb 27, 2024
1 parent b3b2736 commit c19c68e
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 2 deletions.
27 changes: 25 additions & 2 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,31 @@ end

Experimental.register_error_hint(noncallable_number_hint_handler, MethodError)

# handler for displaying a hint in case the user tries to call setindex! on
# something that doesn't support it:
# - a number (probably attempting to use wrong indexing)
# eg: a = [1 2; 3 4]; a[1][2] = 5
# - a type (probably tried to initialize without parentheses)
# eg: d = Dict; d["key"] = 2
function nonsetable_type_hint_handler(io, ex, arg_types, kwargs)
@nospecialize
if ex.f == setindex!
T = arg_types[1]
if T <: Number
print(io, "\nAre you trying to index into an array? For multi-dimensional arrays, separate the indices with commas: ")
printstyled(io, "a[1, 2]", color=:cyan)
print(io, " rather than a[1][2]")
else isType(T)
Tx = T.parameters[1]
print(io, "\nYou attempted to index the type $Tx, rather than an instance of the type. Make sure you create the type using its constructor: ")
printstyled(io, "d = $Tx([...])", color=:cyan)
print(io, " rather than d = $Tx")
end
end
end

Experimental.register_error_hint(nonsetable_type_hint_handler, MethodError)

# Display a hint in case the user tries to use the + operator on strings
# (probably attempting concatenation)
function string_concatenation_hint_handler(io, ex, arg_types, kwargs)
Expand All @@ -1035,7 +1060,6 @@ end

Experimental.register_error_hint(string_concatenation_hint_handler, MethodError)


# Display a hint in case the user tries to use the min or max function on an iterable
# or tries to use something like `collect` on an iterator without defining either IteratorSize or length
function methods_on_iterable(io, ex, arg_types, kwargs)
Expand All @@ -1061,7 +1085,6 @@ end

Experimental.register_error_hint(methods_on_iterable, MethodError)


# ExceptionStack implementation
size(s::ExceptionStack) = size(s.stack)
getindex(s::ExceptionStack, i::Int) = s.stack[i]
Expand Down
18 changes: 18 additions & 0 deletions test/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ include("testenv.jl")
Base.Experimental.register_error_hint(Base.noncallable_number_hint_handler, MethodError)
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)


@testset "SystemError" begin
err = try; systemerror("reason", Cint(0)); false; catch ex; ex; end::SystemError
Expand Down Expand Up @@ -745,6 +747,22 @@ let err_str
@test count(==("Maybe you forgot to use an operator such as *, ^, %, / etc. ?"), split(err_str, '\n')) == 1
end

let err_str
a = [1 2; 3 4];
err_str = @except_str (a[1][2] = 5) MethodError
@test occursin("\nAre you trying to index into an array? For multi-dimensional arrays, separate the indices with commas: ", err_str)
@test occursin("a[1, 2]", err_str)
@test occursin("rather than a[1][2]", err_str)
end

let err_str
d = Dict
err_str = @except_str (d[1] = 5) MethodError
@test occursin("\nYou attempted to index the type Dict, rather than an instance of the type. Make sure you create the type using its constructor: ", err_str)
@test occursin("d = Dict([...])", err_str)
@test occursin(" rather than d = Dict", err_str)
end

# Execute backtrace once before checking formatting, see #38858
backtrace()

Expand Down

0 comments on commit c19c68e

Please sign in to comment.