Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Codebase Compiler Checker

Nathan Shively-Sanders edited this pageSep 2, 2022 ·1 revision

Checker

Ok, yeah, so it's a 40k LOC file. Why 40k lines in one file? Well there's a few main arguments:

  • All of the checker is in one place.

  • Save memory by making it a global, to quote a comment in the parser:

      // Implement the parser as a singleton module. We do this for perf reasons because creating parser instances  // can actually be expensive enough to impact us on projects with many source files.

Lots of these functions need to know a lot about each other, the top of the functioncreateTypeChecker has a setof variables which are global within all of these functions and are liberally accessed.

Switching to different files means probably making [god objects][god], and the checker needs to be extremely fast.We want to avoid additional calls for ambient context. There are architectural patterns for this, but it's betterto assume good faith that they've been explored already (8 years down the line now.)

Anyway, better to get started somewhere. Iasked onlineabout how people would try to study a file like this and I think one of the best paths is by following aparticular story as a file gets checked.

An entry-point

The likely entry point for type checking is via a Program. The program has a memoized typechecker created ingetDiagnosticsProducingTypeChecker which creates a type checker.

The initial start of type checking starts withgetDiagnosticsWorker, worker in this case isn't a threadingterm I believe ( at least I can't find anything like that in the code ) - it is set up to listen for diagnosticresults (e.g. warns/fails) and then triggerscheckSourceFileWorker.

This function starts at the rootNode of any TS/JS file node tree:SourceFile. It will then have to recursethrough all of theSyntax Tree nodes in it's tree.

It doesn't start with a single recursive function though, it starts by looking through the SourceFile'sstatements and through each one of those to get all the nodes. For example:

// Statement 1consthi=()=>"Hello";// Statement 2console.log(hi());

Which looks a bit like:

SourceFile statements:  - VariableStatement    - declarationList: VariableDeclarationList# (because any const can have many declarations in a row... )      - variables: VariableStatement        - etc  - ExpressionStatement    - expression: CallExpression# outer console.log      - expression: PropertyAccessExpression        - etc      - arguments: CallExpression        - etc

See AST Explorer

Each node has a different variable to work with (so you can't just sayif node.children { node.children.foreach(lookAtNode) } ) but instead you need to examine each node individually.

Checking a Statement

Initially the meat of the work starts incheckSourceElementWorker which has aswitch statement thatcontains all legitimate nodes which can start a statement. Each node in the tree then does it's checking.

Let's try get a really early error, with this bad code:

// A return statement shouldn't exist here in strict mode (or any mode?)return;~~~~~~

It goes intocheckReturnStatement which usesgetContainingFunction to determine if thereturnlives inside afunction-like node ( e.g.MethodSignature,CallSignature,JSDocSignature,ConstructSignature,IndexSignature,FunctionType,JSDocFunctionType,ConstructorType).

Because the parent of thereturn statement is the root (parent: SourceFileObject) then it's going to fail.This triggersgrammarErrorOnFirstToken which will raise the error:A 'return' statement can only be used within a function body.ts(1108) and declare the error underline to thefirst token inside that node.

Checking Type Equality

constmyString="Hello World";constmyInt=123;// Error: This condition will always return 'false' since the types '123' and '"Hello World"' have no overlap.if(myInt===myString){// Do something}

To get to this error message:

Caching Data While Checking

Note there are two ways in which TypeScript is used, as a server and as a one-off compiler. In a server, we wantto re-use as much as possible between API requests, and so the Node tree is treated as immutable data until thereis a new AST.

This gets tricky inside the Type Checker, which for speed reasons needs to cache data somewhere. The solution tothis is theNodeLinks property on a Node. The Type Checker fills this up during the run and re-uses it,then it is discarded next time you re-run the checker.

Type Flags

Because TypeScript is astructural type system, every type can reasonably be compared with every other type.One of the main ways in which TypeScript keeps track of the underlying data-model is via theenum TypeFlags. Accessed via.flags on any node, it is a value which is used via bitmasking to let youknow what data it represents.

If you wanted to check for whether the type is a union:

if(target.flags&TypeFlags.Union&&source.flags&TypeFlags.Object){// is a union object}

When running the compiler in debug mode, you can see a string version of this viatarget.__debugFlags.

Type Comparison

The entry point for comparing two types happens incheckTypeRelatedTo. This function is mostly abouthandling the diagnostic results from any checking though and doesn't do the work. The honour of that goes to[isRelatedTo][18] which:

  • Figures out what the source and target types should be (based on freshness (a literal which was created in anexpression), whether it is substituted () or simplifiable (a type which depends first on resolving anothertype))

  • First, check if they're identical viaisIdenticalTo. The check for most objects occurs inrecursiveTypeRelatedTo, unions and intersections have a check that compares each value ineachTypeRelatedToSomeType which eventually callsrecursiveTypeRelatedTo for each item in thetype.

  • The heavy lifting of the comparison depending on what flags are set on the node is done instructuredTypeRelatedTo. Where it picks off one by one different possible combinations for matching andreturns early as soon as possible.

A lot of the functions related to type checking return a [Ternary][24], which an enum with three states, true,false and maybe. This gives the checker the chance to admit that it probably can't figure out whether a match istrue currently (maybe it hit the 100 depth limit for example) and potentially could be figured out coming in froma different resolution.

TODO: what are substituted types?

Debugging Advice

  • If you want to find a diagnostic in the codebase, search fordiag(WXZY e.g``, you'll findsrc/compiler/diagnosticInformationMap.generated.ts has it being referenced by a key. Searchfor that key.

  • If you're working from an error and want to see the path it took to get there, you can add a breakpoint increateFileDiagnostic which should get called for all diagnostic errors.

Want to contribute to this Wiki?

Fork it and send a pull request.

News

Debugging TypeScript

Contributing to TypeScript

Building Tools for TypeScript

FAQs

The Main Repo

Clone this wiki locally

[8]ページ先頭

©2009-2025 Movatter.jp