Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch statement and runtimeType #35052

Closed
mdebbar opened this issue Nov 5, 2018 · 7 comments
Closed

Switch statement and runtimeType #35052

mdebbar opened this issue Nov 5, 2018 · 7 comments
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) web-dart2js

Comments

@mdebbar
Copy link
Contributor

mdebbar commented Nov 5, 2018

I was looking at dart-lang/language#83 and dart-lang/language#79 to see if type promotion would be useful inside switch statements when I stumbled upon this bug:

class Cat {
  String meow() => 'meow';
}

String foo(dynamic a) {
  switch(a.runtimeType) {
    case String: return 'str';
    case int: return 'int';
    case Cat: return a.meow();
    default: return 'unknown';
  }
}

void main() {
  print(Cat().runtimeType == Cat); // true ✅
  print(foo('abc')); // 'str' ✅
  print(foo(Cat())); // 'unknown' (why not 'meow' ??)
}

I only tested this on DartPad.

@lrhn
Copy link
Member

lrhn commented Nov 5, 2018

It's a bug in dart2js. Or two bugs, actually.

First if all, you should not be allowed to use Cat as a switch case expression. Switch case expressions must be constants that do not override Object.== (or integers, strings or certain constant Symbol instances). The Type object overrides Object.==. If you check print(identical(Cat().runtimeType, Cat)); you get false, so it has to override equality to return true for the == check.

Second, the dart2js implementation of switch assumes that you can use identity for checking (since the values should not override Object.==), but since Symbol also overrides ==, that is a false assumption. Even if we decided to allow Type instances as switch case expressions, dart2js would still need to fix this.

(I'm filing this as a dart2js error for the switch case using identity since allowing Type objects is a front-end missing error case, and possibly already known).

So, example:

main() {
  switch(Symbol("foo")) {
    case #foo: print("yes"); break;
    default: print("no");
  }
}

This prints "no" in dart2js. It must print "yes". If we ever allow Type objects as switch case expressions, that too must use == for comparison.

@lrhn lrhn added web-dart2js type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) labels Nov 5, 2018
@lrhn
Copy link
Member

lrhn commented Nov 8, 2018

Since all implementations allow it anyway, we now intend to formally allow Type objects as switch case expression. That still means that dart2js is doing it wrong.

@vsmenon vsmenon added the area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. label Jul 20, 2019
@stegrams
Copy link

Hello from 2020 👍
The state has not change yet but a, maybe sloppy, solution for both above examples would be:

class Cat {
  String meow() => 'meow';
}

String foo(dynamic a) {
  switch (a.runtimeType.toString()) {
    case 'String':
      return 'str';
    case 'int':
      return 'int';
    case 'Cat':
      return a.meow();
    default:
      return 'unknown';
  }
}

void main() {
  print(foo('abc')); // 'str' ✅
  print(foo(42)); // 'int' ✅
  print(foo(Cat())); // 'meow' ✅
  
  // prints 'yes' ✅
  switch (Symbol("foo").toString()) {
    case 'Symbol("foo")':
      print("yes");
      break;
    default:
      print("no");
  }
}

@rakudrama
Copy link
Member

dart2js has a new implementation of runtime Type objects that are canonicalized.
The Type implementation no longer overrides ==.
This means that the testing using identical() on the switch cases is now a correct implementation for Type objects (The issue with Symbols might remain).

However, this does not mean that the original program will behave as expected. The types int, List, String etc are interfaces. There is no guarantee on the type returned by, say, 5.runtimeType.
dart2js returns a Type instance for the implementation type, which is called JSInt.

@lrhn
Copy link
Member

lrhn commented Apr 30, 2020

With ahead-of-time compilation, we cannot really assume anything about compiler-provided implementation types. Even if dart2js canonicalizes types, the compiler front-end won't know that, and the same code might fail when compiled for a different back-end.

That's why we special-cased constant Type objects created from type literals in the specification, so it doesn't actually matter whether they override ==.

(That is really a hole in the specification - it talks about creating objects at compile-time, but that only makes sense for classes which are available at compile-time, not those which are provided by the run-time system. In fact, the specification kind-of assumes that it does know the whole program, which just isn't true).

We make no promises that integer values return int as their runtimeType. The VM integer implementations do (by overriding runtimeType to return int), but dart2js has the problem that 5.0 is both an int and a double, so it cannot pretend to be only int or double.

I recommend never using runtimeType for anything. Just use subtype checks. If you do use runtimeType, only use it on classes that you have written yourself, so you know that they are providing the correct value.

@sigmundch
Copy link
Member

Now that the new RTI is out we believe the Cat case in the original post will start working. I don't believe there are any additional action items on our end, so I'll close this issue.

I want to emphasize @lrhn's advice. It's best to not rely on runtimeType for almost anything. In addition, its important to be cautious about .runtimeType.toString() - the resulting String is allowed to change and dart2js will use a different string under minification. (e.g. you may get "minified:xQ" instead of "Cat").

@ImanGhasemiArani
Copy link

I was looking at dart-lang/language#83 and dart-lang/language#79 to see if type promotion would be useful inside switch statements when I stumbled upon this bug:

class Cat {
  String meow() => 'meow';
}

String foo(dynamic a) {
  switch(a.runtimeType) {
    case String: return 'str';
    case int: return 'int';
    case Cat: return a.meow();
    default: return 'unknown';
  }
}

void main() {
  print(Cat().runtimeType == Cat); // true ✅
  print(foo('abc')); // 'str' ✅
  print(foo(Cat())); // 'unknown' (why not 'meow' ??)
}

I only tested this on DartPad.

Use it, its work fine.

    String foo (dynamic a) {
      switch (a.runtimeType) {
        case const (String) :
          return 'str';
        case const (int):
          return 'int';
        case const (Cat):
          return a.meow();
        default:
          return 'unknown';
      }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) web-dart2js
Projects
None yet
Development

No branches or pull requests

7 participants