Skip to content

Error "TS2320" when interface extends interface that has optional member and another interface that has same named required member #3598

Closed

Description

I have 3 interfaces:

interface A {
    member1: string;
    ...
}

interface B {
    member1?: string;
    ...
}

interface C extends A, B {
    ...
}

I'm getting TS2320: [Object Object] error,
because interface A's member1 is required property, and B's member1 is optional.

How to resolve it?

Activity

  1. MrKomish commented on Jun 22, 2015

    @MrKomish
    Author

    However if I do:

    interface C extends A, B {
        member1: string;
    }

    it works.

    but it doesn't work when I do:

    interface C extends A, B {
        member1?: string;
    }
  2. DanielRosenwasser commented on Jun 22, 2015

    @DanielRosenwasser
    Member

    Users might be better served if the message said something like "Consider declaring a property that is compatible with both."

  3. kitsonk commented on Jun 22, 2015

    @kitsonk
    Contributor

    And also what you are describing here is a union of two interfaces, which conflict with each other. Because it is option in B means that it can be extended to be not optional, but in A, it is clearly not properly extended.

    For example, how would you suggest this be resolved?

    interface A {
        member1: string;
    }
    
    interface B {
        member1: number;
    }
    
    interface C extends A, B {
    
    }
  4. benliddicott commented on Jul 16, 2015

    @benliddicott

    Kitson Kelly (@kitsonk) it is more like an intersection than a union... The intersection of the types would have member1 as non-optional. However since a non-optional member can still be undefined it seems like a non-issue to me, and should be allowed.

  5. mhegazy commented on Aug 10, 2015

    @mhegazy
    Contributor

    intersection types are now available in npm install typescript@next; consider using:

    type C = A & B;
    
  6. benliddicott commented on Aug 11, 2015

    @benliddicott

    Mohamed Hegazy (@mhegazy) Intersection types are great and would be a workaround for the MCV as given but unfortunately don't solve the underlying problem. If you have two interfaces you don't control and need to extend both of them you cannot. (The workaround would be to declare all the methods by hand and rely on structural typing to recognise the second interface, but you lose some of the benefit of the compiler checking you have implemented it correctly).

    Maybe this should be a new issue, but: It seems like the rules for a) implementing multiple interfaces and b) intersection types should be consistent.

    Example 1: Optional members

    interface A { m1: string;}
    interface B { m1?: string;}
    // allowed, m1 is required.
    type CT = A&B;
    var a : CT = {m1:undefined};// allowed, string can be undefined
    // error, m1 is required, 
    //TS2322: Type '{}' is not assignable to type 'A & B', Property 'm1' is missing in type '{}'.
    var b : CT = {};
    
    // not allowed, TS2320, Named property 'm1' of types 'A' and 'B' are not identical.
    interface C extends A, B {}
    

    Example 2: Protected members

    class P { protected m1: number; }
    class Q { m1: number; }
    // allowed, m1 is public
    type PQT = P&Q;
    var p : PQT = <PQT>{m1:undefined};
    p.m1; // allowed, m1 is public in PQT
    
    // not allowed
    // TS2420: Class 'PQ' incorrectly implements interface 'Q'
    // Property 'm1' is protected in type 'PQ' but public in type 'Q'.
    class PQ extends P implements Q {}
    // TS2420: Class 'QP' incorrectly implements interface 'P'.
    // Property 'm1' is protected but type 'Q' is not a class derived from 'P'.
    class QP extends Q implements P {}
    
    // Allowed to relax protection explicitly in a derived class
    class PQX extends P implements Q { m1: number;}
    
    // Not allowed - again, seems a bug.
    class QPX extends Q implements P { m1: number;}
    
    // Workaround is to use an intermediate class to relax the protection
    class PX extends P {m1: number;}
    class QPX2 extends Q implements PX { m1: number;}
    
    1. Implementing multiple interfaces should create a type which is the intersection of the types of the interfaces (but with any new members added).
    2. It should not be an error to make an optional member non-optional in a derived class. Non-optional members can be undefined, so nothing should break. This is consistent with intersection types. (Arguably also vice versa.)
    3. It should not be an error to relax the protected or private status of a member in a derived or implementing class, consistent with intersection types. (in 1.6 you can do it in a derived class but not an implementing class). Or at any rate it should be possible to do so if you need to. See also Allow interfaces to declare protected members and allow implementors to implement protected and private members. #3854
      (Surely from a strict point of view Q above is a subtype of P, because it can be used wherever P is used. All members visible externally on P are visible externally on Q, and all members visible internally on P are visible internally on Q. So I think that makes Q a subtype of P.)

    Implementing two or more interfaces should create the same type as the intersection of the types. That they don't seems like a bug.

    In pseudocode, this:

    interface C extends A, B {}
    

    Should be identical to this (if it was a thing - is this in 1.6? It's not in typescript@next as of 1.6.0-dev.20150811):

    type C1 = A & B;
    interface C extends C1 {}
    
  7. danquirk commented on Aug 11, 2015

    @danquirk
    Member

    benliddicott this seems like a reasonable idea, can you make a separate issue for it?

  8. benliddicott commented on Aug 11, 2015

    @benliddicott

    Dan Quirk (@danquirk) , done Rules for intersection types are inconsistent with rules for implementing multiple interfaces. #4278. Cheers!

  9. benliddicott commented on Aug 14, 2015

    @benliddicott

    I've posted a workaround for this on #4278.

  10. locked and limited conversation to collaborators on Jun 19, 2018
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

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions