Flow: Transitivity using literals defining type X with matching structure for type Y

Created on 2 Apr 2016  路  3Comments  路  Source: facebook/flow

Consider this:

// @flow
type Suit =
  | "Diamonds"
  | "Clubs"
  | "Hearts"
  | "Spades";

type Rank =
  | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
  | "Jack"
  | "Queen"
  | "King"
  | "Ace";

type Card = {
  suit: Suit,
  rank: Rank,
}

type DiamondCard = { suit: "Diamonds", rank: Rank };

let x : Card = { suit: "Diamonds", rank: 2 };
let y : DiamondCard = { suit: "Diamonds", rank: 2 };

let cardIdentity = (c: Card) : Card => {
  return c;
}

cardIdentity(x);
cardIdentity(y);


/*

src/state/reducers/test.js:30
 30: cardIdentity(y);
     ^^^^^^^^^^^^^^^ function call
  4:   | "Clubs"
         ^^^^^^^ string literal `Clubs`. Expected string literal `Diamonds`, got `Clubs` instead
 20: type DiamondCard = { suit: "Diamonds", rank: Rank };
                                ^^^^^^^^^^ string literal `Diamonds`

src/state/reducers/test.js:30
 30: cardIdentity(y);
     ^^^^^^^^^^^^^^^ function call
  5:   | "Hearts"

       ^^^^^^^^ string literal `Hearts`. Expected string literal `Diamonds`, got `Hearts` instead
 20: type DiamondCard = { suit: "Diamonds", rank: Rank };
                                ^^^^^^^^^^ string literal `Diamonds`

src/state/reducers/test.js:30
 30: cardIdentity(y);
     ^^^^^^^^^^^^^^^ function call
  6:   | "Spades";
         ^^^^^^^^ string literal `Spades`. Expected string literal `Diamonds`, got `Spades` instead
 20: type DiamondCard = { suit: "Diamonds", rank: Rank };
                                ^^^^^^^^^^ string literal `Diamonds`

*/

If "Diamonds":Suit is correct, and type Card = { suit: Suit, rank: Rank, } and given that object types are structural and not nominal, and a type type DiamondCard = { suit: "Diamonds", rank: Rank }; (totally matches Card). Shouldn't any DiamondCard be usable in place of a Card because they have the same structure since "Diamond" is an effective correct value for Suit?

I understand why it could be proper to reject this premise with a type annotation on the definition of DiamondCard, something like this: type DiamondCard = { suit: ("Diamonds":Suit), rank: Rank }; but of course that's not possible.

Is this a bug or am I missing something?

Note: The title sucks, feel free to change it to something more appropriate!

Most helpful comment

Great question.

Given types A and B where A <: B and object types OA = { p: A } and OB = { p: B }, it is _not_ the case that OA <: OB.

Because objects are mutable, the subtyping relationship between objects is property-wise _invariant_.

We're working on readonly properties, which would enable property-wise covariance.

All 3 comments

Great question.

Given types A and B where A <: B and object types OA = { p: A } and OB = { p: B }, it is _not_ the case that OA <: OB.

Because objects are mutable, the subtyping relationship between objects is property-wise _invariant_.

We're working on readonly properties, which would enable property-wise covariance.

So many things I dind't get there, but that's just my ignorance on the topic and the syntax you used I suppose. So does it make sense to leave this open while 1: I learn a bit and can keep up with the conversation, and 2: you release what you are working on and consider a possible way to approach this?

And, this now works on master with a small change:

type Card = {
  +suit: Suit,
  +rank: Rank,
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

funtaps picture funtaps  路  3Comments

mmollaverdi picture mmollaverdi  路  3Comments

Beingbook picture Beingbook  路  3Comments

damncabbage picture damncabbage  路  3Comments

john-gold picture john-gold  路  3Comments