Look at the following of statement in Kotlin (it's from an Android project I'm working on):

val totalDurationInMinutes =
    currentActivity().durationInMinutes
    + remainingActivities().sumBy { it.durationInMinutes }

The statement calculates the total duration of activities by summing up the duration of the current activity with the total sum of durations of the remaining activities.

Let's say the currentActivity's duration is 10 minutes, and the sum of remainingActivities is 50 minutes. What would you expect the value of totalDurationInMinutes to be?

10+50 = 60 minutes, right? Well, sadly (and shockingly to me), wrong answer. I will tell you the answer and explain why the answer is correct in a moment, but first...

..a disclaimer

I am new to Kotlin and I am not a language design expert. This post is not intended to bash Kotlin, Kotlin designers or developers. I actually like Kotlin and I think it's a great language, albeit with one potentially serious syntax quirk. Also, I don't want to a part or a tool of any schadenfreude received from non-Kotlin developers. I'm trying to write this post in a constructive tone.

On the other hand, some of the responses from Kotlin community to my initial Twitter message about noticing this behavior have been baffling to me, ranging from simply acting like this is not a real issue, to explaining to me that "I'm holding it wrong" (not in these exact words), to saying "some people just need an excuse to complain". I beg to differ.

So what's going on?

The correct answer to my question above is 10 minutes. In the code above, Kotlin simply disregards the third line of code, without any errors or even warnings.

Let me start explaining what is happening by quoting Wikipedia article on Kotlin here:

Semicolons are optional as a statement terminator; in most cases a newline is sufficient for the compiler to deduce that the statement has ended.

So, basically, in Kotlin, you don't need to end the statement with a semicolon (like JavaScript). Also, indentation is not syntactically relevant (unlike languages like Python and F#). In addition to that, there is no line continuation character (unlike Python). So how does the compiler know whether the two consecutive lines of code belong to a single statement or are separate statements?

A good explanation can be found on Kotlin's Hello World tutorial of all places (emphasis is mine):

...a line is automatically joined with one or more of the subsequent lines if that's the only way to make the code parse correctly. In practice, that means that a statement continues on the next line if we're inside an open parenthesis (like in Python), or if the line ends with a "dangling operator" (unlike in Python) or the following line doesn't parse unless it's joined to the previous one (also unlike in Python).

So in the case of this code:

val totalDurationInMinutes =
    currentActivity().durationInMinutes
    + remainingActivities().sumBy { it.durationInMinutes }

... the + sign is treated as a unary operator, which results in Kotlin being able to successfully parse the first and second lines separately from the third line. Which then means we have two separate statements (and the second statement, although executed, has no effect).

The thing is, this only happens with operators that can serve both as unary and binary operators (+ and -). For example, if we replaced addition with multiplication:

val totalDurationInMinutes =
    currentActivity().durationInMinutes
    * remainingActivities().sumBy { it.durationInMinutes }

the compiler would complain with an error: Expecting an element.

The idiomatic way to split lines in Kotlin would be to put the binary operator at the end of the line, before the split

val totalDurationInMinutes =
    currentActivity().durationInMinutes + 
    remainingActivities().sumBy { it.durationInMinutes }

... or surround the whole expression with parentheses:

val totalDurationInMinutes =
    (currentActivity().durationInMinutes
    + remainingActivities().sumBy { it.durationInMinutes })

So why is this behavior bad?

Kotlin's main selling point is being a better, more modern alternative to Java. So I would guess a large percentage of Kotlin developers have previously been used to Java syntax. The sample I gave above is perfectly valid and common code style in Java, especially if you care about readability of your code by splitting long statements into multiple lines.

Also, there's bound to be a lot of copy-pasting of the old Java code into Kotlin. After pasting, you fix all the syntax errors, the code compiles, but you can still be left with the code that is wrong.

The key problem is that neither the Kotlin compiler, nor the IDE (I used Android Studio) nor ktlint are warning you about any kind of problems with this kind of code. They are happily assuming you will write (and format) the code correctly, the way Kotlin compiler likes it, and if not, it's your problem. Or, to quote Khan Academy's introduction to Kotlin:

Don't split lines if the resulting two lines are also grammatically valid on their own

This is not something one would expect from a modern language. After all, these kinds of devilishly hidden errors can hurt a lot (remember Apple's goto fail bug?)

What can be done?

Some would say that these kinds of bugs can be detected by unit testing, and I agree - this is how I discovered this syntax quirk. But relying on all the code written in Kotlin to be covered by tests is just as unrealistic as relying on developers to not write statements that can be misinterpreted by the Kotlin compiler.

The first thing I would suggest is to add a ktlint rule that warns about these kinds of dangling expressions (in fact, someone already suggested this). Perhaps the rule should enforce that any statements starting with + or - should be enclosed in parentheses, to avoid confusion.

But ktlint is not an integral part of the Kotlin compiler, so this would only help on projects that actually use it. I think that, in the long run, this kind of rule should be enforced by the compiler itself, at the expense of introducing breaking changes to the existing code.