Why doesn't Object.keys return a keyof type in TypeScript?2019 Community Moderator ElectionAvoid implicit...

Graphing random points on the XY-plane

Sometimes a banana is just a banana

Levi-Civita symbol: 3D matrix

Book about a time-travel war fought by computers

Are there any other Chaos-worshipping races?

What is a term for a function that when called repeatedly, has the same effect as calling once?

Why are special aircraft used for the carriers in the united states navy?

Dystopian novel where telepathic humans live under a dome

Inverse of the covariance matrix of a multivariate normal distribution

What type of investment is best suited for a 1-year investment on a down payment?

Are small insurances worth it

Citing contemporaneous (interlaced?) preprints

It took me a lot of time to make this, pls like. (YouTube Comments #1)

What are all the squawk codes?

Reason why dimensional travelling would be restricted

Is divide-by-zero a security vulnerability?

How do ISS astronauts "get their stripes"?

What type of postprocessing gives the effect of people standing out

Heating basement floor with water heater

VAT refund for a conference ticket in Sweden

Why doesn't Object.keys return a keyof type in TypeScript?

Can throughput exceed the bandwidth of a network

Where is the fallacy here?

How can I create a Table like this in Latex?



Why doesn't Object.keys return a keyof type in TypeScript?



2019 Community Moderator ElectionAvoid implicit 'any' type when using Object.keys in TypescriptWhat is TypeScript and why would I use it in place of JavaScript?Type definition in object literal in TypeScriptAre strongly-typed functions as parameters possible in TypeScript?is return-type signature in function overloading useless?Referencing TypeScript Type DefinitionsTypescript: Interfaces vs TypesWhy did TypeScript 2.0 change IteratorResult<K>?Typescript - type to represent any class?How to overwrite incorrect TypeScript type definition installed via @types/packagetypescript compiler bug? knockout.validation.d.ts doesn't compile anymore












8















Title says it all - why doesn't Object.keys(x) in TypeScript return the type Array<keyof typeof x>? That's what Object.keys does, so it seems like an obvious oversight on the part of the TypeScript definition file authors to not make the return type simply be keyof T.



Should I log a bug on their GitHub repo, or just go ahead and send a PR to fix it for them?










share|improve this question



























    8















    Title says it all - why doesn't Object.keys(x) in TypeScript return the type Array<keyof typeof x>? That's what Object.keys does, so it seems like an obvious oversight on the part of the TypeScript definition file authors to not make the return type simply be keyof T.



    Should I log a bug on their GitHub repo, or just go ahead and send a PR to fix it for them?










    share|improve this question

























      8












      8








      8








      Title says it all - why doesn't Object.keys(x) in TypeScript return the type Array<keyof typeof x>? That's what Object.keys does, so it seems like an obvious oversight on the part of the TypeScript definition file authors to not make the return type simply be keyof T.



      Should I log a bug on their GitHub repo, or just go ahead and send a PR to fix it for them?










      share|improve this question














      Title says it all - why doesn't Object.keys(x) in TypeScript return the type Array<keyof typeof x>? That's what Object.keys does, so it seems like an obvious oversight on the part of the TypeScript definition file authors to not make the return type simply be keyof T.



      Should I log a bug on their GitHub repo, or just go ahead and send a PR to fix it for them?







      typescript






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked 1 hour ago









      Ryan CavanaughRyan Cavanaugh

      100k27171180




      100k27171180
























          2 Answers
          2






          active

          oldest

          votes


















          9














          The current return type (string[]) is intentional. Why?



          Consider some type like this:



          interface Point {
          x: number;
          y: number;
          }


          You write some code like this:



          function fn(k: keyof Point) {
          if (k === "x") {
          console.log("X axis");
          } else if (k === "y") {
          console.log("Y axis");
          } else {
          throw new Error("This is impossible");
          }
          }


          Let's ask a question:




          In a well-typed program, can a legal call to fn hit the error case?




          The desired answer is, of course, "No". But what does this have to do with Object.keys?



          Now consider this other code:



          interface NamedPoint extends Point {
          name: string;
          }

          const origin: NamedPoint = { name: "origin", x: 0, y: 0 };


          Note that according to TypeScript's type system, all NamedPoints are valid Points.



          Now let's write a little more code:



          function doSomething(pt: Point) {
          for (const k of Object.keys(pt)) {
          // A valid call iff Object.keys(pt) returns (keyof Point)[]
          fn(k);
          }
          }
          // Throws an exception
          doSomething(origin);


          Our well-typed program just threw an exception!



          Something went wrong here!
          By returning keyof T from Object.keys, we've violated the assumption that keyof T forms an exhaustive list, because having a reference to an object doesn't mean that the type of the reference isn't a supertype of the type of the value.



          Basically, (at least) one of the following four things can't be true:





          1. keyof T is an exhaustive list of the keys of T

          2. A type with additional properties is always a subtype of its base type

          3. It is legal to alias a subtype value by a supertype reference


          4. Object.keys returns keyof T


          Throwing away point 1 makes keyof nearly useless, because it implies that keyof Point might be some value that isn't "x" or "y".



          Throwing away point 2 completely destroys TypeScript's type system. Not an option.



          Throwing away point 3 also completely destroys TypeScript's type system.



          Throwing away point 4 is fine and makes you, the programmer, think about whether or not the object you're dealing with is possibly an alias for a subtype of the thing you think you have.



          The "missing feature" to make this legal but not contradictory is Exact Types, which would allow you to declare a new kind of type that wasn't subject to point #2. If this feature existed, it would presumably be possible to make Object.keys return keyof T only for Ts which were declared as exact.






          share|improve this answer


























          • However, there is common case when point 3. is excluded, when for example T is inferred and is guaranteed to be precise: const f: <T>(t: T) => void = (t) => { Object.keys(t).forEach(k => t[k]) }. I have lots of places like that in my code, where I really want Object.keys() to return (keyof T)[].

            – artem
            1 hour ago





















          0














          I opened and closed a PR today related to this topic.
          My PR was only focusing on the case where keys are coming from an enum of strings.



          In this precise case, it does not seem that inheritance is feasible.



          I need to double check before reopening it https://github.com/Microsoft/TypeScript/pull/30228






          share|improve this answer























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55012174%2fwhy-doesnt-object-keys-return-a-keyof-type-in-typescript%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            9














            The current return type (string[]) is intentional. Why?



            Consider some type like this:



            interface Point {
            x: number;
            y: number;
            }


            You write some code like this:



            function fn(k: keyof Point) {
            if (k === "x") {
            console.log("X axis");
            } else if (k === "y") {
            console.log("Y axis");
            } else {
            throw new Error("This is impossible");
            }
            }


            Let's ask a question:




            In a well-typed program, can a legal call to fn hit the error case?




            The desired answer is, of course, "No". But what does this have to do with Object.keys?



            Now consider this other code:



            interface NamedPoint extends Point {
            name: string;
            }

            const origin: NamedPoint = { name: "origin", x: 0, y: 0 };


            Note that according to TypeScript's type system, all NamedPoints are valid Points.



            Now let's write a little more code:



            function doSomething(pt: Point) {
            for (const k of Object.keys(pt)) {
            // A valid call iff Object.keys(pt) returns (keyof Point)[]
            fn(k);
            }
            }
            // Throws an exception
            doSomething(origin);


            Our well-typed program just threw an exception!



            Something went wrong here!
            By returning keyof T from Object.keys, we've violated the assumption that keyof T forms an exhaustive list, because having a reference to an object doesn't mean that the type of the reference isn't a supertype of the type of the value.



            Basically, (at least) one of the following four things can't be true:





            1. keyof T is an exhaustive list of the keys of T

            2. A type with additional properties is always a subtype of its base type

            3. It is legal to alias a subtype value by a supertype reference


            4. Object.keys returns keyof T


            Throwing away point 1 makes keyof nearly useless, because it implies that keyof Point might be some value that isn't "x" or "y".



            Throwing away point 2 completely destroys TypeScript's type system. Not an option.



            Throwing away point 3 also completely destroys TypeScript's type system.



            Throwing away point 4 is fine and makes you, the programmer, think about whether or not the object you're dealing with is possibly an alias for a subtype of the thing you think you have.



            The "missing feature" to make this legal but not contradictory is Exact Types, which would allow you to declare a new kind of type that wasn't subject to point #2. If this feature existed, it would presumably be possible to make Object.keys return keyof T only for Ts which were declared as exact.






            share|improve this answer


























            • However, there is common case when point 3. is excluded, when for example T is inferred and is guaranteed to be precise: const f: <T>(t: T) => void = (t) => { Object.keys(t).forEach(k => t[k]) }. I have lots of places like that in my code, where I really want Object.keys() to return (keyof T)[].

              – artem
              1 hour ago


















            9














            The current return type (string[]) is intentional. Why?



            Consider some type like this:



            interface Point {
            x: number;
            y: number;
            }


            You write some code like this:



            function fn(k: keyof Point) {
            if (k === "x") {
            console.log("X axis");
            } else if (k === "y") {
            console.log("Y axis");
            } else {
            throw new Error("This is impossible");
            }
            }


            Let's ask a question:




            In a well-typed program, can a legal call to fn hit the error case?




            The desired answer is, of course, "No". But what does this have to do with Object.keys?



            Now consider this other code:



            interface NamedPoint extends Point {
            name: string;
            }

            const origin: NamedPoint = { name: "origin", x: 0, y: 0 };


            Note that according to TypeScript's type system, all NamedPoints are valid Points.



            Now let's write a little more code:



            function doSomething(pt: Point) {
            for (const k of Object.keys(pt)) {
            // A valid call iff Object.keys(pt) returns (keyof Point)[]
            fn(k);
            }
            }
            // Throws an exception
            doSomething(origin);


            Our well-typed program just threw an exception!



            Something went wrong here!
            By returning keyof T from Object.keys, we've violated the assumption that keyof T forms an exhaustive list, because having a reference to an object doesn't mean that the type of the reference isn't a supertype of the type of the value.



            Basically, (at least) one of the following four things can't be true:





            1. keyof T is an exhaustive list of the keys of T

            2. A type with additional properties is always a subtype of its base type

            3. It is legal to alias a subtype value by a supertype reference


            4. Object.keys returns keyof T


            Throwing away point 1 makes keyof nearly useless, because it implies that keyof Point might be some value that isn't "x" or "y".



            Throwing away point 2 completely destroys TypeScript's type system. Not an option.



            Throwing away point 3 also completely destroys TypeScript's type system.



            Throwing away point 4 is fine and makes you, the programmer, think about whether or not the object you're dealing with is possibly an alias for a subtype of the thing you think you have.



            The "missing feature" to make this legal but not contradictory is Exact Types, which would allow you to declare a new kind of type that wasn't subject to point #2. If this feature existed, it would presumably be possible to make Object.keys return keyof T only for Ts which were declared as exact.






            share|improve this answer


























            • However, there is common case when point 3. is excluded, when for example T is inferred and is guaranteed to be precise: const f: <T>(t: T) => void = (t) => { Object.keys(t).forEach(k => t[k]) }. I have lots of places like that in my code, where I really want Object.keys() to return (keyof T)[].

              – artem
              1 hour ago
















            9












            9








            9







            The current return type (string[]) is intentional. Why?



            Consider some type like this:



            interface Point {
            x: number;
            y: number;
            }


            You write some code like this:



            function fn(k: keyof Point) {
            if (k === "x") {
            console.log("X axis");
            } else if (k === "y") {
            console.log("Y axis");
            } else {
            throw new Error("This is impossible");
            }
            }


            Let's ask a question:




            In a well-typed program, can a legal call to fn hit the error case?




            The desired answer is, of course, "No". But what does this have to do with Object.keys?



            Now consider this other code:



            interface NamedPoint extends Point {
            name: string;
            }

            const origin: NamedPoint = { name: "origin", x: 0, y: 0 };


            Note that according to TypeScript's type system, all NamedPoints are valid Points.



            Now let's write a little more code:



            function doSomething(pt: Point) {
            for (const k of Object.keys(pt)) {
            // A valid call iff Object.keys(pt) returns (keyof Point)[]
            fn(k);
            }
            }
            // Throws an exception
            doSomething(origin);


            Our well-typed program just threw an exception!



            Something went wrong here!
            By returning keyof T from Object.keys, we've violated the assumption that keyof T forms an exhaustive list, because having a reference to an object doesn't mean that the type of the reference isn't a supertype of the type of the value.



            Basically, (at least) one of the following four things can't be true:





            1. keyof T is an exhaustive list of the keys of T

            2. A type with additional properties is always a subtype of its base type

            3. It is legal to alias a subtype value by a supertype reference


            4. Object.keys returns keyof T


            Throwing away point 1 makes keyof nearly useless, because it implies that keyof Point might be some value that isn't "x" or "y".



            Throwing away point 2 completely destroys TypeScript's type system. Not an option.



            Throwing away point 3 also completely destroys TypeScript's type system.



            Throwing away point 4 is fine and makes you, the programmer, think about whether or not the object you're dealing with is possibly an alias for a subtype of the thing you think you have.



            The "missing feature" to make this legal but not contradictory is Exact Types, which would allow you to declare a new kind of type that wasn't subject to point #2. If this feature existed, it would presumably be possible to make Object.keys return keyof T only for Ts which were declared as exact.






            share|improve this answer















            The current return type (string[]) is intentional. Why?



            Consider some type like this:



            interface Point {
            x: number;
            y: number;
            }


            You write some code like this:



            function fn(k: keyof Point) {
            if (k === "x") {
            console.log("X axis");
            } else if (k === "y") {
            console.log("Y axis");
            } else {
            throw new Error("This is impossible");
            }
            }


            Let's ask a question:




            In a well-typed program, can a legal call to fn hit the error case?




            The desired answer is, of course, "No". But what does this have to do with Object.keys?



            Now consider this other code:



            interface NamedPoint extends Point {
            name: string;
            }

            const origin: NamedPoint = { name: "origin", x: 0, y: 0 };


            Note that according to TypeScript's type system, all NamedPoints are valid Points.



            Now let's write a little more code:



            function doSomething(pt: Point) {
            for (const k of Object.keys(pt)) {
            // A valid call iff Object.keys(pt) returns (keyof Point)[]
            fn(k);
            }
            }
            // Throws an exception
            doSomething(origin);


            Our well-typed program just threw an exception!



            Something went wrong here!
            By returning keyof T from Object.keys, we've violated the assumption that keyof T forms an exhaustive list, because having a reference to an object doesn't mean that the type of the reference isn't a supertype of the type of the value.



            Basically, (at least) one of the following four things can't be true:





            1. keyof T is an exhaustive list of the keys of T

            2. A type with additional properties is always a subtype of its base type

            3. It is legal to alias a subtype value by a supertype reference


            4. Object.keys returns keyof T


            Throwing away point 1 makes keyof nearly useless, because it implies that keyof Point might be some value that isn't "x" or "y".



            Throwing away point 2 completely destroys TypeScript's type system. Not an option.



            Throwing away point 3 also completely destroys TypeScript's type system.



            Throwing away point 4 is fine and makes you, the programmer, think about whether or not the object you're dealing with is possibly an alias for a subtype of the thing you think you have.



            The "missing feature" to make this legal but not contradictory is Exact Types, which would allow you to declare a new kind of type that wasn't subject to point #2. If this feature existed, it would presumably be possible to make Object.keys return keyof T only for Ts which were declared as exact.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited 20 mins ago









            Tholle

            38.1k54264




            38.1k54264










            answered 1 hour ago









            Ryan CavanaughRyan Cavanaugh

            100k27171180




            100k27171180













            • However, there is common case when point 3. is excluded, when for example T is inferred and is guaranteed to be precise: const f: <T>(t: T) => void = (t) => { Object.keys(t).forEach(k => t[k]) }. I have lots of places like that in my code, where I really want Object.keys() to return (keyof T)[].

              – artem
              1 hour ago





















            • However, there is common case when point 3. is excluded, when for example T is inferred and is guaranteed to be precise: const f: <T>(t: T) => void = (t) => { Object.keys(t).forEach(k => t[k]) }. I have lots of places like that in my code, where I really want Object.keys() to return (keyof T)[].

              – artem
              1 hour ago



















            However, there is common case when point 3. is excluded, when for example T is inferred and is guaranteed to be precise: const f: <T>(t: T) => void = (t) => { Object.keys(t).forEach(k => t[k]) }. I have lots of places like that in my code, where I really want Object.keys() to return (keyof T)[].

            – artem
            1 hour ago







            However, there is common case when point 3. is excluded, when for example T is inferred and is guaranteed to be precise: const f: <T>(t: T) => void = (t) => { Object.keys(t).forEach(k => t[k]) }. I have lots of places like that in my code, where I really want Object.keys() to return (keyof T)[].

            – artem
            1 hour ago















            0














            I opened and closed a PR today related to this topic.
            My PR was only focusing on the case where keys are coming from an enum of strings.



            In this precise case, it does not seem that inheritance is feasible.



            I need to double check before reopening it https://github.com/Microsoft/TypeScript/pull/30228






            share|improve this answer




























              0














              I opened and closed a PR today related to this topic.
              My PR was only focusing on the case where keys are coming from an enum of strings.



              In this precise case, it does not seem that inheritance is feasible.



              I need to double check before reopening it https://github.com/Microsoft/TypeScript/pull/30228






              share|improve this answer


























                0












                0








                0







                I opened and closed a PR today related to this topic.
                My PR was only focusing on the case where keys are coming from an enum of strings.



                In this precise case, it does not seem that inheritance is feasible.



                I need to double check before reopening it https://github.com/Microsoft/TypeScript/pull/30228






                share|improve this answer













                I opened and closed a PR today related to this topic.
                My PR was only focusing on the case where keys are coming from an enum of strings.



                In this precise case, it does not seem that inheritance is feasible.



                I need to double check before reopening it https://github.com/Microsoft/TypeScript/pull/30228







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered 23 mins ago









                DubZzzDubZzz

                306




                306






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55012174%2fwhy-doesnt-object-keys-return-a-keyof-type-in-typescript%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Webac Holding Inhaltsverzeichnis Geschichte | Organisationsstruktur | Tochterfirmen |...

                    What's the meaning of a knight fighting a snail in medieval book illustrations?What is the meaning of a glove...

                    Salamanca Inhaltsverzeichnis Lage und Klima | Bevölkerungsentwicklung | Geschichte | Kultur und...