Repository navigation
Proposal: Type Relationship API #9879
Description
Activity
weswigham commented
on Jul 21, 2016 MemberAuthorMore actions- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptDomain: APIRelates to the public API for TypeScriptRelates to the public API for TypeScriptIn DiscussionNot yet reached consensusNot yet reached consensus
on Jul 21, 2016 I was discussing something like this today with Mark Wubben (@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")?
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.
What's the status of this API? It is needed for checking yields in redux-saga (described in #19602)
Agree with Sam Vervaeck (@samvv) -
isTypeAssignableTowould 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
As PR #9943 is closed now, is it possible to export only
isTypeRelatedToand*Relationwhich should cover Kirill Agalakov (@raveclassic) and Sam Vervaeck (@samvv) cases, also withidentityRelationit will cover:- tslint rule no-inferrable-type
where only primitive number, string and boolean types are supported
- tslint-consistent-codestyle no-unnecessary-type-annotation (Klaus Meinhardt (@ajafff))
where
typeToStringis 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 } - fimbullinter/wotan no-useless-assertion and prefer-for-of
- 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.
Reacted by Peter Burns, Kirill Agalakov and Josh Ghoulberg 👻- tslint rule no-inferrable-type
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 currentDateis assignable toElementTagNameMap['date-picker'].date.This is applicable to many template systems.
Reacted by Justin Fagnani, Kirill Agalakov, Rune Mehlsen, Abdón Rodríguez, Hana Joo, Val Liu and Andrew JakubowiczWould 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 Anders Hejlsberg (@ahejlsberg) pointed out, it's hard to know where to stop with these APIs, but I liked Wesley Wigham (@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?
Reacted by Igor Bezkrovnyi, Kirill Agalakov, Rune Mehlsen and Hana JooJust chatted with Pine (@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.
Reacted by Pine, Kirill Agalakov, Rune Mehlsen, Abdón Rodríguez, Andrii Oriekhov and Hana JooI've just discovered https://www.npmjs.com/package/ts-simple-type by way of https://twitter.com/RuneMehlsen/status/1095401856493330433.
It looks like a reasonable alternative until the official TS api is public.
Reacted by Pine, Rune Mehlsen, Josh Ghoulberg 👻, tyleo, ark120202, Abdón Rodríguez, Andrii Oriekhov and Hana JooReacted by Justin Fagnani, Rune Mehlsen, Abdón Rodríguez and Hana JooI was trying to write a lint rule where the following would not be allowed,
declare const x : { foo : string }; //Should have lint error const y : { foo : string|number } = x; y.foo = 42; console.log(x.foo);
And had to write a lot of type checking code that's probably super brittle.
I'll give ts-simple-type a whirl
[EDIT]
I just tried
ts-simple-typeand it... Crashes a lot.It exceeds the max call stack size regularly and also runs into,
Couldn't find 'typeParameter' for type 'FUNCTION'
When it doesn't crash, it works great, though.
11 remaining items
Right, but before asking which method to call, you first gotta figure out what operation you want it to perform. i.e. when it should return true and when it should return false
Goal: Are these the same shape (neglecting partiality)?
This feels dirtier every second I think about it -- it seems I'm asking for:
latter = former former = latter
I don't think you want that, because that doesn't type check: https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEEQBd4DMD2MC2IYC54BveKQgZ2RgEsA7Ac3gF8BuAKFElgSVWmWR5CJKAH4KVOo1Zs2-QTHgBedFlwx2mHHmWIoAvOyA
If that is what you want, then
isAssignableTomight be the method you need.This can make writing semantic-analysis informed lint rules very difficult
Such rules would be useful, but a bit limited given that ESlint works on a per-file basis. I don't think the full type information would be available - just what can be learnt from parsing that single file.
Reacted by Robat WilliamsRobat Williams (@robatwilliams) it's entirely possible to "work around" the single file limitation and provide type-aware lint rules within ESLint.
Reacted by Robat Williams, Josh Ghoulberg 👻, islandryu and Omri LuzonIt would be great to expose these – most are already implemented and used internally. Specifically, I'd like
lookupGlobalType("Array")- added a commit that references this issue
on Jun 9, 2022 cc Daniel Rosenwasser (@DanielRosenwasser)
My use-case that needs these features but not type construction APIs is about traversing types to use Typescript as a kind of interface definition language.
I need to accomplish the following tasks:
- Break a discriminated union type down into its member parts.
- Find the discriminant property of the union type.
- Traverse all properties of a plain object type or interface type, looking for instances of a few known special relation types:
RelationPointer<Table>RelationPointer<Table>[]UUIDof<Table>UUIDof<Table>[]
- Traverse the return types of methods of interface types, including traversing promise return values.
- Detect and understand primitive types like
string,numberetc. - Detect and understand "type brands" of primitives like
string & { __meta?: SomeMetadataType }
To accomplish these tasks, I would like to use the following APIs proposed in this discussion:
- All APIs for fetching primitive types.
lookupGlobalType('Array')- needed to understand if a type is an array type.isArrayTypeis even better, but not part of the original proposal.
lookupGlobalType('Promise')- needed to traverse promises.getAwaitedTypeis even better, but not part of the original proposal.
isAssignableTo- needed to understand branded primitive types.isIdenticalTo- needed to understand non-branded primitive types.
I am happy to start a new PR (I have a branch here: https://github.com/microsoft/TypeScript/compare/main...justjake:jake--9879-type-relation-api?expand=1
Or, I can pick up the languished branch #9943
But first, can we conclude that this idea has merit, and move from "Suggestion" to "Backlog"?
Right now I believe I can work around the lack of some of these APIs by doing something like this:
// landmarks.ts export type GlobalArray<T> = Array<T> export type GlobalPromise<T> = Promise<T> export type GlobalString = string export type GlobalNumber = number
// analyze.ts const program = ts.createProgram(['landmarks.ts', /* ... */], config) interface LandmarkQuery { filePath: string exportName: string } interface LandmarkResult extends LandmarkQuery { sourceFile: ts.SourceFile symbol: ts.Symbol type: ts.Type } export function getProgramLandmarkTypes< T extends Record<string, LandmarkQuery> >(program: ts.Program, landmarks: T): { [K in keyof T]: LandmarkResult } { const typeChecker = program.getTypeChecker() const result: { [K in keyof T]: LandmarkResult } = {} as any for (const [landmarkName, { filePath, exportName }] of Object.entries( landmarks )) { const sourceFile = program.getSourceFile(rootPath(filePath)) if (!sourceFile) { throw new Error(`Cannot find source file ${filePath}`) } const fileSymbol = typeChecker.getSymbolAtLocation(sourceFile) if (!fileSymbol) { throw new Error( `Could not find type symbol for schema file ${Files.schemas}` ) } const symbol = fileSymbol.exports?.get(exportName as ts.__String) if (!symbol) { throw new Error( `Could not find export symbol ${exportName} in ${filePath}` ) } const type = getTypeOfSymbolOrThrow(typeChecker, symbol, "declared") result[landmarkName as keyof T] = { filePath, exportName, sourceFile, symbol, type, } } return result } const landmarks = getProgramLandmarkTypes(program, { string: ..., number: ..., })
But, computing assignability is a little beyond my means at the moment.
Just trying to keep this alive 😅 It would be immensely useful to have at least the
isTypeAssignableTomethod available to be used fromtypescript-eslint. My use case is a rule that prevents dangerous type assertions (i.e., that allows to do'a' as string | numberbut notstring as 'a'. See typescript-eslint/typescript-eslint#7173Reacted by Emily and Josh Ghoulberg 👻JoshuaKGoldberg commented
on Oct 11, 2023 ContributorMore actionsFun fact: https://github.com/JoshuaKGoldberg/TypeStat has been using TypeScript's internal
isTypeAssignableTofor over 4 years now (JoshuaKGoldberg/TypeStat@0129b6d). It does so by rewritingtypescript.json 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:
- Rule enforcing typeAnnotation on a complex query is correct eslint-community/eslint-plugin-eslint-plugin#340
- Feature: add a new assertion that uses assignability checking JoshuaKGoldberg/eslint-plugin-expect-type#18
- Use union types for all common classes of AST nodes #54148 (comment)
- Rule proposal: Strict interface implementation typescript-eslint/typescript-eslint#277
- Enhancement: Rule to prefer type annotation over type assertion when possible typescript-eslint/typescript-eslint#709
- Bug: [prefer-readonly] autofixer doesn't add type to property that is mutated in the constructor typescript-eslint/typescript-eslint#1056
- Bug: [prefer-reduce-type-parameter] unfixable reporting typescript-eslint/typescript-eslint#3440
- Enhancement: [no-unnecessary-type-arguments] Report inferred required types typescript-eslint/typescript-eslint#4978
- Rule proposal: prevent comparing object types typescript-eslint/typescript-eslint#5647
- Enhancement: [consistent-type-assertions] Prefer type annotation over assertion for arrays typescript-eslint/typescript-eslint#6374 (in that it presents a known edge case bug, see PR description)
- Rule proposal: disallow unused return types typescript-eslint/typescript-eslint#6442
- Rule proposal: no-unsafe-type-assertion typescript-eslint/typescript-eslint#7173
- Rule proposal: Enforce related types for getter and setter pairs typescript-eslint/typescript-eslint#7236
- Bug: [no-unnecessary-type-assertion] Does not work with enums typescript-eslint/typescript-eslint#7652
- Enhancement: [no-redundant-type-constituents] Use assignability checking for redundancy checks typescript-eslint/typescript-eslint#7742
- (edit 10/19) Rule proposal: no unchecked casts out of a potentially nullish type typescript-eslint/typescript-eslint#7757
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
isTypeAssignableToAPI. But this is really painful.Edit: Jake Bailey (@jakebailey) and I just noticed that
checker.isTypeAssignableTodoes exist now - but it's marked as internal. So although libraries like TypeStat no longer need to manually rewrite files to get toisTypeAssignableTo, it's still not "public".Reacted by Jake Bailey, Omri Luzon, Lucas Genzelis, Sam Estep, Daniel Lever, Eli and ErikReacted by SlurpTheo, Daniel Lever and ErikJoshuaKGoldberg commented
on Nov 15, 2023 ContributorMore actionsJust for visibility, I'm planning on having typescript-eslint take a dependency on the private
checker.isTypeAssignableToAPI 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. 🙂Reacted by Omri Luzon, Justin Fagnani, Cameron Little and ErikReacted by Kevin Verdieck and ErikI just merged #56448 which makes
isTypeAssignableTopublic 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!
Reacted by Sunyeop Lee, Jan Potoms, Josh Ghoulberg 👻, Sébastien Mischler, T.J. Crowder, Erik and VimHaxReacted by Omri Luzon, Josh Ghoulberg 👻, Sébastien Mischler, Tommy, Luke Deen Taylor, David Duong, Erik and EliReacted by Josh Ghoulberg 👻, Sébastien Mischler and ErikIs there any way to use
isTypeAssignableToinside 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 compatibleBut it's not typescript only and will polute the output
You're effectively asking for #23689/#40468, but you can check this without any runtime emit using
extendsand a type which only accepts true: Playground Link


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:
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.