Skip to main content

tsz_checker/types/
type_node.rs

1//! Type Node Checking
2//!
3//! This module handles type resolution from AST type nodes (type annotations,
4//! type references, union types, intersection types, etc.).
5//!
6//! It follows the "Check Fast, Explain Slow" pattern where we first
7//! resolve types, then use the solver to explain any failures.
8
9use crate::context::CheckerContext;
10use tsz_parser::parser::NodeIndex;
11use tsz_parser::parser::node::NodeAccess;
12use tsz_solver::TypeId;
13use tsz_solver::Visibility;
14use tsz_solver::recursion::{DepthCounter, RecursionProfile};
15
16/// Type node checker that operates on the shared context.
17///
18/// This is a stateless checker that borrows the context mutably.
19/// All type resolution for type nodes goes through this checker.
20pub struct TypeNodeChecker<'a, 'ctx> {
21    pub ctx: &'a mut CheckerContext<'ctx>,
22    /// Recursion depth counter for stack overflow protection.
23    depth: DepthCounter,
24}
25
26impl<'a, 'ctx> TypeNodeChecker<'a, 'ctx> {
27    /// Create a new type node checker with a mutable context reference.
28    pub const fn new(ctx: &'a mut CheckerContext<'ctx>) -> Self {
29        Self {
30            ctx,
31            depth: DepthCounter::with_profile(RecursionProfile::TypeNodeCheck),
32        }
33    }
34
35    /// Check a type node and return its type.
36    ///
37    /// This is the main entry point for type node resolution.
38    /// It handles caching and dispatches to specific type node handlers.
39    pub fn check(&mut self, idx: NodeIndex) -> TypeId {
40        // Stack overflow protection
41        if !self.depth.enter() {
42            return TypeId::ERROR;
43        }
44
45        // Check cache first
46        if let Some(&cached) = self.ctx.node_types.get(&idx.0) {
47            if cached == TypeId::ERROR {
48                // Always use cached ERROR to prevent duplicate emissions
49                self.depth.leave();
50                return cached;
51            }
52
53            // For non-ERROR cached results, check if we're in a generic context
54            // If we're not in a generic context (type params are empty), the cache is valid
55            if self.ctx.type_parameter_scope.is_empty() {
56                // No type parameters in scope - cache is valid
57                self.depth.leave();
58                return cached;
59            }
60            // If we have type parameters in scope, we need to be more careful
61            // For now, recompute to ensure correctness
62            // TODO: Add cache key based on type param hash for smarter caching
63        }
64
65        // Compute and cache
66        let result = self.compute_type(idx);
67        // Don't cache TYPE_REFERENCE results here — CheckerState's
68        // get_type_from_type_node has its own TYPE_REFERENCE handler that
69        // calls get_type_from_type_reference() which emits diagnostics
70        // (TS2314, TS2304, etc.). If we cache here, the checker's handler
71        // finds the cached result and skips the diagnostic-emitting path.
72        let is_type_ref = self
73            .ctx
74            .arena
75            .get(idx)
76            .is_some_and(|n| n.kind == tsz_parser::parser::syntax_kind_ext::TYPE_REFERENCE);
77        if !is_type_ref {
78            self.ctx.node_types.insert(idx.0, result);
79        }
80
81        self.depth.leave();
82        result
83    }
84
85    /// Compute the type of a type node (internal, not cached).
86    fn compute_type(&mut self, idx: NodeIndex) -> TypeId {
87        use tsz_parser::parser::syntax_kind_ext;
88        use tsz_scanner::SyntaxKind;
89
90        let Some(node) = self.ctx.arena.get(idx) else {
91            return TypeId::ERROR;
92        };
93
94        match node.kind {
95            // Keyword types - use compile-time constant TypeIds
96            k if k == SyntaxKind::NumberKeyword as u16 => TypeId::NUMBER,
97            k if k == SyntaxKind::StringKeyword as u16 => TypeId::STRING,
98            k if k == SyntaxKind::BooleanKeyword as u16 => TypeId::BOOLEAN,
99            k if k == SyntaxKind::VoidKeyword as u16 => TypeId::VOID,
100            k if k == SyntaxKind::AnyKeyword as u16 => TypeId::ANY,
101            k if k == SyntaxKind::NeverKeyword as u16 => TypeId::NEVER,
102            k if k == SyntaxKind::UnknownKeyword as u16 => TypeId::UNKNOWN,
103            k if k == SyntaxKind::UndefinedKeyword as u16 => TypeId::UNDEFINED,
104            k if k == SyntaxKind::NullKeyword as u16 => TypeId::NULL,
105            k if k == SyntaxKind::ObjectKeyword as u16 => TypeId::OBJECT,
106            k if k == SyntaxKind::BigIntKeyword as u16 => TypeId::BIGINT,
107            k if k == SyntaxKind::SymbolKeyword as u16 => TypeId::SYMBOL,
108
109            // Type reference (e.g., "MyType", "Array<T>")
110            k if k == syntax_kind_ext::TYPE_REFERENCE => self.get_type_from_type_reference(idx),
111
112            // Union type (A | B)
113            k if k == syntax_kind_ext::UNION_TYPE => self.get_type_from_union_type(idx),
114
115            // Intersection type (A & B)
116            k if k == syntax_kind_ext::INTERSECTION_TYPE => {
117                self.get_type_from_intersection_type(idx)
118            }
119
120            // Array type (T[])
121            k if k == syntax_kind_ext::ARRAY_TYPE => self.get_type_from_array_type(idx),
122
123            // Tuple type ([T, U, ...V[]])
124            k if k == syntax_kind_ext::TUPLE_TYPE => self.get_type_from_tuple_type(idx),
125
126            // Type operator (readonly, unique, keyof)
127            k if k == syntax_kind_ext::TYPE_OPERATOR => self.get_type_from_type_operator(idx),
128
129            // Indexed access type (T[K], Person["name"])
130            k if k == syntax_kind_ext::INDEXED_ACCESS_TYPE => {
131                self.get_type_from_indexed_access_type(idx)
132            }
133
134            // Function type (e.g., () => number, (x: string) => void)
135            k if k == syntax_kind_ext::FUNCTION_TYPE => self.get_type_from_function_type(idx),
136
137            // Constructor type (e.g., new () => number, new (x: string) => any)
138            k if k == syntax_kind_ext::CONSTRUCTOR_TYPE => self.get_type_from_function_type(idx),
139
140            // Type literal ({ a: number; b(): string; })
141            k if k == syntax_kind_ext::TYPE_LITERAL => self.get_type_from_type_literal(idx),
142
143            // Type query (typeof X) - returns the type of X
144            k if k == syntax_kind_ext::TYPE_QUERY => self.get_type_from_type_query(idx),
145
146            // Mapped type ({ [P in K]: T })
147            // Check for TS7039 before TypeLowering since TypeLowering doesn't emit diagnostics
148            k if k == syntax_kind_ext::MAPPED_TYPE => self.get_type_from_mapped_type(idx),
149
150            k if k == syntax_kind_ext::THIS_TYPE
151                || k == tsz_scanner::SyntaxKind::ThisKeyword as u16 =>
152            {
153                if !self.is_this_type_allowed(idx) {
154                    use crate::diagnostics::{diagnostic_codes, diagnostic_messages};
155                    self.ctx.error(
156                        node.pos,
157                        node.end.saturating_sub(node.pos),
158                        diagnostic_messages::A_THIS_TYPE_IS_AVAILABLE_ONLY_IN_A_NON_STATIC_MEMBER_OF_A_CLASS_OR_INTERFACE.to_string(),
159                        diagnostic_codes::A_THIS_TYPE_IS_AVAILABLE_ONLY_IN_A_NON_STATIC_MEMBER_OF_A_CLASS_OR_INTERFACE,
160                    );
161                    TypeId::ERROR
162                } else {
163                    self.ctx.types.this_type()
164                }
165            }
166
167            // Fall back to TypeLowering for type nodes not handled above
168            // (conditional types, indexed access types, etc.)
169            _ => self.lower_with_resolvers(idx, true, true),
170        }
171    }
172
173    // =========================================================================
174    // Type Reference Resolution
175    // =========================================================================
176
177    /// Get type from a type reference node (e.g., "number", "string", "`MyType`").
178    fn get_type_from_type_reference(&mut self, idx: NodeIndex) -> TypeId {
179        self.lower_with_resolvers(idx, false, false)
180    }
181
182    // =========================================================================
183    // Composite Type Resolution
184    // =========================================================================
185
186    /// Get type from a union type node (A | B).
187    ///
188    /// Parses a union type expression and creates a Union type with all members.
189    ///
190    /// ## Type Normalization:
191    /// - Empty union -> NEVER (the empty type)
192    /// - Single member -> the member itself (no union wrapper)
193    /// - Multiple members -> Union type with all members
194    fn get_type_from_union_type(&mut self, idx: NodeIndex) -> TypeId {
195        let Some(node) = self.ctx.arena.get(idx) else {
196            return TypeId::ERROR;
197        };
198
199        // UnionType uses CompositeTypeData which has a types list
200        if let Some(composite) = self.ctx.arena.get_composite_type(node) {
201            let mut member_types = Vec::new();
202            for &type_idx in &composite.types.nodes {
203                // Recursively resolve each member type
204                member_types.push(self.check(type_idx));
205            }
206
207            if member_types.is_empty() {
208                return TypeId::NEVER;
209            }
210
211            return tsz_solver::utils::union_or_single(self.ctx.types, member_types);
212        }
213
214        TypeId::ERROR
215    }
216
217    /// Get type from an intersection type node (A & B).
218    ///
219    /// Parses an intersection type expression and creates an Intersection type with all members.
220    ///
221    /// ## Type Normalization:
222    /// - Empty intersection -> UNKNOWN (the top type for intersections)
223    /// - Single member -> the member itself (no intersection wrapper)
224    /// - Multiple members -> Intersection type with all members
225    fn get_type_from_intersection_type(&mut self, idx: NodeIndex) -> TypeId {
226        let Some(node) = self.ctx.arena.get(idx) else {
227            return TypeId::ERROR;
228        };
229
230        // IntersectionType uses CompositeTypeData which has a types list
231        if let Some(composite) = self.ctx.arena.get_composite_type(node) {
232            let mut member_types = Vec::new();
233            for &type_idx in &composite.types.nodes {
234                // Recursively resolve each member type
235                member_types.push(self.check(type_idx));
236            }
237
238            if member_types.is_empty() {
239                return TypeId::UNKNOWN; // Empty intersection is unknown
240            }
241
242            return tsz_solver::utils::intersection_or_single(self.ctx.types, member_types);
243        }
244
245        TypeId::ERROR
246    }
247
248    /// Get type from an array type node (string[]).
249    ///
250    /// Parses an array type expression and creates an Array type.
251    fn get_type_from_array_type(&mut self, idx: NodeIndex) -> TypeId {
252        let Some(node) = self.ctx.arena.get(idx) else {
253            return TypeId::ERROR;
254        };
255        let factory = self.ctx.types.factory();
256
257        if let Some(array_type) = self.ctx.arena.get_array_type(node) {
258            let elem_type = self.check(array_type.element_type);
259            return factory.array(elem_type);
260        }
261
262        TypeId::ERROR
263    }
264
265    /// Get type from a tuple type node ([T, U, ...V[]]).
266    ///
267    /// Parses a tuple type expression and creates a Tuple type with proper handling of:
268    /// - Regular elements (e.g., `[number, string]`)
269    /// - Optional elements (e.g., `[number, string?]`)
270    /// - Rest elements (e.g., `[number, ...string[]]`)
271    /// - Named elements (e.g., `[x: number, y: string]`)
272    fn get_type_from_tuple_type(&mut self, idx: NodeIndex) -> TypeId {
273        use tsz_solver::TupleElement;
274
275        let Some(node) = self.ctx.arena.get(idx) else {
276            return TypeId::ERROR;
277        };
278        let factory = self.ctx.types.factory();
279
280        if let Some(tuple_type) = self.ctx.arena.get_tuple_type(node) {
281            let mut elements = Vec::new();
282            let mut seen_optional = false;
283
284            for &elem_idx in &tuple_type.elements.nodes {
285                if elem_idx.is_none() {
286                    continue;
287                }
288
289                let Some(elem_node) = self.ctx.arena.get(elem_idx) else {
290                    continue;
291                };
292
293                // Check if this is an optional/rest type or a regular type
294                use tsz_parser::parser::syntax_kind_ext;
295                if elem_node.kind == syntax_kind_ext::OPTIONAL_TYPE {
296                    // Optional element (e.g., `string?`)
297                    seen_optional = true;
298                    if let Some(wrapped) = self.ctx.arena.get_wrapped_type(elem_node) {
299                        let elem_type = self.check(wrapped.type_node);
300                        elements.push(TupleElement {
301                            type_id: elem_type,
302                            name: None,
303                            optional: true,
304                            rest: false,
305                        });
306                    }
307                } else if elem_node.kind == syntax_kind_ext::REST_TYPE {
308                    // Rest element (e.g., `...string[]`)
309                    // Rest elements can come after optional elements, so we don't error
310                    if let Some(wrapped) = self.ctx.arena.get_wrapped_type(elem_node) {
311                        let elem_type = self.check(wrapped.type_node);
312                        elements.push(TupleElement {
313                            type_id: elem_type,
314                            name: None,
315                            optional: false,
316                            rest: true,
317                        });
318                    }
319                } else {
320                    // Regular element
321                    // TS1257: A required element cannot follow an optional element
322                    if seen_optional {
323                        self.ctx.error(
324                            elem_node.pos,
325                            elem_node.end - elem_node.pos,
326                            crate::diagnostics::diagnostic_messages::A_REQUIRED_ELEMENT_CANNOT_FOLLOW_AN_OPTIONAL_ELEMENT.to_string(),
327                            crate::diagnostics::diagnostic_codes::A_REQUIRED_ELEMENT_CANNOT_FOLLOW_AN_OPTIONAL_ELEMENT,
328                        );
329                    }
330                    let elem_type = self.check(elem_idx);
331                    elements.push(TupleElement {
332                        type_id: elem_type,
333                        name: None,
334                        optional: false,
335                        rest: false,
336                    });
337                }
338            }
339
340            return factory.tuple(elements);
341        }
342
343        TypeId::ERROR
344    }
345
346    // =========================================================================
347    // Type Operators
348    // =========================================================================
349
350    /// Get type from a type operator node (readonly T[], readonly [T, U], unique symbol).
351    ///
352    /// Handles type modifiers like:
353    /// - `readonly T[]` - Creates `ReadonlyType` wrapper
354    /// - `unique symbol` - Special marker for unique symbols
355    fn get_type_from_type_operator(&mut self, idx: NodeIndex) -> TypeId {
356        use tsz_scanner::SyntaxKind;
357        let factory = self.ctx.types.factory();
358
359        let Some(node) = self.ctx.arena.get(idx) else {
360            return TypeId::ERROR;
361        };
362
363        if let Some(type_op) = self.ctx.arena.get_type_operator(node) {
364            let operator = type_op.operator;
365            let inner_type = self.check(type_op.type_node);
366
367            // Handle readonly operator
368            if operator == SyntaxKind::ReadonlyKeyword as u16 {
369                return factory.readonly_type(inner_type);
370            }
371
372            // Handle keyof operator
373            if operator == SyntaxKind::KeyOfKeyword as u16 {
374                return factory.keyof(inner_type);
375            }
376
377            // Handle unique operator
378            if operator == SyntaxKind::UniqueKeyword as u16 {
379                // unique is handled differently - it's a type modifier for symbols
380                // For now, just return the inner type
381                return inner_type;
382            }
383
384            // Unknown operator - return inner type
385            inner_type
386        } else {
387            TypeId::ERROR
388        }
389    }
390
391    // =========================================================================
392    // Indexed Access Types
393    // =========================================================================
394
395    /// Handle indexed access type nodes (e.g., `Person["name"]`, `T[K]`).
396    fn get_type_from_indexed_access_type(&mut self, idx: NodeIndex) -> TypeId {
397        let Some(node) = self.ctx.arena.get(idx) else {
398            return TypeId::ERROR;
399        };
400        let factory = self.ctx.types.factory();
401
402        if let Some(indexed_access) = self.ctx.arena.get_indexed_access_type(node) {
403            let object_type = self.check(indexed_access.object_type);
404            let index_type = self.check(indexed_access.index_type);
405
406            // TS2538: Check if the index type is valid (string, number, symbol, or literal thereof)
407            if let Some(invalid_member) = self.get_invalid_index_type_member(index_type)
408                && let Some(inode) = self.ctx.arena.get(indexed_access.index_type)
409            {
410                let mut formatter = self.ctx.create_type_formatter();
411                let index_type_str = formatter.format(invalid_member);
412                let message = crate::diagnostics::format_message(
413                    crate::diagnostics::diagnostic_messages::TYPE_CANNOT_BE_USED_AS_AN_INDEX_TYPE,
414                    &[&index_type_str],
415                );
416                self.ctx
417                    .error(inode.pos, inode.end - inode.pos, message, 2538);
418            }
419
420            factory.index_access(object_type, index_type)
421        } else {
422            TypeId::ERROR
423        }
424    }
425
426    /// Get the specific type that makes this type invalid as an index type (TS2538).
427    fn get_invalid_index_type_member(&self, type_id: TypeId) -> Option<TypeId> {
428        tsz_solver::type_queries::get_invalid_index_type_member(self.ctx.types, type_id)
429    }
430
431    // =========================================================================
432    // Function and Callable Types
433    // =========================================================================
434
435    /// Get type from a function type node (e.g., () => number, (x: string) => void).
436    fn get_type_from_function_type(&mut self, idx: NodeIndex) -> TypeId {
437        let Some(_node) = self.ctx.arena.get(idx) else {
438            return TypeId::ERROR;
439        };
440        let Some(func_data) = self.ctx.arena.get_function_type(_node) else {
441            return TypeId::ERROR;
442        };
443
444        // EXPLICIT VALIDATION: Check type references in parameters and return type for TS2304.
445        // We must do this before TypeLowering because TypeLowering doesn't emit diagnostics.
446        // This ensures errors like "Cannot find name 'C'" are emitted for: (x: T) => C
447        check_duplicate_parameters_in_type(self.ctx, &func_data.parameters);
448        check_parameter_initializers_in_type(self.ctx, &func_data.parameters);
449
450        use tsz_parser::parser::syntax_kind_ext;
451
452        // Collect type parameter names from this function type (e.g., <T> in <T>(x: T) => T)
453        let mut local_type_params: std::collections::HashSet<String> =
454            std::collections::HashSet::new();
455        if let Some(ref type_params) = func_data.type_parameters {
456            for &tp_idx in &type_params.nodes {
457                if let Some(tp_node) = self.ctx.arena.get(tp_idx)
458                    && let Some(tp_data) = self.ctx.arena.get_type_parameter(tp_node)
459                    && let Some(name_node) = self.ctx.arena.get(tp_data.name)
460                    && let Some(ident) = self.ctx.arena.get_identifier(name_node)
461                {
462                    local_type_params.insert(ident.escaped_text.clone());
463                }
464            }
465        }
466
467        // Helper to check if a type name is a built-in TypeScript type
468        let is_builtin_type = |name: &str| -> bool {
469            matches!(
470                name,
471                // Primitive types
472                "void" | "null" | "undefined" | "any" | "unknown" | "never" |
473                "number" | "bigint" | "boolean" | "string" | "symbol" | "object" |
474                // Special types
475                "Function" | "Object" | "String" | "Number" | "Boolean" | "Symbol" |
476                // Compiler-managed
477                "Array" | "ReadonlyArray" | "Uppercase" | "Lowercase" | "Capitalize" | "Uncapitalize"
478            )
479        };
480
481        // Collect undefined type names first (to avoid borrow checker issues)
482        let mut undefined_types: Vec<(NodeIndex, String)> = Vec::new();
483        let mut renamed_binding_aliases: std::collections::HashSet<String> =
484            std::collections::HashSet::new();
485
486        for &param_idx in &func_data.parameters.nodes {
487            let mut stack = vec![param_idx];
488            while let Some(node_idx) = stack.pop() {
489                let Some(binding_node) = self.ctx.arena.get(node_idx) else {
490                    continue;
491                };
492                if binding_node.kind == syntax_kind_ext::BINDING_ELEMENT
493                    && let Some(binding) = self.ctx.arena.get_binding_element(binding_node)
494                    && binding.property_name.is_some()
495                    && binding.name.is_some()
496                    && let Some(alias_name) = self.ctx.arena.get_identifier_text(binding.name)
497                {
498                    renamed_binding_aliases.insert(alias_name.to_string());
499                }
500                stack.extend(self.ctx.arena.get_children(node_idx));
501            }
502        }
503
504        // Helper: check if a type name is resolvable in any scope (file locals,
505        // lib contexts, enclosing namespace scopes via binder identifier resolution).
506        let is_name_resolvable =
507            |ctx: &CheckerContext, name: &str, name_node_idx: NodeIndex| -> bool {
508                // Check file-level declarations
509                if ctx.binder.file_locals.get(name).is_some() {
510                    return true;
511                }
512                // Check lib declarations
513                if ctx
514                    .lib_contexts
515                    .iter()
516                    .any(|lib_ctx| lib_ctx.binder.file_locals.get(name).is_some())
517                {
518                    return true;
519                }
520                // Check scope-based resolution (handles namespace-scoped names)
521                if ctx
522                    .binder
523                    .resolve_identifier(ctx.arena, name_node_idx)
524                    .is_some()
525                {
526                    return true;
527                }
528                false
529            };
530
531        // Check return type annotation
532        if func_data.type_annotation.is_some()
533            && let Some(tn) = self.ctx.arena.get(func_data.type_annotation)
534            && tn.kind == syntax_kind_ext::TYPE_REFERENCE
535            && let Some(tr) = self.ctx.arena.get_type_ref(tn)
536            && let Some(name_node) = self.ctx.arena.get(tr.type_name)
537            && let Some(ident) = self.ctx.arena.get_identifier(name_node)
538        {
539            let name = &ident.escaped_text;
540            let is_builtin = is_builtin_type(name);
541            let is_local_type_param = local_type_params.contains(name);
542            let is_type_param = self.ctx.type_parameter_scope.contains_key(name);
543            let in_scope = is_name_resolvable(self.ctx, name, tr.type_name);
544
545            if !is_builtin && !is_local_type_param && !is_type_param && !in_scope {
546                undefined_types.push((tr.type_name, name.clone()));
547            }
548        }
549
550        // Check parameter type annotations
551        for param_idx in &func_data.parameters.nodes {
552            if let Some(param_node) = self.ctx.arena.get(*param_idx)
553                && let Some(param_data) = self.ctx.arena.get_parameter(param_node)
554                && param_data.type_annotation.is_some()
555                && let Some(tn) = self.ctx.arena.get(param_data.type_annotation)
556                && tn.kind == syntax_kind_ext::TYPE_REFERENCE
557                && let Some(tr) = self.ctx.arena.get_type_ref(tn)
558                && let Some(name_node) = self.ctx.arena.get(tr.type_name)
559                && let Some(ident) = self.ctx.arena.get_identifier(name_node)
560            {
561                let name = &ident.escaped_text;
562                let is_builtin = is_builtin_type(name);
563                let is_local_type_param = local_type_params.contains(name);
564                let is_type_param = self.ctx.type_parameter_scope.contains_key(name);
565                let in_scope = is_name_resolvable(self.ctx, name, tr.type_name);
566
567                if !is_builtin && !is_local_type_param && !is_type_param && !in_scope {
568                    undefined_types.push((tr.type_name, name.clone()));
569                }
570            }
571        }
572
573        // Now emit all the TS2304 errors
574        for (error_idx, name) in undefined_types {
575            if renamed_binding_aliases.contains(&name) {
576                continue;
577            }
578            if let Some(node) = self.ctx.arena.get(error_idx) {
579                let message = format!("Cannot find name '{name}'.");
580                self.ctx.error(node.pos, node.end - node.pos, message, 2304);
581            }
582        }
583
584        // Delegate to TypeLowering with standard resolvers
585        self.lower_with_resolvers(idx, false, false)
586    }
587
588    /// Get type from a type literal node ({ a: number; `b()`: string; }).
589    fn get_type_from_type_literal(&mut self, idx: NodeIndex) -> TypeId {
590        use tsz_parser::parser::syntax_kind_ext::{
591            CALL_SIGNATURE, CONSTRUCT_SIGNATURE, METHOD_SIGNATURE, PROPERTY_SIGNATURE,
592        };
593        use tsz_solver::{
594            CallSignature, CallableShape, FunctionShape, IndexSignature, ObjectFlags, ObjectShape,
595            PropertyInfo,
596        };
597
598        let Some(node) = self.ctx.arena.get(idx) else {
599            return TypeId::ERROR;
600        };
601
602        let Some(data) = self.ctx.arena.get_type_literal(node) else {
603            return TypeId::ERROR;
604        };
605
606        let mut properties = Vec::new();
607        let mut call_signatures = Vec::new();
608        let mut construct_signatures = Vec::new();
609        let mut string_index = None;
610        let mut number_index = None;
611
612        for &member_idx in &data.members.nodes {
613            let Some(member) = self.ctx.arena.get(member_idx) else {
614                continue;
615            };
616
617            if let Some(sig) = self.ctx.arena.get_signature(member) {
618                match member.kind {
619                    CALL_SIGNATURE => {
620                        let (params, this_type) = self.extract_params_from_signature(sig);
621                        let return_type = self
622                            .resolve_return_type_with_params_in_scope(sig.type_annotation, &params);
623                        call_signatures.push(CallSignature {
624                            type_params: Vec::new(),
625                            params,
626                            this_type,
627                            return_type,
628                            type_predicate: None,
629                            is_method: false,
630                        });
631                    }
632                    CONSTRUCT_SIGNATURE => {
633                        let (params, this_type) = self.extract_params_from_signature(sig);
634                        let return_type = self
635                            .resolve_return_type_with_params_in_scope(sig.type_annotation, &params);
636                        construct_signatures.push(CallSignature {
637                            type_params: Vec::new(),
638                            params,
639                            this_type,
640                            return_type,
641                            type_predicate: None,
642                            is_method: false,
643                        });
644                    }
645                    METHOD_SIGNATURE | PROPERTY_SIGNATURE => {
646                        let Some(name) = self.get_property_name(sig.name) else {
647                            continue;
648                        };
649                        let name_atom = self.ctx.types.intern_string(&name);
650
651                        if member.kind == METHOD_SIGNATURE {
652                            let (params, this_type) = self.extract_params_from_signature(sig);
653                            let return_type = self.resolve_return_type_with_params_in_scope(
654                                sig.type_annotation,
655                                &params,
656                            );
657                            let shape = FunctionShape {
658                                type_params: Vec::new(),
659                                params,
660                                this_type,
661                                return_type,
662                                type_predicate: None,
663                                is_constructor: false,
664                                is_method: true,
665                            };
666                            let factory = self.ctx.types.factory();
667                            let method_type = factory.function(shape);
668                            properties.push(PropertyInfo {
669                                name: name_atom,
670                                type_id: method_type,
671                                write_type: method_type,
672                                optional: sig.question_token,
673                                readonly: self.has_readonly_modifier(&sig.modifiers),
674                                is_method: true,
675                                visibility: Visibility::Public,
676                                parent_id: None,
677                            });
678                        } else {
679                            let type_id = if sig.type_annotation.is_some() {
680                                self.check(sig.type_annotation)
681                            } else {
682                                TypeId::ANY
683                            };
684                            properties.push(PropertyInfo {
685                                name: name_atom,
686                                type_id,
687                                write_type: type_id,
688                                optional: sig.question_token,
689                                readonly: self.has_readonly_modifier(&sig.modifiers),
690                                is_method: false,
691                                visibility: Visibility::Public,
692                                parent_id: None,
693                            });
694                        }
695                    }
696                    _ => {}
697                }
698                continue;
699            }
700
701            if let Some(index_sig) = self.ctx.arena.get_index_signature(member) {
702                let param_idx = index_sig
703                    .parameters
704                    .nodes
705                    .first()
706                    .copied()
707                    .unwrap_or(NodeIndex::NONE);
708                let Some(param_node) = self.ctx.arena.get(param_idx) else {
709                    continue;
710                };
711                let Some(param_data) = self.ctx.arena.get_parameter(param_node) else {
712                    continue;
713                };
714                let key_type = if param_data.type_annotation.is_some() {
715                    self.check(param_data.type_annotation)
716                } else {
717                    TypeId::ANY
718                };
719
720                // TS1268: An index signature parameter type must be 'string', 'number',
721                // 'symbol', or a template literal type.
722                // Suppress when the parameter already has grammar errors (rest/optional) — matches tsc.
723                let has_param_grammar_error =
724                    param_data.dot_dot_dot_token || param_data.question_token;
725                let is_valid_index_type = key_type == TypeId::STRING
726                    || key_type == TypeId::NUMBER
727                    || key_type == TypeId::SYMBOL
728                    || tsz_solver::visitor::is_template_literal_type(self.ctx.types, key_type);
729                if !is_valid_index_type
730                    && !has_param_grammar_error
731                    && let Some(pnode) = self.ctx.arena.get(param_idx)
732                {
733                    self.ctx.error(
734                            pnode.pos,
735                            pnode.end - pnode.pos,
736                            "An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type.".to_string(),
737                            1268,
738                        );
739                }
740
741                let value_type = if index_sig.type_annotation.is_some() {
742                    self.check(index_sig.type_annotation)
743                } else {
744                    TypeId::ANY
745                };
746                let readonly = self.has_readonly_modifier(&index_sig.modifiers);
747                let info = IndexSignature {
748                    key_type,
749                    value_type,
750                    readonly,
751                };
752                if key_type == TypeId::NUMBER {
753                    number_index = Some(info);
754                } else {
755                    string_index = Some(info);
756                }
757                continue;
758            }
759
760            // Handle accessor declarations (get/set) in type literals
761            if (member.kind == tsz_parser::parser::syntax_kind_ext::GET_ACCESSOR
762                || member.kind == tsz_parser::parser::syntax_kind_ext::SET_ACCESSOR)
763                && let Some(accessor) = self.ctx.arena.get_accessor(member)
764                && let Some(name) = self.get_property_name(accessor.name)
765            {
766                let name_atom = self.ctx.types.intern_string(&name);
767                let is_getter = member.kind == tsz_parser::parser::syntax_kind_ext::GET_ACCESSOR;
768                if is_getter {
769                    let getter_type = if accessor.type_annotation.is_some() {
770                        self.check(accessor.type_annotation)
771                    } else {
772                        TypeId::ANY
773                    };
774                    if let Some(existing) = properties.iter_mut().find(|p| p.name == name_atom) {
775                        existing.type_id = getter_type;
776                    } else {
777                        properties.push(PropertyInfo {
778                            name: name_atom,
779                            type_id: getter_type,
780                            write_type: getter_type,
781                            optional: false,
782                            readonly: false,
783                            is_method: false,
784                            visibility: Visibility::Public,
785                            parent_id: None,
786                        });
787                    }
788                } else {
789                    let setter_type = accessor
790                        .parameters
791                        .nodes
792                        .first()
793                        .and_then(|&param_idx| self.ctx.arena.get(param_idx))
794                        .and_then(|param_node| self.ctx.arena.get_parameter(param_node))
795                        .and_then(|param| {
796                            (param.type_annotation.is_some())
797                                .then(|| self.check(param.type_annotation))
798                        })
799                        .unwrap_or(TypeId::UNKNOWN);
800                    if let Some(existing) = properties.iter_mut().find(|p| p.name == name_atom) {
801                        existing.write_type = setter_type;
802                        existing.readonly = false;
803                    } else {
804                        properties.push(PropertyInfo {
805                            name: name_atom,
806                            type_id: setter_type,
807                            write_type: setter_type,
808                            optional: false,
809                            readonly: false,
810                            is_method: false,
811                            visibility: Visibility::Public,
812                            parent_id: None,
813                        });
814                    }
815                }
816            }
817        }
818
819        if !call_signatures.is_empty() || !construct_signatures.is_empty() {
820            let factory = self.ctx.types.factory();
821
822            return factory.callable(CallableShape {
823                call_signatures,
824                construct_signatures,
825                properties,
826                string_index,
827                number_index,
828                symbol: None,
829            });
830        }
831
832        if string_index.is_some() || number_index.is_some() {
833            let factory = self.ctx.types.factory();
834
835            return factory.object_with_index(ObjectShape {
836                flags: ObjectFlags::empty(),
837                properties,
838                string_index,
839                number_index,
840                symbol: None,
841            });
842        }
843
844        let factory = self.ctx.types.factory();
845        factory.object(properties)
846    }
847
848    // =========================================================================
849    // Type Query (typeof)
850    // =========================================================================
851
852    /// Get type from a type query node (typeof X).
853    ///
854    /// Creates a `TypeQuery` type that captures the type of a value.
855    fn get_type_from_type_query(&mut self, idx: NodeIndex) -> TypeId {
856        use tsz_lowering::TypeLowering;
857
858        let Some(node) = self.ctx.arena.get(idx) else {
859            return TypeId::ERROR;
860        };
861
862        let Some(type_query) = self.ctx.arena.get_type_query(node) else {
863            return TypeId::ERROR;
864        };
865
866        // Prefer the already-computed value-space type at this query site when available.
867        // This preserves flow-sensitive narrowing for `typeof expr` in type positions.
868        if let Some(&expr_type) = self.ctx.node_types.get(&type_query.expr_name.0)
869            && expr_type != TypeId::ERROR
870        {
871            return expr_type;
872        }
873
874        let name_opt = if let Some(expr_node) = self.ctx.arena.get(type_query.expr_name) {
875            if expr_node.kind == tsz_scanner::SyntaxKind::Identifier as u16 {
876                self.ctx
877                    .arena
878                    .get_identifier(expr_node)
879                    .map(|id| id.escaped_text.as_str())
880            } else {
881                None
882            }
883        } else {
884            None
885        };
886
887        if name_opt == Some("default") {
888            use crate::diagnostics::{diagnostic_codes, diagnostic_messages, format_message};
889            let msg = format_message(diagnostic_messages::CANNOT_FIND_NAME, &["default"]);
890            self.ctx.error(
891                self.ctx.arena.get(type_query.expr_name).unwrap().pos,
892                self.ctx.arena.get(type_query.expr_name).unwrap().end
893                    - self.ctx.arena.get(type_query.expr_name).unwrap().pos,
894                msg,
895                diagnostic_codes::CANNOT_FIND_NAME,
896            );
897            return TypeId::ERROR;
898        }
899
900        // Check typeof_param_scope — resolves `typeof paramName` in return type
901        // annotations where the parameter isn't a file-level binding.
902        if let Some(expr_node) = self.ctx.arena.get(type_query.expr_name)
903            && expr_node.kind == tsz_scanner::SyntaxKind::Identifier as u16
904            && let Some(ident) = self.ctx.arena.get_identifier(expr_node)
905            && let Some(&param_type) = self.ctx.typeof_param_scope.get(ident.escaped_text.as_str())
906        {
907            return param_type;
908        }
909
910        // For qualified names (e.g., typeof M.F2), resolve the symbol through
911        // the binder's export tables. Simple identifiers are already handled by
912        // the node_types cache above, but qualified names need member resolution.
913        if let Some(sym_id) = self.resolve_type_query_symbol(type_query.expr_name) {
914            let factory = self.ctx.types.factory();
915            return factory.type_query(tsz_solver::SymbolRef(sym_id.0));
916        }
917
918        // Fall back to TypeLowering with proper value resolvers
919        let value_resolver = |node_idx: NodeIndex| -> Option<u32> {
920            let ident = self.ctx.arena.get_identifier_at(node_idx)?;
921            let name = ident.escaped_text.as_str();
922            if name == "default" {
923                return None;
924            }
925            let sym_id = self.ctx.binder.file_locals.get(name)?;
926            Some(sym_id.0)
927        };
928        let type_resolver = |_node_idx: NodeIndex| -> Option<u32> { None };
929        let lowering = TypeLowering::with_resolvers(
930            self.ctx.arena,
931            self.ctx.types,
932            &type_resolver,
933            &value_resolver,
934        );
935
936        lowering.lower_type(idx)
937    }
938
939    /// Resolve the symbol for a type query expression name.
940    ///
941    /// Handles both simple identifiers and qualified names (e.g., `M.F2`).
942    /// For qualified names, walks through namespace exports to find the member.
943    fn resolve_type_query_symbol(&self, expr_name: NodeIndex) -> Option<tsz_binder::SymbolId> {
944        use tsz_parser::parser::syntax_kind_ext;
945
946        let node = self.ctx.arena.get(expr_name)?;
947
948        if node.kind == tsz_scanner::SyntaxKind::Identifier as u16 {
949            let ident = self.ctx.arena.get_identifier(node)?;
950            let name = ident.escaped_text.as_str();
951            if name == "default" {
952                return None;
953            }
954            let sym_id = self.ctx.binder.file_locals.get(name)?;
955            return Some(sym_id);
956        }
957
958        if node.kind == syntax_kind_ext::QUALIFIED_NAME {
959            let qn = self.ctx.arena.get_qualified_name(node)?;
960            // Recursively resolve the left side
961            let left_sym = self.resolve_type_query_symbol(qn.left)?;
962
963            // Get the right name
964            let right_node = self.ctx.arena.get(qn.right)?;
965            let right_ident = self.ctx.arena.get_identifier(right_node)?;
966            let right_name = right_ident.escaped_text.as_str();
967
968            // Look through binder + libs for the left symbol's exports
969            let lib_binders: Vec<std::sync::Arc<tsz_binder::BinderState>> = self
970                .ctx
971                .lib_contexts
972                .iter()
973                .map(|lc| std::sync::Arc::clone(&lc.binder))
974                .collect();
975            let left_symbol = self
976                .ctx
977                .binder
978                .get_symbol_with_libs(left_sym, &lib_binders)?;
979
980            if let Some(exports) = left_symbol.exports.as_ref()
981                && let Some(member_sym) = exports.get(right_name)
982            {
983                return Some(member_sym);
984            }
985        }
986
987        None
988    }
989
990    /// Check a mapped type ({ [P in K]: T }).
991    ///
992    /// This function validates the mapped type and emits TS7039 if the type expression
993    /// after the colon is missing (e.g., `{[P in "bar"]}` instead of `{[P in "bar"]: string}`).
994    fn get_type_from_mapped_type(&mut self, idx: NodeIndex) -> TypeId {
995        use tsz_parser::parser::NodeIndex as ParserNodeIndex;
996
997        let Some(node) = self.ctx.arena.get(idx) else {
998            return TypeId::ERROR;
999        };
1000
1001        let Some(data) = self.ctx.arena.get_mapped_type(node) else {
1002            return TypeId::ERROR;
1003        };
1004
1005        // TS7039: Mapped object type implicitly has an 'any' template type.
1006        // This error occurs when the type expression after the colon is missing.
1007        // Example: type Foo = {[P in "bar"]};  // Missing ": T" after "bar"]
1008        if data.type_node == ParserNodeIndex::NONE {
1009            let message = "Mapped object type implicitly has an 'any' template type.";
1010            self.ctx
1011                .error(node.pos, node.end - node.pos, message.to_string(), 7039);
1012            return TypeId::ANY;
1013        }
1014
1015        // Delegate to TypeLowering with extended resolvers (enum flags + lib search)
1016        self.lower_with_resolvers(idx, true, false)
1017    }
1018
1019    // =========================================================================
1020    // Symbol Resolution Helpers
1021    // =========================================================================
1022
1023    /// Resolve a type symbol from a node index.
1024    ///
1025    /// Looks up the identifier in `file_locals` and `lib_contexts` for symbols with
1026    /// TYPE, `REGULAR_ENUM`, or `CONST_ENUM` flags. Returns the raw symbol ID (u32).
1027    /// Skips compiler-managed types (Array, ReadonlyArray, etc.) that `TypeLowering`
1028    /// handles specially.
1029    fn resolve_type_symbol(&self, node_idx: NodeIndex) -> Option<u32> {
1030        use tsz_binder::symbol_flags;
1031        use tsz_solver::is_compiler_managed_type;
1032
1033        let ident = self.ctx.arena.get_identifier_at(node_idx)?;
1034        let name = ident.escaped_text.as_str();
1035
1036        if is_compiler_managed_type(name) {
1037            return None;
1038        }
1039
1040        if let Some(sym_id) = self.ctx.binder.file_locals.get(name) {
1041            let symbol = self.ctx.binder.get_symbol(sym_id)?;
1042            if (symbol.flags
1043                & (symbol_flags::TYPE | symbol_flags::REGULAR_ENUM | symbol_flags::CONST_ENUM))
1044                != 0
1045            {
1046                return Some(sym_id.0);
1047            }
1048        }
1049
1050        for lib_ctx in &self.ctx.lib_contexts {
1051            if let Some(lib_sym_id) = lib_ctx.binder.file_locals.get(name) {
1052                let symbol = lib_ctx.binder.get_symbol(lib_sym_id)?;
1053                if (symbol.flags
1054                    & (symbol_flags::TYPE | symbol_flags::REGULAR_ENUM | symbol_flags::CONST_ENUM))
1055                    != 0
1056                {
1057                    let file_sym_id = self.ctx.binder.file_locals.get(name).unwrap_or(lib_sym_id);
1058                    return Some(file_sym_id.0);
1059                }
1060            }
1061        }
1062
1063        None
1064    }
1065
1066    /// Resolve a value symbol from a node index (`file_locals` only).
1067    ///
1068    /// Looks for symbols with VALUE or ALIAS flags. Used by `type_reference` and
1069    /// `function_type` resolvers.
1070    fn resolve_value_symbol(&self, node_idx: NodeIndex) -> Option<u32> {
1071        use tsz_binder::symbol_flags;
1072
1073        let ident = self.ctx.arena.get_identifier_at(node_idx)?;
1074        let name = ident.escaped_text.as_str();
1075
1076        if let Some(sym_id) = self.ctx.binder.file_locals.get(name) {
1077            let symbol = self.ctx.binder.get_symbol(sym_id)?;
1078            if (symbol.flags & (symbol_flags::VALUE | symbol_flags::ALIAS)) != 0 {
1079                return Some(sym_id.0);
1080            }
1081        }
1082
1083        None
1084    }
1085
1086    /// Resolve a value symbol from a node index (`file_locals` + libs, with enum flags).
1087    ///
1088    /// Extended variant used by `compute_type` fallback and `mapped_type` resolvers
1089    /// that also checks `lib_contexts` and includes `REGULAR_ENUM/CONST_ENUM` flags.
1090    fn resolve_value_symbol_with_libs(&self, node_idx: NodeIndex) -> Option<u32> {
1091        use tsz_binder::symbol_flags;
1092
1093        let ident = self.ctx.arena.get_identifier_at(node_idx)?;
1094        let name = ident.escaped_text.as_str();
1095
1096        if let Some(sym_id) = self.ctx.binder.file_locals.get(name)
1097            && let Some(symbol) = self.ctx.binder.get_symbol(sym_id)
1098            && (symbol.flags
1099                & (symbol_flags::VALUE
1100                    | symbol_flags::ALIAS
1101                    | symbol_flags::REGULAR_ENUM
1102                    | symbol_flags::CONST_ENUM))
1103                != 0
1104        {
1105            return Some(sym_id.0);
1106        }
1107
1108        for lib_ctx in &self.ctx.lib_contexts {
1109            if let Some(lib_sym_id) = lib_ctx.binder.file_locals.get(name)
1110                && let Some(symbol) = lib_ctx.binder.get_symbol(lib_sym_id)
1111                && (symbol.flags
1112                    & (symbol_flags::VALUE
1113                        | symbol_flags::ALIAS
1114                        | symbol_flags::REGULAR_ENUM
1115                        | symbol_flags::CONST_ENUM))
1116                    != 0
1117            {
1118                let file_sym_id = self.ctx.binder.file_locals.get(name).unwrap_or(lib_sym_id);
1119                return Some(file_sym_id.0);
1120            }
1121        }
1122
1123        None
1124    }
1125
1126    /// Resolve a DefId from a node index via the type resolver.
1127    fn resolve_def_id(&self, node_idx: NodeIndex) -> Option<tsz_solver::def::DefId> {
1128        let sym_id = self.resolve_type_symbol(node_idx)?;
1129        Some(self.ctx.get_or_create_def_id(tsz_binder::SymbolId(sym_id)))
1130    }
1131
1132    /// Resolve a DefId with support for qualified names (e.g., `AnimalType.cat`).
1133    ///
1134    /// Used by the `compute_type` fallback path where template literal types may
1135    /// reference enum members via qualified names inside `${...}`.
1136    fn resolve_def_id_with_qualified_names(
1137        &self,
1138        node_idx: NodeIndex,
1139    ) -> Option<tsz_solver::def::DefId> {
1140        use tsz_parser::parser::syntax_kind_ext;
1141
1142        if let Some(sym_id) = self.resolve_type_symbol(node_idx) {
1143            return Some(self.ctx.get_or_create_def_id(tsz_binder::SymbolId(sym_id)));
1144        }
1145
1146        let node = self.ctx.arena.get(node_idx)?;
1147        if node.kind == syntax_kind_ext::QUALIFIED_NAME {
1148            let qn = self.ctx.arena.get_qualified_name(node)?;
1149            let left_sym_raw = self.resolve_type_symbol(qn.left)?;
1150            let left_sym_id = tsz_binder::SymbolId(left_sym_raw);
1151            let left_symbol = self.ctx.binder.get_symbol(left_sym_id)?;
1152            let right_node = self.ctx.arena.get(qn.right)?;
1153            let right_ident = self.ctx.arena.get_identifier(right_node)?;
1154            let right_name = right_ident.escaped_text.as_str();
1155            let member_sym_id = left_symbol.exports.as_ref()?.get(right_name)?;
1156            return Some(self.ctx.get_or_create_def_id(member_sym_id));
1157        }
1158
1159        None
1160    }
1161
1162    /// Collect type parameter bindings from the current scope.
1163    fn collect_type_param_bindings(&self) -> Vec<(tsz_common::interner::Atom, TypeId)> {
1164        self.ctx
1165            .type_parameter_scope
1166            .iter()
1167            .map(|(name, &type_id)| (self.ctx.types.intern_string(name), type_id))
1168            .collect()
1169    }
1170
1171    /// Run `TypeLowering` with the standard resolvers (type + value + `def_id`).
1172    ///
1173    /// This is the common path used by `compute_type` fallback, `type_reference`,
1174    /// `function_type`, and `mapped_type`. The `use_extended_value_resolver` flag
1175    /// controls whether enum flags and lib search are included in value resolution.
1176    /// The `use_qualified_names` flag enables qualified name support in `def_id` resolution.
1177    fn lower_with_resolvers(
1178        &self,
1179        idx: NodeIndex,
1180        use_extended_value_resolver: bool,
1181        use_qualified_names: bool,
1182    ) -> TypeId {
1183        use tsz_lowering::TypeLowering;
1184
1185        let type_param_bindings = self.collect_type_param_bindings();
1186
1187        let type_resolver =
1188            |node_idx: NodeIndex| -> Option<u32> { self.resolve_type_symbol(node_idx) };
1189
1190        let value_resolver = |node_idx: NodeIndex| -> Option<u32> {
1191            if use_extended_value_resolver {
1192                self.resolve_value_symbol_with_libs(node_idx)
1193            } else {
1194                self.resolve_value_symbol(node_idx)
1195            }
1196        };
1197
1198        let def_id_resolver = |node_idx: NodeIndex| -> Option<tsz_solver::def::DefId> {
1199            if use_qualified_names {
1200                self.resolve_def_id_with_qualified_names(node_idx)
1201            } else {
1202                self.resolve_def_id(node_idx)
1203            }
1204        };
1205
1206        let mut lowering = TypeLowering::with_hybrid_resolver(
1207            self.ctx.arena,
1208            self.ctx.types,
1209            &type_resolver,
1210            &def_id_resolver,
1211            &value_resolver,
1212        );
1213        if !type_param_bindings.is_empty() {
1214            lowering = lowering.with_type_param_bindings(type_param_bindings);
1215        }
1216        lowering.lower_type(idx)
1217    }
1218
1219    // =========================================================================
1220    // Helper Methods
1221    // =========================================================================
1222
1223    /// Extract parameter information from a signature.
1224    fn extract_params_from_signature(
1225        &mut self,
1226        sig: &tsz_parser::parser::node::SignatureData,
1227    ) -> (Vec<tsz_solver::ParamInfo>, Option<TypeId>) {
1228        use tsz_solver::ParamInfo;
1229
1230        let mut params = Vec::new();
1231        let mut this_type = None;
1232
1233        if let Some(ref param_list) = sig.parameters {
1234            for &param_idx in &param_list.nodes {
1235                let Some(param_node) = self.ctx.arena.get(param_idx) else {
1236                    continue;
1237                };
1238                let Some(param_data) = self.ctx.arena.get_parameter(param_node) else {
1239                    continue;
1240                };
1241
1242                // Get parameter name
1243                let name = self.get_param_name(param_data.name);
1244
1245                // Check for 'this' parameter
1246                if name == "this" {
1247                    this_type = (param_data.type_annotation.is_some())
1248                        .then(|| self.check(param_data.type_annotation));
1249                    continue;
1250                }
1251
1252                // Get parameter type
1253                let type_id = if param_data.type_annotation.is_some() {
1254                    self.check(param_data.type_annotation)
1255                } else {
1256                    TypeId::ANY
1257                };
1258
1259                let optional = param_data.question_token || param_data.initializer.is_some();
1260                let rest = param_data.dot_dot_dot_token;
1261
1262                // Under strictNullChecks, optional parameters (with `?`) get
1263                // `undefined` added to their type.
1264                let effective_type = if param_data.question_token
1265                    && self.ctx.strict_null_checks()
1266                    && type_id != TypeId::ANY
1267                    && type_id != TypeId::ERROR
1268                    && type_id != TypeId::UNDEFINED
1269                {
1270                    let factory = self.ctx.types.factory();
1271                    factory.union(vec![type_id, TypeId::UNDEFINED])
1272                } else {
1273                    type_id
1274                };
1275
1276                params.push(ParamInfo {
1277                    name: Some(self.ctx.types.intern_string(&name)),
1278                    type_id: effective_type,
1279                    optional,
1280                    rest,
1281                });
1282            }
1283        }
1284
1285        (params, this_type)
1286    }
1287
1288    /// Resolve return type annotation with parameter names in scope for `typeof`.
1289    ///
1290    /// Pushes parameter names into `typeof_param_scope` so that `typeof paramName`
1291    /// in the return type annotation resolves to the parameter's declared type.
1292    fn resolve_return_type_with_params_in_scope(
1293        &mut self,
1294        type_annotation: NodeIndex,
1295        params: &[tsz_solver::ParamInfo],
1296    ) -> TypeId {
1297        if type_annotation.is_none() {
1298            return TypeId::ANY;
1299        }
1300
1301        // Push param names into typeof_param_scope
1302        for param in params {
1303            if let Some(name_atom) = param.name {
1304                let name = self.ctx.types.resolve_atom(name_atom);
1305                self.ctx.typeof_param_scope.insert(name, param.type_id);
1306            }
1307        }
1308
1309        let return_type = self.check(type_annotation);
1310
1311        // Clear typeof_param_scope
1312        for param in params {
1313            if let Some(name_atom) = param.name {
1314                let name = self.ctx.types.resolve_atom(name_atom);
1315                self.ctx.typeof_param_scope.remove(&name);
1316            }
1317        }
1318
1319        return_type
1320    }
1321
1322    /// Get parameter name from a binding name node.
1323    fn get_param_name(&self, name_idx: NodeIndex) -> String {
1324        if let Some(ident) = self.ctx.arena.get_identifier_at(name_idx) {
1325            return ident.escaped_text.to_string();
1326        }
1327        "_".to_string()
1328    }
1329
1330    /// Get property name from a property name node.
1331    fn get_property_name(&self, name_idx: NodeIndex) -> Option<String> {
1332        use tsz_scanner::SyntaxKind;
1333
1334        let name_node = self.ctx.arena.get(name_idx)?;
1335
1336        // Identifier
1337        if let Some(ident) = self.ctx.arena.get_identifier(name_node) {
1338            return Some(ident.escaped_text.clone());
1339        }
1340
1341        // String literal, no-substitution template literal, or numeric literal
1342        if matches!(
1343            name_node.kind,
1344            k if k == SyntaxKind::StringLiteral as u16
1345                || k == SyntaxKind::NoSubstitutionTemplateLiteral as u16
1346                || k == SyntaxKind::NumericLiteral as u16
1347        ) && let Some(lit) = self.ctx.arena.get_literal(name_node)
1348        {
1349            // Canonicalize numeric property names (e.g. "1.", "1.0" -> "1")
1350            if name_node.kind == SyntaxKind::NumericLiteral as u16
1351                && let Some(canonical) = tsz_solver::utils::canonicalize_numeric_name(&lit.text)
1352            {
1353                return Some(canonical);
1354            }
1355            return Some(lit.text.clone());
1356        }
1357
1358        None
1359    }
1360
1361    /// Check if a modifier list contains the readonly modifier.
1362    fn has_readonly_modifier(&self, modifiers: &Option<tsz_parser::parser::NodeList>) -> bool {
1363        self.ctx
1364            .arena
1365            .has_modifier(modifiers, tsz_scanner::SyntaxKind::ReadonlyKeyword)
1366    }
1367
1368    /// Get the context reference (for read-only access).
1369    pub const fn context(&self) -> &CheckerContext<'ctx> {
1370        self.ctx
1371    }
1372}
1373
1374#[cfg(test)]
1375#[path = "../../tests/type_node.rs"]
1376mod tests;
1377
1378// Check duplicate parameters from a TypeNodeChecker context.
1379pub(crate) fn check_duplicate_parameters_in_type(
1380    ctx: &mut crate::CheckerContext,
1381    parameters: &tsz_parser::parser::NodeList,
1382) {
1383    let mut seen_names = rustc_hash::FxHashSet::default();
1384    for &param_idx in &parameters.nodes {
1385        if let Some(param_node) = ctx.arena.get(param_idx)
1386            && let Some(param) = ctx.arena.get_parameter(param_node)
1387        {
1388            collect_names_in_type(ctx, param.name, &mut seen_names);
1389        }
1390    }
1391}
1392
1393fn collect_names_in_type(
1394    ctx: &mut crate::CheckerContext,
1395    name_idx: tsz_parser::parser::NodeIndex,
1396    seen: &mut rustc_hash::FxHashSet<String>,
1397) {
1398    use tsz_scanner::SyntaxKind;
1399    let Some(node) = ctx.arena.get(name_idx) else {
1400        return;
1401    };
1402    if node.kind == SyntaxKind::Identifier as u16 {
1403        if let Some(name) = ctx
1404            .arena
1405            .get_identifier(node)
1406            .map(|i| i.escaped_text.clone())
1407            && !seen.insert(name.clone())
1408        {
1409            let msg = crate::diagnostics::format_message(
1410                crate::diagnostics::diagnostic_messages::DUPLICATE_IDENTIFIER,
1411                &[&name],
1412            );
1413            ctx.error(
1414                node.pos,
1415                node.end - node.pos,
1416                msg,
1417                crate::diagnostics::diagnostic_codes::DUPLICATE_IDENTIFIER,
1418            );
1419        }
1420    } else if (node.kind == tsz_parser::parser::syntax_kind_ext::OBJECT_BINDING_PATTERN
1421        || node.kind == tsz_parser::parser::syntax_kind_ext::ARRAY_BINDING_PATTERN)
1422        && let Some(pattern) = ctx.arena.get_binding_pattern(node)
1423    {
1424        for &elem_idx in &pattern.elements.nodes {
1425            if let Some(elem_node) = ctx.arena.get(elem_idx) {
1426                if elem_node.kind == tsz_parser::parser::syntax_kind_ext::OMITTED_EXPRESSION {
1427                    continue;
1428                }
1429                if let Some(elem) = ctx.arena.get_binding_element(elem_node) {
1430                    if elem.property_name.is_some()
1431                        && let Some(prop_node) = ctx.arena.get(elem.property_name)
1432                        && prop_node.kind == SyntaxKind::Identifier as u16
1433                        && let Some(name_node) = ctx.arena.get(elem.name)
1434                        && name_node.kind == SyntaxKind::Identifier as u16
1435                    {
1436                        let prop_name = ctx
1437                            .arena
1438                            .get_identifier(prop_node)
1439                            .map(|i| i.escaped_text.trim_end_matches(":").trim().to_string())
1440                            .unwrap_or_default();
1441                        let name_str = ctx
1442                            .arena
1443                            .get_identifier(name_node)
1444                            .map(|i| i.escaped_text.clone())
1445                            .unwrap_or_default();
1446                        let msg = crate::diagnostics::format_message(crate::diagnostics::diagnostic_messages::IS_AN_UNUSED_RENAMING_OF_DID_YOU_INTEND_TO_USE_IT_AS_A_TYPE_ANNOTATION, &[&name_str, &prop_name]);
1447                        ctx.error(name_node.pos, name_node.end - name_node.pos, msg, crate::diagnostics::diagnostic_codes::IS_AN_UNUSED_RENAMING_OF_DID_YOU_INTEND_TO_USE_IT_AS_A_TYPE_ANNOTATION);
1448                    }
1449                    collect_names_in_type(ctx, elem.name, seen);
1450                }
1451            }
1452        }
1453    }
1454}
1455
1456impl<'a, 'ctx> TypeNodeChecker<'a, 'ctx> {
1457    fn is_this_type_allowed(&self, this_node_idx: tsz_parser::parser::NodeIndex) -> bool {
1458        use tsz_parser::parser::syntax_kind_ext;
1459
1460        let mut child_idx = this_node_idx;
1461        let mut current = self
1462            .ctx
1463            .arena
1464            .get_extended(this_node_idx)
1465            .map(|ext| ext.parent);
1466
1467        while let Some(parent_idx) = current {
1468            if parent_idx.is_none() {
1469                break;
1470            }
1471            let Some(node) = self.ctx.arena.get(parent_idx) else {
1472                break;
1473            };
1474
1475            match node.kind {
1476                // Nodes that PROVIDE a 'this' type context
1477                syntax_kind_ext::CLASS_DECLARATION
1478                | syntax_kind_ext::CLASS_EXPRESSION
1479                | syntax_kind_ext::INTERFACE_DECLARATION => {
1480                    return true;
1481                }
1482
1483                // Class/Interface members
1484                syntax_kind_ext::METHOD_DECLARATION
1485                | syntax_kind_ext::PROPERTY_DECLARATION
1486                | syntax_kind_ext::GET_ACCESSOR
1487                | syntax_kind_ext::SET_ACCESSOR
1488                | syntax_kind_ext::INDEX_SIGNATURE
1489                | syntax_kind_ext::PROPERTY_SIGNATURE
1490                | syntax_kind_ext::METHOD_SIGNATURE => {
1491                    // If it's static, 'this' type is not allowed
1492                    let is_static = (node.flags & tsz_parser::modifier_flags::STATIC as u16) != 0;
1493                    if is_static {
1494                        return false;
1495                    }
1496                    // Otherwise, it's an instance member, so 'this' type is allowed.
1497                    // We continue walking up, we will eventually hit the class/interface declaration.
1498                }
1499
1500                // Nodes that BLOCK 'this' type context
1501                syntax_kind_ext::CONSTRUCTOR => {
1502                    // 'this' type not allowed in constructor parameters or return type,
1503                    // but it IS allowed in the constructor body.
1504                    if let Some(c) = self.ctx.arena.get_constructor(node)
1505                        && child_idx == c.body
1506                    {
1507                        return true; // The body provides a 'this' context
1508                    }
1509                    return false;
1510                }
1511
1512                syntax_kind_ext::FUNCTION_DECLARATION
1513                | syntax_kind_ext::FUNCTION_EXPRESSION
1514                | syntax_kind_ext::TYPE_ALIAS_DECLARATION
1515                | syntax_kind_ext::MODULE_DECLARATION
1516                | syntax_kind_ext::TYPE_LITERAL
1517                | syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
1518                | syntax_kind_ext::CLASS_STATIC_BLOCK_DECLARATION => {
1519                    return false;
1520                }
1521
1522                // Everything else (ARROW_FUNCTION, MAPPED_TYPE, BLOCK, RETURN_STATEMENT, etc.)
1523                // just passes through to the parent.
1524                _ => {}
1525            }
1526
1527            child_idx = parent_idx;
1528            current = self
1529                .ctx
1530                .arena
1531                .get_extended(parent_idx)
1532                .map(|ext| ext.parent);
1533        }
1534
1535        false
1536    }
1537}
1538pub(crate) fn check_parameter_initializers_in_type(
1539    ctx: &mut crate::CheckerContext,
1540    parameters: &tsz_parser::parser::NodeList,
1541) {
1542    for &param_idx in &parameters.nodes {
1543        if let Some(param_node) = ctx.arena.get(param_idx)
1544            && let Some(param) = ctx.arena.get_parameter(param_node)
1545            && param.initializer.is_some()
1546        {
1547            // TSC anchors the error at the parameter name, not the initializer
1548            let name_node = ctx.arena.get(param.name).unwrap_or(param_node);
1549            ctx.error(
1550                name_node.pos,
1551                name_node.end - name_node.pos,
1552                "A parameter initializer is only allowed in a function or constructor implementation."
1553                    .to_string(),
1554                2371,
1555            );
1556        }
1557    }
1558}