I noticed that, often, the TypeScript compiler doesn’t complain if I don’t pass a parametrised type:
function someFunction<T>(): T {
// Do something
}
const x = someFunction<string>();
// I'd personally expect this line to ALWAYS raise a compiler error
// but this is not the case
const y = someFunction();
In some cases it does raise an error, other times it doesn’t, and I don’t see a pattern behind this behaviour.
I guess that, in some cases, the structure of the function itself suggests the compiler what the type T
is, and in those cases it’s probably correct that it doesn’t get flagged.
But I did a couple of experiments, and there are confusing results.
-
Experiment 1
<code>function cannotBeInferred<T>(): T {return 0 as T;}const a1 = cannotBeInferred();const a2 = cannotBeInferred<string>();</code><code>function cannotBeInferred<T>(): T { return 0 as T; } const a1 = cannotBeInferred(); const a2 = cannotBeInferred<string>(); </code>function cannotBeInferred<T>(): T { return 0 as T; } const a1 = cannotBeInferred(); const a2 = cannotBeInferred<string>();
- Variable
a1
isunknown
: I’d definitely expect the compiler to raise an error - Variable
a2
isstring
, as expected
- Variable
-
Experiment 2
<code>function canBeInferredNativeType<T>(par: T): T {return par;}const b1 = canBeInferredNativeType('test');const b2 = canBeInferredNativeType<string>('test');</code><code>function canBeInferredNativeType<T>(par: T): T { return par; } const b1 = canBeInferredNativeType('test'); const b2 = canBeInferredNativeType<string>('test'); </code>function canBeInferredNativeType<T>(par: T): T { return par; } const b1 = canBeInferredNativeType('test'); const b2 = canBeInferredNativeType<string>('test');
- Variable
b1
is of type"test"
– I’d expect the compiler to raise an error here - Variable
b2
isstring
as expected
- Variable
-
Experiment 3
<code>function canBeInferredInterface<T>(param: { something: T }): T {return param.something;}interface Something { something: string };const param: Something = { something: 'blabla' };const c1 = canBeInferredInterface({ something: 'test' });const c2 = canBeInferredInterface(param);const c3 = canBeInferredInterface<number>({ something: 123 });</code><code>function canBeInferredInterface<T>(param: { something: T }): T { return param.something; } interface Something { something: string }; const param: Something = { something: 'blabla' }; const c1 = canBeInferredInterface({ something: 'test' }); const c2 = canBeInferredInterface(param); const c3 = canBeInferredInterface<number>({ something: 123 }); </code>function canBeInferredInterface<T>(param: { something: T }): T { return param.something; } interface Something { something: string }; const param: Something = { something: 'blabla' }; const c1 = canBeInferredInterface({ something: 'test' }); const c2 = canBeInferredInterface(param); const c3 = canBeInferredInterface<number>({ something: 123 });
- Variable
c1
isstring
; I’d expect the compiler to raise an error here, because how does it know that the interface I am passing is{ something: string }
and not{ something: 'test' }
? - Variable
c2
isstring
; in my view it’s correct that the compiler didn’t raise an error, because the parameter I am passing is explicitly typed; but I do find this example a bit convoluted, I’d still prefer the compiler to fail here - Variable
c3
is correctlynumber
- Variable
What I would like is a uniform behaviour. Either raise an error every single time that I don’t provide the parameter; or otherwise be crystal clear that, when errors are not raised, it means that the type is obvious from the context.
How can I achieve this? Is there a compiler option for this?
2
It is possible to omit generic type parameters in Typescript as soon as:
- They’re optional, ie when a defautl value has been specified
type Type<Optional = string> = .....
=> you can use Type without specifying the generic parameter but then it will bestring
- They are inferable and not partially specified
type Func<Input, Return> = (input: Input) => Return;
const hoist = <Input, Return>(func: Func<Input, Return>, input: Input) => {
return func(input);
}
const hoistReturnNumber = <Input, Return = number>(func: Func<Input, Return>, input: Input) => {
return hoist<Input, Return>(func, input);
}
// none specified, fully inferable => OK
hoist((a: string) => parseInt(a), '12')
// first specified, second optional => OK
hoistReturnNumber<string>((a: string) => parseInt(a), '12')
// fully inferable but first specified while second mandatory => ERROR
hoist<string>((a: string) => parseInt(a), '12')
// fully specified
hoist<string, number>((a: string) => parseInt(a), '12')
Now even if it triggers OCD, having infered generic parameters allows for deep inference and really generic code that will not need painfull refactoring when the manipulated types change.