never doesn't equal never when never is keys/values from filtering generic object wrapped inside another object by exact value
See original GitHub issueBug Report
🔎 Search Terms
never equals filter mapped type
🕗 Version & Regression Information
It’s broken in all tested versions of typescript (4.1.5 until 4.9.0-dev.20221011 in TS playground). Versions prior 4.1.5 do not support mapped types keys filtering.
⏯ Playground Link
Playground both keyof and of Values<> cases
💻 Code
So wee need to:
- Wrap generic argument, lets say
O
, in an object(i.e{anyKey: O}
or just[O]
). - Then filter object keys by exact value via
Equals<>
in such a way to pass further empty object. - Extract keys or values. Since object is empty
never
will be passed. - And finally compare it with never via
Equals<>
// helpers start
type Values<O extends object> =
O extends any[]
? O[number]
: O[keyof O]
type Equals<A, B> =
(<T>() => T extends B ? 1 : 0) extends
(<T>() => T extends A ? 1 : 0)
? true
: false
// helpers end
type FilterByStringValue<O extends object> = {
[K in keyof O as Equals<O[K], string> extends true ? K : never]: any
}
type FilteredValuesMatchNever<O extends object>
= Equals</*never*/Values</*{}*/FilterByStringValue<[O]>>, never>
type filteredValuesMatchNever = FilteredValuesMatchNever<[]> // false wrong
🙁 Actual behavior
filteredValuesMatchNever
is false
🙂 Expected behavior
filteredValuesMatchNever
must be true.
Actually with the FilterByStringValue
rewritten like this:
type FilterByStringValue<O extends object> = {
[K in keyof O as Equals<O[K], string> extends true ? K : never]: O[K]
}
filteredValuesMatchNever
somehow becomes true
Issue Analytics
- State:
- Created a year ago
- Reactions:1
- Comments:5 (4 by maintainers)
Top Results From Across the Web
What is "not assignable to parameter of type never" error in ...
So when you tried to add a string to it, it was a type mismatch, and so it threw the error you saw....
Read more >Documentation - Narrowing - TypeScript
JavaScript has an operator for determining if an object has a property with a name: the in operator. TypeScript takes this into account...
Read more >A complete guide to TypeScript's never type - Zhenghao
Filter out keys in mapped types#. In TypeScript, types are immutable. If we want to delete a property from an object type, we...
Read more >Value Conversions - EF Core - Microsoft Learn
Configuring value converters in an Entity Framework Core model. ... A null value will never be passed to a value converter. A null...
Read more >jq Manual (development version)
Instead of running the filter for each JSON object in the input, read the entire ... was either false or null , or...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
This is quite interesting!
We have 3 different
Equals
implementations here, let’s call them as follows:type Equals<A, B> = [A] extends [B] ? [B] extends [A] ? true : false : false;
type Equals<A, B> = [A, B] extends [B, A] ? true : false;
type Equals<A, B> = (<T>() => T extends B ? 1 : 0) extends (<T>() => T extends A ? 1 : 0) ? true : false
We also have 2 different implementations of the filtering mapped type:
any
at the value position of the mapped type, note that different types at this position can also exhibit the same, broken, behaviorO[K]
at the value position of the mapped typeI don’t intend to explore all combinations of those 2 sets but I will mention particular variants from both of those.
nested Equals + any-value
This one works correctly: TS playground
short Equals + any value
This one doesn’t work correctly: TS playground
[A, B]
and it’s not classified as generic here. It turns out that a generic tuple type is only a one with a variadic element (isGenericTupleType
)checkType
([any, never]
) andinferredExtendsType
([never, any]
)any
is not assignable tonever
this assignability check fails andfalseType
gets selected as theresult
(here)short Equals + indexed access value
This one works correctly: TS playground
It’s kinda interesting because almost everything here is the same as in the variant described above. However, in here checking the assignability of the permissive intantiations returns a different result despite the fact that we seamingly compare the same types (
[any, never]
and[never, any]
).So why is that? In here, the
any
type is actually not the “true any” - it’s a “wildcard type” and that is assignable tonever
. So based on that the overall conditional type stays roughly untouched and gets finally resolved when the actual type arguments are supplied to it.Note: it would be kinda nice if the wildcard type would get printed as
any*
or something like that. The same could be done about thesilentNeverType
(and maybe some others). Since they are printed in the same way, it’s fairly easy to not notice the difference between them when debugging.identity-based Equals + any value
This one doesn’t work correctly: TS playground
This is fairly similar to the “short Equals + any value” case. The only difference here is what permissive instantiations we are comparing here for
checkType
(<T>() => T extends never ? 1 : 0
) andinferredExtendsType
(<T>() => T extends any ? 1 : 0
). Since those are not assignable, we simply select thefalseType
here too.The fact that it still repros on the tuple form is pretty interesting, since that should be going through the normal relational path. Though I wonder if the first thing that gets checked is the index signature between the two types.