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
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
add a comment |
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
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
add a comment |
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
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
typescript
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
add a comment |
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
add a comment |
1 Answer
1
active
oldest
votes
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
fnhit 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:
keyof Tis an exhaustive list of the keys ofT
- A type with additional properties is always a subtype of its base type
- It is legal to alias a subtype value by a supertype reference
Object.keysreturnskeyof 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.
However, there is common case when point 3. is excluded, when for exampleTis 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 wantObject.keys()to return (keyof T)[].
– artem
1 hour ago
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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
fnhit 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:
keyof Tis an exhaustive list of the keys ofT
- A type with additional properties is always a subtype of its base type
- It is legal to alias a subtype value by a supertype reference
Object.keysreturnskeyof 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.
However, there is common case when point 3. is excluded, when for exampleTis 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 wantObject.keys()to return (keyof T)[].
– artem
1 hour ago
add a comment |
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
fnhit 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:
keyof Tis an exhaustive list of the keys ofT
- A type with additional properties is always a subtype of its base type
- It is legal to alias a subtype value by a supertype reference
Object.keysreturnskeyof 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.
However, there is common case when point 3. is excluded, when for exampleTis 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 wantObject.keys()to return (keyof T)[].
– artem
1 hour ago
add a comment |
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
fnhit 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:
keyof Tis an exhaustive list of the keys ofT
- A type with additional properties is always a subtype of its base type
- It is legal to alias a subtype value by a supertype reference
Object.keysreturnskeyof 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.
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
fnhit 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:
keyof Tis an exhaustive list of the keys ofT
- A type with additional properties is always a subtype of its base type
- It is legal to alias a subtype value by a supertype reference
Object.keysreturnskeyof 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.
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 exampleTis 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 wantObject.keys()to return (keyof T)[].
– artem
1 hour ago
add a comment |
However, there is common case when point 3. is excluded, when for exampleTis 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 wantObject.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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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