14

Im expecting the arg param to have inferred type from parent class

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass extends IEngineClas {
  viewer(arg) {
    return true
  }
}

However in practice compiler complains that arg has implicit type of any.

I also tried approach with an interface

export interface IEngine {
  viewer?: (arg: string) => boolean
}

export class MyClass implements IEngine {
  viewer(arg) {
    return true
  }
}

It has the same issue with compiler thinking arg has type of any.

Why is type-inference not working here? And what can I do to get it to work?

1
  • 2
    As far as I know Typescript will not infer parameter types in this case. Commented Mar 4, 2018 at 18:28

3 Answers 3

8
+25

Of course you can infer! Typescript has the most powerful generic system ever seen!
It just takes some mystic syntax.

You may write that (check it on Typescript Playground) :

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass extends IEngineClas {
  viewer(arg: IEngineClas["viewer"] extends (arg: infer U) => any ? U : any) {
    return true
  }
}

let test = (new MyClass()).viewer("hop") // Type OK
let test2 = (new MyClass()).viewer(1)    // Wrong type

Explanation:

IEngineClas["viewer"] can retrieve the type of your parent function: (arg:string) => boolean

Using the conditional types, you can retrieve the arg you want by using the infer keyword to assign it to a generic.

Read it like this: if the type of IEngineClas["viewer"] is (arg: U) => any (a function with an argument), grab the type of U (the first argument) and use it as the type of the parameter arg. Otherwise, use the type any.

Edit

A better way to write it, with a type (check it on Typescript Playground):

type firstArg<T> = T extends (arg: infer U) => any ? U : any

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass extends IEngineClas {
  viewer(arg: firstArg<IEngineClas["viewer"]>) {
    return true
  }
}

let test = (new MyClass()).viewer("hop") // Type OK
let test2 = (new MyClass()).viewer(1)    // Wrong type

The reason

On a different case, I asked one day why these expected inference behaviors concerning the abstract classes weren't the default, and was answered it was due to performance issues. And I admit that on big projects, Typescript becomes excessively slow. Even if a compilation flag to activate the typings or not on the abstract classes would have been welcomed.

The post where I asked: https://github.com/Microsoft/TypeScript/issues/21428

Ah, and...

If you just want to get rid of the implicit any warning, just specify the any type explicitly: viewer(arg:any), or disable the noImplicitAny flag in your compiler options.

Sign up to request clarification or add additional context in comments.

2 Comments

Uh .. that is one long way to avoid writing string, not to mention you still need to know the number of arguments of the function (guessing OP was not looking for just a single argument solution). I'd file this unde fun with conditional types but don't actually use :)
@TitianCernicova-Dragomir I admit I can't imagine the use case, but this sample could be a simplified version of a real scenario. If the abstract function type may vary (being the result of a functional approach, where the type can evolve) then it makes perfect sense.
7

There is no type inference here. Member types aren't inferred by parent class or implemented interface. arg is not inferred string but implicit any.

Child class has a chance to override method signature, as long as it's compatible with parent method. It's possible to define the method as viewer(arg: any) {...}. Since string is a subset of any, this will be allowed, while viewer(arg: boolean) {...} won't.

viewer(arg) {...} results in implicit any for arg, it's same as viewer(arg: any) {...}. It will work in loose compiler mode but will result in type error with strict or noImplicitAny compiler option. noImplicitAny is particularly helpful to avoid accidental inferred any in situations like this one.

4 Comments

Is there a way to make compiler infer string rather than any in this case? That's the desired behavior I want to achieve.
I would prefer it to work the same way as you described, but no, this is how TS already works. This would be very misleading if compiler options that allow to change fundamental behaviour existed. Stick to noImplicitAny if you're want to avoid accidental mistakes caused by any.
On the other hand, type inference on objects passed as function parameters work.
@wkrueger I didn't see you put a reward on the question. If I understood correctly what you mean, type inference in function parameters is not same thing. Technically, current behaviour is correct. I'm not sure if it would be a good thing to infer types like that since it's unclear how strictly it should enforce method implementation. What if viewer implementation is viewer(arg, arg2) {} and not viewer(arg) {}? Should TS infer types as viewer(arg: string, arg2: any) {} or be upset that the signature is different?
3

There isn't a good way to do this. The closest and simplest you are going to get is if you implement an interface (or a class, the abstract class can be implemented as well, but you loose shared code) is to use a function field instead and type it as :

export abstract class IEngineClas {
  abstract viewer(arg: string): boolean
}

export class MyClass implements IEngineClas {
  viewer: IEngineClas['viewer'] = function (this: MyClass, arg) { // we need to be explicit about who this is
    return true;
  }
}

This has the disadvantage that the function is assigned to each instance not to the prototype, you also use super.

If you feel strongly about this, I'd come up with a proposal on the GitHub project.

1 Comment

The relevant issue in this case seems to be microsoft/TypeScript#6118, a failed attempt to implement contextual typing in implemented class properties. It probably should have been done, but apparently too much real-world code would break if this restriction were put in place.

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.