Skip to content

polymorphic arguments validation error #20268

Closed

Description

@amir-arad

TypeScript Version: 2.7.0-dev.20171126
(Introduced in 2.6)

Code

type Processor<T extends object> = <T1 extends T>(subj: T1) => T1
function doStuff<T extends object, T1 extends T>(parentProcessors: Array<Processor<T>>, childProcessors : Array<Processor<T1>>) {
    childProcessors.concat(parentProcessors);
}

Expected behavior:
pass validation (polymorphism etc.)

Actual behavior:
Error:(3, 28) TS2345:Argument of type 'Processor[]' is not assignable to parameter of type 'Processor | ReadonlyArray<Processor>'.
Type 'Processor[]' is not assignable to type 'ReadonlyArray<Processor>'.
Types of property 'indexOf' are incompatible.
Type '(searchElement: Processor, fromIndex?: number | undefined) => number' is not assignable to type '(searchElement: Processor, fromIndex?: number | undefined) => number'.
Types of parameters 'searchElement' and 'searchElement' are incompatible.
Types of parameters 'subj' and 'subj' are incompatible.
Type 'T1' is not assignable to type 'T1'. Two different types with this name exist, but they are unrelated.
Type 'T' is not assignable to type 'T1'.
Type 'object' is not assignable to type 'T1'.

Activity

  1. amir-arad commented on Nov 26, 2017

    @amir-arad
    Author
  2. mhegazy commented on Nov 27, 2017

    @mhegazy
    Contributor

    Seems to have been caused by #17806. a possible solution here is to add an overload for (T | T[])[] or just make it (T | T[] | ReadonlyArray<T>)[]

  3. added this to the milestone on Nov 27, 2017
  4. amir-arad commented on Nov 28, 2017

    @amir-arad
    Author

    Mohamed Hegazy (@mhegazy) thanks for the workaround

  5. ghost closed this as completedin #20455on Dec 7, 2017
  6. ghost added
    FixedA PR has been merged for this issue
    on Dec 8, 2017
  7. modified the milestones: , TypeScript 2.7 on Jan 17, 2018
  8. ahejlsberg commented on Feb 2, 2018

    @ahejlsberg
    Member

    This actually isn't a bug. But it is complicated.

    First, imagine that Processor<T> is written as just

    type Processor<T extends object> = (subj: T) => T;

    This function is invariant for T because it uses T in both input and output positions. Given a types A and B where one is a subtype of the other, neither Processor<A> nor Processor<B> is assignable to the other, and indeed we error if you try.

    Now, changing Processor<T> to the original declaration

    type Processor<T extends object> = <T1 extends T>(subj: T1) => T1;

    This shouldn't really change matters. The function is still invariant for T. However, the type checker has limited capabilities when it comes to checking assignability of function types with generic signatures that differ only in their constraints, so we consider Processor<A> and Processor<B> assignable to each other when really they shouldn't be. But, for some reason, we do end up realizing that Array<Processor<A>> is not assignable to ReadonlyArray<Processor<B>> and that Array<Processor<B>> is not assignable to ReadonlyArray<Processor<A>>. And that's what happens in the original example (where T and T1 in doStuff are like A and B). I'm not 100% clear on how we get there, but it definitely should be an error. The issue here is actually that other cases should also be errors.

    My conclusion is that the error in the original example is correct, and that the "fix" in #20455 isn't actually a fix, but rather a change that causes us to not error when we still should.

    I think the correct course of action is to reverse #20455 and leave things the way they were.

  9. added
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    and removed
    BugA bug in TypeScript
    on Feb 2, 2018
  10. ahejlsberg commented on Feb 2, 2018

    @ahejlsberg
    Member

    I was right that it is complicated. But I was wrong about it not being a bug. It is.

    The original Processor<T> is naturally contra-variant, not invariant as I was arguing above. Intuitively, it is a function that always returns the same type as you pass it, but what you can pass it is constrained, and the constraint acts in a contra-variant manner (i.e. a Processor<A> can safely be treated as a Processor<B>, but not vice versa).

    The actual issue is a combination of two changes: #15104 (covariant checking for callback parameters) and #17806 (arguments to concat should be ReadonlyArray<T>). Because of #17806 we end up in a structural comparison of Array and ReadonlyArray when checking a call to concat. During this structural comparison we treat the searchElement parameter of the indexOf method as a callback parameter and only perform a co-variant comparison, and this comparison fails.

    It is actually correct that there is an error. What's wrong is the way indexOf is declared to take T in an input (contra-variant) position, which technically makes Array<T> and ReadonlyArray<T> invariant. That's usually masked by the fact that we always compare methods bivariantly, but in this scenario we don't because of #15104. We've previously discussed changing indexOf (and the others like it) to be properly co-variant, but we'd need the ability to specify super constraints or simply resort to type any, and we didn't want to go there.

  11. added
    BugA bug in TypeScript
    and removed
    Working as IntendedThe behavior described is the intended behavior; this is not a bug
    on Feb 2, 2018
  12. locked and limited conversation to collaborators on Jul 3, 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

    BugA bug in TypeScriptDomain: lib.d.tsThe issue relates to the different libraries shipped with TypeScriptFixedA PR has been merged for this issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions