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

P.instanceof cannot be used for classes which has a private constructor. #285

Open
LumaKernel opened this issue Sep 20, 2024 · 3 comments
Open

Comments

@LumaKernel
Copy link

Describe the bug
A clear and concise description of what the bug is.

if (instanceof C) can be used for classes which has private constructors potentially, but P.instanceof can't. It's because type AnyConstructor = abstract new (...args: any[]) => any; cannot accept private constructors.

TypeScript playground with a minimal reproduction case

Playground

export class ColorRgb {
  private constructor(
    public readonly r: number,
    public readonly g: number,
    public readonly b: number,
  ) {
    if (new.target !== ColorRgb) {
      throw new Error('OptionRed cannot be extended');
    }
    if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
      throw new Error('Invalid color');
    }
  }
  static new(r: number, g: number, b: number): ColorRgb {
    return new ColorRgb(r, g, b);
  }
}
export class ColorHsl {
  private constructor(
    public readonly h: number,
    public readonly s: number,
    public readonly l: number,
  ) {
    if (new.target !== ColorHsl) {
      throw new Error('ColorHsl cannot be extended');
    }
    if (h < 0 || h > 360 || s < 0 || s > 100 || l < 0 || l > 100) {
      throw new Error('Invalid color');
    }
  }
  new(h: number, s: number, l: number): ColorHsl {
    return new ColorHsl(h, s, l);
  }
}

type Color = ColorRgb | ColorHsl;

const color: Color = ColorRgb.new(20, 40, 60);
if (color instanceof ColorRgb) {
  color;
  // ^?
}

// Type error!
match(color)
  .with(P.instanceOf(ColorRgb), (color) => {
    console.log(`rgb(${color.r}, ${color.g}, ${color.b})`);
  })
  .with(P.instanceOf(ColorHsl), (color) => {
    console.log(`hsl(${color.h}, ${color.s}, ${color.l})`);
  })
  .exhaustive();

Versions

  • TypeScript version: 5.5.4
  • ts-pattern version: 5.0.6
  • environment: Any

I tried to find the universal class of all classes including private constructors, but I coundn't get it. Maybe I'm hitting the limitation of TypeScript...

@LumaKernel
Copy link
Author

here, it's important to combine

  • the customized programmed validation,
  • providing nominal by class,
  • disallow extending from misusage and unexpected violation of post-conditions of constructors,
  • and achieve exhaustive checked match as disjoint union

@LumaKernel
Copy link
Author

By trying {} instanceof {}, I got the following message.

The right-hand side of an 'instanceof' expression must be either of type
'any', a class, function, or other type assignable to the 'Function'
interface type, or an object type with a 'Symbol.hasInstance' method.

So it seems we can just use Function | { readonly [Symbol.hasInstance]: any } instead to support any types which can be also used in instanceof right-hand operand.

@gvergnaud
Copy link
Owner

Ah thanks for the report, that's too bad. I'm not sure I can fix it in ts-pattern because instanceOf requires it's parameter to be a subtype of abstract new (...any) => any in order to pass it to the native InstanceType helper. Without this contraint, I can't narrow the input type for this branch:

export class Color {
  private constructor(
    public readonly r: number,
    public readonly g: number,
    public readonly b: number,
  ) {}
  static new(r: number, g: number, b: number): Color {
    return new Color(r, g, b);
  }
}

type X = InstanceType<Color> // ❌

Playground

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

No branches or pull requests

2 participants