Hone logo
Hone
Problems

TypeScript Type Compatibility Checker

This challenge involves creating functions that can determine if one TypeScript type is compatible with another. This is a fundamental concept in TypeScript's design, enabling it to perform static type checking and catch potential errors before runtime. Mastering this will deepen your understanding of how TypeScript infers and validates types.

Problem Description

Your task is to implement a set of functions in TypeScript that act as type compatibility checkers. You will need to simulate a simplified version of TypeScript's structural typing system. Specifically, you need to create functions that can check for compatibility between:

  1. Primitive types: Compatibility between string, number, boolean, null, undefined, symbol, and bigint.
  2. Object types: Compatibility between object shapes, considering their properties and their types.
  3. Union types: Compatibility between types that can be one of several possibilities.
  4. Intersection types: Compatibility between types that must satisfy all of several possibilities.
  5. Function types: Compatibility between functions, considering their parameter types and return types.

Key Requirements:

  • Structural Typing: Compatibility should be based on structure, not name. An object type A is compatible with an object type B if A has at least all the properties of B, and the types of those properties are also compatible.
  • Directionality: For object types, compatibility is typically one-way (e.g., a more specific type is compatible with a more general type).
  • Recursive Checking: Type compatibility checks should recursively handle nested types (objects within objects, unions within objects, etc.).
  • Error Handling (Implicit): The functions should return true if compatible and false otherwise. No explicit error objects are required.

Expected Behavior:

Your checker functions should accurately reflect how TypeScript would determine type compatibility in the scenarios described.

Edge Cases to Consider:

  • Empty object types.
  • Types with optional properties.
  • any type: any is compatible with everything and everything is compatible with any.
  • unknown type: unknown is not compatible with any, but any is compatible with unknown. unknown is compatible with itself.
  • Functions with varying numbers of parameters (if not all parameters are optional, the number must match).
  • Functions with rest parameters.

Examples

Example 1: Primitive Types

// Assume a function `arePrimitivesCompatible(typeA: PrimitiveType, typeB: PrimitiveType): boolean` exists

// Input:
const typeA = "string";
const typeB = "string";

// Output:
true

// Explanation: Two strings are compatible.

// Input:
const typeC = "string";
const typeD = "number";

// Output:
false

// Explanation: A string is not compatible with a number.

Example 2: Object Types (Structural)

// Assume a function `areObjectTypesCompatible(objA: TypeShape, objB: TypeShape): boolean` exists

// Input:
const objA = { a: 1, b: "hello" }; // Represents { a: number, b: string }
const objB = { a: 1 };             // Represents { a: number }

// Output:
true

// Explanation: `objA` has all the properties of `objB` (only `a`), and their types are compatible.

// Input:
const objC = { a: 1 };             // Represents { a: number }
const objD = { a: 1, b: "hello" }; // Represents { a: number, b: string }

// Output:
false

// Explanation: `objC` does not have the `b` property that `objD` requires.

Example 3: Union and Intersection Types

// Assume functions `areUnionTypesCompatible(unionType: UnionType, targetType: Type): boolean`
// and `areIntersectionTypesCompatible(intersectionType: IntersectionType, targetType: Type): boolean`

// Input (Union):
const unionType = ["string", "number"]; // Represents string | number
const targetTypeA = "string";

// Output:
true

// Explanation: "string" is one of the types in the union.

// Input (Union):
const unionTypeB = ["string", "number"]; // Represents string | number
const targetTypeB = "boolean";

// Output:
false

// Explanation: "boolean" is not present in the union.

// Input (Intersection):
const intersectionType = ["string", { length: number }]; // Represents string & { length: number }
const targetTypeC = "abc"; // Has a 'length' property (string has length)

// Output:
true

// Explanation: "abc" is a string and has a 'length' property of type number.

// Input (Intersection):
const intersectionTypeD = ["string", { length: number }]; // Represents string & { length: number }
const targetTypeD = { length: 5 }; // Does not have string type

// Output:
false

// Explanation: { length: 5 } is not a string, even though it has a length property.

Constraints

  • You will be provided with abstract representations of types. You do not need to parse TypeScript code directly. Instead, focus on implementing functions that operate on these abstract representations.
  • Representations can be primitive strings (e.g., "string"), arrays of types for unions (e.g., ["string", "number"]), and objects with property names as keys and their types as values for object types (e.g., { a: "number", b: "string" }).
  • The depth of nested types will not exceed a reasonable limit (e.g., 5-10 levels deep) to avoid excessive recursion complexity within the challenge.
  • Focus on correctness over extreme performance optimization.

Notes

  • You will likely need to define your own type aliases or interfaces to represent the abstract types (e.g., PrimitiveType, ObjectTypeShape, UnionType, IntersectionType, GeneralType).
  • Consider how to handle the any and unknown types as special cases.
  • For object type compatibility, remember that all properties in the "target" type must exist in the "source" type with compatible types.
  • For function compatibility, consider parameters and return types. A function A is compatible with a function B if B can be called with arguments that satisfy A's parameter types, and A's return type is compatible with B's return type. This can be quite complex, so focus on the common cases: same number of required parameters with compatible types, and compatible return types.
  • This challenge is about understanding the logic of type compatibility, not about building a full TypeScript compiler.
Loading editor...
typescript