Description
Dart defines the following "regular" binary operators *
, /
, %
, ~/
, +
, -
, <<
, >>
, >=
, >
, <=
, <
, ==
, &
, ˆ
, |
. By "regular", I mean that they do no short-circuiting and are just syntactic sugar for a regular method call. These can all be implemented by user-defined classes.
It also defines three short-circuiting binary operators ||
, &&
, and ??
. These cannot be overridden and do not desugar to method calls.
The regular operators also have self-assignment forms (except for the comparison operators), +=
, -=
, etc. It used to be the case that none of the short-circuiting operators did. However, when ??
was added, ??=
was too. Given that, there is no good reason to omit ||=
and &&=
. They are consistent with ??=
and—more importantly!—are sometimes useful.
So, to harmonize this, we intend to add ||=
and &&=
to Dart.
Syntax
We add &&=
and ||=
to compoundAssignmentOperator in the grammar.
Semantics
The semantics are pretty straightforward. The general vibe is that evaluation is "maximally lazy". A subexpression is evaluated at most once, and only if its result is needed.
To describe the desugaring, we need something akin to a "template language". It's basically Dart syntax, with a couple of extra bits:
C
is any identifier that resolves to a type name.- A name starting with
e
is a placeholder for an arbitrary expression (except for a typename). It gets inserted as-is in the result. name
can be any identifier and is substituted as-is.bool_convert()
invokes the "boolean conversion" procedure in the language spec.
&&=
// An expression like `name &&= e3` desugars to:
(() {
if (!name) return false;
return name = bool_convert(e3);
})()
// An expression like `C.name &&= e3` desugars to:
(() {
if (!C.name) return false;
return C.name = bool_convert(e3);
})()
// An expression like `e1.name &&= e3` desugars to:
(() {
var x = e1; // Don't double evaluate e1.
if (!x.name) return false;
return x.name = bool_convert(e3);
}())
// An expression like `e1?.name &&= e3` desugars to:
(() {
var x = e1; // Don't double evaluate e1.
if (x == null) return null;
if (!x.name) return false;
return x.name = bool_convert(e3);
}())
// An expression like `e1[e2] &&= e3` desugars to:
(() {
var x = e1, y = e2; // Don't double evaluate e1 and e2.
if (!x[y]) return false;
return x[y] = bool_convert(e3);
}())
||=
// An expression like `name ||= e3` desugars to:
(() {
if (name) return true;
return name = bool_convert(e3);
})()
// An expression like `C.name ||= e3` desugars to:
(() {
if (C.name) return true;
return C.name = bool_convert(e3);
})()
// An expression like `e1.name ||= e3` desugars to:
(() {
var x = e1; // Don't double evaluate e1.
if (x.name) return true;
return x.name = bool_convert(e3);
}())
// An expression like `e1?.name ||= e3` desugars to:
(() {
var x = e1; // Don't double evaluate e1.
if (x == null) return null;
if (x.name) return true;
return x.name = bool_convert(e3);
}())
// An expression like `e1[e2] ||= e3` desugars to:
(() {
var x = e1, y = e2; // Don't double evaluate e1 and e2.
if (x[y]) return true;
return x[y] = bool_convert(e3);
}())
This desugaring has one wrinkle. It doesn't handle cases where the surrounding code is async or a generator since the function literals don't correctly propagate the "async-ness" or "generator-ness". Instead of actual functions, imagine them to be "block expressions" that let you define variables scoped to the expression instead of actually declaring a function, and you'll have the right idea.
Flag
While this is being implemented, to ensure we don't expose users to inconsistency in our tools, it should be put behind a flag named logical-self-assign
.
Tracking issues
- Analyzer (Analyzer: Support "&&=" and "||=" operators #26997)
- dart2js (dart2js: Support "&&=" and "||=" operators #26998)
- dev_compiler(Support "&&=" and "||=" operators dart-archive/dev_compiler#617)
- Formatter (Support formatting "&&=" and "||=" dart_style#515)
- Specification (#26999)
- VM (VM: Support "&&=" and "||=" operators #27000)
- IntelliJ-plugin (IntelliJ plug-in: Support "&&=" and "||=" operators #27091)