2

First of all, there is a similar question here: what-is-the-syntax-for-typescript-arrow-functions-with-generics

But, I'd like to know the culprit of the syntax error.

I am using an external library, and this is what the definition file (index.d.ts) looks like:


External Library's index.d.ts

declare namespace Student {
    export interface Lecture {
        lectureName: string;
    }

    export interface Student {
        new (): Student;

        on1(eventName: string, callback: (<T>(lecture: T, oldLecture: T) => void) |
                                        ((name: string, ...args: any[]) => void)): void;

        on2(eventName: string, callback: (<T>(lecture: T, oldLecture: T) => void)): void;
    }
}

declare var Student: Student.Student;

declare module "student" {
    export = Student;
}

Note that there are two functions: on1 and on2 in Student.Student - the function on1 has a bit more code.

So here are my code examples.


Case 1

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', (lecture: Lecture, oldLecture: Lecture) => {
            // Argument of type error
        });

        this.student.on2('test', (lecture: Lecture, oldLecture: Lecture) => {
            // Argument of type error
        });
    }
}

The function on1 gives the below error:

Argument of type '(lecture: Lecture, oldLecture: Lecture) => void' is not assignable to parameter of type '((lecture: T, oldLecture: T) => void) | ((name: string, ...args: any[]) => void)'. Type '(lecture: Lecture, oldLecture: Lecture) => void' is not assignable to type '(name: string, ...args: any[]) => void'. Types of parameters 'lecture' and 'name' are incompatible. Type 'string' is not assignable to type 'Lecture'.

The function on2 gives the below error:

Argument of type '(lecture: Lecture, oldLecture: Lecture) => void' is not assignable to parameter of type '(lecture: T, oldLecture: T) => void'. Types of parameters 'lecture' and 'lecture' are incompatible. Type 'T' is not assignable to type 'Lecture'.

I thought this example is the right way to implement the code - but why this gives an error?


Case 2

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', <Lecture>(lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName; 
            // Error: Property 'lectureName' does not exist on type 'Lecture'
        });

        this.student.on2('test', <Lecture>(lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName;
            // Error: Property 'lectureName' does not exist on type 'Lecture'
        });
    }
}

In this example, I put <Lecture> in front of the arrow function - so there is no error in the implementation, but now I cannot use lecture.lectureName at all. Why?


Case 3

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', <T extends Lecture>(lecture: T, oldLecture: T) => {
            lecture.lectureName; // Yay! No problem!
        });

        this.student.on2('test', <T extends Lecture>(lecture: T, oldLecture: T) => {
            // Argument of type error
        });
    }
}

So this example has the correct answer - however, the function on2 still gives the argument of type error, just like the case 1's example. Shouldn't it be okay since the function on1 is okay?


Case 4

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', () => () => (lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName; // Yay! No error!
        });

        this.student.on2('test', () => () => (lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName; // Yay! No error!
        });
    }
}

I found this solution accidentally - and both functions are working fine. But I have no idea why this is working.


I spent some time trying to figure out the exact cause by looking at these references (because I love TypeScript):

but I am still wondering the exact cause of this issue.

3 Answers 3

1

You are a bit confused by how to declare a generic function and how to call a generic function.

You can summarize your issue with this:

// Define the Identity function type
// The result type = input type
type TIdentityFunc = <T>(input: T) => T;

// Implement the TIdentity function
// We followed the rule.
const identityImpl: TIdentityFunc = <T>(input: T) => input;

// Now we call this implementation
const num = identity(5);   // num is always number
const str = identity('hi') // str is always a string

In your example you implemented the requested callback, this means that when someone will call this callback, she will know the parameters types.

Remember, you are not calling the callback, you are only implementing it!

So your code should look like this:

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent {
  student: Student.Student;

  constructor() {
    this.student = new Student();

    this.student.on1('test', <T>(l1: T | string, l2: T, ...args) => {
      // This is a bit complicated overloading, 
      // But it follows the rules of the declaration
    });

    this.student.on2('test', <T>(lecture: T, oldLecture: T) => {
      // Your only assumption is that lecture, and oldLecture are the same type
    });
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I really appreciate your comment and example codes! I have a question - in your example, how can I write a code without error? For example, if I wrote a code like: lecture.lectureName, it says the property not exists, just like the case 2.
As per your declarations, you are expected to implement a callback with two parameters with identical types, that's it. Nothing there is saying a Lecture object... This is the reason TypeScript tells you that you miss-implemented the requested declarations. BTW: In Case 2, you just named the generic "Lecture" instead of "T". Remember that you are not the caller of this function, you are only expected to implement this generic function. The caller will be expected to call your callback with two parameters with identical types.
1

I know this doesn't answer your question directly but you can avoid this issue by using class methods, instead of nested anonymous callbacks (which feels very 2015)

type Handler = <T>(t: T) => void;

class Student {
  on1(s:string, callback:Handler) : void {
    callback<string>("hi")
  }
}

class MyStudent { 
    student: Student

    constructor() {
        this.student = new Student()
        this.student.on1('test', this.log)
    }

    log<T>(t:T) : void { 
         console.log("hi " + t)
    }
}

Comments

1

The problem is the typing was declaring the generic at the wrong place:

declare interface Lecture {
  lectureName: string;
}

declare interface Student {
  new (): Student;

  on1<T>(eventName: string, callback: ((lecture: T, oldLecture: T) => void) |
    ((name: string, ...args: any[]) => void)): void;
  on2<T>(eventName: string, callback: (lecture: T, oldLecture: T) => void): void;
}

let s: Student;

s.on1('x', (a: Lecture, b: Lecture) => {

})
s.on2('y', (a: string, b: string) => {

})

2 Comments

Thanks for the comment - but unfortunately, I can't (and I don't) want to change the declaration file since it's a 3rd party library. Plus, the code declare interface will override the existing definition
Other people would face the same problem as you experienced. Is this 3rd party open source? May be it is good to at least open an issue there so the author may fix it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.