Repository: TypeScript
Issue: new.target type is too narrow
Labels: Suggestion,Needs Proposal

# Bug Report

### 🔎 Search Terms

new.target, narrow

### 🕗 Version & Regression Information

- This is the behavior in every version I tried, and I reviewed the FAQ for entries about new.target

### ⏯ Playground Link

[Playground link with relevant code](https://www.typescriptlang.org/play?#code/LAKFGMBsEMGdYAQEF4FMBOAXAlgewHYCi66u6CqAHpqvgCaLGnkDeoCHC+0AtqggF4EAIhSwMOAkzLCA3KAC+oUADMArvnCT8COOKwAKdpwBGuXJFTR8ALgRmLV-ABpjHPvGgBzVHdiZ0bHwvQRExCTwdFWhsSzphVxAASjs9CUQHS2sENhBOBGwVBAMAQkynJJy3fIRMAAtSAHcuVGbwrEjpdAMPWG9UJPk8ziUQUdBMAE8AB34ABWh6gEl8bExQ3PzsWAAlcxo6O3LrRPzp6CxYOx2rOgJISaQSaEmAHn9A4IA+RIUhiZm-CQJlgFjUNAW9QAMtgANb8ITA0GQcGoSF1BAAHwQAAMAPQAEhYCA+QRCChxQymswQ6Jh8NC6KxJICZP+IGgIIC0C0CCgelpiwxm04-kW2HACHO6HEBnO9Ts+KJLM+5JxKWQILBEKFQ3yYpwkulsvldT8rOCGqR2rRQuZNxgOAAbrb6nrRZhxUaLiahebVVatSidfV7ahHdgXUyRTU+QR-AVdvtUHRQqaAHRiy4AdTWdQMwjxwkG1S2RQM2z2uAOlRjsc46FQmDU6B0+FamuRqPRcqFmcgEtQBgAjElM9MB5gC0WkiXhrHRvWEI3m62Ws0HeKo0Le-Vx5Pp8W5-lFx6vQgVKQeLuzZ2bXS4QM7Nbg666u6OAaJRerzfruGtzfDVN2dN8PxZc9L1wa9TTsB94UDLsQwxbEQMjMDqi-SUoJgv1BWhR9EPvO1UIA0Do1LThCmKalUFwIpTUEAQhGEUlgmLKp5yXZcmxbHR0XTY0h1NY961PWMVz4qVdWqcSIMNBAACtcCCIwuPyFRsBlTB-wjbd6lOet02M00rmQZ43ng1AvmqYCyPQ9FwKwpSVPwNSl007TnyDbshXpVBDNjYzBKFMynnQF5XismyuKI19HMwz0FOU1TKI4Tz-DgvzH0CmpgtM58LKi7L4Ri-I4t80NSL0jCuOclK3LSi8tMy-C6n83L8ny0LCoiyySus2zvKQt8wxqij1M4DL1iEAScIMabRNjcB43WaBIEgdFECEABtabnAQbr6lgABddMeGgaYDBvSoBC+Nr03mkSlpqaiFpazB022F9UQMWdOO4hteLXds2h85D3MBmp1s20LHsdABZS7rpEwR7ozABiaVMFgJJOvrF6TyatJDGmr7dnsl0-sJoHVzbDs0P0-MmvyGGtvhxYkaum60ekvcsYuHG8aal65MbaA7nwB4EAxytk0OexzCyfBwPFyXpYFy5-wl+5HiKtivC+cCVvwD41C0MgDDrLYk2rFN8Y4bHYFyhQsuWVZMFrJq3tB9NPXQHx1hKZi73ioUmoAMgj9c-YuQOEGDoRGaAgGofqJp1wQAAVQEugLJlYDqXA1EgVN8Grex+BNs2tBTBA6C01AtAeYtwKJyaOGr9ZxdhrXzL694LUN0IdpOtuprIYou+krAEHomehdTwG3uxr7NBROhUFgQ9-uttOGlwZpQYQHZrB8POcUY8QvD4fAcYQC7Ji4CuTc9IIRCLPk6guHkaBldVx4LgoJAcQBRyzYyYixdMwgsTYggcxFiHE96A1fkENQqBAE1AUMA0ByDuI9wWJcQSahC69iwDTBcTU5L5HqNsdMstbYHFCHLO2dBMG0NgPQp2oQADyJhFJN0+peVAqAABeQ4CGC1xuBOS31wZDg1BwxModKrCiapJNcHD6EsIODI6olZKYKLsEo7YJ9DETSXBonQJQtEMKrLo2S1QMaYFwAAZSHn9f0ZIl41EsOsRiQhbFO3TA1HemC3q2J0SmL2HcagBNxISYkjEKSYOobTKSpo9FcXjqaTxKpvF4KsbUOodDnFuI8aLaoOSpF5JuDrKWesB4G3uoU4GOgglSKyaMfk8AzHjTtFQGg9BEAWM4F3dA5sXHdCdtrdWjTIrNJiUuWAahZjdDwTbexKY7DRBAQFFmnAnYOwQAoSpYwIAwF6T9ZCFBqC0AYG1Hx4zJmWxmWY+pDxwoLKHl8JZ9YVlrKtgcjgUSFYBHQccs4UjjmnKyfkeOmlLAAFUdhQjyQbHx+Rp4tkgKEY+KK0XCERb4PEM5ME4pCvUbgfBQhKmJFo4JF1ubY1uvdWgK1N4oqWAAYWgtMAgtApwsrHKEwsHEUnqLaQgClDRUAqC6UAA)

### 💻 Code

```ts
class AssertionError extends Error {
    name = "AssertionError";
}

function assert(
    boolean: boolean,
    message: string = "Assertion failed",
): asserts boolean {
    if (!boolean) {
        throw new AssertionError(message);
    }
}

type PathInit = {
    isRooted: boolean,
    parts: ReadonlyArray<string>,
};

type AbsolutePathLike = AbsolutePath | `/${ string }`;
type PathLike = Path | string;

abstract class Path {
    static parse(path: `/${ string }`): AbsolutePath;
    static parse(path: string): AbsolutePath | RelativePath;
    static parse(path: string): AbsolutePath | RelativePath {
        const isRooted = path.startsWith("/");
        if (isRooted) {
            return new AbsolutePath(path.slice(1).split("/"));
        }
        return new RelativePath(path.split("/"));
    }
    static from(path: AbsolutePathLike): AbsolutePath;
    static from(path: RelativePath): RelativePath;
    static from(path: PathLike): AbsolutePath | RelativePath;
    static from(path: PathLike): AbsolutePath | RelativePath {
        if (typeof path === "string") {
            return Path.parse(path);
        }
        return path;
    }
    static join(
        first: RelativePath,
        ...paths: Array<PathLike>
    ): RelativePath;
    static join(
        first: AbsolutePathLike,
        ...paths: Array<PathLike>
    ): AbsolutePath;
    static join(
        first: PathLike,
        ...paths: Array<PathLike>
    ): AbsolutePath | RelativePath;
    static join(
        first: PathLike,
        ...paths: Array<PathLike>
    ): AbsolutePath | RelativePath {
        first = Path.from(first);
        const allPaths = [first, ...paths].map((path) => Path.from(path));
        if (first.isAbsolute()) {
            return new AbsolutePath(
                allPaths.flatMap((path) => path.#parts),
            );
        }
        assert(first.isRelative());
        return new RelativePath(
            allPaths.flatMap((path) => path.#parts),
        );
    }
    readonly #isRooted: boolean;
    readonly #parts: ReadonlyArray<string>;
    constructor({
        isRooted,
        parts,
    }: PathInit) {
        if (new.target !== AbsolutePath
        && new.target !== RelativePath) {
            throw new TypeError("Path should not be constructed directly");
        }
        const realParts: Array<string> = [];
        for (const part of parts) {
            if (part.includes("/")) {
                throw new RangeError(`path segments may not contain "/" characters`);
            } else if (part === "." || part === "") {
                continue;
            } else {
                realParts.push(part);
            }
        }
        this.#isRooted = isRooted;
        this.#parts = Object.freeze(realParts);
    }
    isAbsolute(): this is AbsolutePath {
        return this.#isRooted;
    }
    isRelative(): this is RelativePath {
        return !this.#isRooted;
    }
    #toString(): string {
        let path = this.#parts.join("/");
        if (this.#isRooted) {
            path = `/${ path }`;
        }
        return path;
    }
    get path(): string {
        return this.#toString();
    }
    get parts(): ReadonlyArray<string> {
        return this.#parts;
    }
}
class RelativePath extends Path {
    constructor(parts: ReadonlyArray<string>) {
        super({
            isRooted: false,
            parts,
        });
    }
}
class AbsolutePath extends Path {
    constructor(parts: ReadonlyArray<string>) {
        super({
            isRooted: true,
            parts,
        });
    }
    get fileURL(): string {
        const url = new URL("file://");
        url.pathname = `/${ this.parts.map((part) => encodeURIComponent(part)).join("/") }`;
        return url.href;
    }
}
```

### 🙁 Actual behavior

The value of `new.target` is too narrow as it does not actually allow proper subclasses to assume the value of `new.target` despite this being entirely possible. This is because, in this case, `typeof Path` has an incompatible construct signature with `typeof AbsolutePath/RelativePath`.

### 🙂 Expected behavior

In general subclasses can be expected to change the signature of the constructor, this is a core part of class-based OO, hence `new.target` type is overspecified.

I think the simplest resolution would be to simply widen `new.target` to `new (...args: any[]) => this`, as the only thing guaranteed about a subclass signature is that it will return an instance of `this`.