Simple Type Checker in Rust
This challenge asks you to implement a basic type checker for a simplified expression language in Rust. Type checking ensures that operations are performed on compatible data types, preventing runtime errors and improving code reliability. This exercise will test your understanding of Rust's type system, pattern matching, and data structures.
Problem Description
You are tasked with building a type checker for a language with the following data types: Int, Bool, and String. Expressions in this language can be:
- Literals:
Int(i32),Bool(bool),String(String) - Binary Operations:
BinOp(Operator, Expression, Expression)whereOperatorcan beAdd,Sub,Mul,Div,Eq,Lt. - Variables:
Var(String)(for simplicity, variable types are not stored; the type checker must infer them).
The type checker should determine the type of each expression. If an expression is type-safe (i.e., operations are valid for the given types), it should return Ok(Type). If an expression is type-unsafe (e.g., adding a string to an integer), it should return Err(String) with a descriptive error message.
Key Requirements:
- Implement the
Expressionenum andOperatorenum as described above. - Implement a
Typeenum with variantsIntType,BoolType, andStringType. - Implement a
type_checkfunction that takes anExpressionand returns aResult<Type, String>. - Handle all supported operators and their type compatibility rules.
- Infer the type of variables (assume variables are always defined and have a type). For this challenge, assume that
Var("x")always has typeIntType,Var("y")always has typeBoolType, andVar("z")always has typeStringType. All other variables should return an error.
Expected Behavior:
The type checker should correctly determine the type of valid expressions and return an error for invalid ones. Error messages should be informative.
Edge Cases to Consider:
- Division by zero (should not be a type error, but a runtime error – this challenge does not require handling runtime errors, only type errors).
- Unsupported operators between incompatible types.
- Undefined variables.
- Empty expressions (though the language doesn't explicitly define these, consider how your code handles them).
Examples
Example 1:
Input: BinOp(Add, Int(5), Int(10))
Output: Ok(IntType)
Explanation: Adding two integers results in an integer.
Example 2:
Input: BinOp(Eq, Int(5), Bool(true))
Output: Err("Type mismatch: cannot compare Int and Bool")
Explanation: Comparing an integer and a boolean is not allowed.
Example 3:
Input: BinOp(Mul, Var("x"), Int(2))
Output: Ok(IntType)
Explanation: Variable "x" is assumed to be of type Int, and multiplying an integer by an integer results in an integer.
Example 4:
Input: BinOp(Add, Var("x"), Var("y"))
Output: Err("Type mismatch: cannot add Int and Bool")
Explanation: Variable "x" is assumed to be of type Int, and variable "y" is assumed to be of type Bool. Adding an integer and a boolean is not allowed.
Example 5:
Input: BinOp(Lt, Var("y"), Var("x"))
Output: Ok(BoolType)
Explanation: Variable "y" is assumed to be of type Bool, and variable "x" is assumed to be of type Int. Comparing a boolean and an integer is allowed, resulting in a boolean.
Constraints
- The language only supports
Int,Bool, andStringtypes. - The language only supports the operators
Add,Sub,Mul,Div,Eq, andLt. - Variable names are limited to single characters: "x", "y", and "z".
- The type checker should be reasonably efficient (avoid unnecessary recursion or data structures). Performance is not a primary concern, but avoid obviously inefficient solutions.
- Error messages should be clear and concise.
Notes
- Consider using pattern matching extensively to handle different expression types.
- Think about how to represent types and operators in a clear and maintainable way.
- Start with simple expressions and gradually increase complexity.
- The variable type inference is a simplification for this exercise. In a real-world type checker, variable types would be stored and used during type checking.
- Focus on correctness and clarity over extreme optimization.
- The
Divoperator should not trigger a type error if the divisor is zero. It's a runtime concern, not a type concern.