Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #351, define prototype field on Class type. #632

Merged
merged 5 commits into from
Jun 20, 2023

Conversation

ajvincent
Copy link
Contributor

@ajvincent ajvincent commented Jun 10, 2023

Yes, I know this is potentially risky, changing one of the most fundamental types in type-fest. I was unable to apply the same fix to AbstractClass.

Still, I have added a test for the prototype field which fails on the existing type and passes with the new type.

Credit to @jcalz for the corrected type itself, at microsoft/TypeScript#54585.

Fixes #351

@sindresorhus
Copy link
Owner

Can you apply it to AbstractClass too?

@ajvincent
Copy link
Contributor Author

Can you apply it to AbstractClass too?

Having just discovered // @ts-expect-error from other tests here, I believe so.

That said, I think abstract-class.ts isn't really testing AbstractConstructor or AbstractClass. None of the code in there now currently exercises abstract new(). I may have to rewrite the entire test. When I redefined AbstractClass to match, the existing tests broke immediately for that reason.

@ajvincent
Copy link
Contributor Author

ajvincent commented Jun 10, 2023

I've been thinking about AbstractClass a bit more, and I think that it's just not fixable.

interface AbstractClassAsInterface<T, Arguments extends unknown[] = any[]> {
    prototype: T;
    abstract new(...args: Arguments): T
    // 'abstract' modifier cannot appear on a type member.(1070)
};

type AbstractClass<T, Arguments extends unknown[] = any[]> = {
    prototype: T;
    abstract new(...args: Arguments): T
    // 'abstract' modifier cannot appear on a type member.(1070)
};

Playground link

// @ts-expect-error is not something that should appear in the source code. Yet to fix AbstractClass, I would be required to. This is a logical contradiction. Further, users of this corrected type would also have compilation errors, I would expect.

I recognize it's only 3 months old. I recommend removing AbstractClass and adding a note to the README about this. But I won't do so without your concurrence, @sindresorhus .

@sindresorhus
Copy link
Owner

I recognize it's only 3 months old. I recommend removing AbstractClass and adding a note to the README about this.

I don't really see the need to remove the type. From what I understand, the AbstractClass test is not entirely incorrect, just not strict enough. The current AbstractClass test does test that it works, unless I'm missing something?

@jcalz
Copy link

jcalz commented Jun 14, 2023

I guess I'm tagged on this? If I were trying to write AbstractClass I'd probably do it this way:

type _AC<T, A extends unknown[]> = abstract new (...args: A) => T;
interface AbstractClass<T, Arguments extends unknown[] = any[]> extends _AC<T, Arguments> {
    prototype: T;
};

type Test = AbstractClass<Date>["prototype"];
// type Test = Date

const d: AbstractClass<Date> = Date; // okay
new d(); // Cannot create an instance of an abstract class.

Playground link

@ajvincent
Copy link
Contributor Author

ajvincent commented Jun 16, 2023

We (or at least, I) still haven't nailed this corrected type yet. I didn't test for constructor parameters in the class type. The following test fails in the playground.

type Class<T, Arguments extends unknown[] = any[]> = {
    new (...args: Arguments): T;
    prototype: T;
};

type ClassWithArgs = Class<{counter: number}, [number]>;

const MyClassWithArgs: ClassWithArgs = class {
    counter: number;

    // @ts-expect-error I expect this to require a single argument
    constructor() {
        this.counter = 7;
    }
}

Playground link

What am I missing here? I also tried jcalz's second variation.

EDIT Here's what I missed. The constructor parameters are type-checked when we try to construct an instance:

type A = [number] extends unknown[] ? true : false;
//   ^?

type Class<T> = {
    new (p: number): T;
    prototype: T;
};

class E {
    constructor() {

    }
};

const B: Class<object> = E;

type C = ConstructorParameters<typeof B>;
//   type C = [number]

type D = ConstructorParameters<typeof E>;
//   type D = any[]

// @ts-expect-error for not having a parameter
const F = new B();

const G = new B(7);

Updated playground link

@sindresorhus sindresorhus merged commit 8edb681 into sindresorhus:main Jun 20, 2023
8 checks passed
@ajvincent ajvincent deleted the class-fix branch June 20, 2023 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to use prototype property of type constructed with Class utility
3 participants