TypeError in typeassert, expected

This is when I got 2 colons for types mixed up with what I thought might be a default of “1” for the
middle value in StepRanges:

julia> for i in 1::4 
          println(i)
          end
ERROR: TypeError: in typeassert, expected Type, got a value of type Int64
Stacktrace:
 [1] top-level scope
   @ ./REPL[1]:1

not particularly helpful. Might be helpful if it gave two or three or four options:

  • the one shown (The double colon is correct, the 4 is wrong)
  • the first colon is correct, after that is wrong
  • the “1” is correct, after that is wrong
  • the “in” is correct, after that is wrong

I’m not sure Julia can handle this any better than it does, since :: is valid syntax for a type-assert. Julia understands your code along the lines of this snippet:

julia> for i in 1::Int64
       println(i)
       end
1

The problem is that instead of Int64, you had 4, which is an integer instead of the expected Type. So the error message is appropriate. Obviously, you meant :4, but Julia can’t exactly read your mind. A good linter might be able to catch this with a better heuristic, and give you a better error. Probably, the linter would catch on to for i in 1, with 1 not being something iterable, which is almost certainly not something you intend.

P.S.: Thanks for figuring out how to format your posts!

1 Like

In fact, 1 is an iterable, but I agree that it would be unusual with such a construction with a literal 1, so a linter should probably flag it:

julia> iterate(1)
(1, nothing)

Well, I think this confirms for me what at least part of the problem is, and that is: The Julia error messages are written as if they are one expert communicating with another expert, instead of communicating with a novice, (Which everyone who doesn’t change their message level is
saying they are a novice). So, we have what I think are some truths:

  1. Error messages are a place for simplicity, not for jargon (if the error message explained typeassert, that would have been helpful, but it assumes I already know the jargon; could have said … in typeassert (started by the :: operator) …)
  2. Julia knows a lot more information than it is printing out (for example, the arg types to functions are often printed, but not the names). Printing out the extra information, while not useful to the gurus, could be a huge win for the novices, and not much effort in the big scheme of things.
2 Likes

Could you clarify this because argument names are printed in stacktraces:

julia> foo(x) = bar(x);

julia> bar(y) = y+1;

julia> foo("one")
ERROR: MethodError: no method matching +(::String, ::Int64)
The function `+` exists, but no method is defined for this combination of argument types.
...
Stacktrace:
 [1] bar(y::String)
   @ Main .\REPL[2]:1
 [2] foo(x::String)
   @ Main .\REPL[1]:1
 [3] top-level scope
   @ REPL[3]:1

I think the author is referencing another thread where MethodError’s “closest candidates” list does not show field names:

julia> struct TEST_STRUCT; A; B end

julia> TEST_STRUCT(1)
ERROR: MethodError: no method matching TEST_STRUCT(::Int64)
The type `TEST_STRUCT` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  TEST_STRUCT(::Any, ::Any)
   @ Main REPL[131]:1

Stacktrace:
 [1] top-level scope
   @ REPL[132]:1

julia> methods(TEST_STRUCT) # me checking that default constructors have field names
# 1 method for type constructor:
 [1] TEST_STRUCT(A, B)
     @ REPL[131]:1

That might be a helpful addition.

1 Like

There is actually (experimental) functionality for adding more detailed error messages. It could be an idea to have a module which adds such messages. It could be loaded with e.g.
using ErrorMessageSuggestions. This is how it works:

function Base.Experimental.show_error_hints(io, ex::TypeError)
    if ex.func == :typeassert
        println(io, """\n
    - The object following :: is not a type
    - Or, the `::` should have been a `:`
        """)
    end
end

julia> for i in 1::4 
          println(i)
       end
ERROR: TypeError: in typeassert, expected Type, got a value of type Int64

- The object following :: is not a type
- Or, the `::` should have been a `:`

However, it all depends on where the TypeError was thrown. There is a field in the TypeError struct for adding a textual context. Some more explanation could have been inserted there.

Looks like sometimes they are, and sometimes not, from earlier code:

ERROR: MethodError: no method matching Son(::Int64)
The type `Son` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  Son(::Any, ::Any)
   @ Main REPL[23]:1

Stacktrace:
 [1] top-level scope
   @ REPL[24]:1

so, to clarify, it would be nice to always print the arg names and types.

It’s not possible to know the argument names if the methods aren’t known, it’s the same reason for MethodError: no method matching +(::String, ::Int64). It might be possible to adding argument names to the methods listed “Closest candidates”, but that wouldn’t be necessary to fix input types. The argument names aren’t useful for the call site or knowing how the methods work anyway, we use the line numbers to follow those.

[quote=“Tigerbyte, post:9, topic:133021”]

Closest candidates are:
  Son(::Any, ::Any)
   @ Main REPL[23]:1

That’s what keeps getting overlooked: the argument names aren’t useful to you, knowing how…
Correct, they are not useful to you — I am saying that I think they would be useful to non-gurus
who are looking for every scrap of info they can find.

1 Like

As a non-guru myself, how would the argument names be useful in that example?

The problem with error messages is that many of them are not due to syntax errors, but inherently runtime phenomena.
Consider a function like:

function f(a, b)
    for i in a::b
        println(i)
    end
end

The call f(1, 4) fails, whereas f(1:4, UnitRange{Int}) succeeds.
f(eachindex(rand(4)), Base.OneTo{Int}) also succeeds, as well as f(3:0, OrdinalRange) and f(1,Int), but f(1:10, StepRange) fails.
This is of course an artificial example, but it illustrates a problem with catching errors in a meaningful way when types are determined at runtime.
It’s simply quite hard to give good runtime error texts when functions have been compiled, and the actual syntax which was in question no longer is easily available.


julia> abstract type AbstractParentType <: Any end

julia> struct Son AbstractParentType
        field1
        end

julia> Son(44)
ERROR: MethodError: no method matching Son(::Int64)
The type `Son` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  Son(::Any, ::Any)
   @ Main REPL[11]:1

Stacktrace:
 [1] top-level scope
   @ REPL[12]:1

julia> "In this case, if arg names were printed, the line after Closest candidates are would read:" ;

julia> "Son(AbstractParentType::Any,field1::Any";

julia> "which would be **huge**, telling me that julia thinks that AbstractParentType is a field name,";

julia> "wheras I am thinking it is a type name; fixing that then fixes the problem";

julia> 
1 Like

I suppose the first thing to do is to look at what methods exists?

julia> methods(Son)
# 1 method for type constructor:
 [1] Son(AbstractParentType, field1)

Argument names are usually printed in stack traces, and in print of method tables as above, I don’t know why they are not included in the “Closest candidates” output.

1 Like

Agree with everything you said, FWIW.
Just means I get to dazzle you with my terrific hindsight: :slight_smile:
It is difficult or impossible to give good runtime error texts, so it needs to be “designed in”
to the language from the start.

So in this case it would help because the struct definition (and also metaprogramming) generated methods, so it’s not visibly clear what went wrong in those methods. Again, we’d normally follow the stack trace’s line number to the struct definition, but that doesn’t help when you already misunderstood what the struct definition did. As sgaure commented, reflection can help, but so would putting more of the method signature into the Closest Candidates printout.

Maybe for brevity? In other failed dispatch cases where there isn’t a misunderstanding of what methods were defined, this printout is intended for finding method ambiguities or mistaken input types for the call, in which case method argument names really are useless. But I personally wouldn’t be bothered by the extra information of a full method signature, and evidently it can save a few clicks and some time in narrow circumstances.

Not sure what you mean by “start” here, but runtime error messages can be modified over Julia versions because it doesn’t break code. Making the changes is just a matter of making a good argument in a Github issue, submitting a fix in a pull request, and convincing enough developers to merge it into newer patches and minor versions.

I’m still stuck in the 80’s I guess, The Compiler Age. When I think of making things better I am
thinking about a different BNF. One with more tokens to disambiguate,
like if the degree symbol ( º ) were used in Ranges instead of colons ( : ) , all of the ambiguity of
Ranges vs Types goes away. Difficult when backward compatibility is important.

The linter stuff looks good but did not work for me (after downloading it,the linter_file command was not found).

Thanks for listening.

This technically was not an ambiguity that would pose trouble to a parser, but a typo that miscommunicates intent. At least you ran into an error in this case, it’s just as possible to write something that would always execute thoroughly at runtime, a much quieter mistake. For a classic example in C that also appears in many other languages, if (x = 0) {"X is Zero.\n";} mistakes = for ==.

Your assumption that 1::4 would default to 1 for an “empty” middle slot comes from another specific programming language, and a language’s parser just generally does not work on any part of another language because they can all have similar names somewhere and do different things with them. Of the languages that use : for ranges, they may not support non-unit steps or may only support them in particular contexts. Many languages use : for type annotations/declarations instead of ::. Many languages use :: for namespace specification. It’s not straightforward to design an error message that figures out why you made a particular typo because the parser is not aware of what you expected to happen based on an entirely different language.

Julia does have \degree tab-completion to specifically type the degree symbol and none for the similar characters, but it’s parsed as a unary prefix operator instead of an infix operator so it’s not possible to implement it for ranges.

A hypothetical language could use more unique characters to reduce typos, sure. The main problem is that’s a lot harder to type on typical keyboards, and as abstract languages are, they are shaped by practical implementation. Typos are still possible; the degree symbol has some similar-looking friends e.g. °˚º which may or may not have visual distinctions depending on the rendering. Even longer alphanumerical names risk typos e.g. adjoint vs Adjoint. To go back to the prior point, there’s really no convenient way around learning a particular language’s syntax and semantics.

You can use the composition symbol (∘), though it’s already used for composition. It’s possible to redfine it:

julia> const ∘ = :
(::Colon) (generic function with 16 methods)

julia> for i in 1 ∘ 10; println(i); end
1
2
3
...

However, .. is unused, so you can use that:

julia> const .. = :
(::Colon) (generic function with 16 methods)

julia> for i in 1..10; println(i); end
1
2
3
...

Something worth mentioning is that operator precedence and parsing are fixed characteristics of the names, not the functions. Neither of those choices are capable of 3-argument ranges, in fact a 2nd .. will fail as unexpected symbols after a complete expression. Only .. shares the precedence of :, other choices will behave differently in chained operations without parentheses. In practice, operator aliases don’t work very well unless resorting to parenthetical function call syntax.

1 Like