If you want a non-recursive way to get an intersection of all the elements of a tuple type, you can do it like this:
type TupleToIntersection<T extends readonly any[]> =
    { [N in keyof T]: (x: T[N]) => void }[number] extends
    (x: infer I) => void ? I : never;
It works via some type juggling that is easier to write than to explain (but I'll try):  First we map the tuple T to a new version where each element at numeric index N (that is, T[N]) has been placed in a contravariant position (as the parameter to a function, (x: T[N]) => void), and then index into that tuple with number to get a union of those functions.  From this we  use conditional type inference to infer a single function parameter type for that union of functions, which (as mentioned in the linked doc) produces an intersection I.
Whether that makes sense or not, you can observe that it works:
type ABC = TupleToIntersection<[A, B, C]>
// type ABC = A & B & C
type AorBandC = TupleToIntersection<[A | B, C]>
// type AorBandC = (A | B) & C
Note that the compiler preserves that A | B union in the second example.
Anyway that means we can write the output of merge() as TupleToIntersection<T>:
function merge<T extends any[]>(...args: T): TupleToIntersection<T> {
    return args.reduce((previous, current) => {
        return Object.assign(previous, current);
    });
}
And it produces the expected output:
const m = merge(a, b, c);
// const m: A & B & C
m.a;
m.b;
m.c;
const m2 = merge(Math.random() < 0.5 ? a : b, c)
// const m2: (A | B) & C
Playground link to code