openlang

Typescript Callable Contract

Why does Typescript accept my function definition even though it differs from the interface signature, yet report a problem when I actually call that same function.

  interface ITextRenderer {
      (text: string, capitalize : boolean): string;
  };

  //No compiler diagnostic at this assignment.
  var capitalRenderer: ITextRenderer = function (text: string) {
      return text.capitalizeCase();
  }  

  capitalRenderer("demo"); //A diagnostic occurs at this call instead.

The conversation continues 2

Follow the reasoning
  1. Permalink

    The interface requires every caller of an implementing function to provide both mandatory parameters, text and capitalize.

    TypeScript knows that JavaScript tolerates receiving extra arguments which go unused, so implementations are permitted to omit them.

    What makes that acceptable? It lets you replace one implementation with another without changing the caller.

    For instance: Both implementations of ITextRenderer below can be substituted and still work.

      interface ITextRenderer {
          (text: string, capitalize: boolean): string;
      };
    
      var capitalRenderer: ITextRenderer = function (text: string) {
          return text.capitalizeCase();
      }
    
      var flexibleRenderer: ITextRenderer = function (text: string, capitalize: boolean) {
          if (capitalize) {
              return text.capitalizeCase();
          }
    
          return text.toLowerCase();
      }
    
      // Either implementation can go here
      //var renderer = capitalRenderer;
      var renderer = flexibleRenderer;
    
      renderer("demo", true);

    Without this allowance in TypeScript, capitalRenderer would need a capitalize parameter even though its body never uses that parameter at all, making the function harder to read.

  2. Permalink

    The accepted explanation is misleading, since it could encourage experimenting with additional arguments in production purely to bypass a type check that production never performs in the first place.

    For type checking to accept variants taking different argument counts, declare those alternatives explicitly as overloads, for example

      interface ITextRenderer {
          (text: string, capitalize: boolean): string;
          (text: string): string;
      };
    
      var capitalRenderer: ITextRenderer = function (text: string) {
          return text.capitalizeCase();
      }  
    
      capitalRenderer("demo"); // selects the single-parameter signature
      capitalRenderer("demo","false"); // selects the double-parameter signature

    That still leaves unanswered why TypeScript checks a function differently at its definition and at its invocation. Without official documentation explaining this, one could guess that technical limitations arise from TypeScript being implemented in TypeScript / JavaScript, and see whether taht changes when TypeScript 7 is implemented using Go: https://github.com/microsoft/typescript-go?tab=readme-ov-file#typescript-7

Other sides of the same question

  1. TypeScript Map initialization with string array entries

    I'm creating a Map whose entries each hold an array of strings. How can I declare its type and initialize it together in one…

    Replies 2Latest activity typescript
  2. Solidity conversion failure from address into uint256

    I made an array containing structs, then attempted to retrieve each account's values from that array. But passing an address…

    Replies 3Latest activity solidity
  3. Is a read-only List<Int> a snapshot?

    List<Int> is a read-only view, not a snapshot. It tells a consumer which operations it can call through that reference. It pr…

    Replies 4Latest activity kotlin