Description
Dart SDK version: 3.8.0-149.0.dev (dev) (Thu Feb 27 04:01:43 2025 -0800) on "macos_x64"
There are several ways to use Never
to create dead code or declarations that are impossible to use, without getting any static warnings.
Examples
All the dead code or declarations discussed here are not currently recognized by static analysis.
The following type cast is dead code. The exit
is performed but the casting of the result is dead code.
int i()=>
exit(1) as int;
The following function can never run because it needs a Never
instance as input.
void f<T extends Never>(T never)
{}
The following function can be run without issue because its type parameter is satisfied by Never
.
void f<T extends Never>()
{}
void main()
{ f();
}
Static analysis can even be satisfied at the call site by using an expression which evaluates to Never
as input. Since that expression evaluates to Never
the subsequent evaluation of f
is impossible, and that call is dead code.
import 'dart:io';
void f<T extends Never>(T never)
{}
void main()
{ f(exit(1));
}
Similarly I can define a class with an assignable Never
field.
final class No
{ Never field;
No(this.field);
}
This can never be instantiated, the entire declaration's instance members and constructors become dead declarations. Its static members would still be usable, as long as they don't require Never
objects to be inputted. The Never
object doesn't necessarily need to be declared as a stored field, the result is the same if the class can only be constructed via constructors which require Never
input.
final class No
{ No(Never field);
}
It is possible for only constructors to be dead declarations and still have instance API be usable if there exists a way to construct without Never
input.
final class Yes
{ Yes.no(Never field);//This is a dead declaration.
Yes();//This is not, so the class can still be instantiated and any instance members can be used.
}
Similarly from #60251, impossible to use instance API can be declared.
extension on Never
{ int get impossible=>
0;
}
In that issue, @FMorschel also pointed this out for extension types.
extension type No(Never no)
{}
Expectation
Any function which requires a Never
object to be specified as input should be marked as a dead declaration/code. Any type declaring a Never
object as a stored field or requiring Never
as input in order to be instantiated should have all its instance methods and constructors marked as dead declarations. Furthermore, any type that extends or mixes in such code would have to do the same. In the case of implements it is possible to override the field as a getter and therefore does not necessarily render the type nonusable.
final class No
{ final Never field;
No(this.field);//Can't instantiate!
}
final class Yes implements No
{ @override Never get field=>
throw Error();
Yes();//Can instantiate!
}
It can also be indicated that if the type is intended to only be implemented, it should be declared as an abstract interface class
instead.
Related Issues and Discussions
Related to the currently open issues
- Nonusable Declarations #60251
- Flow analysis. No error in the analyzer in case
x=
operators invocation for the typeNever
#60318
Related to the recently closed issue
There's also the discussion which Never
analysis issues spawned from dart-lang/language#4279