Type System Simulator in TypeScript
This challenge asks you to build a simplified type system simulator in TypeScript. Simulating a type system is crucial for understanding how languages like TypeScript, Java, and C# enforce type safety, and it's a valuable exercise in abstracting complex concepts. Your simulator will focus on basic type checking and inference.
Problem Description
You are to implement a type system simulator that can evaluate expressions and determine their types based on a predefined set of types and operations. The simulator should be able to handle basic types (number, string, boolean), and a few common operations (addition, string concatenation, logical AND). The core of the simulator is a TypeChecker class that takes an expression and returns its inferred type.
What needs to be achieved:
- Define a set of basic types (e.g.,
NumberType,StringType,BooleanType). - Implement a
TypeCheckerclass with aninferTypemethod. - The
inferTypemethod should take an expression (represented as a JavaScript object – see below) and return the inferred type. - The simulator should handle basic type checking for operations like addition and string concatenation.
- The simulator should support basic type inference.
Key Requirements:
- Expression Representation: Expressions will be represented as JavaScript objects with the following structure:
type: A string indicating the type of the expression (e.g., "literal", "binary").value: (Optional) The literal value of the expression (e.g., 10, "hello", true). Only present for literal expressions.operator: (Optional) The operator for binary expressions (e.g., "+", "+").left: (Optional) The left-hand side expression for binary expressions.right: (Optional) The right-hand side expression for binary expressions.
- Type Definitions: Create TypeScript classes or interfaces to represent the different types (e.g.,
NumberType,StringType,BooleanType). These should be abstract enough to represent the core concept of a type. - Type Checking: The
inferTypemethod should perform type checking based on the expression's structure and the types of its operands. - Type Inference: The
inferTypemethod should infer the type of an expression if it's not explicitly specified.
Expected Behavior:
The inferType method should return a type object (e.g., an instance of NumberType, StringType, or BooleanType) that represents the inferred type of the expression. If the expression is invalid or the types are incompatible, the method should return null.
Edge Cases to Consider:
- Operations on incompatible types (e.g., adding a number and a string).
- Expressions with missing operands.
- Literal values of different types.
- Nested expressions.
Examples
Example 1:
Input: { type: "literal", value: 10 }
Output: { type: "NumberType" }
Explanation: A literal value of 10 is a number.
Example 2:
Input: { type: "literal", value: "hello" }
Output: { type: "StringType" }
Explanation: A literal value of "hello" is a string.
Example 3:
Input: { type: "binary", operator: "+", left: { type: "literal", value: 5 }, right: { type: "literal", value: 3 } }
Output: { type: "NumberType" }
Explanation: Adding two numbers results in a number.
Example 4:
Input: { type: "binary", operator: "+", left: { type: "literal", value: 5 }, right: { type: "literal", value: "3" } }
Output: null
Explanation: Adding a number and a string is an invalid operation.
Example 5:
Input: { type: "binary", operator: "&&", left: { type: "literal", value: true }, right: { type: "literal", value: false } }
Output: { type: "BooleanType" }
Explanation: Logical AND of two booleans results in a boolean.
Constraints
- Expression Complexity: The expressions will be relatively simple, with a maximum nesting depth of 3.
- Supported Operators: Only the following operators are supported:
+(addition),+(string concatenation),&&(logical AND). - Type Set: Only the following types are supported:
NumberType,StringType,BooleanType. - Performance: The
inferTypemethod should execute in O(n) time, where n is the size of the expression tree. While optimization isn't the primary focus, avoid excessively inefficient algorithms.
Notes
- Start by defining the
Typeinterface/classes. This will provide a foundation for your type checking logic. - Consider using recursion to traverse the expression tree.
- Think about how to handle type compatibility between different types.
- Error handling is important. Return
nullwhen an invalid expression or type mismatch is encountered. - This is a simplified simulator. Real-world type systems are significantly more complex. The goal is to demonstrate the core principles of type checking and inference.
- Focus on clarity and correctness over extreme optimization. Well-structured code is more important than micro-optimizations.