dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.21k stars 1.57k forks source link

[extension types] Relational pattern issue #54506

Open sgrekhov opened 9 months ago

sgrekhov commented 9 months ago

We have the following:

// SharedOptions=--enable-experiment=inline-class

extension type const BoolET(bool _) {}

const True = BoolET(true);

main() {
  bool b = true;
  switch (b) {
    case == True: // Error: The constant expression type 'BoolET' is not assignable to the parameter type 'Object' of the '==' operator.
      print("true");
    default:
      print("false");
  }
}

There is no this issue if an extension type explicitly implements Object. Is the above expected?

cc @eernstg @lrhn

lrhn commented 9 months ago

I'd say that's at least an implementation bug.

The intended behavior is that you can wrote switch (e1) { case == e2: ...} whenever you can write e1 == e2, and we should make every case work like that. You can add print(b == True); and see that it works, so case == True: should work too.

The context type of == e with mached value type M should be T?, where T is the parameter type of M.operator==. That's satisfied here.

The runtime behavior with matched value m is evaluate e to value v. If v is null and m is not null, fail. If v is null and m is null, succeed. Otherwise invoke m.==(v as T), where the as T is definitely a no-op when v has type T? and is not null.

That is, the == True only needs True to have type Object?. It doesn't actually need NON_NULL(BoolET) to be assignable to Object. It does not need to promote the type of the == operand before passing it to b.operator==.

Checked the spec. Type checking relational patterns include (C is the type of the constant, A is the type of the operator's parameter):

  1. If op is == or != then a compile-time error occurs if C is not assignable to A?. Otherwise op is <, <=, >=, or >, and a compile-time error occurs if C is not assignable to A.

so the spec is correct, and the implementations are wrong.

Does seem to only be visible related to extension types that are not non-nullable.

(I do notice it says assignable to, which is tricky, because it implies coercions, which may not be constant. We should probably say, somehow, that any implied coercions must be constant. At least implementations handle it correctly.)