Why doesn't Object.keys return a keyof type in TypeScript?2019 Community Moderator ElectionAvoid implicit...
Didactic impediments of using simplified versions
Is there a math equivalent to the conditional ternary operator?
It took me a lot of time to make this, pls like. (YouTube Comments #1)
I encountered my boss during an on-site interview at another company. Should I bring it up when seeing him next time?
What type of postprocessing gives the effect of people standing out
Is there a full canon version of Tyrion's jackass/honeycomb joke?
How to kill a localhost:8080
What are all the squawk codes?
When was drinking water recognized as crucial in marathon running?
Where is the fallacy here?
VAT refund for a conference ticket in Sweden
Six real numbers so that product of any five is the sixth one
How to lift/raise/repair a segment of concrete slab?
Are small insurances worth it
How to evaluate the limit where something is raised to a power of x?
How do you say "powers of ten"?
Can I become debt free or should I file for bankruptcy? How do I manage my debt and finances?
A right or the right?
Pure Functions: Does "No Side Effects" Imply "Always Same Output, Given Same Input"?
Reason why dimensional travelling would be restricted
Does an unattuned Frost Brand weapon still glow in freezing temperatures?
Non-Italian European mafias in USA?
How can I be pwned if I'm not registered on the compromised site?
What is a term for a function that when called repeatedly, has the same effect as calling once?
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
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
add a comment |
add a comment |
2 Answers
2
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 |
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
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
2 Answers
2
active
oldest
votes
2 Answers
2
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 21 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 |
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
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 https://github.com/Microsoft/TypeScript/pull/30228
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 https://github.com/Microsoft/TypeScript/pull/30228
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
answered 25 mins ago
DubZzzDubZzz
306
306
add a comment |
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