dukat icon indicating copy to clipboard operation
dukat copied to clipboard

This parameters are de-facto not supported

Open Schahen opened this issue 5 years ago • 3 comments

I've just realised that we've completely misinterpreting this param in functions. As of now we just escape them.

Let me just quite relevant part of typescript documentation:

let deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function() {
    // NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
    return () => {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);

      return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
    };
  }
};

this parameters Unfortunately, the type of this.suits[pickedSuit] is still any. That’s because this comes from the function expression inside the object literal. To fix this, you can provide an explicit this parameter. this parameters are fake parameters that come first in the parameter list of a function:

function f(this: void) {
  // make sure `this` is unusable in this standalone function
}

Let’s add a couple of interfaces to our example above, Card and Deck, to make the types clearer and easier to reuse:

interface Card {
  suit: string;
  card: number;
}
interface Deck {
  suits: string[];
  cards: number[];
  createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  // NOTE: The function now explicitly specifies that its callee must be of type Deck
  createCardPicker: function(this: Deck) {
    return () => {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);

      return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
    };
  }
};

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

this parameters in callbacks You can also run into errors with this in callbacks, when you pass functions to a library that will later call them. Because the library that calls your callback will call it like a normal function, this will be undefined. With some work you can use this parameters to prevent errors with callbacks too. First, the library author needs to annotate the callback type with this:

interface UIElement {
  addClickListener(onclick: (this: void, e: Event) => void): void;
}

Schahen avatar Apr 12 '20 23:04 Schahen

Here are some real-life examples:


// very obvious
type TransformFunction<S, T> = (this: TransformContext<T>, value: S) => void;
type FlushFunction<T> = (this: TransformContext<T>) => void;

declare function onHeaders(res: ServerResponse, listener: (this: ServerResponse) => void): void

// less obvious to me:
interface LoDashImplicitWrapper<TValue> {
        /**
         * @see _.max
         */
        max<T>(this: LoDashImplicitWrapper<List<T> | null | undefined>): T | undefined;
    }


interface Stream<S, T> {
                /**
                 * Compresses all files using the given compressor
                 */
                compress(this: {}, type: plugin.compressor.Compressor, options?: {
                    /**
                     * Whether to rename files, adds corresponding extname
                     */
                    rename?: boolean,
                    [key: string]: any
                }): this;

                /**
                 * Decompresses all files using the given compressor
                 */
                decompress(type: plugin.compressor.Compressor, options?: object): this;
            }

Schahen avatar Apr 12 '20 23:04 Schahen

What makes things worse - we can not have function type with receiver in external declarations

Schahen avatar Apr 13 '20 11:04 Schahen

Related issue in Kotlin - https://youtrack.jetbrains.com/issue/KT-15140

Schahen avatar Apr 13 '20 15:04 Schahen