Overloaded functions in Typescript are achieved by splitting the function's type signature apart into two sides: the one-or-more call signatures seen by the callers of the function, and after this, the single implementation signature seen by the implementer of the function. You left the implementation signature out (or rather, you accidentally used the last call signature as the implementation signature).
Call signatures are just declared, not implemented. So you end a call signature with just a semicolon ; and not an implementation block { ... }. In your case, you want combinedHash to be called in one of four possible ways, corresponding to each of first and second being either Buffer or null:
// the call signatures, seen by the callers:
static combinedHash(first: null, second: null): null;
static combinedHash(first: null, second: Buffer): Buffer;
static combinedHash(first: Buffer, second: null): Buffer;
static combinedHash(first: Buffer, second: Buffer): Buffer;
The implementation signature is both declared and implemented, like a normal function. This signature must be "compatible" with each of the call signatures: you must be able to assign the parameter list of each of the call signatures to the parameter list of the implementation (this is type safe); and you must be able to assign the return type of each of the call signatures to the return type of the implementation (this is actually unsafe, but allowed). In your case then, you need first and second to both be at least as wide as Buffer | null, and you need the return type to be something like Buffer | null as well:
// and then the implementation signature, seen by the implementation:
static combinedHash(first: Buffer | null, second: Buffer | null) {
if (!first) {
return second;
}
if (!second) {
return first;
}
return keccak256(MerkleTree.sortAndConcat(first, second));
}
This should now work as desired.
Note that you now cannot call combinedHalf() where either argument is Buffer | null. Each argument must either be either Buffer or null. If you find this too restrictive, you might want to change your call signatures to something like this:
static combinedHash(first: null, second: null): null;
static combinedHash(first: Buffer | null, second: Buffer): Buffer;
static combinedHash(first: Buffer, second: Buffer | null): Buffer;
static combinedHash(first: Buffer | null, second: Buffer | null): Buffer | null;
This allows Buffer | null to be passed in for each argument. If either argument is known to be Buffer, then the output is Buffer. If both arguments are known to be null, then the output is null. And if neither argument is known to be narrower than Buffer | null, then the output is Buffer | null:
function doStuff(buffer: Buffer, maybeBuffer: Buffer | null) {
Foo.combinedHash(buffer, buffer).length; // okay
Foo.combinedHash(buffer, maybeBuffer).length; // okay
Foo.combinedHash(maybeBuffer, buffer).length; // okay
Foo.combinedHash(maybeBuffer, null).length // error, possibly null
Foo.combinedHash(null, null) // definitely null
}
Playground link to code