Skip to content

Add note about the checker's type relations #5

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

Merged
merged 3 commits into from
Apr 10, 2023

Conversation

gabritto
Copy link
Member

@gabritto gabritto commented Apr 3, 2023

No description provided.

y = x; // Error: Type 'number' is not assignable to type 'string'.
```

When computing the type of an array literal from the types of its elements, we use a different relation: **strict subtyping**. To compute the type of an array literal, we need to compare the type of each array element to one another, removing the types that are a subtype of another element type. To do this comparison between array element types, we use the strict subtyping relation, as seen in the following example:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just about arrays - it's about subtype reduction. I think we do that during inference too.


When doing overload resolution, we try to match the argument type (`any`) to the parameter type of each overload signature using the subtype relation. Because `any` is not a subtype of `string` or `number`, we pick the last signature. If we used the assignability relation, we'd go match an `any` argument with the parameter type `string` on the first `myFunc` signature, and it would match, because `any` is assignable to `string`.

> **_Note:_** something that can be confusing is how `any` has a different behavior depending on the relation being used to compare it, and how it differs from `unknown`. Basically, `any` is special because it is assignable to every type, but `any` is not a subtype of every type. And every type is assignable to `any`, and every type is a subtype of `any`. So `any` has this special behavior during assignability where it is assignable to **any**thing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you style it like

> **Note**: something that can be confusing...

it'll render like

Note: something that can be confusing...

Another usage of identity is in inference. When inferring from one type into another, we get rid of the parts of both types that are identical. For example, when inferring from one union to another union, e.g. from `boolean | string | number` to `T | string | number`, we get rid of the identical types on both sides (`string` and `number`) to end up inferring from `boolean` into `T`.
Yet another example is when checking assignability of two instantiations of the same invariant generic type: two instantiations are still related if their type arguments is identical.

> **_Implementation note:_** Most of the times, identity is used for object types, and we try to make it quick to check that by interning that information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if identity is special in this regard. We do a fast equality check and cache object types for every type relationship.

@DanielRosenwasser
Copy link
Member


We use those relations for different things in the checker, and in general we have all those different relations because sometimes we wanna be more strict, and sometimes we don't.

We use **assignability** to check if an argument can be passed to a function, or if an expression can be assigned to a variable:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true - but we use assignability for almost everything. Even for overriding types.

interface Base<T> {
    value: { prop?: T };
}

interface Derived<T> extends Base<T> {
    value: {};
}

Copy link
Member

@DanielRosenwasser DanielRosenwasser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine as-is. Consider some of the feedback I brought up, and make the examples more explicitly examples. Use the words "for example" or "for instance" to signal that subtype reduction is not specific to coming up with a best type for arrays, but it is useful

Also, I think you might want to at least briefly mention things like type narrowing where we use a combination of the relationships, and what happens when we don't use certain type relationships. You might be familiar with some examples of this based on recent design meetings, where narrowing introduces a mutual subtype, which is undesirable when joining types from multiple control-flow antecedents, and it's better to leverage strict subtypes when possible.

@gabritto gabritto merged commit 3142567 into main Apr 10, 2023
@gabritto gabritto deleted the gabritto/checker-relations branch April 10, 2023 19:12
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.

3 participants