Hone logo
Hone
Problems

Structural Subtyping Engine in TypeScript

Structural subtyping allows us to determine if one type can be used where another type is expected, based on the structure of the types (their properties and methods) rather than their declared names. This is a powerful concept found in languages like Go and provides flexibility in code design. This challenge asks you to implement a basic structural subtyping engine in TypeScript.

Problem Description

You are tasked with creating a TypeScript function isSubtype that determines if one type A is a subtype of another type B based on structural subtyping. The function should take two type definitions (represented as TypeScript type objects, not string representations) and return true if A is a subtype of B, and false otherwise.

Key Requirements:

  • Structural Comparison: The function must compare the properties and methods of the two types to determine subtyping.
  • Property Compatibility: A property in A is compatible with a property in B if:
    • The property exists in both A and B.
    • The type of the property in A is a subtype of the type of the property in B. (Recursively apply isSubtype to the property types).
    • If the property exists in B but not in A, it's not considered compatible (A must have at least the properties of B).
  • Method Compatibility: Methods are treated the same as properties for compatibility.
  • Optional Properties: Optional properties in B are always considered compatible with properties in A.
  • Readonly Properties: Readonly properties are treated the same as regular properties.
  • Union Types: Handle union types correctly. A property type in A is compatible with a property type in B if A's type is a subtype of any of the types in B's union.
  • Intersection Types: Handle intersection types correctly. A property type in A is compatible with a property type in B if A's type is a subtype of all of the types in B's intersection.
  • Primitive Types: Primitive types (string, number, boolean, symbol, etc.) are always subtypes of themselves.

Expected Behavior:

The function should return true if A is structurally a subtype of B, and false otherwise.

Edge Cases to Consider:

  • Circular references in types. (Handle these gracefully, potentially by limiting recursion depth).
  • Complex nested types.
  • Types with no properties.
  • Empty intersection types.
  • Union types with primitive types.

Examples

Example 1:

Input:
A: { x: number; y: string; }
B: { x: number; z: boolean; }
Output: false
Explanation: A is not a subtype of B because A is missing the property 'z' that B has.

Example 2:

Input:
A: { x: string; }
B: { x: number | string; }
Output: true
Explanation: A's 'x' is a subtype of B's 'x' (string is a subtype of number | string).

Example 3:

Input:
A: { x: { a: number; b: string; }; }
B: { x: { a: number; b: number; }; }
Output: true
Explanation: A's 'x' is a subtype of B's 'x' because the inner object {a: number; b: string;} is a subtype of {a: number; b: number;} (string is a subtype of number).

Example 4:

Input:
A: { x?: number; }
B: { x: number; }
Output: true
Explanation: Optional properties in B are always compatible with properties in A.

Constraints

  • The input types A and B will be valid TypeScript type objects.
  • The maximum depth of nested types will be 10. This is to prevent infinite recursion with circular references.
  • The function must be performant enough to handle reasonably sized type objects (up to 50 properties).
  • The function should not throw errors. It should gracefully handle invalid or unexpected type structures.

Notes

  • You'll likely need to use TypeScript's type manipulation capabilities (e.g., typeof, keyof, conditional types) to inspect the structure of the types.
  • Consider using recursion to handle nested types.
  • Think carefully about how to handle union and intersection types.
  • This is a simplified structural subtyping engine. It doesn't cover all the complexities of real-world type systems (e.g., generics, advanced type inference). Focus on the core concepts of structural comparison.
  • You can assume that the input types are well-formed and do not contain syntax errors.
  • The goal is to create a robust and accurate implementation of structural subtyping.
Loading editor...
typescript