I'm trying to do the following:
interface RgbColor {
r: number;
g: number;
b: number;
}
function rgbToHex(r: RgbColor, g?: undefined, b?: undefined): string
function rgbToHex(r: number, g: number, b: number): string
function rgbToHex(r: number|RgbColor, g?: number, b?: number): string {
if (r instanceof Object) {
// must be RgbColor
g = r.g;
b = r.b;
r = r.r;
}
// A) r was an RgbColor and we've already set r, g, and b to numbers in the if block
// B) we're meeting the second overload and r, g, and b are all numbers
// either way we know they are all numbers now
let rHex = r.toString(16);
let gHex = g.toString(16); // ERROR: Object is possibly 'undefined'.
let bHex = b.toString(16); // ERROR: Object is possibly 'undefined'.
if (rHex.length == 1) rHex = "0" + rHex;
if (gHex.length == 1) gHex = "0" + gHex;
if (bHex.length == 1) bHex = "0" + bHex;
return "#" + rHex + gHex + bHex;
}
I'm getting the errors on the lines indicated by ERROR
comments.
From a javascript perspective the function is fine:
function rgbToHex(r , g, b) {
if (r instanceof Object) {
g = r.g;
b = r.b;
r = r.r;
}
let rHex = r.toString(16);
let gHex = g.toString(16);
let bHex = b.toString(16);
if (rHex.length == 1) rHex = "0" + rHex;
if (gHex.length == 1) gHex = "0" + gHex;
if (bHex.length == 1) bHex = "0" + bHex;
return "#" + rHex + gHex + bHex;
}
rgbToHex({ r: 120, g: 50, b: 5 }) // "#783205"
rgbToHex(120, 50, 5) // "#783205"
My question is how can I setup the function signatures such that this just works without having to do any casting and without setting up any redundant variables in the function body. i.e. I want to avoid doing let gVal: number = g || (r as any).g;
.
The other alternative would be to split this into two function and have one call the other; but I wouldn't do that with javascript, so it seems wrong that I'd have to do that with typescript.
Sorry if this is a duplicate. I spent a couple of hours looking through similar questions but I couldn't find anything that addressed this issue.
After a bit more googling, reading of other SO questions, and flicking through the typescript github issues, it looks like this is a yet-to-be-implemented feature within typescript.
The overload narrowing that works outside of a function does not yet apply within a function:
// function definition for swapping between number and string types
function foo(bar: string): number // overload 1
function foo(bar: number): string // overload 2
function foo(bar: number | string): number|string
{ ... }
const a = foo('string');
a.toPrecision(); // TS knows a is a number, so this has no errors
const b = foo(5);
b.toLowerCase(); // TS knows b is a string, so this has no errors
const c = Math.random() < .5 ? a : b; // TS knows c as string | number
// the actual implementation of an overloaded method does not count as
// one of the possible overloads, so calling foo with a variable of type (string|number)
// is not valid
const d = foo(c); // error on parameter 'c': No overload matches this call.
So that's all as expected. But when we try to implement foo
we see the same exclusionary logic is not applied within the method:
function foo(bar: string): number // overload 1
function foo(bar: number): string // overload 2
function foo(bar: number | string): number | string {
return bar; // this is valid, it clearly should not be
}
It looks like the typescript team have not yet made the code-analysis within a function aware of the overloads of that function, it seems like strictly the signature of the implementation is enforced. This is obviously wrong.
QED: Error in tooling. Wait for fix.