dart-lang / language

Design of the Dart language
Other
2.67k stars 205 forks source link

Add octal literals #2708

Open nex3 opened 1 year ago

nex3 commented 1 year ago

Related to https://github.com/dart-lang/language/issues/581, I'd also like to see octal literals using the common 0o123 syntax. The lack of these actually caused a security issue for Dart Sass recently (see https://github.com/google/dart_cli_pkg/pull/109) because we incorrectly converted Unix permissions bits from octal to decimal and ended up creating world-writable executables under certain circumstances.

lrhn commented 1 year ago

Unix permissions is the only use for octal numerals that I'm aware of. It seems like something that could be fixed by using an abstraction, rather than adding a language feature with no other practical uses. Will probably be more readable than 0o754 to readers too.

Take:

const x = 1;
const w = 2;
const r = 4;
const rw = r | w;
const rx = r | x;
const wx = w | x;
const rwx = r | w | x;

int perm({int u = 0, int f = 0, int o = 0, int user = 0, int group = 0, int others = 0}) => 
   ((u | user) << 6) | ((g | group) << 3) | (o | others);

so you can just write perm(u: rw, g: r, o: w), and not use (low-readability) number literals at all. Or use perm(u: 7, g: 5, o: 5) if you prefer the numbers to the names. Use the longer names if you like that better. Add extra parameters for special bits. Or parse a string.

Or just introduce a helper like oct(7, 5, 5) like:

int oct(int d1, [int? d2, int? d3, int? d4, int? d5]) {  
  int ensureOctal(int d) => d >= 0 && d <= 7 ? d : (throw ArgumentError.value(d, null, "not octal digit"));
  var value = ensureOctal(d1);
  if (d2 != null) {
     value = (value << 3) | d2;
     if (d3 != null) {
         value = (value << 3) | d3;
         // etc.
     }
  }
  return value;
}

(If I were to design a Posix files system API in the near future, I'd use an inline class on an int for permissions, rather than a plain int.)

nex3 commented 1 year ago

Creating an abstraction for this is likely to feel like overkill in places (like the one in question) where you just need to specify a couple permissions flags. Parsing octal literals is a standard way to make this easy enough that authors can use it without jumping through hoops.

This is a widely-supported feature across many programming languages. JavaScript, Java, Go, Python, Ruby, Rust, C, and Swift all support it using either 0o123, 0123, or both—of the widely-used languages, C# is the only one that doesn't. So I'd argue that it's more surprising for a user to see it absent in Dart than it would be to see it present.

mateusfccp commented 3 months ago

Should we take advantage of https://github.com/dart-lang/language/issues/581#issuecomment-2259691898 and add octal literals? It seems @srawlins patch could be easily adapted to also solve this one.

lrhn commented 3 months ago

IMO, no thanks. I'd rather not.

Octal numbers have precisely one semi-common use (Unix file permissions). That's not enough of a reason to put them into the Dart language. There really is not enough use-cases for the feature to be worth the effort (and syntax real-estate), no matter how small that effort is. (A library with 512 constants, const p000 = 0; through const p777 = 0x1FF;, will solve that one use-case.)

Dart doesn't have to support every base of literal, and the reason to have octal is so minusculy bigger than having quaternary that I don't think it should make a difference. (Or if Dart should support all radixes, then have one syntax parameterized with the base, say 8r775, with r meaning "radix", since b for "base" is taken by binary.).

nex3 commented 3 months ago

To my mind, the most compelling difference between octal and quaternary is that octal is widely supported by programming languages using a consistent, well-known syntax and quaternary is not. Realistically, it's not giving up "syntax real-estate" to support it because if you defined 0o755 to mean anything other than 493, you'd be direly undermining user expectations and even potentially posing a security risk by making it easy to write code that looked like it was permissions-correct but in fact was not.

Dart isn't an abstract spec sitting in the realm of platonic forms—it's a programming language in the real world, used by programmers who use many other languages and have expectations accordingly. This doesn't mean it always has to do everything the same, but when there's a clear expectation that's very inexpensive to meet and provides real value, why not meet it?

lrhn commented 3 months ago

If no other language had octal literals, would we add them? Probably not. Just too obscure a use-case.

But other languages do have octal literals. Some use 045 as syntax. That's a horrible design mistake that should not be repeated. Others use 0o45 which makes sense by itself. Not great for reading, and particularly bad if it also allows a capital O, 0O45 is just not readable. I wouldn't allow that, lower case only if we ever did it.

We don't have to do things just because other languages do. If other languages do something, and lots of users use it and expect it, that's another thing. As yours say, if there is a clear expectation, we probably should meet it.

But there isn't. Octal literals are not table stakes, they're an esoteric feature. Most users of the languages probably don't even know they exist.

Of the languages you mention above: JavaScript, Java, Go, Python, Ruby, Rust, C, and Swift, hour many can you actually remember the syntax for octals in? Or that they had octal numbers at all, without looking it up?

I remember one, JavaScript. I'd guess that Java and C have the same syntax, but it is a guess. I wrote enterprises Java apps for years and never had to use an octal literal.

It's an obscured feature that I actually think adds more complexity to a language than it's worth. Even if it's not a lot of complexity.

munificent commented 2 months ago

Of the languages you mention above: JavaScript, Java, Go, Python, Ruby, Rust, C, and Swift, hour many can you actually remember the syntax for octals in? Or that they had octal numbers at all, without looking it up?

I was curious:

So, aside from C and Java, every listed language supports 0o123-style octal literals. A few support the old style ones too.

nex3 commented 2 months ago

The last case I'll make before I leave this up to the language team: although it's true that octal literals have narrow use-cases relative to binary and hexadecimal literals, the use-cases they do have are specifically security-related. The cost of getting a manual conversion to octal wrong is substantially higher than the cost of an average bug. I think this, as well as the wide availability of the 0o style that @munificent demonstrated, tips the balance into making this a worthwhile addition.