Skip to content
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

Proposal: Type Relationship API #9879

Open
weswigham opened this issue Jul 21, 2016 · 37 comments
Open

Proposal: Type Relationship API #9879

weswigham opened this issue Jul 21, 2016 · 37 comments
Labels
API Relates to the public API for TypeScript In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@weswigham
Copy link
Member

weswigham commented Jul 21, 2016

They type checker has no public API for checking type relationships or for looking up types in a given scope. This can make writing semantic-analysis informed lint rules very difficult (either because you need to replicate a lot of logic, or because you simply don't have enough information). Since tslint enabled semantic checks we now have a popular consumer who would very much like to be able to perform meaningful semantic comparisons (See this rule for instance).

Proposal

A small new set of public API methods on the type checker:

interface TypeChecker {
  isIdenticalTo(a: Type, b: Type): boolean; // Exposes internal identity relationship
  isSubtypeOf(a: Type, b: Type): boolean; // Exposes internal structural subtype relationship
  isAssignableTo(a: Type, b: Type): boolean; // Exposes internal assignable relationship
  isComparableTo(a: Type, b: Type): boolean; // Exposes internal comparable relationship
  isInstantiationOf(a: GenericType, b: GenericType): boolean; // While not an internal relationship, it is a straightforward semantic question to ask an answer which requires internal APIs to answer

  lookupGlobalType(name: string): Type; // Looks up a global symbol named "name" with meaning SymbolFlags.Type (Identical to getGlobalType but without the expected arity based return value) - returns unknownType on failure
  lookupTypeAt(name: string, position: Node): Type // Resolve a symbol lexically at the position specified with meaning SymbolFlags.Type (identical to resolveName, but with less levers) - returns unknownType on failure

  getAnyType(): IntrinsicType;
  getStringType(): IntrinsicType;
  getNumberType(): IntrinsicType;
  getBooleanType(): IntrinsicType;
  getVoidType(): IntrinsicType;
  getUndefinedType(): IntrinsicType;
  getNullType(): IntrinsicType;
  getESSymbolType(): IntrinsicType;
  getNeverType(): IntrinsicType;
  getUnknownType(): IntrinsicType;
  getStringLiteralType(text: string): StringLiteralType; // When numeric literal types merge, they should have a similar endpoint
}

While this isn't a complete set of APIs for manipulating the type checker (I've left off an API to programatically create new types for comparison), it is enough to do meaningful analysis with existing types in the compilation without resorting to brittle (or often wrong) syntax-based solutions.

@weswigham
Copy link
Member Author

@mhegazy mhegazy added Suggestion An idea for TypeScript API Relates to the public API for TypeScript In Discussion Not yet reached consensus labels Jul 21, 2016
This was referenced Jul 21, 2016
@kitsonk
Copy link
Contributor

kitsonk commented Jul 22, 2016

I was discussing something like this today with @novemberborn in that sometimes we aren't sure why the types are what they are at a particular point, especially when they are contextually inferred.

Would it be possible to also consider potentially offering up additional meta data that would explain the type at a particular node (sort of like good ole SQL "explain plan")?

@mhegazy
Copy link
Contributor

mhegazy commented Jul 22, 2016

Would it be possible to also consider potentially offering up additional meta data that would explain the type at a particular node (sort of like good ole SQL "explain plan")?

can you elaborate on the scenario here? there are multiple ways the compiler can find a type. though, all is specified by the spec, the information is not persisted any ways. what we know is a type.

@ghost
Copy link

ghost commented Jul 3, 2017

Is there any progress on this? It would be incredibly useful to have. Just an isTypeAssignableTo would be great. Right now the only option is to link to a customly patched version of TypeScript.

@raveclassic
Copy link

raveclassic commented Oct 31, 2017

What's the status of this API? It is needed for checking yields in redux-saga (described in #19602)

Agree with @samvv - isTypeAssignableTo would be enough to check lhr and rhs of a yield expression with a custom type-based comparison which is needed sooo badly.

UPD: Have found #9943

@ibezkrovnyi
Copy link

As PR #9943 is closed now, is it possible to export only isTypeRelatedTo and *Relation which should cover @raveclassic and @samvv cases, also with identityRelation it will cover:

  1. tslint rule no-inferrable-type

    where only primitive number, string and boolean types are supported

  2. tslint-consistent-codestyle no-unnecessary-type-annotation (@ajafff)

    where typeToString is used, however it often gives false negatives, e.g. in cases like { x: number, y: number } type is not equal to { y: number, x: number }

  3. fimbullinter/wotan no-useless-assertion and prefer-for-of
  4. no-inferrable-return-types

also it will cover few closed issues, where authors were waiting for PR #9943 like:

also at least partly it should cover this request: #11728 (comment)

also it will close a few more closed by bot issues.

@weswigham @mhegazy

@rictic
Copy link
Contributor

rictic commented Nov 29, 2018

As another use case, this would enable type checking HTML template systems such as lit-html. For this code:

html`<date-picker .date=${currentDate}></date-picker>`

We'd want to assert that typeof currentDate is assignable to ElementTagNameMap['date-picker'].date.

This is applicable to many template systems.

@lukewis
Copy link

lukewis commented Nov 29, 2018

Would love to see some baby steps that could occur in this area. It seems like the tooling that would be enabled by having APIs like this would be tremendous! As @ahejlsberg pointed out, it's hard to know where to stop with these APIs, but I liked @weswigham's suggestion in the original pull request #9943 about separating the "type comparison" apis from the "type building" apis. It seems like the "type building" apis is a big task, but the type comparison apis might be a smaller effort?

@rictic
Copy link
Contributor

rictic commented Jan 10, 2019

Just chatted with @octref about improving the custom element editing experience. I think the need for these APIs is showing up across the editing ecosystem as every template system provider eventually runs into the need to type check templates.

He pointed out that the Vue LSP is working on implementing type checking of templates by doing code generation, running the typescript compiler over the generated code, then mapping diagnostics on that code back to the original source: https://github.com/vuejs/vetur/pull/681/files

Having a type relationship API would make this sort of work simpler, faster, and more robust.

@lgenzelis
Copy link

Just trying to keep this alive 😅 It would be immensely useful to have at least the isTypeAssignableTo method available to be used from typescript-eslint. My use case is a rule that prevents dangerous type assertions (i.e., that allows to do 'a' as string | number but not string as 'a'. See typescript-eslint/typescript-eslint#7173

@JoshuaKGoldberg
Copy link
Contributor

JoshuaKGoldberg commented Oct 11, 2023

Fun fact: https://github.com/JoshuaKGoldberg/TypeStat has been using TypeScript's internal isTypeAssignableTo for over 4 years now (JoshuaKGoldberg/TypeStat@0129b6d). It does so by rewriting typescript.js on disk. Over those >4 years, TypeStat has never had any issue with the API changing or having bugs - stateful or otherwise.

Here's a rough list of the typescript-eslint / area issues & PRs that are blocked or restricted on a TypeScript type assignability API:

Note that this is definitely incomplete. For many of the use cases we could have filed issues for, people didn't because they knew it was blocked on #9879. typescript-eslint/typescript-eslint#7742 is an example.

To my knowledge, we've almost never (perhaps truly never) had a need for a relationship API other than assignability. We're trying to be good citizens and not taking the TypeStat strategy of cracking open TypeScript's files for the isTypeAssignableTo API. But this is really painful.

Edit: @jakebailey and I just noticed that checker.isTypeAssignableTo does exist now - but it's marked as internal. So although libraries like TypeStat no longer need to manually rewrite files to get to isTypeAssignableTo, it's still not "public".

@JoshuaKGoldberg
Copy link
Contributor

Just for visibility, I'm planning on having typescript-eslint take a dependency on the private checker.isTypeAssignableTo API in its next major version (v7): typescript-eslint/typescript-eslint#7936. Hopefully that will give helpful feedback on where to take a future public API later on. Someone should go and yell at us over there if that's a bad idea. 🙂

@jakebailey
Copy link
Member

I just merged #56448 which makes isTypeAssignableTo public on the checker for TS 5.4. Check out the PR description and associated links for the justification; the design meeting feedback was all positive. Hard not to be when the external usage has made it load-bearing!

This obviously isn't the entire proposal in the thread (which contains many more relationships and getters), but it is likely the most valuable function given all of the feedback above. It also has the benefit of having been around for a very long time with the same name and behavior, so is a good option for those looking to just "start using it now".

This issue is nowhere near closed, of course. Please do provide feedback for the rest!

@Tofandel
Copy link

Tofandel commented Mar 13, 2024

Is there any way to use isTypeAssignableTo inside a ts file?

I'm looking for a way to check that a type I'm maintaining is assignable to the type of a library (For a unified data model that is extensible)

I simply want typescript to error if my provided type is not assignable to the library type

It seems right now I can do this

const product = null as unknown as ProductCatalog as SfProductCatalogItem; // Error if ProductCatalog and SfProductCatalogItem are not compatible

But it's not typescript only and will polute the output

@jakebailey
Copy link
Member

You're effectively asking for #23689/#40468, but you can check this without any runtime emit using extends and a type which only accepts true: Playground Link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Relates to the public API for TypeScript In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.