Syntax error when trying to determine if a variable is of a certain type - dart

Pretty much as the title says: If you have a Type stored in a variable, there's no way to compare your actual object to this type variable, as far as I can tell. I can probably accomplish what I'm trying to do with mirrors, but I'd prefer not to if at all possible.
void example() {
Type myType = String;
String myExample = "Example";
//Syntax error here: The name 'myType' is not a type and cannot be used in an 'is' expression
if (myExample is myType) {
}
}

You can't generally test if a value is of a type using the Type object.
Type objects are reflected types, not real types. They represent the real type, but you can't use them in the code where you need a type: as type assertions, as generic type parameters or with the is/as operators. You must use the name of a type in those places, and not the name of a normal variable that happens to hold a Type object.
Clever stuff using mirrors might get there, but it's likely overkill for most cases (and I understand that you don't want it).
What you might be able to do instead, is to not pass around raw Type objects. You could instead make your own type abstraction, something like:
class MyType<T> {
const MyType();
Type get type => T;
bool isA(Object object) => object is T;
}
Then you can use that to represent types, not a Type object, and do something like:
void main(List<String> args) {
MyType myType = const MyType<String>();
String myExample = "Example";
if(myType.isA(myExample)) {
print('is');
} else {
print('is not');
}
}
That does require that your entire program uses your type objects to pass around types, but it also gives you a lot of control over those objects, so you can implement the functionality that you need.

I tried
library x;
void main(List<String> args) {
Type myType = String;
String myExample = "Example";
if(myExample.runtimeType == myType) {
print('is');
} else {
print('is not');
}
}
and it worked.
I have not much experience with such code in Dart though. Maybe that is not a fail-safe approach.

import 'package:reflection/reflection.dart';
void main() {
var childType = typeInfo(Child);
var baseType = typeInfo(Base);
if(childType.isA(baseType)) {
print("Child is Base");
}
if(baseType.isAssignableFrom(childType)) {
print("Base is assignable from Child");
}
}
class Base {
}
class Child extends Base {
}
Child is Base
Base is assignable for Child
P.S.
The "reflection" package incompatible with dart2js. It work only when used in Dart language.

Related

Dart: lists of supertype takes subtype only at runtime

I ran into an issue similar to this:
void main() {
_buildMixedList([1,2.3,4,5.6,7.6,8]);
_buildHomogeneousList([1,2,4,5,7,8]);
}
abstract class NumberWrapper {}
class DoubleWrapper extends NumberWrapper{
final double myDouble;
DoubleWrapper(this.myDouble);
}
class IntWrapper extends NumberWrapper{
final int myInt;
IntWrapper(this.myInt);
}
List<NumberWrapper?> _buildMixedList(List<dynamic> numbers) {
List<NumberWrapper?> wrappers = numbers.map((number) {
if(number is int){
return IntWrapper(number);
}
if(number is double){
return DoubleWrapper(number);
}
return null;
}).toList();
wrappers.add(DoubleWrapper(0.2));
return wrappers;
}
List<NumberWrapper?> _buildHomogeneousList(List<dynamic> numbers) {
List<NumberWrapper?> wrappers = numbers.map((number) {
if(number is int){
return IntWrapper(number);
}
return null;
}).toList();
wrappers.add(DoubleWrapper(0.2));
return wrappers;
}
As you can see, the two methods are doing something similar (adding object of different types to a list). The first one adds different objects inside a map() function and the other adds only one type in map() and then adds another after.
The second one throws this error:
: TypeError: Instance of 'DoubleWrapper': type 'DoubleWrapper' is not a subtype of type 'IntWrapper?'Error: TypeError: Instance of 'DoubleWrapper': type 'DoubleWrapper' is not a subtype of type 'IntWrapper?'
As if the list is being changed to List<IntWrapper?> just because we only added IntWrappers in the map().
I wrote this test code after encountering this in one of my projects, so it's not representative of a real case. I tried it on dartPad.
Coming from a java background I was expecting the second method to work. Is it a bug or is it intended? If intended, why is that so?
Your problem is that there are a difference between the type of the variable and the type of the object which you are pointing to.
So in this case:
List<NumberWrapper?> wrappers = numbers.map((number) {
if(number is int){
return IntWrapper(number);
}
return null;
}).toList();
What you are actually are doing is creating a List<IntWrapper?> which you are using a variable of the type List<NumberWrapper?> to point at. Why? Because the type of the variable in this case does not change the type of the returned List from toList() (which type is determined by what type map() returns).
The reason the type is List<IntWrapper?> is because Dart are trying to be smart about automatically assigning the type. In this case, the analyzer can see you List will only contain IntWrapper or null.
I think the best solution here is to rewrite this part to something like this:
List<NumberWrapper?> _buildHomogeneousList(List<num> numbers) {
final wrappers = <NumberWrapper?>[
for (final number in numbers)
if (number is int) IntWrapper(number) else null
];
wrappers.add(DoubleWrapper(0.2));
return wrappers;
}
By using the [] syntax to create the List, it is easier to specify the type you want the List to be.
Alternative, you can do this where we add the expected type to the map method:
List<NumberWrapper?> _buildHomogeneousList(List<num> numbers) {
List<NumberWrapper?> wrappers = numbers.map<NumberWrapper?>((number) {
if (number is int) {
return IntWrapper(number);
}
return null;
}).toList();
wrappers.add(DoubleWrapper(0.2));
return wrappers;
}

Multiple types for a single variable (parameter/return type)

I am very new to Dart so excuse me if I didnt see this part.
I want to make a union type e.g. for a function input. In TS this would be:
let variableInput: string | number
typedef doesnt really define types but functions and enums dont really help too.
On the other side how should it look like when a function return either one or the other of two types? There must be something I dont see here.
There are no union types in Dart.
The way to do this in Dart is returning/accepting dynamic as a type:
dynamic stringOrNumber() { ... }
void main() {
final value = stringOrNumber();
if (value is String) {
// Handle a string value.
} else if (value is num) {
// Handle a number.
} else {
throw ArgumentError.value(value);
}
}
See also: https://dart.dev/guides/language/sound-dart

Dart - Instantiate Generic Type by Mapping Constructors

You cannot instantiate a generic type in Dart and you cannot use mirrors in Flutter. However, you can map types to their constructors in a closure.
This could be useful for something like JSON serialization when working with handwritten classes, especially if the data source returns the same data shape in many forms, i.e Future<T> or Stream<T>.
Question: Is this a valid pattern and are there any potential drawbacks? Better alternatives?
class Dog{}
class Cat{}
Map models = {
Dog: () => Dog(),
Cat: () => Cat()
};
class Database<T> {
T data;
Database() {
data = models[T](); // <-- this feels a bit weird, but works
}
}
void main() {
Database<Cat> cat = Database();
print(cat.data is Cat);
}

How to get the subtypes of a generic type using `DartType` from `analyzer` package?

How can I get the subtypes of an element using the class DartType from the analyzer package?
for example if the type is List<String>, I would like to get String. Also will be useful to get if the type is generic.
Another more complex example would be Map<String, String> where I want to get a list of the subtypes, in this case: [String, String].
This one is a little tricky - because DartType actually itself has some super types - the one that will interest you here is ParameterizedType:
import 'package:analyzer/dart/element/type.dart';
Iterable<DartType> getGenericTypes(DartType type) {
return type is ParameterizedType ? type.typeArguments : const [];
}
I don't know if it's possible to know if the type is generic - after all, it's just a type. But you can check if the type accepts generic parameters, again, using ClassElement:
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
bool canHaveGenerics(DartType type) {
final element = type.element;
if (element is ClassElement) {
return element.typeParameters.isNotEmpty;
}
return false;
}
Hope that helps!

How can I get a constructor's parameters via reflection in Dart?

I'm playing around with the mirrors stuff in Dart. I can't find any way reflect a class and figure out if it has a constructor and, if so, what the parameters to this constructor are.
With the ClassMirror, it looks like the "declarations" collection of DeclarationMirror objects will include an entry for the constructor, but there's no way with a DeclarationMirror to tell if whether or not it's a constructor, and no way to see information about the parameters.
With the "instanceMembers" collection of MethodMirror objects, it looks like constructors are not even included. I assume this is because a constructor isn't a normal kind of method that one could invoke, but still, it's odd since MethodMirror has an "isConstructor" attribute.
Is there any way to, given an object type, figure out if it has a constructor and, if so, get information on the parameters to that constructor?
The below code illustrates the problem:
import 'dart:mirrors';
class Person {
String name;
int age;
Person(this.name, this.age);
string getNameAndAge() {
return "${this.name} is ${this.age} years old";
}
}
void main() {
ClassMirror classMirror = reflectClass(Person);
// This will show me the constructor, but a DeclarationMirror doesn't tell me
// anything about the parameters.
print("+ Declarations");
classMirror.declarations.forEach((symbol, declarationMirror) {
print(MirrorSystem.getName(symbol));
});
// This doesn't show me the constructor
print("+ Members");
classMirror.instanceMembers.forEach((symbol, methodMirror) {
print(MirrorSystem.getName(symbol));
});
}
Firstly, you need to find contructors in declarations map.
ClassMirror mirror = reflectClass(Person);
List<DeclarationMirror> constructors = new List.from(
mirror.declarations.values.where((declare) {
return declare is MethodMirror && declare.isConstructor;
})
);
Then, you can cast DeclarationMirror into MethodMirror and use getter MethodMirror.parameters to get all parameters of the constructor. Something like:
constructors.forEach((construtor) {
if (constructor is MethodMirror) {
List<ParameterMirror> parameters = constructor.parameters;
}
});

Resources