3 ms·
I'm the author of the post and work on the Dart language. I spent a bunch of time last year trying to figure out a good way to make semicolons optional in Dart
by munificent 7y ago
I'm the author of the post and work on the Dart language. I spent a bunch of time last year trying to figure out a good way to make semicolons optional in Dart without breaking a lot of stuff.
Because of a handful of syntax quirks particular to Dart, it's really difficult. Most other languages that have optional semicolons were designed from the start to make them optional. Dart was not. On top of that it:
* Uses C-style variable declarations which means there's no keyword to indicate the start of a variable declaration.
* Likewise uses C syntax for function and even local function declarations. Again no keyword marking the beginning of a function.
* Has complex syntax for things like function types which means a type annotation can be arbitarily long and may split over multiple lines.
* Has lots of "contextual keywords" that are not true reserved words but behave like them only in certain contexts.
* Has a rich syntax that overloads a lot of tokens to mean different things in different contexts. "{}" can be an empty map, empty set, empty block, empty function body, or empty lambda body.
All of that makes optional semicolons just really difficult. I haven't given up entirely. We have a thing now called "language versioning" that lets different Dart libraries target different specific versions of the language. That lets us evolve the language in nominally "breaking" ways without actually breaking existing code. (For example, this is how we will migrate the world to non-nullable types.)
That may give us a way to make other grammar simplifications that would then also let us make semicolons optional. But syntax changes like this are always difficult. People have very strong opinions about you changing their existing syntax under them, and the value proposition in return isn't super compelling.
There's an old saying, the best time to plant a tree is twenty years ago. The second best time is today.
Language syntax isn't always like that because the migration cost once you have a lot of extant code (and users) can just be too painful. The best time to make semicolons optional was twenty years ago. The next best time may be today, but it may just be never.
- chubot 7y agoCould Dart use this solution, which Python uses and Oil shell now uses? 1. Newlines are significant tokens and they behave like semicolons (this is how shell is) 2. Within () [] {}, newlines are suppressed. The lexer has to recognize the matching pairs. (Oil added an expression language to shell which does this) This means that var x = 1 + 2 is not valid because there's a statement terminator after +, but var x = f(1 + 2) is valid. I feel like this captures the fast majority of cases where you want to break a statement over lines. I don't know Dart, but since it seems to have JavaScript-like syntax, I don't see why that wouldn't work? JavaScript has some weird rules, the common gotcha seems to be is: return { } // oops this does not return a dictionary like I thought With those rules you would write: return { } and hopefully the former is a syntax error, not a mis-parsed program. ---- edit: OK my guess is that Dart probably doesn't want to enforce a brace style like Go (and Oil) do? That is in Go only if (x) { } is valid, rather than if (x) { } which is pretty much like the 'return' example. I have been using that brace style for so long that I forgot other people don't :)
- pansa2 7y ago> Could Dart use this solution, which Python uses and Oil shell now uses? This works well for Python, because the following causes IndentationError: x = 1 + 2 In a language without significant indentation, would this be two statements, an assignment and a separate unary `+` expression? Is that OK? If not, one possible solution would be to disallow arbitrary expression statements. AFAIK this is what Go does. > JavaScript has some weird rules, the common gotcha seems to be `return \n {...}` Disallowing expression statements also fixes this particular example, but it doesn't solve the `return` problem in general. It's not practical to disallow standalone function calls, so `return \n f()` still has the same issue. Lua solves this by enforcing that a `return` must be the last statement in a block.
- dragonwriter 7y ago> It's not practical to disallow standalone function calls If your language structurally differentiated procedures (i.e., void-returning functions) from (other) functions, it would be practical to disallow standalone functions (not procedures), though you might have a modifier to explicitly discard the result of a function that allows it to be standalone.
- zem 7y ago> you might have a modifier to explicitly discard the result of a function that allows it to be standalone. ocaml does this and i find it very annoying, even though i know it's theoretically saving me from accidentally not consuming a return value.
- munificent 7y ago> Within () [] {}, newlines are suppressed. Unfortunately, no. That doesn't work. Consider: var map = { key: long - value } // block { long - value } The newlines should be ignored inside the map literal, but not inside the block. A correct semicolon insertion for this should be: var map = { key: long // <-- no semicolon here - value }; // block { long; // <-- but there is one here - value; } But the lexer doesn't know when curlies are maps and when they are blocks. The parser does, which means you could theoretically define the semicolon insertion rules in the grammar, which is what we'd likely have to do, but that makes it much more complex.