found Unit: required Int. Why is the error not obvious?
In scala there's no single if
statement
Since each expression when evaluated must return a value (it can be an empty value, of type Unit
), the if
expression must be always matched with an else
branch, and both must return the same type, or in the worst case scala will infer the most common supertype.
In your code you return a Int
from the if
branch, but the else
branch is missing.
updated
As stated correctly in other answers:
the single if
expression returns the only alternative within it, which for the original post is the return value of an assignment, which is ()
of type Unit
I have a feeling you need to change your method to look like one of the following
def contains1(sfType: TokeType): Int = {
if (Tokens.KEYWORDS.contains(sfType))
TokenTypes.RESERVED_WORD
else
-1
}
def contains1(sfType: TokenType) = if (Tokens.KEYWORDS.contains(sfType)) TokenTypes.RESERVED_WORD else -1
I'll first explain what type Unit
is, just in case. Even if you know already, other people having the same kind of problem might well not know it.
Type Unit
is similar to what is known in C or Java as void
. In those languages, that means "this does not return anything". However, every method in Scala returns a value.
To bridge the gap between every method returning something and not having anything useful to return, there's Unit
. This type is an AnyVal
, which means it isn't allocated on the heap, unless it gets boxed or is the type of a field on an object. Furthermore, it has only one value, whose literal is ()
. That is, you could write this:
val x: Unit = ()
The practical effect of this is that, when a method "returns" Unit
, the compiler doesn't have to actually return any value, since it already knows what that value is. Therefore, it can implement methods returning Unit
by declaring them, on the bytecode level, as being void
.
Anyway, if you don't want to return anything, you return Unit
.
Now let's look at the code given. Eclipse says it returns Unit
, and, in fact, Eclipse is correct. However, most people would actually make the error of having that method return AnyVal
or Any
, not Unit
. See the following snippet for an example:
scala> if (true) 2
res0: AnyVal = 2
So, what happened? Well, when Scala finds an if
statement, it has to figure out what is the type returned by it (in Scala, if
statements return values as well). Consider the following hypothetical line:
if (flag) x else y
Clearly, the returned value will be either x
or y
, so the type must be such that both x
and y
will fit. One such type is Any
, since everything has type Any
. If both x
and y
were of the same type -- say, Int
-- then that would also be a valid return type. Since Scala picks the most specific type, it would pick Int
over Any
.
Now, what happens when you don't have an else
statement? Even in the absence of an else
statement, the condition may be false -- otherwise, there would be no point in using if
. What Scala does, in this case, is to add an else
statement. That is, it rewrites that if
statement like this:
if (true) 2 else ()
Like I said before: if you do not have anything to return, return Unit
! That is exactly what happens. Since both Int
and Unit
are AnyVal
, and given that AnyVal
is more specific than Any
, that line returns AnyVal
.
So far I have explained what others might have seen, but not what happens in that specific code in the question:
if (Tokens.KEYWORDS.contains(sfType)) {
val retVal = TokenTypes.RESERVED_WORD
}
We have seen already that Scala will rewrite it like this:
if (Tokens.KEYWORDS.contains(sfType)) {
val retVal = TokenTypes.RESERVED_WORD
} else ()
We have also seen that Scala will pick the most specific type between the two possible results. Finally, Eclipse tells use that the return type is Unit
, so the only possible explanation is that the type of this:
val retVal = TokenTypes.RESERVED_WORD
is also Unit
. And that's precisely correct: statements that declare things in Scala have type Unit
. And, so, by the way, do assignments.
The solution, as others have pointed out, is to remove the assignment and add an else
statement also returning an Int
:
def contains1(sfType: TokenType): Int =
if (Tokens.KEYWORDS.contains(sfType)) TokenTypes.RESERVED_WORD
else -1
(note: I have reformatted the method to follow the coding style more common among Scala programmers)
Eclipse thinks that if the "if" block is false, i.e, Tokens.KEYWORDS.contains(sfType)
is false
, then the return type will indeed be Unit
, which is the problem.