Skip to content

X is Enum reporting false for enum X #52824

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

Closed
lukehutch opened this issue Jun 30, 2023 · 10 comments
Closed

X is Enum reporting false for enum X #52824

lukehutch opened this issue Jun 30, 2023 · 10 comments

Comments

@lukehutch
Copy link

enum X { a, b }

void main() {
  print(X is Enum);
}

prints

false

Huh? The documentation for Enum states This class is implemented by all types and values introduced using an enum declaration.

  • Dart SDK Version (dart --version): Dart SDK version: 3.0.5 (stable) (Mon Jun 12 18:31:49 2023 +0000) on "linux_x64"
  • Whether you are using Windows, MacOSX, or Linux (if applicable): Linux
  • Whether you are using Chrome, Safari, Firefox, Edge (if applicable): N/A
@lukehutch
Copy link
Author

PS I understand that

X x = X.a;
print(x is Enum);

should print true...

Why does the code in the original report even compile, if class names can't be used for static inheritance checks like that?

@eernstg
Copy link
Member

eernstg commented Jun 30, 2023

Asking X is Enum is similar to asking whether the set {1} is a member of {1, 2, 3}. It isn't, but if you use a different relation ("is subset of" rather than "is member of") then you'd get yes. We do have the "is member of" operator, which is is, but we do not have an "is subset of" (which would be "is subtype of" when applied to types).

You can still emulate it:

enum X { a, b }

void main() {
  print(<X>[] is List<Enum>);
}

This works because you can create an instance of List<X>, and that object will be a member of List<Enum> if and only if X is a subtype of Enum (which it is).

@lukehutch
Copy link
Author

lukehutch commented Jun 30, 2023

Thanks for the explanation, but that makes me think the documentation This class is implemented by all types and values introduced using an enum declaration is misleading, since X is Y tests whether X "implements" (extends) Y.

(I guess I'm having trouble getting my mind around your is subset of vs. is member of analogy -- the only thing I can relate to this is duck-typing...)

@lukehutch
Copy link
Author

lukehutch commented Jun 30, 2023

Ah, I see what the documentation is hinting at -- it is the values, not the enum itself, that have type Enum. So really Enum should be thought of as "enum value", not "enum"?

If that is the case, it is unfortunate that the naming is Enum, not EnumValue...

This is tricky coming from a Java backround, because in Java, the enum class itself is a class that extends Enum. The enum values are just instances of the subclass. This is (if I understand you correctly) very different from Dart, where the enum values are instances of Enum, typed to the enum type, but the enum type is not itself an Enum.

Maybe the documentation could be updated to explain this a bit better? It is very terse at present.

@eernstg
Copy link
Member

eernstg commented Jun 30, 2023

No, that's the whole point, X is Y does not test whether X is a subtype of Y (which could be caused by implements, extends, with, or, for a mixin: on), it tests whether an object of type Type has the type Y, and that is false unless Y is Type, Object, or a supertype of Object. It makes absolutely no difference whether X is int or num or any other type, the value of X as an expression is always an instance of Type.

For the is subset of relation, you'd compare types with types (that is, from one perspective, sets with sets). So {1} ⊆ {1, 2, 3} is similar to X extends Y (as in a type parameter declaration with a bound). We usually use the symbol <: to indicate the subtype relation, so we'd say X <: Y.

But Dart does not support <: as an operator on types, that's just a syntax error.

For the is member of relation, you'd compare objects with types (that is, elements with sets). So 1 ∈ {1, 2, 3} is similar to 1 is int (because int is similar to a set of integer values). That one can be expressed directly in Dart using is.

So if you want to compare two types in order to determine whether they are subtype related then you can't use an operator, it simply doesn't exist. But <X>[] is List<Y> will do the job. Another way is this one:

void f<X extends Y, Y>() {}

void main() {
  (f as Function)<T1, T2>(); // Will throw if and only if `T1` is not a subtype of `T2`.
}

In this case you are indirectly using the extends relation that declares the bound of the type variable X, but you can't really say that there is an "operator" extends that determines whether or not two types have a subtype relation, we're just testing exactly that relation because we must get a run-time error if it is isn't true.

@eernstg
Copy link
Member

eernstg commented Jun 30, 2023

in Java, the enum class itself is a class that extends Enum

That's true in Dart, too.

enum E { a, b }
Enum e = E.a; // OK, because `E <: Enum`.

The enum declaration is indeed similar to a class declaration, and the values E.a and E.b are indeed instances of the type E.

@lukehutch
Copy link
Author

OK, so I guess my misunderstanding was that is would match the entire superclass hierarchy (like instanceof in Java), not just the specific named class? (I know you have explained this very clearly -- thanks -- but I feel like I'm still missing something, sorry...)

I have a weird Enum subtyping issue which may or may not be related to my lack of understanding of this: #52826.

@blaugold
Copy link
Contributor

blaugold commented Jul 1, 2023

@lukehutch The code in your first comment has roughly the same semantics as this Java program, which also prints false:

public class App {
    enum X {}

    public static void main(String args[]) {
      System.out.println((Object)X.class instanceof Enum);
    }
}

I don't think Java has an operator that can check if one type is assignable to another type, just like Dart.

I think the confusion might be around being able to use types as expressions (type literals) in Dart and what type such an expression has. As @eernstg mentioned, it's always Type.

They are a foot gun in switch cases (dart-lang/language#2911) and there is a proposal to remove them (dart-lang/language#2393).

@lukehutch
Copy link
Author

@blaugold Java has Class.isAssignableFrom. But you don't need it for this case, because instanceof also matches any superclass or interface of the requested class:

public class Main {
    static class X{}
    static class Y extends X{}
    
    public static void main(String[] args) {
        Y y = new Y();
        System.out.println(y instanceof X);
        System.out.println(X.class.isAssignableFrom(y.getClass()));
    }
}

output:

true
true

@lukehutch
Copy link
Author

Actually it looks like Dart has the same behavior via is that Java has via instanceof, as shown in my previous comment.

I guess this means I'm still missing something about Dart's semantics -- but I'll close this issue, since it's not a bug. Thanks for the valiant efforts to explain this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants