Description
The any
type is more permissive than is desired in many circumstances. The problem is that any
implicitly conforms to all possible interfaces; since no object actually conforms to every possible interface, any
is implicitly type-unsafe. Using any
requires type-checking it manually; however, this checking is easy to forget or mess up. Ideally we'd want a type that conforms to {}
but which can be refined to any interface via checking.
I'll refer to this proposed type as unknown
. The point of unknown
is that it does not conform to any interface but refines to any interface. At the simplest, type casting can be used to convert unknown
to any interface. All properties/indices on unknown
are implicitly treated as unknown
unless refined.
The unknown
type becomes a good type to use for untrusted data, e.g. data which could match an interface but we aren't yet sure if it does. This is opposed to any
which is good for trusted data, e.g. data which could match an interface and we're comfortable assuming that to be true. Where any
is the escape hatch out of the type system, unknown
is the well-guarded and regulated entrance into the type system.
(edit) Quick clarification: #10715 (comment)
e.g.,
let a = JSON.parse(); // a is unknown
if (Arary.isArray(a.foo)) ; // a is {foo: Array<unknown>} extends unknown
if (a.bar instanceof Bar) // a is {bar: Bar} extends unknown
let b = String(a.s); // b is string
a as MyInterface; // a implements MyInterface
Very roughly, unknown
is equivalent to the pseudo-interface:
pseudo_interface unknown extends null|undefined|Object {
[key: any]: unknown // this[?] is unknown
[any]: unknown // this.? is unknown
[key: any](): ()=>unknown // this.?() returns unknown
}
I'm fairly certain that TypeScript's type model will need some rather large updates to handle the primary cases well, e.g. understanding that a type is freely refinable but not implicitly castable, or worse understanding that a type may have non-writeable properties and allowing refinement to being writeable (it probably makes a lot of sense to treat unknown
as immutable at first).
A use case is user-input from a file or Web service. There might well be an expected interface, but we don't at first know that the data conforms. We currently have two options:
- Use the
any
type here. This is done with theJSON.parse
return value for example. The compiler is totally unable to help detect bugs where we pass the user data along without checking it first. - Use the
Object
type here. This stops us from just passing the data along unknown, but getting it into the proper shape is somewhat cumbersome. Simple type casts fail because the compiler assumes any refinement is impossible.
Neither of these is great. Here's a simplified example of a real bug:
interface AccountData {
id: number;
}
function reifyAccount(data: AccountData);
function readUserInput(): any;
const data = readUserInput(); // data is any
reifyAccount(data); // oops, what if data doesn't have .id or it's not a number?
The version using Object
is cumbersome:
function readUserInput(): Object;
const data = readUserInput();
reifyAccount(data); // compile error - GOOD!
if (data as AccountData) // compile error - debatably good or cumbersome
reifyAccount(data);
if (typeof data.id === 'number') // compile error - not helpful
reifyAccount(data as AccountInfo);
if (typeof (data as any).id === 'number') // CUMBERSOME and error-prone
reifyAccount((data as any) as AccountInfo); // still CUMBERSOME and error-prone
With the proposed unknown
type;
function readUserInput(): unknown;
const data = readUserInput(); // data is unknown
if (typeof data.id === 'number') // compiles - GOOD - refines data to {id: number} extends unknown
reifyAccount(data); // data matches {id: number} aka AccountData - SAFE