1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//! Enum type checking (detection, member types, assignability, const enums).
//!
//! This module extends `CheckerState` with utilities for enum-related
//! type checking operations.
use crate::state::CheckerState;
use tsz_binder::symbol_flags;
use tsz_solver::TypeId;
// =============================================================================
// Enum Type Checking Utilities
// =============================================================================
impl<'a> CheckerState<'a> {
// =========================================================================
// Enum Type Detection
// =========================================================================
/// Check if a type is an enum type.
///
/// Returns true if the type represents a TypeScript enum.
pub fn is_enum_type(&self, type_id: TypeId) -> bool {
// Use resolve_type_to_symbol_id which handles both Lazy and Enum variants
if let Some(sym_id) = self.ctx.resolve_type_to_symbol_id(type_id)
&& let Some(symbol) = self.ctx.binder.get_symbol(sym_id)
{
return (symbol.flags & symbol_flags::ENUM) != 0;
}
false
}
/// Check if a type is a const enum type.
///
/// Const enums are fully inlined and cannot be accessed at runtime.
pub fn is_const_enum_type(&self, type_id: TypeId) -> bool {
// Use resolve_type_to_symbol_id which handles both Lazy and Enum variants
if let Some(sym_id) = self.ctx.resolve_type_to_symbol_id(type_id)
&& let Some(symbol) = self.ctx.binder.get_symbol(sym_id)
{
return (symbol.flags & symbol_flags::ENUM) != 0
&& (symbol.flags & symbol_flags::CONST_ENUM) != 0;
}
false
}
/// Check if a type is a regular (non-const) enum type.
pub fn is_regular_enum_type(&self, type_id: TypeId) -> bool {
self.is_enum_type(type_id) && !self.is_const_enum_type(type_id)
}
// =========================================================================
// Enum Member Type Utilities
// =========================================================================
/// Check if a type could be an enum member type.
///
/// Enum members can be:
/// - String literals (for string enums)
/// - Numeric literals (for numeric enums)
/// - Computed values (for heterogeneous enums)
pub fn is_enum_member_type(&self, type_id: TypeId) -> bool {
use tsz_solver::type_queries::LiteralTypeKind;
// Check for string or number literals
match tsz_solver::type_queries::classify_literal_type(self.ctx.types, type_id) {
LiteralTypeKind::String(_) | LiteralTypeKind::Number(_) => return true,
_ => {}
}
// Check for union type
if tsz_solver::type_queries::is_union_type(self.ctx.types, type_id) {
return true;
}
// Check for primitive types
type_id == TypeId::STRING || type_id == TypeId::NUMBER
}
/// Get the base member type for an enum (string or number).
///
/// Returns:
/// - STRING for string enums
/// - NUMBER for numeric enums
/// - UNION for heterogeneous enums
/// - UNKNOWN if the enum kind cannot be determined
pub fn get_enum_member_base_type(&self, type_id: TypeId) -> TypeId {
// If this is already a primitive type, return it
if type_id == TypeId::STRING || type_id == TypeId::NUMBER {
return type_id;
}
// Check if it's a union - indicates heterogeneous enum
if tsz_solver::type_queries::is_union_type(self.ctx.types, type_id) {
return type_id; // Return the union as-is
}
// Check for enum types (both Lazy and Enum variants)
if self.ctx.resolve_type_to_symbol_id(type_id).is_some() {
// For enum types, we need to check the member types
// Default to STRING for string enums, NUMBER for numeric enums
return TypeId::UNKNOWN;
}
TypeId::UNKNOWN
}
// =========================================================================
// Enum Assignability
// =========================================================================
/// Check if enum member types are compatible.
///
/// TypeScript allows enum members to be compared if they are from
/// compatible enum types (string enum members are string-compatible,
/// number enum members are number-compatible).
pub fn enum_members_compatible(&self, enum_type1: TypeId, enum_type2: TypeId) -> bool {
// If both are the same enum type, they're compatible
if enum_type1 == enum_type2 {
return true;
}
// Check if both are string enums or both are number enums
let base1 = self.get_enum_member_base_type(enum_type1);
let base2 = self.get_enum_member_base_type(enum_type2);
(base1 == TypeId::STRING && base2 == TypeId::STRING)
|| (base1 == TypeId::NUMBER && base2 == TypeId::NUMBER)
}
/// Check if an enum type is assignable to another type.
///
/// Enums are assignable to:
/// - Their exact enum type
/// - The primitive type (string/number) for literal enum members
pub fn is_enum_assignable_to(&mut self, enum_type: TypeId, target_type: TypeId) -> bool {
// Exact type match
if enum_type == target_type {
return true;
}
// Check if target is the base primitive type
let base_type = self.get_enum_member_base_type(enum_type);
if base_type == target_type {
return true;
}
// Use subtype checking for more complex cases
self.is_assignable_to(enum_type, target_type)
}
// =========================================================================
// Enum Expression Utilities
// =========================================================================
/// Check if an enum access is allowed (not a const enum).
///
/// Const enums cannot be accessed as values at runtime - they are fully inlined.
/// This check prevents runtime errors from accessing const enum members.
pub fn is_enum_access_allowed(&self, enum_type: TypeId) -> bool {
!self.is_const_enum_type(enum_type)
}
/// Get the type of an enum member access.
///
/// For enum members, this returns the literal type of the member
/// (e.g., "Red" for `Color.Red` in a string enum).
pub fn get_enum_member_access_type(&self, enum_type: TypeId) -> TypeId {
self.get_enum_member_base_type(enum_type)
}
// =========================================================================
// Boxed Primitive Type Detection
// =========================================================================
/// Check if a type is a boxed primitive type (Number, String, Boolean, `BigInt`, Symbol).
///
/// TypeScript has two representations for primitives:
/// - `number`, `string`, `boolean` - primitive types (valid for arithmetic)
/// - `Number`, `String`, `Boolean` - interface wrapper types from lib.d.ts (NOT valid for arithmetic)
///
/// This method detects the boxed interface types to emit proper TS2362/TS2363/TS2365 errors.
pub fn is_boxed_primitive_type(&self, type_id: TypeId) -> bool {
// Get the symbol associated with this type
let sym_id = match self.ctx.resolve_type_to_symbol_id(type_id) {
Some(sym_id) => sym_id,
None => return false,
};
// Get the symbol
let symbol = match self.ctx.binder.get_symbol(sym_id) {
Some(symbol) => symbol,
None => return false,
};
// Check if it's an interface (not a class or type alias)
if (symbol.flags & symbol_flags::INTERFACE) == 0 {
return false;
}
// Get the symbol name
let name = &symbol.escaped_name;
// Check if it's one of the known boxed primitive types
matches!(
name.as_str(),
"Number" | "String" | "Boolean" | "BigInt" | "Symbol"
)
}
}