isSubtypeOf method
Checks if this type is a subtype of other.
Implementation
@override
bool isSubtypeOf(RuntimeType other, {Object? value}) {
if (other is FunctionRuntimeType) {
// An untyped function type matches any function and vice-versa.
if (_isUntyped || other._isUntyped) return true;
// Return type is covariant.
if (!_runtimeTypeCompatible(returnType, other.returnType)) return false;
// Required positional parameters must line up 1:1 (contravariant).
if (positionalParameterTypes.length !=
other.positionalParameterTypes.length) {
return false;
}
for (var i = 0; i < positionalParameterTypes.length; i++) {
if (!_runtimeTypeCompatible(
other.positionalParameterTypes[i],
positionalParameterTypes[i],
)) {
return false;
}
}
// Named parameters: every named parameter the supertype declares must be
// present here with a contravariant-compatible type.
for (final entry in other.namedParameterTypes.entries) {
final mine = namedParameterTypes[entry.key];
if (mine == null) return false;
if (!_runtimeTypeCompatible(entry.value, mine)) return false;
}
return true;
}
// GEN-125 / SCD136 — a function TYPEDEF is bridged as
// `BridgedClass(nativeType: Function, name: typedef.name)`, so its name is
// `VoidCallback`, never `Function`. The name is the one property of that
// bridge deliberately not `Function`, so the nominal test below could
// never match one and every Flutter callback was refused:
//
// type 'dynamic Function()' is not a subtype of type 'VoidCallback?'
//
// Ask what the bridge IS rather than what it is called. This subsumes the
// nominal test — `dart:core`'s own `Function` bridge carries
// `nativeType: Function` too — and because the argument check, the return
// check and `is`/`as` all route through here, one rule repairs all three.
//
// WHY THIS FILE MAY KNOW ABOUT `BridgedClass`, given that it is the
// interface file everything else depends on: it already did. The line
// below is a STRING GUESS about how bridges are named, which is the same
// coupling in its worst form. Replacing a guess about an implementor with
// a typed question to it removes knowledge from this file rather than
// adding it.
//
// Deliberately arity-blind, exactly as the nominal test it subsumes was: a
// bridged typedef carries `nativeType: Function` and nothing else, so
// there is no signature here to check a closure against. Making the bridge
// carry one is a generator change, tracked as scd137. Until then the
// posture is the documented one — be permissive rather than reject working
// callbacks.
if (other is BridgedClass && other.nativeType == Function) {
// SCD137 — when the bridge carries the typedef's positional arity, a
// callable that PROVABLY cannot be invoked is refused here instead of
// throwing later at the call, with a message that names neither the
// parameter nor the typedef.
//
// The rule is deliberately the conservative one. Only the typedef's
// REQUIRED count is checked: that is the one invocation shape it
// guarantees, and rejecting on a possibility is how a working callback
// gets refused. Optional positionals on the callable side count toward
// what it can accept, so `(a, [b]) {}` serves a one-argument typedef.
//
// No arity means no opinion — scd136's acceptance, unchanged. Every
// `.b.dart` generated before the generator carried signatures through is
// in that state, so this is inert until both have shipped.
final required = other.typedefRequiredPositional;
if (required == null) return true;
final minAccepted = positionalParameterTypes.length;
final maxAccepted = minAccepted + optionalPositionalParameterTypes.length;
return minAccepted <= required && required <= maxAccepted;
}
// Every function is a `Function` and an `Object`.
return _isWildcardTypeName(other.name) || other.name == 'Function';
}