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

Switching over generic runtime type does not work #1455

Closed
ltOgt opened this issue Feb 16, 2021 · 3 comments
Closed

Switching over generic runtime type does not work #1455

ltOgt opened this issue Feb 16, 2021 · 3 comments
Labels
request Requests to resolve a particular developer problem state-duplicate This issue or pull request already exists

Comments

@ltOgt
Copy link

ltOgt commented Feb 16, 2021

I have been using the switch statement to react to the specific implementation of an Object via its runtimeType.
This does not work for Objects with a generic type parameter:

main() {
  BaseClass c = GenericExtension<SubClass>();
  
  switch(c.runtimeType) {
    case Extension:
      print("Extension");
      break;
    /** Error: In constant expressions, operands of this operator must be of type 'num' 
    case GenericExtension<SubClass>:
      print("SubClass Generic Extension");
      break;
    */
    case GenericExtension:
      print("Unspecified Generic Extension");
      break;
    default:
      print("Missed");
  }
  
  print(c.runtimeType);

  if (c is GenericExtension<SubClass>) {
    print("works");
  }
}


class SubClassBase {}
class SubClass extends SubClassBase {}

class BaseClass {}
class Extension extends BaseClass {}
class GenericExtension<T extends SubClassBase> extends BaseClass{}

Output:

Missed
GenericExtension<SubClass>
works

This is not a huge issue, since checking for the type with is works as expected.

I was just wondering whether this is by design, since I had expected the switch version to work.

@ltOgt ltOgt added the request Requests to resolve a particular developer problem label Feb 16, 2021
@eernstg
Copy link
Member

eernstg commented Feb 17, 2021

It's a delicate exercise to use runtimeType and compare the returned value (known as a reified type) with anything: Object-oriented modeling encourages substitutability, and if you are expecting o to be an instance of SomeClass and actually encounter an instance of ASubclassOfSomeClass then you will get false from o.runtimeType == SomeClass.

That said, you can still make it work.

The error occurs because you are trying to use GenericExtension<SubClass> as an expression, and that's a syntax error. It is a <type>, but not an <expression>.

You can in general use a function like typeOf:

class SubClassBase {}

class SubClass extends SubClassBase {}

class BaseClass {}

class Extension extends BaseClass {}

class GenericExtension<T extends SubClassBase> extends BaseClass {}

Type typeOf<X>() => X;

main() {
  BaseClass c = GenericExtension<SubClass>();
  var cRuntimeType = c.runtimeType;
  if (cRuntimeType == Extension) {
    print("Recognized: Extension");
  } else if (cRuntimeType == typeOf<GenericExtension<SubClass>>()) {
    print("Regocnized: GenericExtension<SubClass>");
  } else if (c is GenericExtension) { // Use `is` to cover subtypes of `GenericExtension<dynamic>`.
    print("Recognized: Unspecified Generic Extension");
  } else {
    print("Missed");
  }
  print('Runtime type is ${c.runtimeType}');
}

The missing bit is that you cannot use a function invocation as a constant, which means that you cannot use a switch statement. You already need to use something other than switch because you need to use is in order to recognize all subtypes of GenericExtension (which is just a concise way to write GenericExtension<dynamic>).

We're about to introduce a generalization of type aliases (that is, declarations starting with typedef), and this will allow us to obtain a reified type for a parameterized type which is constant (because we can give the type a name, and that name can be used as a constant expression):

// Requires `--enable-experiment=nonfunction-type-aliases`

typedef GenericExtensionOfSubclass = GenericExtension<SubClass>;

class SubClassBase {}

class SubClass extends SubClassBase {}

class BaseClass {}

class Extension extends BaseClass {}

class GenericExtension<T extends SubClassBase> extends BaseClass {}

Type typeOf<X>() => X;

main() {
  BaseClass c = GenericExtension<SubClass>();
  var cRuntimeType = c.runtimeType;
  switch (cRuntimeType) {
    case Extension:
    print("Recognized: Extension");
    break;
    case GenericExtensionOfSubclass:
    print("Regocnized: GenericExtension<SubClass>");
    break;
    default:
    if (c is GenericExtension) {
      print("Recognized: Unspecified Generic Extension");
    } else {
      print("Missed");
    }
  }
  print('Runtime type is ${c.runtimeType}');
}

@eernstg
Copy link
Member

eernstg commented Feb 17, 2021

I'll close this issue as a duplicate of #123, where there is a request to allow parameterized type as an expression.

@eernstg eernstg added the state-duplicate This issue or pull request already exists label Feb 17, 2021
@eernstg eernstg closed this as completed Feb 17, 2021
@ltOgt
Copy link
Author

ltOgt commented Feb 17, 2021

Thank you for the detailed response.
Learned something new

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem state-duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

2 participants