dart-lang / language

Design of the Dart language
Other
2.65k stars 201 forks source link

Proposal: add a context for RHS of equality operations. #3653

Open stereotype441 opened 6 months ago

stereotype441 commented 6 months ago

The analyzer and front end currently use the following rules to perform type inference for equality expressions (expressions of the form e1 op e2, where op is either == or !=):

It seems odd to me that the type K? is used for coercions but not to supply a context when type inferring e2. This matters if a user decides to declare an operator== with a covariant argument type. For example:

class ComparableList<T> {
  final List<T> _values;
  ComparableList(this._values);

  bool operator==(covariant ComparableList<T> other) {
    if (_values.length != other._values.length) return false;
    for (var i = 0; i < _values.length; i++) {
      if (_values[i] != other._values[i]) return false;
    }
    return true;
  }
}

f(ComparableList<double> doubles) => doubles == ComparableList([0]);

main() {}

This code is rejected by both the analyzer and front end, with the error message:

The argument type 'ComparableList<int>' can't be assigned to the parameter type 'ComparableList<double>?'.

However, if C.operator== is replaced with any other user definable operator, then the code is accepted:

class ComparableList<T> {
  final List<T> _values;
  ComparableList(this._values);

  bool operator+(covariant ComparableList<T> other) {
    if (_values.length != other._values.length) return false;
    for (var i = 0; i < _values.length; i++) {
      if (_values[i] != other._values[i]) return false;
    }
    return true;
  }
}

f(ComparableList<double> doubles) => doubles + ComparableList([0]);

main() {}

This seems unnecessarily inconsistent. I think we should change the third bullet in the type inference rules to be:

This would make type inference for operator == more consistent with other operators.

lrhn commented 6 months ago

Agree. We do allow overriding operator==, so we should also use the overridden type in the minuscule amount of cases where someone does that. (But… like why? And like don't!)

The other alternative is to remove coercion as well, but this is a statically decided behavior, so it's cost-free in most cases (because K? will be Object? in almost every case), and in the hypothetical case where we know that calling is going to throw if we don't coerce, we might as well do the consistent thing.

(We still can't change the return type to the declared return type, even if we can now have extension types as subtypes of bool. Unless Erik's proposal of allowing extension type return types for fx async functions can be extended to operator==.)