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

Allow to compare generic class types with == operator #1326

Closed
nt4f04uNd opened this issue Nov 20, 2020 · 8 comments
Closed

Allow to compare generic class types with == operator #1326

nt4f04uNd opened this issue Nov 20, 2020 · 8 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@nt4f04uNd
Copy link

I'll talk about this feature in context of the problem I faced in my app.

The problem

At some point I needed to runtime check the generic type of my class in its method.

Assume we have a code something like this

class SomeClass<T> {
  bool method(T value) {
    // somewhere in this method I need to know if `T` generic type
    // eauals to `List<String>`, or `bool`, or etc.
  }
}

Solutions

I started digging into how to do that and found several ways:

  1. is operator on types
  2. == operator on types
  3. Helper comparison function

1. is operator on types

As far as I can understand, is operator is not what I need and what it does is that it compares the variable type to the given type, and in case of comparing two types, it will always give me false. That's okay, but this operator is not documented fairly and I would encourage dart team to do that.

2. == operator on types

When I use the == all goes fine until I decide to use some generic type in it, for example T == List<String>. This syntax is just not allowed for some reason. Enabling it is the feature I'm requesting for.

3. Helper comparison function

Just a simple function as this will always compare the types as I need, even generic ones

bool isType<T1, T2>() => T1 == T2;

Some other weird ways of doing this:

  1. Comparing some method argument type with is, but it's not viable in cases when method doesn't have any arguments
  2. Even more weirder - comparing some non-nullable instance variable type with is

Interesting thing about these two ones that the first will work even if agrument is null, but the second one won't. I would be glad to hear some explanations here on why is that aswell.

Example

In the code example below there are illustrations on how this works/not works all methods, except the 5 one.

bool isType<T1, T2>() => T1 == T2;

class SomeClass<T> {
  bool method(T value) {
    /// Comparing [SomeClass<bool>] to [bool]
    void repro0() {
      var object = SomeClass<bool>();
      print(object.method(false));
    }
    return value is bool;              // true
    return T is bool;                  // false
    return T == bool;                  // true
    return isType<T, bool>();          // true
  
    //*******************************************************

    /// Comparing [SomeClass<List<String>>] to [List<String>]
    void repro1() {
      var object = SomeClass<List<String>>();
      print(object.method(["string"]));
    }
    return value is List<String>;      // true
    return T is List<String>;          // false
    return T == List<String>;          // not allowed syntax, but would be useful
    return isType<T, List<String>>();  // true

    //*********************************************************

    /// Comparing [SomeClass<List<String>>] to [List]
    /// Repro code is the same, [repro1]
    ///
    return value is List;              // true
    return T is List;                  // false
    return T == List;                  // false
    return isType<T, List>();          // false

    //*********************************************************

    /// Comparing [SomeClass<List>] to [List<String>]
    void repro2() {
      var object = SomeClass<List<String>>();
      print(object.method(["string"]));
    }
    return value is List<String>;      // false
    return T is List<String>;          // false
    return T == List<String>;          // not allowed syntax, but would be useful
    return isType<T, List<String>>();  // true
  }
}
@nt4f04uNd nt4f04uNd added the feature Proposed language feature that solves one or more problems label Nov 20, 2020
@eernstg
Copy link
Member

eernstg commented Nov 20, 2020

You should be unblocked by a function like typeOf as in the following example:

Type typeOf<X>() => X;

void main() {
  print("List<int> != int: ${typeOf<List<int>>() != int}");
}

Terms like List<int> are not expressions, and it would be somewhat costly (in terms of available language design space for expression syntax) if we allow such terms to be expressions. Also, as long as you need to compare run-time types you will do it in terms of instances of Type anyway, so there's nothing lost in passing those Type instances around (the function call can surely be inlined, so it shouldn't take time).

Your helper comparison function will work as well, but the typeOf as above is perhaps more convenient, especially if you don't want to compare a type with another type right "here", you just want to pass the Type as an actual argument or assign it to a variable.

@eernstg
Copy link
Member

eernstg commented Nov 20, 2020

Cf. #123.

@nt4f04uNd
Copy link
Author

@eernstg ok, so I got 3 points in my issue, one of them is tracked by another issue, 2 are remaining:

As far as I can understand, is operator is not what I need and what it does is that it compares the variable type to the given type, and in case of comparing two types, it will always give me false. That's okay, but this operator is not documented fairly and I would encourage dart team to do that.

Interesting thing about these two ones that the first will work even if agrument is null, but the second one won't. I would be glad to hear some explanations here on why is that aswell.

@lrhn
Copy link
Member

lrhn commented Nov 20, 2020

The is operator checks whether a value (not variable) is an instance of a type. It is slightly confusing that a type can also be an expression with a value, but int is int checks the same thing as 2 is int: Whether the value on the left is an instance of the type on the right. The value of the expression int is a Type object representing the type int, which is not an int.

I'm not sure what numbers 4 and 5 mean.

@eernstg
Copy link
Member

eernstg commented Nov 21, 2020

I think it might be helpful to spell out the actual actions taken at run time:

With e is T, the expression e is evaluated to an object. That object has a run-time type R, which can be any subtype of the static type of e. Then the is expression evaluates to true if R is a subtype of T, and false otherwise.

We don't specify the nature of the run-time entities used to determine whether or not the subtype relationship S <: R holds, but it must involve some run-time entities since S cannot be known before run time. However, it is perfectly possible that T is known statically and the subtype relationship is determined without any run-time representation of T (e.g., S might have a type-id, and perhaps it's enough to do S.id > 38221 where 38221 is the statically known type-id of T). The point is that it is not guaranteed (or even likely) that this subtype test works on instances of Type.

Instances of Type are used to represent types as objects in a Dart execution, and you can obtain them by executing myObject.runtimeType and by using one or two identifiers as an expression (e.g., int, p.SomeType, or X, where p is an import prefix and X is a type variable declared in an enclosing scope). It is important that the type is used as an expression, and in particular: with int is Type (which is true), the left operand is an expression and the right operand is a type. So the left operand is evaluated and yields an instance of Type, but the right operand is used for a subtype test, and that is a different kind of operation than expression evaluation.

You can find some documentation about the is operator here: https://dart.dev/guides/language/language-tour#type-test-operators. More details can be found in the language specification: https://dart.dev/guides/language/spec, https://spec.dart.dev/DartLangSpecDraft.pdf, section 'Type Test'.

  1. Comparing some method argument type with is, but it's not viable in cases
    when method doesn't have any arguments
  2. Even more weirder - comparing some non-nullable instance variable type with is

.. the first will work even if agrument is null, but the second one won't

Let's say we wish to test against List<num>. Then it sounds like you're thinking about something like this:

// Assuming null safety.

void f<X>(X x) {
  if (x is List<num>) {...} // 4.
}

class C<X extends Object> {
  X x;
  void f() {
    if (x is List<num>) {...} // 5.
  }
}

These tests are unsafe: x is List<num> may be true even in the case where the value of X is dynamic, Object, Iterable<Object>, List<num>, List<int> (and there are many more types that will work), both for the function and for the class. This is because all those types will indeed allow x to have a value which is a List<num>, e.g., it could be a List<int>. Conversely, if x is List<num> is false then it is definitely not the case that X is List<num>, and it is also not one of the supertypes of List<num>. But this is a very weak result, so you probably never want to use that kind of test in order to learn anything about the value of X.

@nt4f04uNd
Copy link
Author

@tatumizer obfuscation will fail that code

@nt4f04uNd
Copy link
Author

don't see why toString method call would cancel obfuscation. it's how obfuscation works you know, it obfuscates things https://flutter.dev/docs/deployment/obfuscate#caveat

@eernstg
Copy link
Member

eernstg commented Nov 24, 2020

@nt4f04uNd, I think we covered the two remaining issues at this point, so I'll close the issue as a duplicate of #123. Please create a new issue if you think some parts still need further discussion.

@eernstg eernstg closed this as completed Nov 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants