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

TypeScript type inferrer / resolver #158

Open
camchenry opened this issue Mar 13, 2025 · 0 comments
Open

TypeScript type inferrer / resolver #158

camchenry opened this issue Mar 13, 2025 · 0 comments

Comments

@camchenry
Copy link
Member

camchenry commented Mar 13, 2025

NOTE: These are some hazy thoughts I'm currently thinking through. I've been thinking about them a lot recently and I had planned to write it down regardless, but the typescript-go announcement motivated me to think about it more and write it down.

What it is

  • A new crate called something like oxc_type_inferrer or maybe even just oxc_checker for conciseness
  • A new part of the oxc toolchain that allows for analyzing and resolving types in code, in support of issues like ($495 Bounty) Type Aware Linting oxc#3105

Theoretical API:

let allocator = Allocator::default();
let source = "
const a = 1 + 2;
const x = true;
const y = "hello world";
";
let ast = Parser::new(&allocator, source, SourceType::mjs()).parse();
let resolver = Resolver::new(&allocator, ast).resolve();

let a = somehow_get_the_identifier_reference();
let arithmetic = somehow_get_expression_for("1 + 2");

let ty = resolver.getTypeAtLocation(a.span); // => Type::Number
let ty = resolver.getTypeAtLocation(arithmetic.span); // => Type::Number

assert!(!ty.isUnionType());
assert!(ty.isNumberType());

assert_eq!(resolver.getApparentType(get_expr_for("true")), Type::Boolean)
assert_eq!(resolver.getApparentType(get_expr_for("'hello world'")), Type::String)

What it is not

  • A complete type checker or drop-in replacement for tsc or tsgo

Insights that make this idea possible

Rewriting the entirely TypeScript compiler would be impossible for a team like ours. It is just too complicated, and even for the TypeScript team at Microsoft, it is a large undertaking to rewrite the compiler in Go.

1. The TypeScript compiler does a lot of things we don't need to do

The TypeScript compiler includes a lot of different functionalities, not just type checking. Much of this is already done by other oxc crates, like oxc_parser, oxc_ast, oxc_semantic, and so on. As a result, a crate for type inference can just focus on that task, and not worry about producing an ECMAScript AST or transforming code, or handling language server protocols.

Here's a general list of things the TypeScript compiler does and how it might compare with this idea:

Key:

  • 🟢 Things we would need to implement as part of this
  • 🟧 Things we might need to include, at least partially
  • ❌ Things we don't need to implement at all in this crate likely

Features:

  • 🟧 Parsing: majority of AST parsing is already done by oxc_parser
  • ❌ CLI: We don't need to support any commandline functionality, as this would be used by other crates instead.
  • 🟢 Type resolution: this is the core of the crate. Ideally, this would the only thing it does.
  • ❌ Type checking: we do not need to traverse the AST and see if types match and emit diagnostics. This new crate would support someone who was trying to create a type checker though.
  • ❌ Types in .js files and JSDoc type resolution: this could theoretically be supported in the future probably, but this is more of an IDE-specific feature for people who don't use TypeScript.
  • ❌ Code generation and transformation: this is already handled by oxc_transformer mostly
  • ❌ Language server protocol
  • ❌ Watching files and incremental rebuilds

2. Even partially working type inference would be useful

In order for this crate to be useful, it at least needs to support a subset of TypeScript's type system, but it doesn't need to support all of it.

For example, one of the "north star" lint rules for typed information is typescript-eslint's no-floating-promises rule: https://typescript-eslint.io/rules/no-floating-promises/, because it requires type information but users find it very helpful. The type inference involved with this rule primarily involves function expressions, function calls, and of course general type resolution things like inferring generic type parameters, control flow analysis, and so on. But it would not require us to have a 100% tsc conformant type inference algorithm.

Just supporting some of the cases for the typescript-eslint/no-floating-promises rule would be tremendously helpful to users. For example, just checking these example cases doesn't require all of the type system to be implemented:

const promise = new Promise((resolve, reject) => resolve('value'));
promise;

async function returnsPromise() {
  return 'value';
}
returnsPromise().then(() => {});

Promise.reject('value').catch();

Promise.reject('value').finally();

[1, 2, 3].map(async x => x + 1);

3. There is a test suite we can use for conformance and regression testing

The TypeScript compiler test suite includes thousands of test files: https://github.com/microsoft/TypeScript/tree/main/tests/baselines/reference

Each of these test cases includes:

  1. Test case file (.js)
  2. Resolved types file (.types)
  3. Symbols and scopes table file (.symbols)

Similar to the test262 repository, this would provide us with a large test suite that we can use to measure our conformance with the TypeScript type system and help prevent regressions.

Example test case:

//// [tests/cases/compiler/ArrowFunctionExpression1.ts] ////

//// [ArrowFunctionExpression1.ts]
var v = (public x: string) => { };

//// [ArrowFunctionExpression1.js]
var v = function (x) { };
//// [tests/cases/compiler/ArrowFunctionExpression1.ts] ////

=== ArrowFunctionExpression1.ts ===
var v = (public x: string) => { };
>v : Symbol(v, Decl(ArrowFunctionExpression1.ts, 0, 3))
>x : Symbol(x, Decl(ArrowFunctionExpression1.ts, 0, 9))
//// [tests/cases/compiler/ArrowFunctionExpression1.ts] ////

=== ArrowFunctionExpression1.ts ===
var v = (public x: string) => { };
>v : (x: string) => void
>  : ^ ^^      ^^^^^^^^^
>(public x: string) => { } : (x: string) => void
>                          : ^ ^^      ^^^^^^^^^
>x : string
>  : ^^^^^^

We could parse this information into a custom format and use that as part of our coverage tasks.

Reasons to work on this

  • There is huge demand for type-aware linting, which this would help support
  • We could act as an additional type provider for typescript-eslint potentially in the future, helping to speed up even projects just using ESLint still
  • Type information could assist with better minification and transformation

Reasons not to work on this

  • Implementing the TypeScript type system is a huge undertaking, and would add yet another area of maintenance/expertise
  • The new typescript-go implementation might be fast enough for all purposes, rendering the need for a Rust version obsolete

Prior art / references

@camchenry camchenry changed the title TypeScript type inferrer TypeScript type inferrer / resolver Mar 13, 2025
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

No branches or pull requests

1 participant