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

Filling in Area Under Curve Causes Alignment Issues

In Adventurer's League, is it possible to keep the Ring of Winter if you manage to acquire it in the Tomb of Annihilation adventure?

Is it possible to convert a suspension fork to rigid by drilling it?

Didactic impediments of using simplified versions

How can I create a Table like this in Latex?

If a set is open, does that imply that it has no boundary points?

How can I handle a player who pre-plans arguments about my rulings on RAW?

How to mitigate "bandwagon attacking" from players?

Don't know what I’m looking for regarding removable HDDs?

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

Is there a full canon version of Tyrion's jackass/honeycomb joke?

Detect if page is on experience editor Sitecore 9 via Javascript?

What happened to QGIS 2.x

Is there any relevance to Thor getting his hair cut other than comedic value?

The need of reserving one's ability in job interviews

Second-rate spelling

At what level can a party fight a mimic?

How can atoms be electrically neutral when there is a difference in the positions of the charges?

Levi-Civita symbol: 3D matrix

Real life puzzle: Unknown alphabet or shorthand

Are there any other Chaos-worshipping races?

Is divide-by-zero a security vulnerability?

Citing contemporaneous (interlaced?) preprints

Erro: incompatible type for argument 1 of 'printf'



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























  • 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 github.com/Microsoft/TypeScript/pull/30228

    – DubZzz
    34 mins ago
















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























  • 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 github.com/Microsoft/TypeScript/pull/30228

    – DubZzz
    34 mins ago














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













  • 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 github.com/Microsoft/TypeScript/pull/30228

    – DubZzz
    34 mins ago



















  • 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 github.com/Microsoft/TypeScript/pull/30228

    – DubZzz
    34 mins ago

















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 github.com/Microsoft/TypeScript/pull/30228

– DubZzz
34 mins ago





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 github.com/Microsoft/TypeScript/pull/30228

– DubZzz
34 mins ago












1 Answer
1






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













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

























1 Answer
1






active

oldest

votes








1 Answer
1






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 30 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






















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

117736 Шеррод Примітки | Див. також | Посилання | Навігаційне...

As a Security Precaution, the user account has been locked The Next CEO of Stack OverflowMS...

Маріан Котлеба Зміст Життєпис | Політичні погляди |...