Repository navigation
For object type C, type parameter T and mapped type { [K in keyof T & C]: any }, keyof C isn't assignable to K #18538
Description
Activity
DanielRosenwasser commented
on Sep 18, 2017 MemberMore actionsOPropscould be any type, in which case, it could have properties that you're simply not satisfying here.I take it back; we do certain checks over mapped types and really you're not depending on the original type of
OProps; it sounds like you're just trying to convey to the type system that a consumer will potentially get some other properties.Any time that you use a type parameter that's only used in an output position, that's usually something that we see as pretty fishy. Could you on elaborate what you're trying to write?
Daniel Rosenwasser (@DanielRosenwasser) Yes, of course.
The example was just a simplification of the problem.
My case is that I'm doing a react component decorator (HOC); that is a function with type:
type withConfirmation<OProps> = (c: Component<OProps>) => Component<OProps & ConfirmationModalProps>
So, it returns a new component that receives the original props (OProps) and new ones.
The problem is when I implement the decorator, there I define a new React Component (the wrapper) that expects both properties; and try to define
propTypesfor it:interface ConfirmationComponentProps { title: string; } class ConfirmationComponent extends React.Component<ConfirmationComponentProps & OProps > { static propTypes: { title: PropTypes.string.; } .... }
now, typings for react, define that
propTypeshas type:type ValidationMap<T> = {[K in keyof T]?: Validator<T> };
where
Tare the props, in my caseConfirmationComponentProps & OProps.As you can see, is not that I'm trying to do something fishy myself. It's just a combination of what i believe two valid usecases. On one side, having a decorator that expands the properties you may pass to the component, on the other side trying to define the
propTypesof such a component.propTypesyou may argue is not necessary when you type your components with typescript, and probably will remove then later. But I'm performing a migration from js to typescript, so I wanted to leave them for now.Hope it helps clarifying the need!
I think I just ran into this too.
function f<K extends string>( key: K, recordPlain: Record<K, any>, recordIntersection: Record<K, any> & {foo: string} ) { // keyof recordPlain is K recordPlain[key]; // okay // keyof recordIntersection is just "foo"? Why not K | "foo"? recordIntersection[key]; // error: Type 'K' cannot be used // to index type 'Record<K, any> & { foo: string; }'. }
This makes type guards for
inthat narrow the object fail to behave as desired. Is this the same issue? Should I file a new one?Is this also the same issue with trying to use the new inferred
intype guards with generics?function foo<T>(x: T) { if ("a" in x) { x.a // Property 'a' does not exist on type 'T'. } }
Or is this something else?
Mariano Cortesi (@mcortesi) An even more simplified view of the problem is assignment of the string literal
"title"toK extends keyof (OProps & ConfirmationComponentProps):function propTypes<OProps extends {}, K extends keyof (OProps & ConfirmationComponentProps)>( k: K, t: 'title'): K { k = t return k };
Unfortunately, since
K extends keyof (OProps & ConfirmationComponentProps), it may be a smaller union -- with fewer properties. Here are a couple of calls that show off whyk = tis incorrect:var n: never = propTypes<{}, never>('title', null as never) // 'n' now has the value 'title'! var a: 'a' = propTypes<{ a: number }, 'a'>('title', 'a') // 'a' now has the value 'title'!
I'll see if I can think of a workaround.
But in the original issue, it's
[K in keyof X]: ..., which should span all keys, notK extends keyof X, where it can be fewer.Good point. But
K in ...is modelled asK extends ...most places in the compiler -- it's represented internally as a TypeParameter. So it would need special-case code to behave differently, which is probably not there.Hi! Nathan Shively-Sanders (@sandersn) thanks for the reply.
I agree with Joe Calzaretta (@jcalz) since the example use
inin every other example I've used it expands to all the keys. liketype Readonly<T> = { readonly [P in keyof T]: T[P]; }
implies all keys of
Tmust be present.It's probably a special case, as you mention.
Nevertheless, it worths nothing that if I don't use the intersection types (
&), it works:function propTypes2() : {[K in keyof (ConfirmationComponentProps)]?: any } { return { title: null, }; };
An idea here, is that it might be related to the fact that, since OProps is unkwown by
propTypes,keyof (OProps & ConfirmationComponentProps)means I know only the keys of ConfirmationComponentProps, but not of OProps. So it's impossible for the function to create a object with those keys. In my example it should be possible since I'm using{[K in keyof (OProps & ConfirmationComponentProps)]?: any }with the?, so It's a partial view of that.But it's kinda messy.
Don't know if that helps!
Joe Calzaretta (@jcalz) regarding your
intype guard question: narrowing only removes types from a union, andif ("a" in x)is only treated as narrowing, not as an assertion. Contrast its behaviour toinstanceof, which does act as an assertion for classes with an inheritance relationship.It's a separate issue. The first question that I think needs to be resolved is what to do when you have concrete types:
interface A { a } interface B extends A { b } function f(a: A) { if ("b" in a) { a // what is the type of a? A? A & { b } ? never? } } declare let a: A declare let b: B f(a) f(b)
Reacted by Bradley AyersSo should I file it separately or not bother?
Joe Calzaretta (@jcalz) Yes, it's a good feature request. Might be impossible, but we should at least have an issue for people to explore the design space.
Reacted by Joe CalzarettaI have a scenario that I think is caused by this issue, I've simplified it to the following:
interface A { prop: string; } declare let partialA: Partial<A>; declare let a: A; if (partialA.prop !== undefined) { /* TS2322: Type 'Partial<A>' is not assignable to type 'A'. Types of property 'prop' are incompatible. Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string' */ a = partialA; a; }
- changed the title
[-]Error when mixing keyof and intersection type and type variable[/-][+]For object type C, type parameter T and mapped type { [K in keyof T & C]: any }, keyof C isn't assignable to K[/+]on Mar 30, 2018 Was this magically fixed somewhere? I can't reproduce the issue anymore. Playground link
We've made a lot of changes to type parameters, and I believe intersections as well, in 4.3. This does indeed look fixed.
Joe Calzaretta (@jcalz) What were you doing reproducing 4-year-old bugs???
Reacted by Ryan Cavanaugh- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
TypeScript Version: 2.5.2
Code
Expected behavior:
Compile with no errors.
Actual behavior:
Fails with:
{ title: null }is not assignable to {[K in keyof (OProps & ConfirmationComponentProps)]?: any }`The problem disappears if I remove the intersection type
&and just useConfirmationComponentProps; or if I replace OProps with a concrete type like: