Yuck, I see what's happening and I think the only reasonable answer here is to do what you did: annotate legs as Leg[].
So, the library type signature for Array's find() method is overloaded and the overload that accepts a user-defined type guard callback and returns a narrowed element type is generic:
find<S extends T>(
predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any
): S | undefined;
find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
When you don't annotate legs, it gets the union type Array<Leg> | Array<LegA>. This type is considered compatible with Array<Leg> but it won't treat it as such automatically (which is probably good. An Array<LegA> should not let you push() a LegB onto it, but an Array<Leg> should).
That means that the type of legs.find is itself a union of functions whose signatures are not identical:
type LegsFind = {
<S extends LegA>(p: (this: void, v: LegA, i: number, o: LegA[]) => v is S, t?: any): S | undefined;
(p: (v: LegA, i: number, o: LegA[]) => unknown, t?: any): LegA | undefined;
} | {
<S extends Leg>(p: (this: void, v: Leg, i: number, o: Leg[]) => v is S, t?: any): S | undefined;
(p: (v: Leg, i: number, o: Leg[]) => unknown, t?: any): Leg | undefined;
};
Prior to TypeScript 3.3 the compiler would just give up and tell you it doesn't know how to call a union of functions like this. And someone would tell you to change Array<X> | Array<Y> to Array<X | Y> if you're calling non-mutating methods like find(), and you'd use Leg[] and move on.
TypeScript 3.3 introduced improved behavior for calling union types. There are cases when the compiler can take a union of functions and represent it as a single function which takes an intersection of the parameters from each member of the union. You can read more about it in the relevant pull request, microsoft/TypeScript#29011.
But, there are cases when it can't do this: specifically if multiple legs of the union are overloaded or generic functions. Unfortunately for find(), that's the case you're in. It looks like the compiler tries to figure out a way to make this callable, but it gives up on the generic signatures corresponding to user-defined-type-guard narrowing and ends up with just the "regular" one, probably because they are compatible with each other:
type LegsFind33 = (p: (v: LegA, i: number, o: Leg[]) => unknown, thisArg?: any) => Leg | undefined;
So you call it successfully, no narrowing takes place, and you run into the problem here. The issue of calling unions of functions is not completely solved; its GitHub issue microsoft/TypeScript#7294 is closed and labeled "Revisit".
So until that is revisited, the advice here is still to treat Array<X> | Array<Y> likeArray<X | Y> if you're calling non-mutating methods like find()... use Leg[] and move on.
Okay, hope that helps; good luck!