28

Kotlin provides “semicolon inference”: syntactically, subsentences (e.g., statements, declarations etc) are separated by the pseudo-token SEMI, which stands for “semicolon or newline”. In most cases, there’s no need for semicolons in Kotlin code.

This is what the grammar page says. This seems to imply that there is a need to specify semicolons in some cases, but it doesn't specify them, and the grammar tree below doesn't exactly make this obvious. Also I have suspicions that there are some cases where this feature may not work correctly and cause problems.

So the question is when should one insert a semicolon and what are the corner cases one needs to be aware of to avoid writing erroneous code?

double-beep
  • 5,031
  • 17
  • 33
  • 41
Malcolm
  • 41,014
  • 11
  • 68
  • 91
  • @JaysonMinard I'm not exactly a specialist in JavaScript, I'm simply aware that there are some language constructs there which seem fine but don't actually behave correctly. And the problem with them is that you don't know that there is a problem until you've found a bug in your program, which may or may not happen soon after you make a mistake. I'm pretty sure that none or almost none of JavaScript's problematic expressions apply here, but that doesn't mean that Kotlin doesn't have its own set of such tricky cases. And if I knew what they are, I wouldn't need to ask, that's the problem. – Malcolm Sep 04 '16 at 17:58

3 Answers3

37

You only need to specify semicolons in cases where it is ambiguous to the compiler what you are trying to do, and the absence of a semicolon would result in an obvious compiler error.

The rule is: Don't worry about this and don't use semicolons at all (other than the two cases below). The compiler will tell you when you get it wrong, guaranteed. Even if you accidentally add an extra semicolon the syntax highlighting will show you it is unnecessary with a warning of "redundant semicolon".

The two common cases for semi-colons:

An enum class that has a list of enums and also properties or functions in the enum requires a ; after the enum list, for example:

enum class Things {
    ONE, TWO;

    fun isOne(): Boolean = this == ONE
}

And in this case the compiler will tell you directly if you fail to do it correctly:

Error:(y, x) Kotlin: Expecting ';' after the last enum entry or '}' to close enum class body

Otherwise the only other common case is when you are doing two statements on the same line, maybe for brevity sake:

myThingMap.forEach { val (key, value) = it; println("mapped $key to $value") } 

Absence of a semicolon in this last example will give you a more mysterious error at the point where it is confused what you are doing. It is really hard to make some code that would both be valid as two statements separated by a semicolon that are also valid when the semicolon is removed and they become one.

In the past there were other cases like an initialization block of a class which was more "anonymous" { ... } before Kotlin 1.0 and later became init { ... } which no longer needed the semicolon because it is much clearer. These cases no longer remain in the language.

Confidence in this feature:

Also I have suspicions that there are some cases where this feature may not work correctly and cause problems.

The feature works well, there is no evidence anywhere that there are problems with this feature and years of Kotlin experience have not turned up any known cases where this feature backfires. If there is a problem with a missing ; the compiler will report an error.

Searching all of my open-source Kotlin, and our internal rather large Kotlin projects, I find no semi-colons other than the cases above -- and very very few in total. Supporting the notion of "don't use semicolons in Kotlin" as the rule.

However, it is possible that you can intentionally contrive a case where the compiler doesn't report an error because you created code that is valid and has different meaning with and without a semicolon. This would look like the following (a modified version of the answer by @Ruckus):

fun whatever(msg: String, optionalFun: ()->Unit = {}): () -> Unit = ...

val doStuff: () -> Unit = when(x) {
    is String -> {
        { doStuff(x) }
    }
    else -> { 
        whatever("message") // absence or presence of semicolon changes behavior
        { doNothing() }
    }
}

In this case doStuff is being assigned the result of the call to whatever("message") { doNothing() } which is a function of type ()->Unit; and if you add a semicolon it is being assigned the function { doNothing() } which is also of type ()->Unit. So the code is valid both ways. But I have not seen something like this occur naturally since everything has to line up perfectly. The feature suggested emit keyword or ^ hat operator would have made this case impossible, and it was considered but dropped before 1.0 due to strongly opposed opinions and time constraints.

Pang
  • 9,564
  • 146
  • 81
  • 122
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • 5
    How about this example that indeed "occurred naturally": https://android.jlelse.eu/why-i-missed-semi-colons-today-e2fb136f58e5 – eekboom Sep 02 '19 at 14:53
  • 1
    @MikeSamuel that question is best asked in Kotlin slack under the contributors channel, or in the forums. – Jayson Minard Nov 27 '19 at 22:29
  • 3
    i have seen it occur naturally. i am insisting on using semicolons in a our kotlin code because code quality/correctness/readability should always take precedence over aesthetics. there is nothing gained by leaving semicolons out. – glycoslave Dec 10 '20 at 00:32
  • 1
    @glycoslave you're one of the few devs who really understood the key point! I also think not using semicolons is an error, because it forces me (or devs) to go to the compiler level and know the inference rules, which is the very last thing I want to think about when I code – Alessio Mar 22 '21 at 15:36
10

I addition to Jayson Minard's answer, I've run into one other weird edge case where a semicolon is needed. If you are in a statement block that returns a function without using the return statement, you need a semicolon. For example:

val doStuff: () -> Unit = when(x) {
    is String -> {
        { doStuff(x) }
    }
    else -> { 
        println("This is the alternate");  // Semicolon needed here
        { doNothing() }
    }
}

Without the semicolon, Kotlin thinks the { doNothing() } statement is a second argument to println() and the compiler reports an error.

Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
Ruckus T-Boom
  • 4,566
  • 1
  • 28
  • 42
  • Note that in this case the compiler will still give an error saying it can't find a version of println that takes both a String and a function argument, but if the marked line were a function that either took a function as the last argument or had an invoke operation that took a function, it wouldn't show an error as that is now acceptable code. – Ruckus T-Boom Sep 05 '16 at 07:31
  • 2
    Thanks a lot, that's precisely the type of errors I was looking for! That's why I'm usually very cautious when someone says that inference always works correctly. Jayson Minard seems to be a bit too fixated on my mention of JavaScript, and this is a great example of how similar problems manifest themselves in Kotlin, albeit to a much lesser degree. – Malcolm Sep 05 '16 at 09:36
  • 1
    @Malcolm the difference is that Kotlin will give you a compile time error in this case as it does in the others. It does not just let the code run and give unexpected behavior. This case is actually valid syntax with and without the `;` if the method accepted a lambda as the last parameter, so the compile catches the ambiguous situation and gives an error. It is not dangerous. And that is what I wanted to point out vs. javascript in that it does not introduce danger by having inference of `;` in Kotlin. – Jayson Minard Sep 06 '16 at 13:36
  • @malcom, I'm not fixated on anything you didn't bring up in your question, and hope you appreciate people who take time to give you comprehensive answers. – Jayson Minard Sep 06 '16 at 13:46
  • @JaysonMinard Your answer has a whole section which assumes that I "carry JavaScript experience into Kotlin". If you look at my profile, you will find that I have _zero_ activity in the JavaScript tag. Also why would I try to reproduce corner cases from one language in another? All I want to know if there are any corner cases with this feature in Kotlin, and I couldn't care less if there are any similarities with JavaScript. You can skip the "like in JavaScript" in my question, in no way it will change the question's intent. – Malcolm Sep 06 '16 at 13:57
  • @malcom other readers and people searching might take the comparison to JavaScript differently than you, and answers here are for the community, not just for the one person asking a question. – Jayson Minard Sep 06 '16 at 14:03
  • @JaysonMinard Why do you ask me about "specific cases of semi colon inference that worry [me] (from the JavaScript world)" then? If other people need comparison between JavaScript and Kotlin, they should open another question, that's out of scope of this one. I'm removing the mention of JavaScript from the question since it creates so much confusion. P.S. It's Malcolm, not "malcom". – Malcolm Sep 06 '16 at 14:11
  • @Malcolm I asked that because I was really asking "did you do your own homework". I updated my answer based on you edit, thanks. – Jayson Minard Sep 06 '16 at 14:31
  • @JaysonMinard Yeah, it's much more complete now. I'll accept it in a day or two unless someone comes up with something else. – Malcolm Sep 06 '16 at 14:32
9

Kotlin seems to mostly infer semicolons eagerly. There seem to be exceptions (as shown in Jayson Minard's enum example).

Generally, the type system will catch badly inferred semicolons, but here are some cases where the compiler fails.

If an invocation's arguments are in the next line (including the parenthesis), Kotlin will assume that arguments are simply a new parenthesised expression statement:

fun returnFun() : (x: Int) -> Unit {
  println("foo")
  return { x -> println(x) }
}

fun main(args: Array<String>) {
    println("Hello, world!")
    returnFun()
       (1 + 2)    // The returned function is not called.
}

A more common case could be the following, where we have a return with the expression in the next line. Most of the time the type system would complain that there is no return value, but if the return type is Unit, then all bets are off:

fun voidFun() : Unit {
    println("void")
}

fun foo() : Unit {
    if (1 == 1) return 
    voidFun()  // Not called.
 }

fun bar() : Unit {
    if (1 == 1)
        return 
            voidFun()  // Not called.
 }

The bar function could potentially happen, if the return voidFun() wouldn't fit on one line. That said, must developers would simply write the call to the function on a separate line.

Florian Loitsch
  • 7,698
  • 25
  • 30