Repository navigation
Omit helper loses type information when used with extended Records. #36981
Copy link
Copy link
Open
Labels
Needs InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
Milestone
Description
TypeScript Version: 3.8 & Nightly
Search Terms:
- Omit
- Record
- Any
- Loses type information
Expected behavior:
I expect the following types to be as described in the inline comments.
type AnyRecord = Record<string, any>;
interface ExtendsAny extends AnyRecord {
myKey1: string;
myKey2: string;
}
// ExtendsAny['myKey1'] === string
// ExtendsAny['otherKey'] === any
type OmitsKey = Omit<ExtendsAny, "myKey2">;
// OmitsKey['myKey'] === string
// OmitsKey['otherKey'] === anyPlease note that this works without the omit, or without the record, but not when both are used together.
Actual behavior:
What actually happens is that after using omit, all keys become "any" and any information for code completion, etc that the specifically defined keys even exist, are removed, even thought they were working in the original extension.
type AnyRecord = Record<string, any>;
interface ExtendsAny extends AnyRecord {
myKey1: string;
myKey2: string;
}
// ExtendsAny['myKey1'] === string
// ExtendsAny['otherKey'] === any
type OmitsKey = Omit<ExtendsAny, "myKey2">;
// 🚨Problem here 🚨
// OmitsKey['myKey1'] === any
// OmitsKey['otherKey'] === any
const instance: OmitsKey = {
myKey1: "test",
}
// 🚨 When typing this no autocomplete is provided for myKey1 🚨
instance.myKey1.Related Issues:
Potential Workaround
In the meantime, I am redefining the base interface (very large) because it comes from a library without the record type mixed in. I do my omit, then add it back in afterwards.
Code
/**
* @copyright 2020 Adam (charrondev) Charron
* @license MIT
*/
export interface IBase<T = number> {
baseProp: T;
baseProp2: T;
baseFunction: () => T;
}
const base: IBase<number> = {
baseProp: 0,
baseProp2: 0,
baseFunction: () => 0,
};
// number
base.baseProp;
///
/// Without the omit
///
type AnyRecord = Record<string, any>;
interface IExtend<T> extends IBase<T>, AnyRecord {
extendProp: T;
}
const extended: IExtend<number> = {
baseProp: 0,
baseProp2: 0,
baseFunction: () => 0,
extendProp: 0,
};
// number
extended.baseProp2;
///
/// With an Omit
///
interface IOmittedInterface extends Omit<IExtend<number>, "baseProp"> {}
const omitted: IOmittedInterface = {
baseProp2: 0,
baseFunction: () => 0,
};
// any
omitted.baseProp2;Output
/**
* @copyright 2020 Adam (charrondev) Charron
* @license MIT
*/
const base = {
baseProp: 0,
baseProp2: 0,
baseFunction: () => 0,
};
// number
base.baseProp;
const extended = {
baseProp: 0,
baseProp2: 0,
baseFunction: () => 0,
extendProp: 0,
};
// number
extended.baseProp2;
const omitted = {
baseProp2: 0,
baseFunction: () => 0,
};
// any
omitted.baseProp2;Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"useDefineForClassFields": false,
"alwaysStrict": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"downlevelIteration": false,
"noEmitHelpers": false,
"noLib": false,
"noStrictGenericChecks": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"esModuleInterop": true,
"preserveConstEnums": false,
"removeComments": false,
"skipLibCheck": false,
"checkJs": false,
"allowJs": false,
"declaration": true,
"experimentalDecorators": false,
"emitDecoratorMetadata": false,
"target": "ES2017",
"module": "ESNext"
}
}Playground Link: Provided
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Needs InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.RescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
Activity
RyanCavanaugh commentedon Mar 10, 2020
Maybe I'm just being dense here, but you declared two properties
myKey1andmyKey2, then referenced the non-existent keymyKeyand expected different behavior from the other non-existent keyotherKey? I see expected behavior when I refer tomyKey1in both cases.charrondev commentedon Mar 10, 2020
Ryan Cavanaugh (@RyanCavanaugh) Sorry it looks my first example has some typos. The second one (that was linked from the code sandbox was correct.)
I've updated my example above and will out it here as well.
The problem is that
myKey1which was present as a fully-defined key in the original interface, loses it's type information when Omit is used on it.eugene-korobko commentedon Apr 22, 2020
I have similar problem
Line
this.g(v.value);generates an errorI believe the code is corrent
zlanich commentedon Oct 17, 2020
I have this exact problem, and it's kept from compiler from passing for 3 days now. I just realized it's because my top interface extends Record<string, any> (TSC: 3.7.2):
13 remaining items
Dan503 commentedon Aug 17, 2022
This is such a majorly painful issue.
I have this problem
(note: in this simple example the problem doesn't occur if it is all laid out in the one file. When importing the types, that is when the types can get lost, possibly only from an npm registry)
Dan503 commentedon Oct 24, 2022
Ok I finally figured out the real problem behind why Omit was causing me so many headaches.
I have a type called
UnknownPropsthat looks like this:And I was extending my interfaces using it as a way of allowing extra props that were not strictly defined
Then when I tried to combine
ComponentPropswith Omit I got no types as allGetting rid of
extends UnknownPropsfixed the issue for me (though I would prefer ifOmitdidn't hard fail and instead used the known keys that it had access to)This is also a problem for
keyofand probably a few other things. Might need to go under a separate issue...douglasg14b commentedon Jan 12, 2023
Dan Tonon (@Dan503) Did you figure out a solution that lets you have a type with a couple defined props, and allows any others? This is also being problematic for me as well.
Dan503 commentedon Jan 12, 2023
Douglas Gaskell (@douglasg14b) I ended up saving the known props into a static interface and then creating a seperate type for using on the component props.
Then if I want to do something with the component props I target the staticly known
StaticComponentPropsinterface instead of the dynamicComponentPropstypeEptagone commentedon Oct 23, 2026
This is still happening. Any updates?