Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type inference in typescript classes

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?

like image 416
Tony Avatar asked Mar 04 '18 17:03

Tony


People also ask

Does TypeScript have type inference?

TypeScript infers types of variables when there is no explicit information available in the form of type annotations. Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters.

What do you mean by inferred typing in TypeScript?

In TypeScript, there are several places where type inference is used to provide type information when there is no explicit type annotation. For example, in this code. let x = 3; let x: number. The type of the x variable is inferred to be number .

What would be the best common type inferred by TypeScript?

The best common type algorithm In this case, TypeScript selects the number array type ( number[] ) as the best common type. In this example, TypeScript infers the type for arr to be (RegExp | Date)[] .

How does type inference work?

Type inference is the ability to automatically deduce, either partially or fully, the type of an expression at compile time. The compiler is often able to infer the type of a variable or the type signature of a function, without explicit type annotations having been given.


2 Answers

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.

like image 107
arvymetal Avatar answered Oct 04 '22 18:10

arvymetal


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.

like image 34
Estus Flask Avatar answered Oct 04 '22 18:10

Estus Flask