I just wrote this function:
const cloneUrl = <T extends (URL | undefined)>(url: T): T => url instanceof URL
? new URL(url.toString())
: undefined;
However, this has errors (or at least, errors very close to this):
'URL | undefined' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'URL | undefined'.
Type 'undefined' is not assignable to type 'T'. 'undefined' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'URL | undefined'.
Clearly I am not grasping something fundamental about TypeScript...
My goal is to make it so that when cloneUrl is called with a type known to be a URL, the return type is URL, but when cloneUrl is called with a type that is URL | undefined, the return type is URL | undefined.
Example:
class UrlWrapper {
private readonly _urlOne: URL
private readonly _urlTwo?: URL
constructor(urlOne: URL, urlTwo?: URL) {
this._urlOne = urlOne;
this._urlTwo = urlTwo;
}
get urlOne(): URL { return cloneUrl(this._urlOne); }
get urlTwo(): URL | undefined { return cloneUrl(this._urlTwo); }
}
See how urlWrapper.urlOne should always be a non-undefinable value, but urlTwo can be?
I have tried different type constraints:
<T extends URL><T extends URL ? URL : undefined>
I have tried different parameters:
url?: Turl: T | undefined
I've tried casting the results:
undefined as undefinedundefined as T | undefined
I've tried making the private field optional, or changing its type.
I've tried using a type checker function:
const checkIsUrl = (url: unknown): url is URL => url instanceof URL;
...and many more.
But there is something about how TypeScript thinks about types that I don't have the right mental model for, so I'm just not getting it.