New 'unknown' top type #24439
New 'unknown' top type #24439
Conversation
Where'd we end up with mapped conditional types w.r.t |
Does that make sense (is this derived from something else)? If |
@weswigham No, that probably doesn't make sense. We should be similar to |
@RyanCavanaugh Do you mean the behavior of |
@@ -19624,7 +19644,7 @@ namespace ts { | |||
} | |||
|
|||
// 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)) { |
weswigham
May 29, 2018
Member
I think a function returning an explicit unknown
should probably have return values? Otherwise you should've written void
or undefined
, right?
I think a function returning an explicit unknown
should probably have return values? Otherwise you should've written void
or undefined
, right?
ahejlsberg
May 29, 2018
Author
Member
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.
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.
weswigham
May 29, 2018
Member
Hm. Fair. Do we handle unions with undefined
in accordance with that, then?
Hm. Fair. Do we handle unions with undefined
in accordance with that, then?
ahejlsberg
May 30, 2018
Author
Member
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.
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.
@@ -403,7 +403,7 @@ interface B1<T> extends A1<T> { | |||
>T : T | |||
|
|||
boom: T extends any ? true : true | |||
>boom : T extends any ? true : true |
mhegazy
May 29, 2018
Contributor
why did this type collapse to true
?
why did this type collapse to true
?
ahejlsberg
May 29, 2018
Author
Member
I changed this to eagerly resolve to true
when the extends type is any
or unknown
(since it always will be).
I changed this to eagerly resolve to true
when the extends type is any
or unknown
(since it always will be).
weswigham
May 29, 2018
Member
Ah, but what about T extends any ? { x: T } : never
- that needs to distribute.
Ah, but what about T extends any ? { x: T } : never
- that needs to distribute.
ahejlsberg
May 30, 2018
Author
Member
Hmm, yes, I suppose we can only optimize when the conditional type isn't distributive.
Hmm, yes, I suppose we can only optimize when the conditional type isn't distributive.
weswigham
May 30, 2018
•
Member
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)
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)
Will there be any core typings changes? The original issue #10715 speaks of making |
no. this would be a breaking change that we are not planning on doing. you can define locally interface JSON {
parse(text: string, reviver?: (key: any, value: any) => any): unknown;
} |
In ways this could be quite similar to the proposal of void being allowed to morph into into the first type that that it is operated on.... Mabye need to compare differences pros and cons. |
|
Hi, Just a couple of other use cases for unknown with infer typings, which just found myself wanting to be able to do, which you could use also use as test cases and proof for this implementation type CallBackMethod = ((callback: (err: any, result: any) => void) => void)
type CallBackMethodExtract<T> = T extends ((callback: (err: any, result: infer R) => void) => void) ? R : undefined
type ExtractCallbackResultCallBack<T> = T extends CallBackMethod ? CallBackMethodExtract<T> : undefined
type ExtractCallbackResult<T> = T extends ((callback: (err: any, result: infer T) => void) => void) ? T: undefined
const testsCallBack = {
test : function (callback : (err : any, result : string) => void)
}
function Prom<R extends any, M extends keyof O = unknown, O extends {} = unknown>(object : O, method : M) : () => Bluebird<R extends undefined ? ExtractCallbackResult<O[M]> : R>
function Prom<T>(propA: (callback: (err: any, result: T) => void) => void) : () => Bluebird<T>;
function Prom(propA : any, propB : any = undefined) : any
{
if (propB === undefined)
return Bluebird.promisify(propA)();
else
return Bluebird.promisify(propA[propB], propB)();
}
const sftpWrapper = Prom<ssh2.SFTPWrapper>(testsCallBack, 'test');// required to overide a bad implementation of the typings.
const sftpWrapper = Prom<ssh2.SFTPWrapper>(testsCallBack, 'tet');// Compiler Error
const sftpWrapper = Prom(ssh2, 'fastGet');// returns a good implementation of the types libary It could also be argued, that type inference, should override defaults value for generics and that this is a bug type inference/generics bug.
|
The other case to mention is the following when dealing with generics, which is a problem were generic parameters that are dangling, at the start of function are given the {} which means, one is not able to detect a default infer type from thin air, to do something with it, because extends {} is basically that infer type for the dangling Generic. It should rather infer unknown as the type when coming form thin air. This case is not detectable, as T1 is infer from thin air. function func<T1, T2, T3>(param1 : boolean, param2 : string) : T1 extends {} ? never : T1
{
}
const results = func(true, "something") // this is always never for any object, now. It would be better if the following was the case function func<T1, T2, T3>(param1 : boolean, param2 : string) : T1 extends unkown ? never : T1
{
}
const results = func(true, "something") // this is never, only when T1 is not specified other wise would be the object type.
type MyObject = {}
const results func<MyObject>(true, "something");// this results in {}, which what we want. This case is not detectable, as T1 is infer from thin air, say this should not be the function signature to use, by explicitly returning unknown, versus indirectly as pass thought type or inference, extract from a generic. As it would allow one to differentiate between function signatures, that are similar. function fun<K extends keyof O, O extends {}>(param1 : O, param2 : K) : ExtractionOfType<O[K]>
function func<T1, T2, T3>(param1 : boolean, param2 : string) : T1 extends {} ? unknown : T1
function(param1 : any, param2 : any)
{
}
const results = func({key:...}, "key") // were the type is of the extracted type function from O[K],
because other function signature explicitly returned unknown. |
Probably also a good think to keep in mind with all of this, return type statment... Typically I envisage this to be order thing, were one would typically choose the least constraining function call when the type system was to intersect the function type signature. What are you thoughts regarding, this belongs with the other post, it fit with the partial infer type from air of that post. const testsCallBack = {
test : function (callback : (err : any, result : string) => void)
}
function Prom<R>(object : {}, method : string) : () => Bluebird<R>
function Prom<O extends {}>(object : O, method : keyof O) : Bluebird<Extract<O>>
const sftpWrapper = Prom(testsCallBack, 'test'); // return type BlueBird<{}> I think the part of concern would be the return type and finding the most restrictive match to return the return type to use, see some work has been done on looks like be on in the next release regarding remain non explicit types, will be inferred. |
The main reason for this update is that the token function returns the token object and not a string. Fixes: - use unknown instead of any (microsoft/TypeScript#24439) - token object is defined in README - user object is defined in README
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 `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 PR adds a new top type
unknown
which is the type-safe counterpart ofany
. Anything is assignable tounknown
, butunknown
isn't assignable to anything but itself andany
without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on anunknown
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.Fixes #10715.