Skip to content

Field promotions should apply inside irrefutable patterns. #4347

@stereotype441

Description

@stereotype441

During the implementation of patterns, we decided that irrefutable patterns shouldn't cause type promotions to happen. For example, this promotes d from type dynamic to int:

main() {
  dynamic d = 0;
  if (d case int x) {
    d; // Static type: `int`
  }
}

but this doesn't:

main() {
  dynamic d = 0;
  var (int x) = d;
  d; // Static type: `dynamic`
}

The reason we decided this was primarily so that pattern assignments and pattern variable declarations would behave consistently with ordinary assignments and variable declarations (see past discussion).

Unfortunately, the way I implemented this essentially caused all promotion related to the scrutinee of the irrefutable pattern match to be disabled, including field promotions. So, for example, if a field is promoted before the irrefutable pattern match, that promotion is "forgotten" during the irrefutable pattern match:

class A {
  final int? _i;
  A(this._i);
}
main() {
  var a = A(0);
  a._i!; // Promotes `a._i` to `int`
  var A(_i: i) = a; // Promotion is forgotten, therefore:
  int j = i; // ERROR: `int?` not assignable to `int`
}

But it all works works fine if the pattern match is refutable:

// Same definition of `class A`
main() {
  var a = A(0);
  a._i!; // Promotes `a._i` to `int`
  if (a case A(_i: var i)) {
    int j = i; // OK: `i` has type `int`.
  }
}

Similarly, a field promotion performed by ! or as on the inside of the left hand side of an irrefutable pattern match doesn't have any effect on the code that follows:

// Same definition of `class A`
main() {
  var a1 = A(0);
  var A(_i: i1!) = a1;
  int j = a1._i; // ERROR: `int?` not assignable to `int`
  var a2 = A(0);
  var A(_i: i2 as int) = a2;
  int k = a2._i; // ERROR: `int?` not assignable to `int`
}

Whereas these same promotions do have an effect after a refutable pattern match:

// Same definition of `class A`
main() {
  var a1 = A(0);
  if (a1 case A(_i: _!)) {}
  int j = a1._i; // OK: `a1._i` has been promoted to `int`
  var a2 = A(0);
  if (a2 case A(_i: _ as int)) {}
  int k = a2._i; // OK: `a2._i` has been promoted to `int`
}

This feels weird and surprising to me, and it feels like it goes well beyond the original intent.

I'm tempted to change the rules so that irrefutable pattern matches handle field promotion in the same way as refutable pattern matches, and the only difference in promotion between irrefutable and refutable patterns is that an irrefutable pattern match never promotes the type of the scrutinee itself. I think this would be much more in keeping with the original intent, and less confusing to users.

(See #4344 (comment) for further context.)

@dart-lang/language-team What do you think?

Metadata

Metadata

Assignees

No one assigned

    Labels

    field-promotionIssues related to addressing the lack of field promotionflow-analysisDiscussions about possible future improvements to flow analysispatternsIssues related to pattern matching.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions