Skip to content

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

TypeScript Version: 2.5.2

Code

export interface ConfirmationComponentProps {
  title?: string,
}
function propTypes<OProps extends {}>() : {[K in keyof (OProps & ConfirmationComponentProps)]?: any } {
  return {
    title: null,
  };
};

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 use ConfirmationComponentProps; or if I replace OProps with a concrete type like:

function propTypes<OProps extends {}>() : {[K in keyof {x: number} & ConfirmationComponentProps]?: any } {
  return {
    title: null,
  };
};

Activity

  1. DanielRosenwasser commented on Sep 18, 2017

    @DanielRosenwasser
    Member

    OProps could 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?

  2. mcortesi commented on Sep 18, 2017

    @mcortesi
    Author

    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 propTypes for it:

    interface ConfirmationComponentProps {
       title: string;
    }
    
    class ConfirmationComponent extends React.Component<ConfirmationComponentProps & OProps > {
       static propTypes: {
          title: PropTypes.string.;
       }
      ....
    }

    now, typings for react, define that propTypes has type:

    type ValidationMap<T> = {[K in keyof T]?: Validator<T> };

    where T are the props, in my case ConfirmationComponentProps & 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 propTypes of such a component.

    propTypes you 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!

  3. jcalz commented on Jan 25, 2018

    @jcalz
    Contributor

    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 in that narrow the object fail to behave as desired. Is this the same issue? Should I file a new one?

  4. jcalz commented on Feb 1, 2018

    @jcalz
    Contributor

    Is this also the same issue with trying to use the new inferred in type 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?

  5. sandersn commented on Feb 1, 2018

    @sandersn
    Member

    Mariano Cortesi (@mcortesi) An even more simplified view of the problem is assignment of the string literal "title" to K 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 why k = t is 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.

  6. jcalz commented on Feb 1, 2018

    @jcalz
    Contributor

    But in the original issue, it's [K in keyof X]: ..., which should span all keys, not K extends keyof X, where it can be fewer.

  7. sandersn commented on Feb 1, 2018

    @sandersn
    Member

    Good point. But K in ... is modelled as K 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.

  8. mcortesi commented on Feb 6, 2018

    @mcortesi
    Author

    Hi! Nathan Shively-Sanders (@sandersn) thanks for the reply.

    I agree with Joe Calzaretta (@jcalz) since the example use in in every other example I've used it expands to all the keys. like

    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    }

    implies all keys of T must 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!

  9. sandersn commented on Feb 7, 2018

    @sandersn
    Member

    Joe Calzaretta (@jcalz) regarding your in type guard question: narrowing only removes types from a union, and if ("a" in x) is only treated as narrowing, not as an assertion. Contrast its behaviour to instanceof, 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)
  10. jcalz commented on Feb 7, 2018

    @jcalz
    Contributor

    So should I file it separately or not bother?

  11. sandersn commented on Feb 7, 2018

    @sandersn
    Member

    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.

  12. bradleyayers commented on Mar 30, 2018

    @bradleyayers

    I 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;
    }
  13. 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
  14. jcalz commented on May 6, 2021

    @jcalz
    Contributor

    Was this magically fixed somewhere? I can't reproduce the issue anymore. Playground link

  15. sandersn commented on May 7, 2021

    @sandersn
    Member

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

  16. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions