Why was `Subject & Array` necessary for a compatible overload signature?












6















I am getting:




Overload signature is not compatible with function implementation.ts(2394)




On:



/** Iterate through an Array. */
export default function eachr<Value>(
subject: Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject


Of this entire snippet:



export interface IteratorCallback<Subject, Key, Value> {
(this: Subject, value: Value, key: Key, subject: Subject): void | boolean
}

/** Iterate through an Array. */
export default function eachr<Value>(
subject: Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject

/** Iterate through an Object. */
export default function eachr<RecordKey extends keyof any, Value>(
subject: Record<RecordKey, Value>,
callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject

/** Iterate through the subject. */
export default function eachr<RecordKey extends keyof any, Value>(
input: Array<Value> | Record<RecordKey, Value>,
callback: IteratorCallback<typeof input, RecordKey | number, Value>
): typeof input {
if (Array.isArray(input)) {
// Array
const subject = input as Array<Value>
for (let key = 0; key < subject.length; ++key) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
} else {
// Object
const subject = input as Record<RecordKey, Value>
for (const key in subject) {
if (subject.hasOwnProperty(key)) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
}
}

// Return
return input
}


I could make it work by changing it to:



/** Iterate through an Array. */
export default function eachr<Subject extends Array<Value>, Value>(
subject: Subject & Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject


However, I don't understand why that fixed it. What was the problem, and why did that change make the problem go away?



What is even more surprising to me, is that if I apply that same change to a pure object iterator function, it causes it to fail:



/** Iterate through an Object. */
export default function eachrObject<
Subject extends Record<RecordKey, Value>,
RecordKey extends keyof any,
Value
>(
subject: Subject & Record<RecordKey, Value>,
callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject {
for (const key in subject) {
if (subject.hasOwnProperty(key)) {
const value = subject[key]
// above fails with: Element implicitly has an 'any' type because type 'Record<RecordKey, Value>' has no index signature.ts(7017)
// below fails with: Argument of type 'string' is not assignable to parameter of type 'RecordKey'.ts(2345)
if (callback.call(subject, value, key, subject) === false) {
break
}
}
}
return subject
}


Whereas this works:



/** Iterate through an Object. */
export default function eachrObject<RecordKey extends keyof any, Value>(
subject: Record<RecordKey, Value>,
callback: IteratorCallback<typeof subject, RecordKey, Value>
): typeof subject {
for (const key in subject) {
if (subject.hasOwnProperty(key)) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
}
return subject
}


Yet both forms work fine for the Array iterator:



/** Iterate through an Array. */
export default function eachrArray<Subject extends Array<Value>, Value>(
subject: Subject & Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject {
for (let key = 0; key < subject.length; ++key) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
return subject
}

/** Iterate through an Array. */
export default function eachrArray<Value>(
subject: Array<Value>,
callback: IteratorCallback<typeof subject, number, Value>
): typeof subject {
for (let key = 0; key < subject.length; ++key) {
const value = subject[key]
if (callback.call(subject, value, key, subject) === false) {
break
}
}
return subject
}


So how come the change to Subject extends Array<Value> was necessary for overloading compatibility for the areay iterator, yet Subject extends Record<RecordKey, Value> breaks the object iterator?



Sorry for the shear amount of code here, this was the minimum use case I could bring it down to, that contained all the considerations at play.










share|improve this question





























    6















    I am getting:




    Overload signature is not compatible with function implementation.ts(2394)




    On:



    /** Iterate through an Array. */
    export default function eachr<Value>(
    subject: Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
    ): typeof subject


    Of this entire snippet:



    export interface IteratorCallback<Subject, Key, Value> {
    (this: Subject, value: Value, key: Key, subject: Subject): void | boolean
    }

    /** Iterate through an Array. */
    export default function eachr<Value>(
    subject: Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
    ): typeof subject

    /** Iterate through an Object. */
    export default function eachr<RecordKey extends keyof any, Value>(
    subject: Record<RecordKey, Value>,
    callback: IteratorCallback<typeof subject, RecordKey, Value>
    ): typeof subject

    /** Iterate through the subject. */
    export default function eachr<RecordKey extends keyof any, Value>(
    input: Array<Value> | Record<RecordKey, Value>,
    callback: IteratorCallback<typeof input, RecordKey | number, Value>
    ): typeof input {
    if (Array.isArray(input)) {
    // Array
    const subject = input as Array<Value>
    for (let key = 0; key < subject.length; ++key) {
    const value = subject[key]
    if (callback.call(subject, value, key, subject) === false) {
    break
    }
    }
    } else {
    // Object
    const subject = input as Record<RecordKey, Value>
    for (const key in subject) {
    if (subject.hasOwnProperty(key)) {
    const value = subject[key]
    if (callback.call(subject, value, key, subject) === false) {
    break
    }
    }
    }
    }

    // Return
    return input
    }


    I could make it work by changing it to:



    /** Iterate through an Array. */
    export default function eachr<Subject extends Array<Value>, Value>(
    subject: Subject & Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
    ): typeof subject


    However, I don't understand why that fixed it. What was the problem, and why did that change make the problem go away?



    What is even more surprising to me, is that if I apply that same change to a pure object iterator function, it causes it to fail:



    /** Iterate through an Object. */
    export default function eachrObject<
    Subject extends Record<RecordKey, Value>,
    RecordKey extends keyof any,
    Value
    >(
    subject: Subject & Record<RecordKey, Value>,
    callback: IteratorCallback<typeof subject, RecordKey, Value>
    ): typeof subject {
    for (const key in subject) {
    if (subject.hasOwnProperty(key)) {
    const value = subject[key]
    // above fails with: Element implicitly has an 'any' type because type 'Record<RecordKey, Value>' has no index signature.ts(7017)
    // below fails with: Argument of type 'string' is not assignable to parameter of type 'RecordKey'.ts(2345)
    if (callback.call(subject, value, key, subject) === false) {
    break
    }
    }
    }
    return subject
    }


    Whereas this works:



    /** Iterate through an Object. */
    export default function eachrObject<RecordKey extends keyof any, Value>(
    subject: Record<RecordKey, Value>,
    callback: IteratorCallback<typeof subject, RecordKey, Value>
    ): typeof subject {
    for (const key in subject) {
    if (subject.hasOwnProperty(key)) {
    const value = subject[key]
    if (callback.call(subject, value, key, subject) === false) {
    break
    }
    }
    }
    return subject
    }


    Yet both forms work fine for the Array iterator:



    /** Iterate through an Array. */
    export default function eachrArray<Subject extends Array<Value>, Value>(
    subject: Subject & Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
    ): typeof subject {
    for (let key = 0; key < subject.length; ++key) {
    const value = subject[key]
    if (callback.call(subject, value, key, subject) === false) {
    break
    }
    }
    return subject
    }

    /** Iterate through an Array. */
    export default function eachrArray<Value>(
    subject: Array<Value>,
    callback: IteratorCallback<typeof subject, number, Value>
    ): typeof subject {
    for (let key = 0; key < subject.length; ++key) {
    const value = subject[key]
    if (callback.call(subject, value, key, subject) === false) {
    break
    }
    }
    return subject
    }


    So how come the change to Subject extends Array<Value> was necessary for overloading compatibility for the areay iterator, yet Subject extends Record<RecordKey, Value> breaks the object iterator?



    Sorry for the shear amount of code here, this was the minimum use case I could bring it down to, that contained all the considerations at play.










    share|improve this question



























      6












      6








      6


      2






      I am getting:




      Overload signature is not compatible with function implementation.ts(2394)




      On:



      /** Iterate through an Array. */
      export default function eachr<Value>(
      subject: Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject


      Of this entire snippet:



      export interface IteratorCallback<Subject, Key, Value> {
      (this: Subject, value: Value, key: Key, subject: Subject): void | boolean
      }

      /** Iterate through an Array. */
      export default function eachr<Value>(
      subject: Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject

      /** Iterate through an Object. */
      export default function eachr<RecordKey extends keyof any, Value>(
      subject: Record<RecordKey, Value>,
      callback: IteratorCallback<typeof subject, RecordKey, Value>
      ): typeof subject

      /** Iterate through the subject. */
      export default function eachr<RecordKey extends keyof any, Value>(
      input: Array<Value> | Record<RecordKey, Value>,
      callback: IteratorCallback<typeof input, RecordKey | number, Value>
      ): typeof input {
      if (Array.isArray(input)) {
      // Array
      const subject = input as Array<Value>
      for (let key = 0; key < subject.length; ++key) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      } else {
      // Object
      const subject = input as Record<RecordKey, Value>
      for (const key in subject) {
      if (subject.hasOwnProperty(key)) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      }
      }

      // Return
      return input
      }


      I could make it work by changing it to:



      /** Iterate through an Array. */
      export default function eachr<Subject extends Array<Value>, Value>(
      subject: Subject & Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject


      However, I don't understand why that fixed it. What was the problem, and why did that change make the problem go away?



      What is even more surprising to me, is that if I apply that same change to a pure object iterator function, it causes it to fail:



      /** Iterate through an Object. */
      export default function eachrObject<
      Subject extends Record<RecordKey, Value>,
      RecordKey extends keyof any,
      Value
      >(
      subject: Subject & Record<RecordKey, Value>,
      callback: IteratorCallback<typeof subject, RecordKey, Value>
      ): typeof subject {
      for (const key in subject) {
      if (subject.hasOwnProperty(key)) {
      const value = subject[key]
      // above fails with: Element implicitly has an 'any' type because type 'Record<RecordKey, Value>' has no index signature.ts(7017)
      // below fails with: Argument of type 'string' is not assignable to parameter of type 'RecordKey'.ts(2345)
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      }
      return subject
      }


      Whereas this works:



      /** Iterate through an Object. */
      export default function eachrObject<RecordKey extends keyof any, Value>(
      subject: Record<RecordKey, Value>,
      callback: IteratorCallback<typeof subject, RecordKey, Value>
      ): typeof subject {
      for (const key in subject) {
      if (subject.hasOwnProperty(key)) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      }
      return subject
      }


      Yet both forms work fine for the Array iterator:



      /** Iterate through an Array. */
      export default function eachrArray<Subject extends Array<Value>, Value>(
      subject: Subject & Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject {
      for (let key = 0; key < subject.length; ++key) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      return subject
      }

      /** Iterate through an Array. */
      export default function eachrArray<Value>(
      subject: Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject {
      for (let key = 0; key < subject.length; ++key) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      return subject
      }


      So how come the change to Subject extends Array<Value> was necessary for overloading compatibility for the areay iterator, yet Subject extends Record<RecordKey, Value> breaks the object iterator?



      Sorry for the shear amount of code here, this was the minimum use case I could bring it down to, that contained all the considerations at play.










      share|improve this question
















      I am getting:




      Overload signature is not compatible with function implementation.ts(2394)




      On:



      /** Iterate through an Array. */
      export default function eachr<Value>(
      subject: Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject


      Of this entire snippet:



      export interface IteratorCallback<Subject, Key, Value> {
      (this: Subject, value: Value, key: Key, subject: Subject): void | boolean
      }

      /** Iterate through an Array. */
      export default function eachr<Value>(
      subject: Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject

      /** Iterate through an Object. */
      export default function eachr<RecordKey extends keyof any, Value>(
      subject: Record<RecordKey, Value>,
      callback: IteratorCallback<typeof subject, RecordKey, Value>
      ): typeof subject

      /** Iterate through the subject. */
      export default function eachr<RecordKey extends keyof any, Value>(
      input: Array<Value> | Record<RecordKey, Value>,
      callback: IteratorCallback<typeof input, RecordKey | number, Value>
      ): typeof input {
      if (Array.isArray(input)) {
      // Array
      const subject = input as Array<Value>
      for (let key = 0; key < subject.length; ++key) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      } else {
      // Object
      const subject = input as Record<RecordKey, Value>
      for (const key in subject) {
      if (subject.hasOwnProperty(key)) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      }
      }

      // Return
      return input
      }


      I could make it work by changing it to:



      /** Iterate through an Array. */
      export default function eachr<Subject extends Array<Value>, Value>(
      subject: Subject & Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject


      However, I don't understand why that fixed it. What was the problem, and why did that change make the problem go away?



      What is even more surprising to me, is that if I apply that same change to a pure object iterator function, it causes it to fail:



      /** Iterate through an Object. */
      export default function eachrObject<
      Subject extends Record<RecordKey, Value>,
      RecordKey extends keyof any,
      Value
      >(
      subject: Subject & Record<RecordKey, Value>,
      callback: IteratorCallback<typeof subject, RecordKey, Value>
      ): typeof subject {
      for (const key in subject) {
      if (subject.hasOwnProperty(key)) {
      const value = subject[key]
      // above fails with: Element implicitly has an 'any' type because type 'Record<RecordKey, Value>' has no index signature.ts(7017)
      // below fails with: Argument of type 'string' is not assignable to parameter of type 'RecordKey'.ts(2345)
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      }
      return subject
      }


      Whereas this works:



      /** Iterate through an Object. */
      export default function eachrObject<RecordKey extends keyof any, Value>(
      subject: Record<RecordKey, Value>,
      callback: IteratorCallback<typeof subject, RecordKey, Value>
      ): typeof subject {
      for (const key in subject) {
      if (subject.hasOwnProperty(key)) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      }
      return subject
      }


      Yet both forms work fine for the Array iterator:



      /** Iterate through an Array. */
      export default function eachrArray<Subject extends Array<Value>, Value>(
      subject: Subject & Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject {
      for (let key = 0; key < subject.length; ++key) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      return subject
      }

      /** Iterate through an Array. */
      export default function eachrArray<Value>(
      subject: Array<Value>,
      callback: IteratorCallback<typeof subject, number, Value>
      ): typeof subject {
      for (let key = 0; key < subject.length; ++key) {
      const value = subject[key]
      if (callback.call(subject, value, key, subject) === false) {
      break
      }
      }
      return subject
      }


      So how come the change to Subject extends Array<Value> was necessary for overloading compatibility for the areay iterator, yet Subject extends Record<RecordKey, Value> breaks the object iterator?



      Sorry for the shear amount of code here, this was the minimum use case I could bring it down to, that contained all the considerations at play.







      typescript






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jan 4 at 14:53







      balupton

















      asked Jan 2 at 14:50









      baluptonbalupton

      31.2k23105155




      31.2k23105155
























          2 Answers
          2






          active

          oldest

          votes


















          5





          +200









          Honestly, this is a lot to wade through, and I don't think I can answer exactly why you've gotten things to work. In my opinion, your overload signatures should both fail. Let's look at a super simple overload/implementation example:



          function foo(x: string): void; // narrower, okay 
          function foo(x: string | number | boolean): void; // wider, error
          function foo(x: string | number): void {} // impl


          Notice how the second overload signature gives the error that it is not compatible with the implementation signature. That's because the overload's x is a wider type than the implementation's x. And overloads require narrower types.



          Also note how in general (since --strictFunctionTypes was introduced in TypeScript 2.6) function types are contravariant in their parameter types. That results in the following behavior:



          type StringAccepter = (x: string) => void;
          const helloAccepter: StringAccepter = (x: "hello") => {}; // error
          const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay


          helloAccepter is not a valid StringAccepter because "hello" is narrower than string, while stringOrNumberAccepter is a valid StringAccepter because string | number is wider than string. And thus function parameters becoming wider make their functions narrower and vice versa:



          function bar(cb: (x: "hello")=>void): void; // error, cb is too wide because x is too narrow
          function bar(cb: (x: string | number)=>void): void; // okay, cb is narrower because x is wider
          function bar(cb: StringAccepter): void {} // impl


          So I'd expect both of your overloads to fail, since the implementation signature's callback type (IteratorCallback<typeof input, RecordKey | number, Value>) is actually narrower than either of your call signature's callback types.



          At this point, instead of trying to slog through your possible solution involving an extra Subject type parameter and understanding why some things work and some things don't (which makes my brain hurt... maybe there's a compiler bug? maybe not? who knows), I'll instead go with the solution I'd suggest... make the implementation signature truly wide enough to support both call signatures:



          /** Iterate through an Array. */
          export function eachr<Value>(
          subject: Array<Value>,
          callback: IteratorCallback<typeof subject, number, Value>
          ): typeof subject

          /** Iterate through an Object. */
          export function eachr<RecordKey extends keyof any, Value>(
          subject: Record<RecordKey, Value>,
          callback: IteratorCallback<typeof subject, RecordKey, Value>
          ): typeof subject

          /** Iterate through the subject. */
          export function eachr<RecordKey extends keyof any, Value>(
          input: Array<Value> | Record<RecordKey, Value>,
          // here is the change
          callback: IteratorCallback<Array<Value>, number, Value> |
          IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
          ): typeof input {
          if (Array.isArray(input)) {
          // Array
          const subject = input as Array<Value>
          // a new assertion:
          const cb = callback as IteratorCallback<Array<Value>, number, Value>;
          for (let key = 0; key < subject.length; ++key) {
          const value = subject[key]
          if (cb.call(subject, value, key, subject) === false) {
          break
          }
          }
          } else {
          // Object
          const subject = input as Record<RecordKey, Value>
          // a new assertion:
          const cb = callback as IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>;
          for (const key in subject) {
          if (subject.hasOwnProperty(key)) {
          const value = subject[key]
          if (cb.call(subject, value, key, subject) === false) {
          break
          }
          }
          }
          }

          // Return
          return input
          }


          The difference is the callback parameter on the implementation signature is a true union of the analogous types of the callback parameter on each call signature. Additionally the implementation itself needs to do a narrowing assertion for callback to cb in much the same way as the assertion you're already doing for input to subject.



          Now the compiler should be happy. Hope that helps; good luck!






          share|improve this answer



















          • 1





            The type assertions on input are unnecessary in the first place... I came to the same conclusion that neither overload should have worked. Very confusing behavior.

            – Gerrit0
            Jan 6 at 3:08











          • Good point about the unnecessary type assertions on input; control flow analysis should handle that narrowing.

            – jcalz
            Jan 6 at 21:00



















          2














          Problem



          This has to do with how union types work. The problem originates from the last (accumulative) overload:



          callback: IteratorCallback<typeof input, RecordKey | number, Value>


          Because input here is of type Array<Value> | Record<RecordKey, Value>, the definition for callback constructed this way allows 4 possible combinations to exist:



          IteratorCallback<Array<Value>, RecordKey, Value>
          IteratorCallback<Array<Value>, number, Value>
          IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
          IteratorCallback<Record<RecordKey, Value>, number, Value>


          but only 2 of them are valid according to your preceding overload definition



          Solution



          This can be fixed by saying callback will be either one of these two types:



          callback: IteratorCallback<Array<Value>, number, Value> | IteratorCallback<Record<RecordKey, Value>, RecordKey, Value> 


          This takes care of the Overload signature is not compatible with function implementation error.
          However, another issue has been uncovered: TypeScript doesn't make the connection between the type of provided input and the callback that goes with it. Because the last overload still uses union types — two for input and two for callback — there are 4 scenarios TypeScript thinks can happen. It seems the most popular workaround for this problem is just using a type assertion.






          share|improve this answer

























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54008406%2fwhy-was-subject-arrayvalue-necessary-for-a-compatible-overload-signature%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            5





            +200









            Honestly, this is a lot to wade through, and I don't think I can answer exactly why you've gotten things to work. In my opinion, your overload signatures should both fail. Let's look at a super simple overload/implementation example:



            function foo(x: string): void; // narrower, okay 
            function foo(x: string | number | boolean): void; // wider, error
            function foo(x: string | number): void {} // impl


            Notice how the second overload signature gives the error that it is not compatible with the implementation signature. That's because the overload's x is a wider type than the implementation's x. And overloads require narrower types.



            Also note how in general (since --strictFunctionTypes was introduced in TypeScript 2.6) function types are contravariant in their parameter types. That results in the following behavior:



            type StringAccepter = (x: string) => void;
            const helloAccepter: StringAccepter = (x: "hello") => {}; // error
            const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay


            helloAccepter is not a valid StringAccepter because "hello" is narrower than string, while stringOrNumberAccepter is a valid StringAccepter because string | number is wider than string. And thus function parameters becoming wider make their functions narrower and vice versa:



            function bar(cb: (x: "hello")=>void): void; // error, cb is too wide because x is too narrow
            function bar(cb: (x: string | number)=>void): void; // okay, cb is narrower because x is wider
            function bar(cb: StringAccepter): void {} // impl


            So I'd expect both of your overloads to fail, since the implementation signature's callback type (IteratorCallback<typeof input, RecordKey | number, Value>) is actually narrower than either of your call signature's callback types.



            At this point, instead of trying to slog through your possible solution involving an extra Subject type parameter and understanding why some things work and some things don't (which makes my brain hurt... maybe there's a compiler bug? maybe not? who knows), I'll instead go with the solution I'd suggest... make the implementation signature truly wide enough to support both call signatures:



            /** Iterate through an Array. */
            export function eachr<Value>(
            subject: Array<Value>,
            callback: IteratorCallback<typeof subject, number, Value>
            ): typeof subject

            /** Iterate through an Object. */
            export function eachr<RecordKey extends keyof any, Value>(
            subject: Record<RecordKey, Value>,
            callback: IteratorCallback<typeof subject, RecordKey, Value>
            ): typeof subject

            /** Iterate through the subject. */
            export function eachr<RecordKey extends keyof any, Value>(
            input: Array<Value> | Record<RecordKey, Value>,
            // here is the change
            callback: IteratorCallback<Array<Value>, number, Value> |
            IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
            ): typeof input {
            if (Array.isArray(input)) {
            // Array
            const subject = input as Array<Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Array<Value>, number, Value>;
            for (let key = 0; key < subject.length; ++key) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            } else {
            // Object
            const subject = input as Record<RecordKey, Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>;
            for (const key in subject) {
            if (subject.hasOwnProperty(key)) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            }
            }

            // Return
            return input
            }


            The difference is the callback parameter on the implementation signature is a true union of the analogous types of the callback parameter on each call signature. Additionally the implementation itself needs to do a narrowing assertion for callback to cb in much the same way as the assertion you're already doing for input to subject.



            Now the compiler should be happy. Hope that helps; good luck!






            share|improve this answer



















            • 1





              The type assertions on input are unnecessary in the first place... I came to the same conclusion that neither overload should have worked. Very confusing behavior.

              – Gerrit0
              Jan 6 at 3:08











            • Good point about the unnecessary type assertions on input; control flow analysis should handle that narrowing.

              – jcalz
              Jan 6 at 21:00
















            5





            +200









            Honestly, this is a lot to wade through, and I don't think I can answer exactly why you've gotten things to work. In my opinion, your overload signatures should both fail. Let's look at a super simple overload/implementation example:



            function foo(x: string): void; // narrower, okay 
            function foo(x: string | number | boolean): void; // wider, error
            function foo(x: string | number): void {} // impl


            Notice how the second overload signature gives the error that it is not compatible with the implementation signature. That's because the overload's x is a wider type than the implementation's x. And overloads require narrower types.



            Also note how in general (since --strictFunctionTypes was introduced in TypeScript 2.6) function types are contravariant in their parameter types. That results in the following behavior:



            type StringAccepter = (x: string) => void;
            const helloAccepter: StringAccepter = (x: "hello") => {}; // error
            const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay


            helloAccepter is not a valid StringAccepter because "hello" is narrower than string, while stringOrNumberAccepter is a valid StringAccepter because string | number is wider than string. And thus function parameters becoming wider make their functions narrower and vice versa:



            function bar(cb: (x: "hello")=>void): void; // error, cb is too wide because x is too narrow
            function bar(cb: (x: string | number)=>void): void; // okay, cb is narrower because x is wider
            function bar(cb: StringAccepter): void {} // impl


            So I'd expect both of your overloads to fail, since the implementation signature's callback type (IteratorCallback<typeof input, RecordKey | number, Value>) is actually narrower than either of your call signature's callback types.



            At this point, instead of trying to slog through your possible solution involving an extra Subject type parameter and understanding why some things work and some things don't (which makes my brain hurt... maybe there's a compiler bug? maybe not? who knows), I'll instead go with the solution I'd suggest... make the implementation signature truly wide enough to support both call signatures:



            /** Iterate through an Array. */
            export function eachr<Value>(
            subject: Array<Value>,
            callback: IteratorCallback<typeof subject, number, Value>
            ): typeof subject

            /** Iterate through an Object. */
            export function eachr<RecordKey extends keyof any, Value>(
            subject: Record<RecordKey, Value>,
            callback: IteratorCallback<typeof subject, RecordKey, Value>
            ): typeof subject

            /** Iterate through the subject. */
            export function eachr<RecordKey extends keyof any, Value>(
            input: Array<Value> | Record<RecordKey, Value>,
            // here is the change
            callback: IteratorCallback<Array<Value>, number, Value> |
            IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
            ): typeof input {
            if (Array.isArray(input)) {
            // Array
            const subject = input as Array<Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Array<Value>, number, Value>;
            for (let key = 0; key < subject.length; ++key) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            } else {
            // Object
            const subject = input as Record<RecordKey, Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>;
            for (const key in subject) {
            if (subject.hasOwnProperty(key)) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            }
            }

            // Return
            return input
            }


            The difference is the callback parameter on the implementation signature is a true union of the analogous types of the callback parameter on each call signature. Additionally the implementation itself needs to do a narrowing assertion for callback to cb in much the same way as the assertion you're already doing for input to subject.



            Now the compiler should be happy. Hope that helps; good luck!






            share|improve this answer



















            • 1





              The type assertions on input are unnecessary in the first place... I came to the same conclusion that neither overload should have worked. Very confusing behavior.

              – Gerrit0
              Jan 6 at 3:08











            • Good point about the unnecessary type assertions on input; control flow analysis should handle that narrowing.

              – jcalz
              Jan 6 at 21:00














            5





            +200







            5





            +200



            5




            +200





            Honestly, this is a lot to wade through, and I don't think I can answer exactly why you've gotten things to work. In my opinion, your overload signatures should both fail. Let's look at a super simple overload/implementation example:



            function foo(x: string): void; // narrower, okay 
            function foo(x: string | number | boolean): void; // wider, error
            function foo(x: string | number): void {} // impl


            Notice how the second overload signature gives the error that it is not compatible with the implementation signature. That's because the overload's x is a wider type than the implementation's x. And overloads require narrower types.



            Also note how in general (since --strictFunctionTypes was introduced in TypeScript 2.6) function types are contravariant in their parameter types. That results in the following behavior:



            type StringAccepter = (x: string) => void;
            const helloAccepter: StringAccepter = (x: "hello") => {}; // error
            const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay


            helloAccepter is not a valid StringAccepter because "hello" is narrower than string, while stringOrNumberAccepter is a valid StringAccepter because string | number is wider than string. And thus function parameters becoming wider make their functions narrower and vice versa:



            function bar(cb: (x: "hello")=>void): void; // error, cb is too wide because x is too narrow
            function bar(cb: (x: string | number)=>void): void; // okay, cb is narrower because x is wider
            function bar(cb: StringAccepter): void {} // impl


            So I'd expect both of your overloads to fail, since the implementation signature's callback type (IteratorCallback<typeof input, RecordKey | number, Value>) is actually narrower than either of your call signature's callback types.



            At this point, instead of trying to slog through your possible solution involving an extra Subject type parameter and understanding why some things work and some things don't (which makes my brain hurt... maybe there's a compiler bug? maybe not? who knows), I'll instead go with the solution I'd suggest... make the implementation signature truly wide enough to support both call signatures:



            /** Iterate through an Array. */
            export function eachr<Value>(
            subject: Array<Value>,
            callback: IteratorCallback<typeof subject, number, Value>
            ): typeof subject

            /** Iterate through an Object. */
            export function eachr<RecordKey extends keyof any, Value>(
            subject: Record<RecordKey, Value>,
            callback: IteratorCallback<typeof subject, RecordKey, Value>
            ): typeof subject

            /** Iterate through the subject. */
            export function eachr<RecordKey extends keyof any, Value>(
            input: Array<Value> | Record<RecordKey, Value>,
            // here is the change
            callback: IteratorCallback<Array<Value>, number, Value> |
            IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
            ): typeof input {
            if (Array.isArray(input)) {
            // Array
            const subject = input as Array<Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Array<Value>, number, Value>;
            for (let key = 0; key < subject.length; ++key) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            } else {
            // Object
            const subject = input as Record<RecordKey, Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>;
            for (const key in subject) {
            if (subject.hasOwnProperty(key)) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            }
            }

            // Return
            return input
            }


            The difference is the callback parameter on the implementation signature is a true union of the analogous types of the callback parameter on each call signature. Additionally the implementation itself needs to do a narrowing assertion for callback to cb in much the same way as the assertion you're already doing for input to subject.



            Now the compiler should be happy. Hope that helps; good luck!






            share|improve this answer













            Honestly, this is a lot to wade through, and I don't think I can answer exactly why you've gotten things to work. In my opinion, your overload signatures should both fail. Let's look at a super simple overload/implementation example:



            function foo(x: string): void; // narrower, okay 
            function foo(x: string | number | boolean): void; // wider, error
            function foo(x: string | number): void {} // impl


            Notice how the second overload signature gives the error that it is not compatible with the implementation signature. That's because the overload's x is a wider type than the implementation's x. And overloads require narrower types.



            Also note how in general (since --strictFunctionTypes was introduced in TypeScript 2.6) function types are contravariant in their parameter types. That results in the following behavior:



            type StringAccepter = (x: string) => void;
            const helloAccepter: StringAccepter = (x: "hello") => {}; // error
            const stringOrNumberAccepter: StringAccepter = (x: string | number) => {}; // okay


            helloAccepter is not a valid StringAccepter because "hello" is narrower than string, while stringOrNumberAccepter is a valid StringAccepter because string | number is wider than string. And thus function parameters becoming wider make their functions narrower and vice versa:



            function bar(cb: (x: "hello")=>void): void; // error, cb is too wide because x is too narrow
            function bar(cb: (x: string | number)=>void): void; // okay, cb is narrower because x is wider
            function bar(cb: StringAccepter): void {} // impl


            So I'd expect both of your overloads to fail, since the implementation signature's callback type (IteratorCallback<typeof input, RecordKey | number, Value>) is actually narrower than either of your call signature's callback types.



            At this point, instead of trying to slog through your possible solution involving an extra Subject type parameter and understanding why some things work and some things don't (which makes my brain hurt... maybe there's a compiler bug? maybe not? who knows), I'll instead go with the solution I'd suggest... make the implementation signature truly wide enough to support both call signatures:



            /** Iterate through an Array. */
            export function eachr<Value>(
            subject: Array<Value>,
            callback: IteratorCallback<typeof subject, number, Value>
            ): typeof subject

            /** Iterate through an Object. */
            export function eachr<RecordKey extends keyof any, Value>(
            subject: Record<RecordKey, Value>,
            callback: IteratorCallback<typeof subject, RecordKey, Value>
            ): typeof subject

            /** Iterate through the subject. */
            export function eachr<RecordKey extends keyof any, Value>(
            input: Array<Value> | Record<RecordKey, Value>,
            // here is the change
            callback: IteratorCallback<Array<Value>, number, Value> |
            IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
            ): typeof input {
            if (Array.isArray(input)) {
            // Array
            const subject = input as Array<Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Array<Value>, number, Value>;
            for (let key = 0; key < subject.length; ++key) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            } else {
            // Object
            const subject = input as Record<RecordKey, Value>
            // a new assertion:
            const cb = callback as IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>;
            for (const key in subject) {
            if (subject.hasOwnProperty(key)) {
            const value = subject[key]
            if (cb.call(subject, value, key, subject) === false) {
            break
            }
            }
            }
            }

            // Return
            return input
            }


            The difference is the callback parameter on the implementation signature is a true union of the analogous types of the callback parameter on each call signature. Additionally the implementation itself needs to do a narrowing assertion for callback to cb in much the same way as the assertion you're already doing for input to subject.



            Now the compiler should be happy. Hope that helps; good luck!







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Jan 5 at 20:36









            jcalzjcalz

            28.1k22750




            28.1k22750








            • 1





              The type assertions on input are unnecessary in the first place... I came to the same conclusion that neither overload should have worked. Very confusing behavior.

              – Gerrit0
              Jan 6 at 3:08











            • Good point about the unnecessary type assertions on input; control flow analysis should handle that narrowing.

              – jcalz
              Jan 6 at 21:00














            • 1





              The type assertions on input are unnecessary in the first place... I came to the same conclusion that neither overload should have worked. Very confusing behavior.

              – Gerrit0
              Jan 6 at 3:08











            • Good point about the unnecessary type assertions on input; control flow analysis should handle that narrowing.

              – jcalz
              Jan 6 at 21:00








            1




            1





            The type assertions on input are unnecessary in the first place... I came to the same conclusion that neither overload should have worked. Very confusing behavior.

            – Gerrit0
            Jan 6 at 3:08





            The type assertions on input are unnecessary in the first place... I came to the same conclusion that neither overload should have worked. Very confusing behavior.

            – Gerrit0
            Jan 6 at 3:08













            Good point about the unnecessary type assertions on input; control flow analysis should handle that narrowing.

            – jcalz
            Jan 6 at 21:00





            Good point about the unnecessary type assertions on input; control flow analysis should handle that narrowing.

            – jcalz
            Jan 6 at 21:00













            2














            Problem



            This has to do with how union types work. The problem originates from the last (accumulative) overload:



            callback: IteratorCallback<typeof input, RecordKey | number, Value>


            Because input here is of type Array<Value> | Record<RecordKey, Value>, the definition for callback constructed this way allows 4 possible combinations to exist:



            IteratorCallback<Array<Value>, RecordKey, Value>
            IteratorCallback<Array<Value>, number, Value>
            IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
            IteratorCallback<Record<RecordKey, Value>, number, Value>


            but only 2 of them are valid according to your preceding overload definition



            Solution



            This can be fixed by saying callback will be either one of these two types:



            callback: IteratorCallback<Array<Value>, number, Value> | IteratorCallback<Record<RecordKey, Value>, RecordKey, Value> 


            This takes care of the Overload signature is not compatible with function implementation error.
            However, another issue has been uncovered: TypeScript doesn't make the connection between the type of provided input and the callback that goes with it. Because the last overload still uses union types — two for input and two for callback — there are 4 scenarios TypeScript thinks can happen. It seems the most popular workaround for this problem is just using a type assertion.






            share|improve this answer






























              2














              Problem



              This has to do with how union types work. The problem originates from the last (accumulative) overload:



              callback: IteratorCallback<typeof input, RecordKey | number, Value>


              Because input here is of type Array<Value> | Record<RecordKey, Value>, the definition for callback constructed this way allows 4 possible combinations to exist:



              IteratorCallback<Array<Value>, RecordKey, Value>
              IteratorCallback<Array<Value>, number, Value>
              IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
              IteratorCallback<Record<RecordKey, Value>, number, Value>


              but only 2 of them are valid according to your preceding overload definition



              Solution



              This can be fixed by saying callback will be either one of these two types:



              callback: IteratorCallback<Array<Value>, number, Value> | IteratorCallback<Record<RecordKey, Value>, RecordKey, Value> 


              This takes care of the Overload signature is not compatible with function implementation error.
              However, another issue has been uncovered: TypeScript doesn't make the connection between the type of provided input and the callback that goes with it. Because the last overload still uses union types — two for input and two for callback — there are 4 scenarios TypeScript thinks can happen. It seems the most popular workaround for this problem is just using a type assertion.






              share|improve this answer




























                2












                2








                2







                Problem



                This has to do with how union types work. The problem originates from the last (accumulative) overload:



                callback: IteratorCallback<typeof input, RecordKey | number, Value>


                Because input here is of type Array<Value> | Record<RecordKey, Value>, the definition for callback constructed this way allows 4 possible combinations to exist:



                IteratorCallback<Array<Value>, RecordKey, Value>
                IteratorCallback<Array<Value>, number, Value>
                IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
                IteratorCallback<Record<RecordKey, Value>, number, Value>


                but only 2 of them are valid according to your preceding overload definition



                Solution



                This can be fixed by saying callback will be either one of these two types:



                callback: IteratorCallback<Array<Value>, number, Value> | IteratorCallback<Record<RecordKey, Value>, RecordKey, Value> 


                This takes care of the Overload signature is not compatible with function implementation error.
                However, another issue has been uncovered: TypeScript doesn't make the connection between the type of provided input and the callback that goes with it. Because the last overload still uses union types — two for input and two for callback — there are 4 scenarios TypeScript thinks can happen. It seems the most popular workaround for this problem is just using a type assertion.






                share|improve this answer















                Problem



                This has to do with how union types work. The problem originates from the last (accumulative) overload:



                callback: IteratorCallback<typeof input, RecordKey | number, Value>


                Because input here is of type Array<Value> | Record<RecordKey, Value>, the definition for callback constructed this way allows 4 possible combinations to exist:



                IteratorCallback<Array<Value>, RecordKey, Value>
                IteratorCallback<Array<Value>, number, Value>
                IteratorCallback<Record<RecordKey, Value>, RecordKey, Value>
                IteratorCallback<Record<RecordKey, Value>, number, Value>


                but only 2 of them are valid according to your preceding overload definition



                Solution



                This can be fixed by saying callback will be either one of these two types:



                callback: IteratorCallback<Array<Value>, number, Value> | IteratorCallback<Record<RecordKey, Value>, RecordKey, Value> 


                This takes care of the Overload signature is not compatible with function implementation error.
                However, another issue has been uncovered: TypeScript doesn't make the connection between the type of provided input and the callback that goes with it. Because the last overload still uses union types — two for input and two for callback — there are 4 scenarios TypeScript thinks can happen. It seems the most popular workaround for this problem is just using a type assertion.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited Jan 11 at 15:17

























                answered Jan 6 at 4:52









                Karol MajewskiKarol Majewski

                4,226215




                4,226215






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54008406%2fwhy-was-subject-arrayvalue-necessary-for-a-compatible-overload-signature%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Monofisismo

                    Angular Downloading a file using contenturl with Basic Authentication

                    Olmecas