Does flow's structural subtyping "forget" specific subtype properties?

114 views Asked by At

I am currently studying structural typing. I am skeptical about the idea that two types are considered equivalent just because they happen to have a portion of their structure in common. This feels a lot like static duck typing and it completely ignores the semantic level of types. So I took a closer look at flow's structural typing of ordinary objects and have encountered the following behavior:

const o:{} = {foo: true};
o.foo; // type error

{} is a structural type and the supertype of all ordinary objects. Hence it makes sense that I can annotate o with it, because {foo: true} is a structural subtype of {}. However, when I try to access the existing foo property, this operation doesn't type check. This is odd because AFAIK a structural subtype can usually contain specific properties as long as it also includes all required properties of its supertype.

It seems as flow's structural subtyping algorithm occasionally forgets properties that are specific to a certain subtype. Is this behavior intended or did I just run into an edge case?

1

There are 1 answers

2
loganfsmyth On

The overall issue you're describing is a fact of casting from a subtype to a supertype. By performing the cast, you're explicitly telling the compiler to discard information about a given object.

For instance, even without structural typing, if you do

class Animal {}

class Cat extends Animal {
  foo: bool = true;
}

const c: Animal = new Cat();

console.log(c.foo);

(On flow.org/try)

it fails to typecheck for the same reason. In your example you've explicitly told the compiler "consider o to have the type {}", just like in my example I've said "consider c to have the type Animal". Because of that, the compiler has explicitly been told to forget that it is working with a Cat, therefore it forgets that the object has a .foo property.

It seems as flow's structural subtyping algorithm occasionally forgets properties that are specific to a certain subtype. Is this behavior intended or did I just run into an edge case?

So to answer this, it doesn't do it "occasionally", it does it exactly when you tell it to do it. The behavior is absolutely intended.

This is odd because AFAIK a structural subtype can usually contain specific properties as long as it also includes all required properties of its supertype.

The object does contain that property, which is 100% fine, but you've explicitly erased that information by casting the value to a supertype.