Given the following dart 3 snippet, is it possible to match against A
being nullable in the switch clause, and to find out which kind of nullable A
is? (String?
, int?
etc)
The type checker incorrectly assumes that A
can never be nullable, and I end up with a case that I can't seem to match against.
void main() {
String? x = test(null);
String y = test("hi");
}
A test<A extends Object?>(A a) {
switch((A,a.runtimeType)){
case (String,Null) :
print("found miscasted nullable string");
case (String,_) :
print("found non-null string: $a");
// This case should match, but something is weird.
case (String?,_) :
print("found nullable string");
case (var x,Null):
print("found something null: $x $a");
if(x is String?)
print("$x is String?");
else
print("$x is not String?");
case (_,_):
print("failed matching completely" );
};
return a;
}
The output becomes
found something null: String? null
String? is not String?
found non-null string: hi
and I can't seem to find a way to match the input type against String?
.
I need this to work in Flutter, so dart:mirror can't be a solution.
Edit: Thanks to the answer from @jamesdlin, I now have the following functions in my code:
Type typeOf<A>() => A;
bool isSomeKindOf<T,S>() => S == typeOf<T>() || S == typeOf<T?>();
. This means that now I can do things like
bool isIntOrNullableInt<A>(A a) => isSomeKindOf<int,A>();
print(isIntOrNullableInt(3)); // true
print(isIntOrNullableInt(null as Int?)); // true
print(isIntOrNullableInt("3")); // false
which means that I can now do stuff like
A getOrDefault<A>(String token, A defaultVal) =>
(
isSomeKindOf<int,A>() ? source.getInt(token) ?? defaultVal :
isSomeKindOf<String,A>() ? source.getString(token) ?? defaultVal :
defaultVal
) as A;
which works whether A
is a Foo
or a Foo?
, which was my secret agenda all along.
This lets me finally have the following working:
String prop1 = getOrDefault("prop1", "property missing");
int? prop2 = getOrDefault("prop2", null);
Note the message from the analyzer (with emphasis added):
The null-check pattern will have no effect because the matched type isn't nullable.
String?
in your pattern isn't checking A == String?
, it's treated as a null-check pattern which a separate kind of pattern, and that pattern is applied to String
.
That's also why the analyzer complains about case (String?, _)
being redundant with case (String, _)
:
This case is covered by the previous cases.
Also note that your check if (x is String?)
won't work either because x
was matched to A
, and A is B
checks if A
is an instance of B
. String
is not an instance of a String?
; String
(and String?
) are instances of the Type
class.
I'm not sure if you can do what you want with patterns, but you instead could do:
void main() {
String? x = test(null);
String y = test("hi");
}
Type identityType<T>() => T;
A test<A extends Object?>(A a) {
if (A == String) {
if (a == null) {
// Note that this is not possible.
print("found miscasted nullable string");
} else {
print("found non-null string: $a");
}
// `A == String?` is not legal syntax.
} else if (A == identityType<String?>()) {
print("found nullable string");
} else if (a == null) {
print("found something null: $A $a");
if (A == identityType<String?>()) {
// This is not possible since it would have been caught earlier.
print("$A is String?");
} else {
print("$A is not String?");
}
} else {
print("failed matching completely");
}
return a;
}