It's because the typings for the method Array<T>.filter() in the standard TypeScript lirbary have an overload signature that specifically narrows the returned array element type if the callback function is known to be a user-defined type guard function:
interface Array<T> {
filter<S extends T>(
callbackfn: (value: T, index: number, array: T[]) => value is S,
thisArg?: any
): S[];
}
Since the type of sheetDataGuard is assignable to (value: SheetData | undefined, index: number, array: Array<SheetData | undefined>) => value is SheetData, then calling filter on an Array<SheetData | undefined> with sheetDataGuard as the callbackfn parameter will cause the compiler to select that overload with SheetData inferred for S.
But when you call it with (sheetData: SheetData | undefined) => sheetDataGuard(sheetData) instead, the type of that function is inferred to return just boolean. That's because user-defined type guard functions do not propagate and are not inferred for you. A type like x is Y generally gets widened to boolean by the compiler as soon as you start doing things with it.
You could tell the compiler that your arrow function callback is also a type guard by using an arrow-function return type annotation, like this:
const sheetData: Array<SheetData> = formattedValues.filter(
(sheetData: SheetData | undefined): sheetData is SheetData =>
sheetDataGuard(sheetData)
);
And the compiler should be happy. Of course if you're going to do this, you might as well forget about defining sheetDataGuard as a separate function:
const sheetData: Array<SheetData> = formattedValues.filter(
(sheetData: SheetData | undefined): sheetData is SheetData =>
!!sheetData
);
Anyway, hope that helps. Good luck!