Skip to content

Instantly share code, notes, and snippets.

@hi-ogawa
Last active July 19, 2022 03:41
Show Gist options
  • Save hi-ogawa/008ff7aa36913749b000b7bea82e9d13 to your computer and use it in GitHub Desktop.
Save hi-ogawa/008ff7aa36913749b000b7bea82e9d13 to your computer and use it in GitHub Desktop.
Reading Typescript

Running Code

npm ci
npx gulp local
npx gulp watch-tsc
node inspect built/local/tsc.js tests/cases/compiler/2dArrays.ts
npx gulp runtests -r spec -t 2dArrays
npx mocha -R scripts/failed-tests -O "reporter=spec" -g "2dArrays" --colors -t 40000 built/local/run.js

# update basline snapshots
npx gulp baseline-accept

Bisecting

git bisect start main v4.3.5 --
git bisect run bash test.sh
  • test.sh
#!/bin/bash

npm i && git restore package-lock.json && npx gulp runtests --no-lint -r spec -t "autoCloseTagCrash" || exit 1
  • tests/cases/fourslash/autoCloseTagCrash.ts
/// <reference path='fourslash.ts' />

// @Filename: /0.tsx
////const x = <li>
////    <div>
////        <div>/*0*/
////    </div>
////</li>

verify.jsxClosingTag({
  0: { newText: "</div>" },
});

Code Organization

/src
  /tsc/tsc.ts
  /executeCommandLine/executeCommondLine.ts
  /testRunner (becomes run.js and loaded to mocha when `gulp runtests`)
    /runner.ts
    /unittests/ (usual mocha tests)
    /compilerRunner.ts
      (programmatically generate "describe/it" based on /tests/cases files and
       compare the output with /tests/baseline/references)
  /compiler
    /types.ts (core interfaces e.g. SourceFile, Program, Node, Type, etc...)
    /program.ts
    /parser.ts (9K LOC)
    /checker.ts (40K LOC)
    /emitter.ts (5K LOC)
    /binder.ts

/tests
  /cases
  /baseline/references

Code Flow

ts.executeCommandLine, executeCommandLineWorker =>
  performCompilation =>
    createCompilerHostWorker
    createProgram (construct `Program` instance) =>
      processRootFile, processSourceFile, findSourceFile, findSourceFileWorker, host.getSourceFile =>
        createSourceFile => Parser.parseSourceFile
    emitFilesAndReportErrorsAndGetExitStatus => emitFilesAndReportErrors =>
      program.getGlobalDiagnostics => getDiagnosticsProducingTypeChecker =>
        createTypeChecker =>
          initializeTypeChecker =>
            bindSourceFile for each source
      program.getSyntacticDiagnostics ... sourceFile.parseDiagnostics
      program.getSemanticDiagnostics ... getBindAndCheckDiagnosticsForFileNoCache => typeChecker.getDiagnostics
      program.emit ... emitFiles

parseSourceFile [parser.ts] => TODO

bindSourceFile [binder.ts] => TODO

getDiagnostics [checker.ts] ... checkSourceFileWorker =>
  checkSourceElement for each statement => checkSourceElementWorker (branches by Node.kind)

emitFiles [emitter.ts] => TODO

control flow analysis and type narrowing

  • examples
    • tests/cases/conformance/expressions/typeGuards
    • tests/cases/conformance/types/union/discriminatedUnionTypes1.ts
    • tests/cases/compiler/narrow[XXX].ts (e.g. narrowTypeByInstanceof.ts)
    • baseline snapshots include inferred types e.g. in tests/baselines/reference/narrowTypeByInstanceof.types
      • TODO: how is .types file generated?
  • types
    • typeof
    • instanceof
    • discriminated union
bindSourceFile => ??

tests

CompilerBaselineRunner.runSuite =>
  new CompilerTest => Harness.Compiler.compileFiles => compiler.compileFiles =>
    ts.createProgram (parser)
    Program.emit
    ts.createCompilerDiagnostic (binder, checker)
    new CompilationResult
  CompilerTest.verifyTypesAndSymbols =>
    Compiler.doTypeAndSymbolBaseline =>
      TypeWriterWalker.getTypes => writeTypeOrSymbol =>
        TypeChecker.getTypeAtLocation => ...
        TypeChecker.getSymbolAtLocation => ...

#
# data structure
#

CompilerTest
  CompilationResult
    ts.Program

type checker

  • control flow type narrowing
  • generic
createTypeChecker =>
  checker: TypeChekcer = { ... }
  initializeTypeChecker => bindSourceFile => ...
  return checker


TypeChecker.getDiagnostics => getDiagnosticsWorker =>
  checkSourceFileWithEagerDiagnostics => checkSourceFile => checkSourceFileWorker =>
    checkGrammarSourceFile => ...
    checkSourceElement => checkSourceElementWorker =>
    checkDeferredNodes => ??


checkSourceElementWorker =>
  check `isReachableFlowNode` for `Diagnostics.Unreachable_code_detected` => ??
  # switch by `SyntaxKind` e.g.
    # if "return <expr>"
      checkReturnStatement =>
        getContainingFunctionOrClassStaticBlock
        getSignatureFromDeclaration => ??
        getReturnTypeOfSignature => ??
        checkExpressionCached => checkExpression => ...
        checkTypeAssignableToAndOptionallyElaborate => ??


checkExpression (return `Type`) =>
  checkExpressionWorker =>
    # switch by SyntaxKind e.g.
      # if Identifier
        checkIdentifier =>
      # if BinaryExpression
        checkBinaryExpression =>
  instantiateTypeWithSingleGenericCallSignature => ??


checkCallExpression =>
  getResolvedSignature => resolveSignature => resolveCallExpression =>
    checkExpression (get type of function) => ...
    getSignaturesOfType (union from function type overload?)
    resolveCall =>
      reorderCandidates
      chooseOverload =>
        checkTypeArguments =>
          # for each type parameter
            getConstraintOfTypeParameter
            createTypeMapper
            checkTypeAssignableTo => ...
        createInferenceContext
        inferTypeArguments =>
          # infer type arguments based on "contextual" return type
            getContextualType => ...
            getReturnTypeOfSignature => ...
          # for each argument
            checkExpressionWithContextualType
            inferTypes => inferFromTypes => ??
          getInferredTypes => ??
        getSignatureInstantiation =>
          getSignatureInstantiationWithoutFillingInTypeArguments => ..
        getSignatureApplicabilityError => ??
      # report error if signature not found
  getReturnTypeOfSignature =>
    instantiateType => instantiateTypeWithAlias => instantiateTypeWorker =>
      # switch by TypeFlags e.g.
      # if TypeParameter
        getMappedType => ??
      # if Object (includes mapped type, interface, etc...)
        getObjectTypeInstantiation => ??

checkIdentifier =>
  getResolvedSymbol => resolveName(.. SymbolFlags.Value | SymbolFlags.ExportValue ..) =>
    resolveNameHelper =>
      # loop to traverse back AST ancestors.
      # if `Node.locals` (TODO: binder sets up `locals`)
        getSymbol(SymbolTable, __String, SymbolFlags) (SymbolFlags filters symbols by its semantics e.g. whether `Value` or `Type`)
  getExportSymbolOfValueSymbolIfExported => ...
  getNarrowedTypeOfSymbol => getTypeOfSymbol (plus additional hack for early destructuring?)
  # magic of control flow narrowing
  getControlFlowContainer
  getFlowTypeOfReference =>
    # main recursive call + loop for traversing flow
    getTypeAtFlowNode =>
      # switch by FlowFlags e.g.
      # if FlowFlags.BranchLabel
        getTypeAtFlowBranchLabel =>
          for each antecedent of FlowLabel
            getTypeAtFlowNode(antecedent)
          getUnionOrEvolvingArrayType => ...

      # if FlowFlags.Condition (i.e. TrueCondition or FalseCondition)
        getTypeAtFlowCondition =>
          getTypeAtFlowNode (recursively for antecedent)
          narrowType(.. FlowCondition.node ..) =>
            # switch by SyntaxKind e.g.
            # if SyntaxKind.Identifier, PropertyAccessExpression, etc...
              narrowTypeByTruthiness (e.g. TypeFacts.Truthy if FlowFlags.TrueCondition) =>
                getAdjustedTypeWithFacts =>
                  getTypeWithFacts =>
                    filterType (e.g. with filter condition of getTypeFacts and TypeFacts.Truthy) =>
                      if TypeFlags.Union
                        filter UnionType.types
                getDiscriminantPropertyAccess => getCandidateDiscriminantPropertyAccess =>
                  # e.g. if PropertyAccessExpression then check isMatchingReference(reference, PropertyAccessExpression.expression)
                narrowTypeByDiscriminant =>
                  getAccessedPropertyName
                  getTypeOfPropertyOfType
                  filterType => ...
            # if unary op with ExclamationToken (i.e. negation expression "! <expr>")
              narrowType (with flipped condition) => ...

#
# binder
#

bindSourceFile => bind(Node) =>
  setParent
  bindWorker(Node) =>
    # switch SyntaxKind e.g.
      # if VariableDeclaration
        bindVariableDeclarationOrBindingElement =>
          # if isBlockOrCatchScoped
            bindBlockScopedDeclaration => declareSymbol(blockScopeContainer.locals, ...) =>
              # validate re-declaration
              getDeclarationName
              createSymbol
              SymbolTable.set
          # if isParameterDeclaration
            declareSymbolAndAddToSymbolTable => ...
      # if FunctionDeclaration
        bindFunctionDeclaration => bindBlockScopedDeclaration => ...
  getContainerFlags(Node)
  # if container
    bindContainer =>
      # update local state e.g. container, blockScopeContainer, currentFlow, etc...
      bindChildren => ...
  # otherwise
    bindChildren =>
      # switch SyntaxKind e.g.
        # "if" <expression> "then" <thenStatement> "else" <elseStatement>
          bindIfStatement =>
            bindCondition (<expression>, "then", "else") =>
              doWithConditionalBranches =>
                currentTrueTarget  = "then" label
                currentFalseTarget = "else" label
                bind (<expression>) => ...
                  # by recursing `bind`, it can mutate state e.g. `currentTrueTarget`, `currentFalseTarget`
                  # based on the structure of <expression> e.g. `! <sub-expr>` will swap these two
              # if not isLogicalExpression, etc... (in these special cases, similar routine is done in `bindLogicalLikeExpression`)
                createFlowCondition => initFlowNode (if isNarrowingExpression)
                addAntecedent
            # for <thenStatement>
              finishFlowLabel ("then")
              bind (<thenStatement>)
            addAntecedent ("postIf" to "current") => only add antescendent if not `FlowFlags.Unreachable`
            # for <elseStatement>
              finishFlowLabel ("else")
              bind (<elseStatement>)
            finishFlowLabel ("postif")
        # if ReturnStatement or ThrowStatement
          bindReturnOrThrow => set currentFlow = unreachableFlow
        # <op> <expr>
          bindPrefixUnaryExpressionFlow =>
            # if "! <expr>" then temporary swap `currentTrueTarget` and `currentFalseTarget`
            bindEachChild(node)
        # <lhs> <op> <rhs>
          bindBinaryExpressionFlow (cf. createBindBinaryExpressionFlow) =>
            onEnter => bindLogicalLikeExpression => ...
        # fallback to simply recurse children AST
          bindEachChild => bind => ...


#
# data structure
#

Node
  SyntaxKind
  FlowNode
  locals SymbolTable

Symbol
  Declaration[]
  SymbolFlags (e.g. separation of `Value` and `Type`)

TypeNode

CallExpression
  typeArguments NodeArray<TypeNode>

Signature
  instantiations Map<string, Signature>
  target Signature
  TypeMapper ??
  resolvedReturnType

ObjectType
  ObjectFlags (e.g. interface, mapped type, etc...)


#
# data structure (binder)
#

ContainerFlags (e.g. HasLocals, IsControlFlowContainer)

FlowFlags

FlowLabel < FlowNodeBase
  antecedents FlowNode[]

FlowCondition < FlowNodeBase
  node: Expression
  antecedent: FlowNode

FlowType

tmp (assignment during control flow narrowing)

bindBinaryExpressionFlow =>
  onExit =>
    # if isAssignmentOperator (e.g. "=", "+=", etc..)
      bindAssignmentTargetFlow(lhs) =>
        # e.g. if identifier
          createFlowMutation(FlowFlags.Assignment, ...)


FlowAssignment
  node: Expression
  antecedent: FlowNode


getTypeAtFlowNode =>
  # if FlowFlags.Assignment
    getTypeAtFlowAssignment =>
      getAssignmentReducedType =>
        getInitialOrAssignedType =>
          getAssignedType =>
            # if BinaryExpression (e.g. "=")
              getAssignedTypeOfBinaryExpression =>
                getTypeOfExpression(rhs) => checkExpression => ...

tmp (object literal with getter as generic parameter)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment