Skip to content

Support "&&=" and "||=" operators #26996

Closed
@munificent

Description

@munificent

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

Metadata

Metadata

Assignees

Labels

area-metaCross-cutting, high-level issues (for tracking many other implementation issues, ...).customer-fluttertype-enhancementA request for a change that isn't a bug

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions