Encountering an issue within the implementation of getBar()
, you're facing a limitation outlined in TypeScript's design, specifically detailed in microsoft/TypeScript#18422.
The compiler views the length
property of a function solely as a type number
, without recognizing any specific numeric literal types such as 1
or 2
. Therefore, checking foo.doIt.length === 1
does not affect control flow analysis, leaving the compiler unsure of which function type is being invoked.
An issue with relying on length
is that it may not correspond to what was anticipated. Functions can utilize rest parameters, potentially resulting in a length
value of 0
.
Additonally, due to TypeScript permitting functions accepting fewer parameters to be assigned where more are expected (refer to this FAQ entry), a function matching
(value: string, provideBar: (bar: Bar) => void) => void
might have a
length
of
1
or
0
given that function implementations can choose to disregard some inputs.
Given these uncertainties surrounding length
, TypeScript essentially refrains from taking action and advises against validating length
in this manner.
Nevertheless, if there is confidence in the accuracy of the check (i.e., no utilization of the potential "gotcha" versions for setting "doIt"
), similar behavior can be achieved by implementing a user-defined type guard function:
function takesOneArg<F extends Function>(x: F): x is Extract<F, (y: any) => any> {
return x.length === 1
}
The takesOneArg()
function evaluates the length
of the specified function-like argument of type F
, returning true
if it equals 1
and otherwise returning false
. The result type predicate
x is Extract<F, (y: any) => any>
indicates that upon a
true
outcome, the type of
x
can be narrowed down to only those members of
F
requiring one argument; conversely, following a
false
outcome,
x
can be narrowed down to other possibilities.
Subsequently, the getBar()
implementation operates as intended:
function getBar(foo: Foo) {
let bar: Bar = 0;
if (takesOneArg(foo.doIt)) {
bar = foo.doIt('hello');
} else {
foo.doIt('hello', (_bar) => bar = _bar);
}
}
In relation to the issue concerning the generation of a Foo
instance triggering an implicit any
error associated with the callback argument, this appears to align with microsoft/TypeScript#37580. Ideally, value
should be contextually typed as
string</code, although the compiler seemingly fails to do so effectively. Limited details are provided on the GitHub issue, hindering a clear understanding of the underlying cause behind the problematic interaction between function unions and contextual typing inference.</p>
<p>If the issue remains unresolved for a considerable period, the recommended workaround mirrors the standard advice when tackling implicit <code>any
concerns: explicitly annotate elements that elude the compiler's inference capabilities:
const foo1: Foo = {
doIt: (value: string) => 1
}
This revised setup compiles without errors.
Playground link to code