Skip to content

avoid_unrelated_type_casts

v1.0.0WarningConfigurableCollection & Type

Flags an as cast or is check between types with no possible common subtype. The cast can only ever throw; the check can only ever be false.

The analyzer accepts value as int on a String without complaint — the cast is legal Dart, it simply fails at runtime. The is form is quieter and worse: a check that is statically always false compiles, runs, and skips its branch forever, leaving dead code that looks live.

The SDK’s unrelated_type_equality_checks covers the == version of this idea; casts and type tests are the gap this rule fills.

The usual cause is a type that changed under the cast — an id that became a String, a field that was widened — leaving a cast nothing can satisfy:

int parseCount(String raw) {
return raw as int;
}

Convert, rather than assert, when the types are genuinely different:

int parseCount(String raw) {
return int.parse(raw);
}
// Don't — the branch is dead code
void report(String message) {
if (message is int) {
log('numeric: $message');
}
}
// Do — `Object` really might be an `int`
void report(Object message) {
if (message is int) {
log('numeric: $message');
}
}

Some codebases narrow a sealed hierarchy in ways that read as unrelated to a purely syntactic check. Turn off the is half and keep the casts:

many_lints.yaml
rules:
avoid_unrelated_type_casts:
report_is_checks: false
// Still reported
final n = someString as int;
// No longer reported
if (someString is int) { }

The rule is deliberately conservative about what counts as “unrelated”. Two ordinary classes are not reported, because a third class could implement both, making the cast unusual rather than impossible:

class Foo {}
class Bar {}
class Both implements Foo, Bar {}
Bar f(Foo value) => value as Bar; // legal: `value` might be a `Both`

Reports are limited to cases where no such subtype can exist: final and sealed classes, enums, and dart:core types like String and int.

dynamic, Object and void are compatible with everything by design, so a cast from them is the normal way to narrow an untyped value and is never reported. Type parameters are skipped too — the real type argument is unknown, so any conclusion would be guesswork. Nullability alone is not a relation difference: String? to String is the analyzer’s business, not this rule’s.

This rule is in the core preset, so it is on with preset: core, preset: recommended or preset: opinionated.

To turn it off:

many_lints.yaml
rules:
avoid_unrelated_type_casts: false

To keep the rule on but skip certain paths, use per-rule exclude.

analysis_options.yaml
many_lints:
rules:
avoid_unrelated_type_casts:
report_is_checks: false
Option Type Default Description
report_is_checks bool true Also report is checks. Set to false to limit the rule to as casts