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