Dioni Ripoll
Dioni Ripoll

Reputation: 1

Flow Type: How to create an instance of an exact type from a class

I have the following type and class:

export type ProductType = {|
  id: number,
  name: string,
  slug: string,
  tooltip: string,
  properties?: string[]
|};

class Product {
  id: number,
  name: string,
  slug: string,
  tooltip: string,
  properties?: string[]

  constructor(props: $Shape<ProductType>) {
    this.id = props.id;
    this.name = props.name || '';
    this.slug = props.slug || '';
    this.tooltip = props.tooltip || '';
    this.properties = props.properties || [];
  }
}

and I'd like to be able to do something like this:

const product: ProductType = new Product({ name: 'test' });

but flows complain saying the following:

Cannot assign `new Product()` to `product` because inexact  `Product` [1] is incompatible with exact  `ProductType`

so I'd like to know if there is any way to return an exact/freeze/seal object from the class constructor or if this is even possible and if not what other alternatives I have.

Upvotes: 0

Views: 1026

Answers (1)

Nat Mote
Nat Mote

Reputation: 4086

Unfortunately, class instances are inexact. Here's an illustration:

class Foo {
  foo: string;
  constructor() {
    this.foo = 'foo';
  }
}

class Bar extends Foo {
  bar: string;
  constructor() {
    super();
    this.bar = 'bar';
  }
}

type JustFoo = {| foo: string |};

const x: Foo = new Bar();

// Expected error -- if Flow allowed this, the exact type would be a lie!
const y: JustFoo = x;

const z: JustFoo = { foo: "foo" };

(playground)

In this case, Bar extends Foo and adds an additional property. Because classes are nominally typed, Bar is a subtype of Foo. Therefore, Foo cannot be a subtype of {| foo: string |} without breaking the type system.

The recommended way to create an exact object is to just write an object literal, like I did for z in this example.

Upvotes: 3

Related Questions