-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Make isTypeAssignableTo public on TypeChecker #56448
Make isTypeAssignableTo public on TypeChecker #56448
Conversation
Looks like you're introducing a change to the public API surface area. If this includes breaking changes, please document them on our wiki's API Breaking Changes page. Also, please make sure @DanielRosenwasser and @RyanCavanaugh are aware of the changes, just as a heads up. |
@@ -5158,7 +5158,7 @@ export interface TypeChecker { | |||
/** @internal */ getPromiseLikeType(): Type; | |||
/** @internal */ getAsyncIterableType(): Type | undefined; | |||
|
|||
/** @internal */ isTypeAssignableTo(source: Type, target: Type): boolean; | |||
isTypeAssignableTo(source: Type, target: Type): boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a doc; please leave suggestions!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking on what I'd want as a user, it's mostly just knowing what kind of caching I'd have to worry about (since that was brought up by TS folks previously) and examples of what direction the assignability runs in. Vaguely:
/**
* Performs an assignability check, including using persistent caches.
* @returns Whether `source` is assignable to `type`.
* @example
* ```ts
* declare const literal: ts.Type; // Type of "abc"
* declare const primitive: ts.Type; // Type of string
* isTypeAssignableTo(literal, literal); // true
* isTypeAssignableTo(literal, primitive); // true
* isTypeAssignableTo(primitive, literal); // false
* isTypeAssignableTo(primitive, primitive); // true
* ```
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added something similar to the above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
YES! Thank you very much for getting this started!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing that might be important is the ability to resolve a type in the context of a file. For example most of our "promise checking" rules currently do really funky checks on the type to see if it has specific signatures.
The ability to do like "grab the global promise type and check if thing is assignable to that type" would vastly simplify our logic.
Similar for like Function (which I believe we shim right now via "has at least one call signature").
Error is another case we've used a few times where we do some shonky thing like "keep checking the inheritance chain until we find the last parent, ensure that parent is named error and it is in the TS lib file"
My impression from the recent thread was that this function was all that was needed to make everyone happy; is that incorrect such that we need to discuss this more? |
This will definitely do for a huge chunk of new cases! If there's ways to fetch "common" types or construct new types based on common types then we could vastly simplify some existing rules. |
#52473 added quite a few of these, but I am curious what else you're looking for. |
The only thing missing would be the ability to get a type from the globals, I think. Or less generally - some of the more "spec standard" types like Promise. As an example the |
This isn't a public method (yet), but theoretically you could use |
Given that assignability is the basic primitive the type system is built on (conditional types check assignability, generic constraints are based on assignability, |
In this instance, I meant that we couldn't remove the function without breaking the ecosystem, even though it's not public. |
@jakebailey The GitHub search in your original comment has non-JavaScript files, try this: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not tried to stick this into your codebase, but testing on ts-ast-viewer.com shows that it seems to work:
A single equality check is almost surely better than 100 lines of code! I actually would have thought people would have already used EDIT: this is not totally accurate; for non-promise Thenables, this method returns |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing: it still might need some discussion of caching.
My general feeling is that because we don't provide any mechanism to create new types (only those derived from actual code), that the cache sizes would be bounded. |
Were there specific concerns we brought up in discord? I don't remember. |
I don't recall any, besides what I mentioned in the thread here (then went back on once I realized that you couldn't just make random types and overflow the cache). |
Alrighty, if there are no more objections, I'm going to merge this one in. |
@@ -5158,7 +5158,20 @@ export interface TypeChecker { | |||
/** @internal */ getPromiseLikeType(): Type; | |||
/** @internal */ getAsyncIterableType(): Type | undefined; | |||
|
|||
/** @internal */ isTypeAssignableTo(source: Type, target: Type): boolean; | |||
/** | |||
* Returns true if the "source" type is assignable to the "target" type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Returns true if the "source" type is assignable to the "target" type. | |
* Returns true if the `source` type is assignable to the `target` type. |
For #9879
See:
This function has been exported on the checker since TypeScript 3.7. It's meaning has not changed in that time, and many people have begun depending on it.
ts-eslint
doesn't use it as it's internal (as they're trying to do "the right thing"), but using it would fix many many lint bugs, and wouldn't require waiting for 5.4 (as stated above, it's been around since 3.7 and has seemingly worked the same).We've previously been unhappy with exporting relation APIs because (1) we're free to add and remove relations at any time, (2) we're free to change relations, and (3) it's sometimes hard to document what they are.
But, assignability is assignability; it's unlikely that this relation is going to go away, and if we change its meaning, it's probably the case that dependents want that behavior change too to match the language. That and its pervasiveness has made it such that we probably can't remove it anyhow (as they say, "load-bearing").
So, this PR just exports it. This does not fully cover #9879, of course, but it seems worse to not do anything when we could just export this one well-defined function so many are already using unsafely.
cc @JoshuaKGoldberg