Ts-toolbelt: O.OptionalKeys<O> returns all keys in TS 3.7

Created on 17 Dec 2019  路  9Comments  路  Source: millsp/ts-toolbelt

馃悶 Bug Report

Describe the bug

O.OptionalKeys<O> is returning a set of keys that includes required keys, not just optional keys in TypeScript 3.7.3.

O.RequiredKeys<O> has a similar issue in always returning as never.

Reproduce the bug

type Test = {
  hello: string;
  world?: string;
}

// Should be "hello", but is "hello" | "world"
type TestOptionalKeys = O.OptionalKeys<Test>

// Should be "world", but is never
type TestRequiredKeys = O.RequiredKeys<Test>

Expected behavior

I expect only the set of keys in a type marked optional (?) to be returned.

Additional context

TypeScript: 3.7.3
ts-toolbelt: 4.14.0
VSCode: 1.41.0
Node.js: 12.4.0

bug needs investigation

All 9 comments

Hi @imahassle,

Everything seems right on my side

type Test = {
    hello: string;
    world?: string;
}

Optional key here is "world" and
Required key here is "hello"

In your sentence, it is the other way around

type OptionalKeys = O.OptionalKeys<Test> // world
type RequiredKeys = O.RequiredKeys<Test> // hello

Ah, yes I did get it flipped around.

But the issue persists locally for me:

image

Strange, I'm also on:

TypeScript: 3.7.3
ts-toolbelt: 4.14.0
VSCode: 1.41.0
Node.js: 12.4.0

Are you using VSCode? obvioulsy
If so:

  • please change the ts version in the bottom right corner & back to 3.7.3
  • and in any case, remove node_modules, package-lock.json, and re-install

Did this work?

Nuked my node_modules and package-lock.json and re-installed, same issue.

Do my TS libraries make a difference?

Part of my tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2016",
    "lib": ["dom", "ES2016", "ES2018", "es2017.object", "scripthost", "dom.iterable"]
  }
}

I'm not aware of anything else that may make a difference.

Found the culprit. Please enable:

"strict": true,

If you can do that for now. I'm going to investigate why this happens exactly.

Thank you for reporting this bug!

Oh that worked for me! We currently don't have strict turned on for our project. I'll have to see if it breaks anything on our side 馃槵

Thanks for the fast response!

And thanks for this awesome project! It's super helpful. 馃榿

Thanks for the compliments! Now thanks to you, we can see there are lots of tests failing. Gotta work on it:


tst/List.ts:56:5 - error TS2322: Type '0' is not assignable to type '1'.

56     check<T.AssignUp<[1], [[2, 1], [3, 2?]]>,   [3, 2 | 1],     Test.Pass>(),
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/List.ts:116:5 - error TS2322: Type '0' is not assignable to type '1'.

116     check<T.Either<T_EITHER, '0' | '1'>,    EITHER_T_01,    Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/List.ts:278:5 - error TS2322: Type '0' is not assignable to type '1'.

278     check<T.Intersect<T, T1, 'equals'>,     INTERSECT_T_T1_NUMBER_EQUALS,   Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/List.ts:463:5 - error TS2322: Type '0' is not assignable to type '1'.

463     check<T.Prepend<never, [4, 5]>,         never,                  Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/List.ts:551:5 - error TS2322: Type '0' is not assignable to type '1'.

551     check<T.Tail<never>,            never,          Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:59:5 - error TS2322: Type '0' is not assignable to type '1'.

59     check<O.AssignUp<O_ASSIGNUP, Os_ASSIGNUP>,    ASSIGNUP_O_Os,   Test.Pass>(),
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:98:5 - error TS2322: Type '0' is not assignable to type '1'.

98     check<O.CompulsoryKeys<O>,  COMPULSORYKEYS_O,   Test.Pass>(),
       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:172:5 - error TS2322: Type '0' is not assignable to type '1'.

172     check<O.Either<O_EITHER, 'a' | 'b'>,            EITHER_O_AB_TRUE,       Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:267:5 - error TS2322: Type '0' is not assignable to type '1'.

267     check<O.Filter<O, string, 'extends->'>,     FILTER_O_DEFAULT,   Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:279:5 - error TS2322: Type '0' is not assignable to type '1'.

279     check<O.FilterKeys<O, string, 'extends->'>,     FILTERKEYS_O_DEFAULT,   Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:295:5 - error TS2322: Type '0' is not assignable to type '1'.

295     check<O.Has<O, 'd', string, 'extends->'>,                       0 | 1,  Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:296:5 - error TS2322: Type '0' is not assignable to type '1'.

296     check<O.Has<O, 'd', 'string0', 'equals'>,                       0,      Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:298:5 - error TS2322: Type '0' is not assignable to type '1'.

298     check<O.Has<O, 'd', undefined, 'extends->'>,                    0 | 1,  Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:299:5 - error TS2322: Type '0' is not assignable to type '1'.

299     check<O.Has<O, 'd', 'string0', 'equals'>,                       0,      Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:305:5 - error TS2322: Type '0' is not assignable to type '1'.

305     check<O.Has<O, 'a' | 'd', string, 'extends->'>,                 0 | 1,  Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:440:5 - error TS2322: Type '0' is not assignable to type '1'.

440     check<O.Merge<O1, O>,           MERGE_O1_O,         Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:579:5 - error TS2322: Type '0' is not assignable to type '1'.

579     check<O.MergeUp<O_MERGEUP, O1_MERGEUP>,             MERGEUP_O_O1,       Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:580:5 - error TS2322: Type '0' is not assignable to type '1'.

580     check<O.MergeUp<O_MERGEUP, O1_MERGEUP, 'deep'>,     MERGEUP_O_O1_DEEP,  Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:630:5 - error TS2322: Type '0' is not assignable to type '1'.

630     check<O.NonNullableKeys<O>, 'a' | 'b' | 'c' | 'f' | 'g' | 'k',    Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:672:5 - error TS2322: Type '0' is not assignable to type '1'.

672     check<O.NullableKeys<O>,    'd' | 'e' | 'h' | 'j',    Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:732:5 - error TS2322: Type '0' is not assignable to type '1'.

732     check<O.OptionalKeys<O>,    'd' | 'e' | 'h',    Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:815:5 - error TS2322: Type '0' is not assignable to type '1'.

815     check<O.PathValid<any,          ['a', 'a']>,        ['a', 'a'],         Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:815:11 - error TS2589: Type instantiation is excessively deep and possibly infinite.

815     check<O.PathValid<any,          ['a', 'a']>,        ['a', 'a'],         Test.Pass>(),
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:819:5 - error TS2322: Type '0' is not assignable to type '1'.

819     check<O.PathValid<O_PATHVALID,  ['b', 'b', 0]>,     ['b', 'b', 0],      Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:819:11 - error TS2589: Type instantiation is excessively deep and possibly infinite.

819     check<O.PathValid<O_PATHVALID,  ['b', 'b', 0]>,     ['b', 'b', 0],      Test.Pass>(),
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Object.ts:971:5 - error TS2322: Type '0' is not assignable to type '1'.

971     check<O.RequiredKeys<O>,    'a' | 'b' | 'c' | 'f' | 'g' | 'j' | 'k',    Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

tst/Union.ts:152:5 - error TS2322: Type '0' is not assignable to type '1'.

152     check<U.Strict<{a: 0} | {b: 0}>,    {a: 0, b?: undefined} | {a?: undefined, b: 0},  Test.Pass>(),
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Yup, our project isn't ready to go with strict checking, though it's a work in progress.

For now, my workaround is to resurrect the old version of OptionalKeys:

type OptionalKeys<T extends object> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T]

Ok, you can update to the latest version and:

"strictNullChecks": true

And if you still cannot enable this, good news:
Only NonNullableKeys, CompulsoryKeys & NullableKeys are affected

Cheers

Was this page helpful?
0 / 5 - 0 ratings