How typescript-go Implements Go Type to TypeScript Type Mapping

typescript-go implements the TypeScript type system by representing every TypeScript type as native Go structs and bit-flag constants, creating a static one-to-one mapping defined in the internal/checker package.

The microsoft/typescript-go repository contains a complete Go port of the TypeScript compiler. Rather than converting between Go and TypeScript types at runtime, the codebase defines a comprehensive Go type to TypeScript type mapping where TypeScript constructs are modeled directly as Go values. This architecture allows the compiler to perform type checking using native Go data structures that mirror the original TypeScript type system exactly.

Architecture of the Type System Mapping

The mapping operates as a static implementation layer. TypeScript types are not translated from Go types; instead, the Go codebase is the TypeScript type system. Every type construct—from primitives to complex generics—has a corresponding Go struct or flag constant in internal/checker.

Primitive Types as Bit-Mask Flags

Primitive TypeScript types utilize a bit-mask flag system defined in internal/core/constants.go. The TypeFlags constants represent intrinsic types like any, unknown, string, number, boolean, void, and never.

The checker.Type struct stores these flags in its flags field. Intrinsic types are created via the newIntrinsicType method:

// internal/checker/checker.go (line 964)
c.anyType = c.newIntrinsicType(core.TypeFlagsAny, "any")

This approach allows the type checker to perform fast bitwise operations to determine type categories. For example, checking if a type is the any type involves a simple bitwise AND operation against TypeFlagsAny.

Complex Object Types via ObjectFlags

For structured types—including classes, interfaces, arrays, tuples, and mapped types—typescript-go employs ObjectFlags. These bit masks are stored in the objectFlags field of checker.Type and defined alongside TypeFlags in the core constants.

When constructing object types, creation functions like newObjectType set appropriate flags to identify the type category:

  • ObjectFlagsClass for class types
  • ObjectFlagsInterface for interface types
  • ObjectFlagsArray for array types
  • ObjectFlagsMapped for mapped types

The checker.Type struct also contains a mapper *TypeMapper field to handle generic instantiations, linking concrete type arguments to their parameters.

Specialized Type Representations

Beyond bit flags, distinct Go structs model specific TypeScript type categories that require additional metadata.

Literal Types as Dedicated Structs

String, numeric, and boolean literals receive specialized struct definitions that embed the base checker.Type:

  • StringLiteralType sets TypeFlagsStringLiteral and stores the literal string value
  • NumberLiteralType sets TypeFlagsNumberLiteral for numeric constants
  • BooleanLiteralType handles true and false literals with TypeFlagsBooleanLiteral

Helper functions like newStringLiteralType construct these instances, allowing the checker to treat literal values as subtypes of their corresponding primitive types while preserving the exact value for literal type checking.

String Mapping Types

Template literal type manipulations like Uppercase<T> and Lowercase<T> map to the StringMappingType struct (defined in internal/checker/types.go around line 401). These types carry TypeFlagsStringMapping and maintain references to their target type and transformation mapper:

// Checking for string mapping types
if t.flags&core.TypeFlagsStringMapping != 0 {
    target := t.AsStringMappingType().Target()
    // Process the underlying target type...
}

Generic Type Substitution with TypeMapper

Generic type parameters and type mappings utilize a hierarchy of mapper structs defined in internal/checker/mapper.go. When instantiating a generic type, the checker builds a mapper that substitutes type parameters with concrete arguments:

  • SimpleTypeMapper maps single parameters to arguments
  • ArrayTypeMapper handles multiple type parameters
  • MergedTypeMapper combines multiple mapping operations

The resulting mapper is stored in the type's mapper field, enabling lazy resolution of generic types during type checking.

Composite Type Construction

Union and intersection types aggregate multiple types into single constructs. These map to UnionType and IntersectionType structs in internal/checker/types.go, which hold slices of constituent *Type pointers.

The getUnionType method in internal/checker/utilities.go (around line 645) handles deduplication and flag computation:

// Building a union type from constituent types
union := c.getUnionType(typeA, typeB) 
// Returns *checker.Type with TypeFlagsUnion set

This method automatically computes the resulting type flags and simplifies the union where possible, mirroring TypeScript's union reduction rules.

Summary

  • typescript-go implements TypeScript types as native Go structs and constants rather than performing runtime conversion.
  • TypeFlags and ObjectFlags bit masks in internal/core/constants.go provide efficient primitive and object type identification.
  • Literal types use dedicated structs (StringLiteralType, NumberLiteralType) to preserve exact values.
  • Generic instantiation relies on the TypeMapper hierarchy in internal/checker/mapper.go for type argument substitution.
  • Union and intersection types aggregate constituent types via UnionType and IntersectionType structs managed by checker utilities.

Frequently Asked Questions

How does typescript-go represent the TypeScript any type in Go?

The any type maps to TypeFlagsAny combined with an intrinsic type creation. In internal/checker/checker.go at line 964, the checker initializes c.anyType using c.newIntrinsicType(core.TypeFlagsAny, "any"), storing it as a *Type with the appropriate bit flag set for fast identification.

What is the difference between TypeFlags and ObjectFlags in typescript-go?

TypeFlags classify primitive and intrinsic types (string, number, any, unknown), while ObjectFlags categorize structured types (classes, interfaces, arrays, tuples). A single *Type value can carry both flag sets simultaneously, with TypeFlags indicating the general category and ObjectFlags providing specific object subcategories.

How are generic type arguments substituted during type checking?

Generic substitution uses the TypeMapper interface implementers in internal/checker/mapper.go. When instantiating a generic, the checker creates a SimpleTypeMapper or ArrayTypeMapper that maps parameter IDs to concrete argument types, then stores this mapper on the resulting type for lazy resolution during subsequent type operations.

Where are union types constructed in the typescript-go codebase?

Union type construction occurs primarily in internal/checker/utilities.go via the getUnionType function. This helper deduplicates constituent types, computes union flags, and returns a *Type backed by the UnionType struct, ensuring TypeScript's union reduction semantics are preserved in the Go implementation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →