GeezerVison is showing github.com — New 'unknown' top type by ahejlsberg · Pull Request #24439 · microsoft/TypeScript
Conversation
This PR adds a new top type unknown which is the type-safe counterpart of any. Anything is assignable to unknown, but unknown isn't assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.
Note that this PR is technically a breaking change since unknown becomes a reserved type name.
// In an intersection everything absorbs unknown
type T00 = unknown & null; // null
type T01 = unknown & undefined; // undefined
type T02 = unknown & null & undefined; // null & undefined (which becomes never in union)
type T03 = unknown & string; // string
type T04 = unknown & string[]; // string[]
type T05 = unknown & unknown; // unknown
type T06 = unknown & any; // any
// In a union an unknown absorbs everything
type T10 = unknown | null; // unknown
type T11 = unknown | undefined; // unknown
type T12 = unknown | null | undefined; // unknown
type T13 = unknown | string; // unknown
type T14 = unknown | string[]; // unknown
type T15 = unknown | unknown; // unknown
type T16 = unknown | any; // any
// Type variable and unknown in union and intersection
type T20<T> = T & {}; // T & {}
type T21<T> = T | {}; // T | {}
type T22<T> = T & unknown; // T
type T23<T> = T | unknown; // unknown
// unknown in conditional types
type T30<T> = unknown extends T ? true : false; // Deferred
type T31<T> = T extends unknown ? true : false; // Deferred (so it distributes)
type T32<T> = never extends T ? true : false; // true
type T33<T> = T extends never ? true : false; // Deferred
// keyof unknown
type T40 = keyof any; // string | number | symbol
type T41 = keyof unknown; // never
// Only equality operators are allowed with unknown
function f10(x: unknown) {
x == 5;
x !== 10;
x >= 0; // Error
x + 1; // Error
x * 2; // Error
-x; // Error
+x; // Error
}
// No property accesses, element accesses, or function calls
function f11(x: unknown) {
x.foo; // Error
x[5]; // Error
x(); // Error
new x(); // Error
}
// typeof, instanceof, and user defined type predicates
declare function isFunction(x: unknown): x is Function;
function f20(x: unknown) {
if (typeof x === "string" || typeof x === "number") {
x; // string | number
}
if (x instanceof Error) {
x; // Error
}
if (isFunction(x)) {
x; // Function
}
}
// Homomorphic mapped type over unknown
type T50<T> = { [P in keyof T]: number };
type T51 = T50<any>; // { [x: string]: number }
type T52 = T50<unknown>; // {}
// Anything is assignable to unknown
function f21<T>(pAny: any, pNever: never, pT: T) {
let x: unknown;
x = 123;
x = "hello";
x = [1, 2, 3];
x = new Error();
x = x;
x = pAny;
x = pNever;
x = pT;
}
// unknown assignable only to itself and any
function f22(x: unknown) {
let v1: any = x;
let v2: unknown = x;
let v3: object = x; // Error
let v4: string = x; // Error
let v5: string[] = x; // Error
let v6: {} = x; // Error
let v7: {} | null | undefined = x; // Error
}
// Type parameter 'T extends unknown' not related to object
function f23<T extends unknown>(x: T) {
let y: object = x; // Error
}
// Anything but primitive assignable to { [x: string]: unknown }
function f24(x: { [x: string]: unknown }) {
x = {};
x = { a: 5 };
x = [1, 2, 3];
x = 123; // Error
}
// Locals of type unknown always considered initialized
function f25() {
let x: unknown;
let y = x;
}
// Spread of unknown causes result to be unknown
function f26(x: {}, y: unknown, z: any) {
let o1 = { a: 42, ...x }; // { a: number }
let o2 = { a: 42, ...x, ...y }; // unknown
let o3 = { a: 42, ...x, ...y, ...z }; // any
}
// Functions with unknown return type don't need return expressions
function f27(): unknown {
}
// Rest type cannot be created from unknown
function f28(x: unknown) {
let { ...a } = x; // Error
}
// Class properties of type unknown don't need definite assignment
class C1 {
a: string; // Error
b: unknown;
c: any;
}
Fixes #10715.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
alexrock, yortus, Kingwl, j-oliveras, ajafff, Jessidhia, ericanderson, zpdDG4gta8XKpMCd, krryan, ahagelstein, and 146 more reacted with thumbs up emoji
alfaproject, yortus, Kingwl, j-oliveras, svieira, Jessidhia, ericanderson, zpdDG4gta8XKpMCd, krryan, pelotom, and 48 more reacted with hooray emoji
yortus, Kingwl, j-oliveras, zpdDG4gta8XKpMCd, krryan, Ebuall, ibezkrovnyi, andrew-morris-orbis, alcuadrado, mohsen1, and 49 more reacted with heart emoji
All reactions
-
👍
156 reactions
-
🎉
58 reactions
-
❤️
59 reactions
|
Where'd we end up with mapped conditional types w.r.t unknown ?
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
type T41 = keyof unknown; // string | number | symbol
Does that make sense (is this derived from something else)? If unknown is effectively the infinite union - a union of all possible types would have no common keys among all of them, leading me to believe the correct result is never (the given types seem correct for never, this being a place where any is more never-like than top-like).
|
glentakahashi reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Wesley Wigham (@weswigham) No, that probably doesn't make sense. We should be similar to keyof {} and keyof object here, both of which are never.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Ryan Cavanaugh (@RyanCavanaugh) Do you mean the behavior of unknown in a distributive conditional type? It's an atomic type so it doesn't distribute. This is unlike {} | null | undefined which distributes and therefore is very hard to test for.
|
RyanCavanaugh, aikoven, HermannGruber, hardfist, vassudanagunta, and AntoniBLopez reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
|
|
|
// Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions.
|
|
|
if (returnType && maybeTypeOfKind(returnType, TypeFlags.Any | TypeFlags.Void)) {
|
|
|
if (returnType && maybeTypeOfKind(returnType, TypeFlags.AnyOrUnknown | TypeFlags.Void)) {
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a function returning an explicit unknown should probably have return values? Otherwise you should've written void or undefined, right?
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
mhegazy, gcnew, and melissab1238 reacted with thumbs up emoji
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about that. undefined is in the domain of unknown (just like it is in the domain of any) and that's really what should guide us here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. Fair. Do we handle unions with undefined in accordance with that, then?
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do, but with a few wrinkles. If the return type annotation includes void, any, or unknown we don't require any return statements. Otherwise, if the return type includes undefined we require at least one return statement somewhere, but don't require return statements to have expressions and allow the end point of the function to be reachable. Otherwise, we require a return statement with an expression at every exit point.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
>T : T
|
|
|
|
|
|
boom: T extends any ? true : true
|
|
|
>boom : T extends any ? true : true
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why did this type collapse to true?
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to eagerly resolve to true when the extends type is any or unknown (since it always will be).
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, but what about T extends any ? { x: T } : never - that needs to distribute.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, yes, I suppose we can only optimize when the conditional type isn't distributive.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a better check (than just distributivity) that I have an outstanding PR for: instantiate the check type (and infer types) in the true/false types with wildcards. If the instantiation isn't the input type, you can't simplify. If it is, you can. (Since the type isn't affected by instantiation)
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Mark Stacey (Gudahtt)
added a commit
to MetaMask/core
that referenced
this pull request
Feb 25, 2021
The BaseController state now uses `unknown` rather than `any` as the
type for state properties. `unknown` is more type-safe than `any` in
cases like this where we don't know what type to expect. See here for
details [1].
This was suggested by @rekmarks during review of #362 [2].
[1]: microsoft/TypeScript#24439
[2]: #362 (comment)
Mark Stacey (Gudahtt)
added a commit
to MetaMask/core
that referenced
this pull request
Feb 25, 2021
* Use `unknown` rather than `any` for BaseController state
The BaseController state now uses `unknown` rather than `any` as the
type for state properties. `unknown` is more type-safe than `any` in
cases like this where we don't know what type to expect. See here for
details [1].
This was suggested by @rekmarks during review of #362 [2].
[1]: microsoft/TypeScript#24439
[2]: #362 (comment)
* Use type alias for controller state rather than interface
The mock controller state in the base controller tests now uses a type
alias for the controller state rather than an interface. This was
required to get around an incompatibility between
`Record<string, unknown>` and interfaces[1].
The `@typescript-eslint/consistent-type-definitions` ESLint rule has
been disabled, as this problem will be encountered fairly frequently.
[1]: microsoft/TypeScript#15300 (comment)
This was referenced Mar 10, 2023
Reviewers
+1 more reviewer
Reviewers whose approvals may not affect merge requirements
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|