Skip to main content

bamts_compiler/
checker.rs

1//! Semantic analysis: the first real checker slice over an immutable
2//! [`SourceFile`].
3//!
4//! The checker never mutates the syntax tree. It builds three immutable models
5//! from one traversal: a lexical [`Scope`] tree, a [`Symbol`] table populated by
6//! binding declarations, and an interned structural [`TypeTable`] used by the
7//! named type algebra and [`TypeTable::assignable`]. Binding detects duplicate
8//! declarations, reference resolution detects unresolved local names, and
9//! variable initializers are checked against their annotations. Its diagnostics
10//! are merged with the front-end hard warnings and returned in canonical order
11//! alongside the [`SemanticModel`], following the crate's `Recovered` contract.
12
13#[path = "checker/intrinsic_environment.rs"]
14mod intrinsic_environment;
15
16use std::collections::{BTreeMap, HashMap};
17
18use crate::diagnostic::{Diagnostic, DiagnosticCode, Recovered};
19use crate::lint::{LintProfile, LintTable};
20use crate::source::{SourceId, TextRange};
21use crate::syntax::{
22    ArrayElement, AssignmentTarget, BindingPattern, CallArgument, ClassDeclaration, ClassMember,
23    EntityName, Expr, Expression, ForBinding, ForInitializer, FunctionBody, FunctionLike,
24    FunctionType, IdentifierNode, ImportBinding, InterfaceDeclaration, KeywordType, Literal,
25    MemberProperty, NodeId, ObjectMember, PropertyName, SourceFile, Statement, Token, Ty,
26    TypeAliasDeclaration, TypeLiteral, TypeMember, TypeNode, TypeReference, VariableDeclaration,
27    VariableKind,
28};
29use crate::warning::analyze_warnings;
30use intrinsic_environment::GlobalEnvironment;
31
32/// Diagnostic emitted when a block-scoped name redeclares an existing binding.
33pub const DUPLICATE_DECLARATION: DiagnosticCode = DiagnosticCode::new("BAMTS-C001");
34/// Diagnostic emitted when a value reference resolves to no local binding.
35pub const CANNOT_FIND_NAME: DiagnosticCode = DiagnosticCode::new("BAMTS-C002");
36/// Diagnostic emitted when a type reference resolves to no local type name.
37pub const CANNOT_FIND_TYPE: DiagnosticCode = DiagnosticCode::new("BAMTS-C003");
38/// Diagnostic emitted when an initializer is not assignable to its annotation.
39pub const TYPE_NOT_ASSIGNABLE: DiagnosticCode = DiagnosticCode::new("BAMTS-C004");
40
41const DUPLICATE_MESSAGE: &str = "A block-scoped declaration cannot redeclare an existing binding.";
42const CANNOT_FIND_NAME_MESSAGE: &str = "Cannot find name in any enclosing scope.";
43const CANNOT_FIND_TYPE_MESSAGE: &str = "Cannot find type name in any enclosing scope.";
44const NOT_ASSIGNABLE_MESSAGE: &str = "Initializer type is not assignable to the annotated type.";
45
46/// A lexical scope's identity within a [`SemanticModel`].
47#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
48pub struct ScopeId(u32);
49
50impl ScopeId {
51    #[must_use]
52    pub const fn get(self) -> u32 {
53        self.0
54    }
55}
56
57/// A bound name's identity within a [`SemanticModel`].
58#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
59pub struct SymbolId(u32);
60
61impl SymbolId {
62    #[must_use]
63    pub const fn new(value: u32) -> Self {
64        Self(value)
65    }
66
67    #[must_use]
68    pub const fn get(self) -> u32 {
69        self.0
70    }
71}
72
73/// An interned structural type's identity within a [`TypeTable`].
74#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
75pub struct TypeId(u32);
76
77impl TypeId {
78    #[must_use]
79    pub const fn get(self) -> u32 {
80        self.0
81    }
82}
83
84/// The kind of lexical scope, used only to describe the model to callers.
85#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
86pub enum ScopeKind {
87    Global,
88    Module,
89    Function,
90    Block,
91    For,
92    Catch,
93    Class,
94}
95
96/// One immutable lexical scope with its two-namespace symbol tables.
97#[derive(Clone, Debug, Eq, PartialEq)]
98pub struct Scope {
99    kind: ScopeKind,
100    parent: Option<ScopeId>,
101    values: BTreeMap<String, SymbolId>,
102    types: BTreeMap<String, SymbolId>,
103}
104
105impl Scope {
106    #[must_use]
107    pub const fn kind(&self) -> ScopeKind {
108        self.kind
109    }
110
111    #[must_use]
112    pub const fn parent(&self) -> Option<ScopeId> {
113        self.parent
114    }
115
116    /// Returns the value binding declared directly in this scope, if any.
117    #[must_use]
118    pub fn value(&self, name: &str) -> Option<SymbolId> {
119        self.values.get(name).copied()
120    }
121
122    /// Returns the type binding declared directly in this scope, if any.
123    #[must_use]
124    pub fn type_binding(&self, name: &str) -> Option<SymbolId> {
125        self.types.get(name).copied()
126    }
127}
128
129/// What a bound name declares. This drives namespace membership and whether a
130/// redeclaration is a legal merge or a duplicate error.
131#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
132pub enum SymbolKind {
133    IntrinsicValue,
134    IntrinsicType,
135    Variable(VariableKind),
136    Function,
137    Parameter,
138    Class,
139    Interface,
140    TypeAlias,
141    Enum,
142    TypeParameter,
143    Import,
144    Namespace,
145}
146
147impl SymbolKind {
148    const fn occupies_value(self) -> bool {
149        matches!(
150            self,
151            Self::IntrinsicValue
152                | Self::Variable(_)
153                | Self::Function
154                | Self::Parameter
155                | Self::Class
156                | Self::Enum
157                | Self::Import
158                | Self::Namespace
159        )
160    }
161
162    const fn occupies_type(self) -> bool {
163        matches!(
164            self,
165            Self::IntrinsicType
166                | Self::Class
167                | Self::Enum
168                | Self::Interface
169                | Self::TypeAlias
170                | Self::TypeParameter
171                | Self::Import
172        )
173    }
174
175    /// Two value bindings may share a name only when both are `var`/`function`.
176    const fn value_mergeable(self) -> bool {
177        matches!(self, Self::Variable(VariableKind::Var) | Self::Function)
178    }
179
180    /// Two type bindings may share a name only when both are interfaces.
181    const fn type_mergeable(self) -> bool {
182        matches!(self, Self::Interface)
183    }
184}
185
186/// One immutable bound name.
187#[derive(Clone, Debug, Eq, PartialEq)]
188pub struct Symbol {
189    name: String,
190    kind: SymbolKind,
191    scope: ScopeId,
192    declaration: NodeId,
193    range: TextRange,
194}
195
196impl Symbol {
197    #[must_use]
198    pub fn name(&self) -> &str {
199        &self.name
200    }
201
202    #[must_use]
203    pub const fn kind(&self) -> SymbolKind {
204        self.kind
205    }
206
207    #[must_use]
208    pub const fn scope(&self) -> ScopeId {
209        self.scope
210    }
211
212    #[must_use]
213    pub const fn declaration(&self) -> NodeId {
214        self.declaration
215    }
216
217    #[must_use]
218    pub const fn range(&self) -> TextRange {
219        self.range
220    }
221}
222
223/// One member of an interned object type.
224#[derive(Clone, Debug, Eq, Hash, PartialEq)]
225pub struct PropertyType {
226    name: Box<str>,
227    optional: bool,
228    type_id: TypeId,
229}
230
231impl PropertyType {
232    #[must_use]
233    pub fn new(name: impl Into<Box<str>>, optional: bool, type_id: TypeId) -> Self {
234        Self {
235            name: name.into(),
236            optional,
237            type_id,
238        }
239    }
240
241    #[must_use]
242    pub fn name(&self) -> &str {
243        &self.name
244    }
245
246    #[must_use]
247    pub const fn optional(&self) -> bool {
248        self.optional
249    }
250
251    #[must_use]
252    pub const fn type_id(&self) -> TypeId {
253        self.type_id
254    }
255}
256
257/// One interned function signature.
258#[derive(Clone, Debug, Eq, Hash, PartialEq)]
259pub struct FunctionSignature {
260    parameters: Vec<TypeId>,
261    return_type: TypeId,
262}
263
264impl FunctionSignature {
265    #[must_use]
266    pub fn parameters(&self) -> &[TypeId] {
267        &self.parameters
268    }
269
270    #[must_use]
271    pub const fn return_type(&self) -> TypeId {
272        self.return_type
273    }
274}
275
276/// The closed space of structural types the first checker slice models.
277///
278/// `Error` is a recovery type produced for unresolved or unsupported syntax; it
279/// behaves like `Any` in assignability so one upstream mistake never cascades.
280#[derive(Clone, Debug, Eq, Hash, PartialEq)]
281pub enum Type {
282    Error,
283    Any,
284    Unknown,
285    Never,
286    Void,
287    Null,
288    Undefined,
289    Boolean,
290    Number,
291    BigInt,
292    String,
293    Symbol,
294    Object,
295    BooleanLiteral(bool),
296    NumberLiteral(Box<str>),
297    StringLiteral(Box<str>),
298    BigIntLiteral(Box<str>),
299    Array(TypeId),
300    Union(Vec<TypeId>),
301    ObjectType(Vec<PropertyType>),
302    Function(FunctionSignature),
303    /// A nominal named type (type parameter, class, or enum) compared by identity.
304    Named(SymbolId),
305    /// A numeric enum value, distinct from both its runtime enum object and number.
306    NumericEnum(SymbolId),
307}
308
309/// An interning table for structural types plus the assignability relation.
310///
311/// The table is append-only while checking and frozen into the immutable
312/// [`SemanticModel`]. It is also a standalone reusable value: [`TypeTable::new`]
313/// yields the primitive types so the algebra can be exercised directly.
314#[derive(Clone, Debug)]
315pub struct TypeTable {
316    types: Vec<Type>,
317    index: HashMap<Type, TypeId>,
318    error: TypeId,
319    any: TypeId,
320    unknown: TypeId,
321    never: TypeId,
322    void: TypeId,
323    null: TypeId,
324    undefined: TypeId,
325    boolean: TypeId,
326    number: TypeId,
327    bigint: TypeId,
328    string: TypeId,
329    symbol: TypeId,
330    object: TypeId,
331}
332
333impl Default for TypeTable {
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339impl TypeTable {
340    /// Creates a table pre-populated with every primitive type.
341    #[must_use]
342    pub fn new() -> Self {
343        let mut table = Self {
344            types: Vec::new(),
345            index: HashMap::new(),
346            error: TypeId(0),
347            any: TypeId(0),
348            unknown: TypeId(0),
349            never: TypeId(0),
350            void: TypeId(0),
351            null: TypeId(0),
352            undefined: TypeId(0),
353            boolean: TypeId(0),
354            number: TypeId(0),
355            bigint: TypeId(0),
356            string: TypeId(0),
357            symbol: TypeId(0),
358            object: TypeId(0),
359        };
360        table.error = table.intern(Type::Error);
361        table.any = table.intern(Type::Any);
362        table.unknown = table.intern(Type::Unknown);
363        table.never = table.intern(Type::Never);
364        table.void = table.intern(Type::Void);
365        table.null = table.intern(Type::Null);
366        table.undefined = table.intern(Type::Undefined);
367        table.boolean = table.intern(Type::Boolean);
368        table.number = table.intern(Type::Number);
369        table.bigint = table.intern(Type::BigInt);
370        table.string = table.intern(Type::String);
371        table.symbol = table.intern(Type::Symbol);
372        table.object = table.intern(Type::Object);
373        table
374    }
375
376    fn intern(&mut self, ty: Type) -> TypeId {
377        if let Some(existing) = self.index.get(&ty) {
378            return *existing;
379        }
380        let id = TypeId(u32::try_from(self.types.len()).expect("type count fits in u32"));
381        self.types.push(ty.clone());
382        self.index.insert(ty, id);
383        id
384    }
385
386    /// Returns the interned representation of a type identity.
387    #[must_use]
388    pub fn get(&self, id: TypeId) -> &Type {
389        &self.types[id.0 as usize]
390    }
391
392    #[must_use]
393    pub const fn error_type(&self) -> TypeId {
394        self.error
395    }
396    #[must_use]
397    pub const fn any(&self) -> TypeId {
398        self.any
399    }
400    #[must_use]
401    pub const fn unknown(&self) -> TypeId {
402        self.unknown
403    }
404    #[must_use]
405    pub const fn never(&self) -> TypeId {
406        self.never
407    }
408    #[must_use]
409    pub const fn void(&self) -> TypeId {
410        self.void
411    }
412    #[must_use]
413    pub const fn null_type(&self) -> TypeId {
414        self.null
415    }
416    #[must_use]
417    pub const fn undefined_type(&self) -> TypeId {
418        self.undefined
419    }
420    #[must_use]
421    pub const fn boolean(&self) -> TypeId {
422        self.boolean
423    }
424    #[must_use]
425    pub const fn number(&self) -> TypeId {
426        self.number
427    }
428    #[must_use]
429    pub const fn bigint(&self) -> TypeId {
430        self.bigint
431    }
432    #[must_use]
433    pub const fn string(&self) -> TypeId {
434        self.string
435    }
436    #[must_use]
437    pub const fn symbol_type(&self) -> TypeId {
438        self.symbol
439    }
440    #[must_use]
441    pub const fn object(&self) -> TypeId {
442        self.object
443    }
444
445    /// Interns a boolean literal type.
446    pub fn boolean_literal(&mut self, value: bool) -> TypeId {
447        self.intern(Type::BooleanLiteral(value))
448    }
449
450    /// Interns a numeric literal type keyed by its source lexeme.
451    pub fn number_literal(&mut self, text: &str) -> TypeId {
452        self.intern(Type::NumberLiteral(text.into()))
453    }
454
455    /// Interns a string literal type keyed by its source lexeme.
456    pub fn string_literal(&mut self, text: &str) -> TypeId {
457        self.intern(Type::StringLiteral(text.into()))
458    }
459
460    /// Interns a bigint literal type keyed by its source lexeme.
461    pub fn bigint_literal(&mut self, text: &str) -> TypeId {
462        self.intern(Type::BigIntLiteral(text.into()))
463    }
464
465    /// Interns a nominal named type.
466    pub fn named(&mut self, symbol: SymbolId) -> TypeId {
467        self.intern(Type::Named(symbol))
468    }
469
470    /// Interns a numeric enum value type.
471    pub fn numeric_enum(&mut self, symbol: SymbolId) -> TypeId {
472        self.intern(Type::NumericEnum(symbol))
473    }
474
475    /// Interns an array type over `element`.
476    pub fn array(&mut self, element: TypeId) -> TypeId {
477        self.intern(Type::Array(element))
478    }
479
480    /// Interns an object type after canonically ordering its members by name.
481    pub fn object_type(&mut self, mut properties: Vec<PropertyType>) -> TypeId {
482        properties.sort_by(|left, right| left.name.cmp(&right.name));
483        properties.dedup_by(|left, right| left.name == right.name);
484        self.intern(Type::ObjectType(properties))
485    }
486
487    /// Interns a function type.
488    pub fn function(&mut self, parameters: Vec<TypeId>, return_type: TypeId) -> TypeId {
489        self.intern(Type::Function(FunctionSignature {
490            parameters,
491            return_type,
492        }))
493    }
494
495    /// Interns a union, normalizing absorption, `never` removal, and duplicates.
496    pub fn union(&mut self, members: &[TypeId]) -> TypeId {
497        let mut flat = Vec::new();
498        for member in members {
499            match self.get(*member) {
500                Type::Any => return self.any,
501                Type::Unknown => return self.unknown,
502                Type::Never => {}
503                Type::Union(nested) => flat.extend(nested.iter().copied()),
504                _ => flat.push(*member),
505            }
506        }
507        flat.sort_by_key(|id| id.get());
508        flat.dedup();
509        match flat.len() {
510            0 => self.never,
511            1 => flat[0],
512            _ => self.intern(Type::Union(flat)),
513        }
514    }
515
516    /// Returns whether a value of `source` may be assigned where `target` is
517    /// expected, using structural rules over the modeled type space.
518    #[must_use]
519    pub fn assignable(&self, source: TypeId, target: TypeId) -> bool {
520        if source == target {
521            return true;
522        }
523        let (from, to) = (self.get(source), self.get(target));
524        match (from, to) {
525            (Type::Error, _) | (_, Type::Error) => true,
526            // `any` is the deliberate escape hatch in both directions.
527            (Type::Any, _) | (_, Type::Any) => true,
528            // `unknown` is the top type: everything flows in, nothing flows out.
529            (_, Type::Unknown) => true,
530            (Type::Unknown, _) => false,
531            // `never` is the bottom type: it flows into everything, nothing else
532            // flows into it (identity already handled above).
533            (Type::Never, _) => true,
534            (_, Type::Never) => false,
535            (Type::StringLiteral(_), Type::String) => true,
536            (Type::NumberLiteral(_), Type::Number) => true,
537            (Type::BooleanLiteral(_), Type::Boolean) => true,
538            (Type::BigIntLiteral(_), Type::BigInt) => true,
539            (Type::NumericEnum(_), Type::Number) | (Type::Number, Type::NumericEnum(_)) => true,
540            (Type::Union(sources), _) => sources.iter().all(|s| self.assignable(*s, target)),
541            (_, Type::Union(targets)) => targets.iter().any(|t| self.assignable(source, *t)),
542            (Type::Array(source_element), Type::Array(target_element)) => {
543                self.assignable(*source_element, *target_element)
544            }
545            (Type::ObjectType(source_props), Type::ObjectType(target_props)) => {
546                self.object_assignable(source_props, target_props)
547            }
548            (Type::Function(source_sig), Type::Function(target_sig)) => {
549                self.function_assignable(source_sig, target_sig)
550            }
551            _ => false,
552        }
553    }
554
555    /// Computes compatibility once while retaining every accepted unsound
556    /// concession for rule consumers.
557    #[must_use]
558    pub fn relation(&self, source: TypeId, target: TypeId) -> TypeRelation {
559        let compatible = self.assignable(source, target);
560        if !compatible {
561            return TypeRelation {
562                compatible,
563                hazards: Box::new([]),
564            };
565        }
566
567        let mut hazards = Vec::new();
568        if let (Type::Function(from), Type::Function(to)) = (self.get(source), self.get(target)) {
569            if from.parameters.len() < to.parameters.len() {
570                hazards.push(RelationHazard::FewerCallbackParameters);
571            }
572            if matches!(self.get(to.return_type), Type::Void)
573                && !matches!(self.get(from.return_type), Type::Void | Type::Never)
574            {
575                hazards.push(RelationHazard::ValueReturnedToVoid);
576            }
577        }
578        if matches!(
579            (self.get(source), self.get(target)),
580            (Type::NumericEnum(_), Type::Number) | (Type::Number, Type::NumericEnum(_))
581        ) {
582            hazards.push(RelationHazard::NumericEnumNumber);
583        }
584        if let (Type::ObjectType(from), Type::ObjectType(to)) = (self.get(source), self.get(target))
585        {
586            for target_property in to.iter().filter(|property| property.optional) {
587                let Some(source_property) = from
588                    .iter()
589                    .find(|property| property.name == target_property.name)
590                else {
591                    continue;
592                };
593                if matches!(self.get(source_property.type_id), Type::Undefined)
594                    && !self.contains_undefined(target_property.type_id)
595                {
596                    hazards.push(RelationHazard::ExplicitUndefinedForOptional);
597                    break;
598                }
599            }
600        }
601        TypeRelation {
602            compatible,
603            hazards: hazards.into_boxed_slice(),
604        }
605    }
606
607    fn contains_undefined(&self, type_id: TypeId) -> bool {
608        match self.get(type_id) {
609            Type::Undefined => true,
610            Type::Union(members) => members
611                .iter()
612                .any(|member| self.contains_undefined(*member)),
613            _ => false,
614        }
615    }
616
617    fn object_assignable(&self, source: &[PropertyType], target: &[PropertyType]) -> bool {
618        // Excess source properties are allowed; each target property must be
619        // satisfied. Members are name-sorted, so a merge walk suffices.
620        target.iter().all(
621            |want| match source.iter().find(|have| have.name == want.name) {
622                Some(have) => {
623                    self.assignable(have.type_id, want.type_id)
624                        || (want.optional && matches!(self.get(have.type_id), Type::Undefined))
625                }
626                None => want.optional,
627            },
628        )
629    }
630
631    fn function_assignable(&self, source: &FunctionSignature, target: &FunctionSignature) -> bool {
632        if source.parameters.len() > target.parameters.len() {
633            return false;
634        }
635        for (source_param, target_param) in source.parameters.iter().zip(&target.parameters) {
636            // Parameters are contravariant: the target must supply a value the
637            // source accepts.
638            if !self.assignable(*target_param, *source_param) {
639                return false;
640            }
641        }
642        matches!(self.get(target.return_type), Type::Void)
643            || self.assignable(source.return_type, target.return_type)
644    }
645}
646
647/// A compatibility decision plus the intentional TypeScript hazards that made
648/// the conversion possible.
649#[derive(Clone, Debug, Eq, PartialEq)]
650pub struct TypeRelation {
651    compatible: bool,
652    hazards: Box<[RelationHazard]>,
653}
654
655impl TypeRelation {
656    #[must_use]
657    pub const fn compatible(&self) -> bool {
658        self.compatible
659    }
660
661    #[must_use]
662    pub fn hazards(&self) -> &[RelationHazard] {
663        &self.hazards
664    }
665}
666
667/// A type-system concession retained for the semantic lint pass.
668#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
669pub enum RelationHazard {
670    ExplicitUndefinedForOptional,
671    FewerCallbackParameters,
672    ValueReturnedToVoid,
673    NumericEnumNumber,
674}
675
676/// Compact identity for one allocated object literal and its aliases.
677#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
678pub struct ObjectId(u32);
679
680/// Source-qualified syntax identity used by cross-file facts.
681#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
682pub struct NodeKey {
683    pub source_id: SourceId,
684    pub node_id: NodeId,
685}
686
687/// One checker-derived condition consumed by a semantic rule.
688#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
689pub enum SemanticHazard {
690    UncheckedIndexRead,
691    ExplicitUndefinedOptional,
692    DetachedMethod,
693    DivergentAccessor,
694    ReadonlyAliasMutation,
695    FewerCallbackParameters,
696    ValueReturnedToVoid,
697    OpenObjectKeys,
698    IndexSignatureDotAccess,
699    ImplicitAny,
700    UncheckedAssertion,
701    DeclarationInferenceDependency,
702    TypeImportedAsValue,
703    TypeReexportedAsValue,
704    UncheckedSideEffectImport,
705    InteropDependentDefaultImport,
706    CjsEsmNamedExportMismatch,
707    VirtualCallInConstructor,
708    InitializedFieldShadowsAccessor,
709    ImplicitOverride,
710    NumericEnumNumber,
711    NumericEnumReverseLookup,
712    NonExhaustiveSwitch,
713    InvalidNumberFormatting,
714    NumericKeyOrder,
715    JsonStringifyUnserializable,
716    UncheckedJsonParse,
717    NumericDefaultSort,
718    LooseEqualityCoercion,
719    ObjectToPrimitive,
720    SymbolInterpolation,
721    UnsafeToStringTag,
722    UninitializedFieldShadowsAccessor,
723}
724
725/// Immutable evidence for one semantic lint.
726#[derive(Clone, Debug, Eq, PartialEq)]
727pub struct HazardFact {
728    pub hazard: SemanticHazard,
729    pub range: TextRange,
730    pub note: Option<Box<str>>,
731}
732
733/// Frozen checker facts. Rule implementations only query this product.
734#[derive(Clone, Debug, Default)]
735pub struct AnalysisFacts {
736    hazards: Vec<HazardFact>,
737}
738
739impl AnalysisFacts {
740    #[must_use]
741    pub fn hazards(&self) -> &[HazardFact] {
742        &self.hazards
743    }
744
745    pub(crate) fn push(&mut self, fact: HazardFact) {
746        if !self
747            .hazards
748            .iter()
749            .any(|existing| existing.hazard == fact.hazard && existing.range == fact.range)
750        {
751            self.hazards.push(fact);
752        }
753    }
754}
755
756/// The immutable product of semantic analysis.
757#[derive(Clone, Debug)]
758pub struct SemanticModel {
759    scopes: Vec<Scope>,
760    symbols: Vec<Symbol>,
761    symbol_types: Vec<TypeId>,
762    references: HashMap<NodeId, SymbolId>,
763    types: TypeTable,
764    module_scope: ScopeId,
765    facts: AnalysisFacts,
766}
767
768impl SemanticModel {
769    /// Returns every lexical scope, with the module scope first.
770    #[must_use]
771    pub fn scopes(&self) -> &[Scope] {
772        &self.scopes
773    }
774
775    /// Returns a scope by identity.
776    #[must_use]
777    pub fn scope(&self, id: ScopeId) -> &Scope {
778        &self.scopes[id.0 as usize]
779    }
780
781    /// Returns the top-level module scope.
782    #[must_use]
783    pub const fn module_scope(&self) -> ScopeId {
784        self.module_scope
785    }
786
787    /// Returns every bound name.
788    #[must_use]
789    pub fn symbols(&self) -> &[Symbol] {
790        &self.symbols
791    }
792
793    /// Returns a symbol by identity.
794    #[must_use]
795    pub fn symbol(&self, id: SymbolId) -> &Symbol {
796        &self.symbols[id.0 as usize]
797    }
798
799    /// Returns the declared or inferred type of a bound name.
800    #[must_use]
801    pub fn symbol_type(&self, id: SymbolId) -> TypeId {
802        self.symbol_types[id.0 as usize]
803    }
804
805    /// Returns the interned type table.
806    #[must_use]
807    pub const fn types(&self) -> &TypeTable {
808        &self.types
809    }
810
811    /// Returns the immutable semantic evidence consumed by lint rules.
812    #[must_use]
813    pub const fn facts(&self) -> &AnalysisFacts {
814        &self.facts
815    }
816
817    pub(crate) fn replace_facts(&mut self, facts: AnalysisFacts) {
818        self.facts = facts;
819    }
820
821    /// Returns the symbol an identifier reference resolved to, if any.
822    #[must_use]
823    pub fn reference(&self, node: NodeId) -> Option<SymbolId> {
824        self.references.get(&node).copied()
825    }
826
827    /// Returns how many identifier references resolved to a local binding.
828    #[must_use]
829    pub fn resolved_reference_count(&self) -> usize {
830        self.references.len()
831    }
832
833    /// Resolves a value name from `scope` outward through its ancestors.
834    #[must_use]
835    pub fn lookup_value(&self, scope: ScopeId, name: &str) -> Option<SymbolId> {
836        let mut current = Some(scope);
837        while let Some(id) = current {
838            let scope = &self.scopes[id.0 as usize];
839            if let Some(symbol) = scope.values.get(name) {
840                return Some(*symbol);
841            }
842            current = scope.parent;
843        }
844        None
845    }
846
847    /// Resolves a type name from `scope` outward through its ancestors.
848    #[must_use]
849    pub fn lookup_type(&self, scope: ScopeId, name: &str) -> Option<SymbolId> {
850        let mut current = Some(scope);
851        while let Some(id) = current {
852            let scope = &self.scopes[id.0 as usize];
853            if let Some(symbol) = scope.types.get(name) {
854                return Some(*symbol);
855            }
856            current = scope.parent;
857        }
858        None
859    }
860}
861
862/// Analyzes one source with the default lint profile.
863#[must_use]
864pub fn check(source_file: &Recovered<SourceFile>) -> Recovered<SemanticModel> {
865    check_with_lints(source_file, &LintTable::new(LintProfile::Default))
866}
867
868/// Analyzes one source using an already-resolved lint table.
869#[must_use]
870pub fn check_with_lints(
871    source_file: &Recovered<SourceFile>,
872    levels: &LintTable,
873) -> Recovered<SemanticModel> {
874    let source = source_file.product();
875    let (mut model, mut diagnostics) = check_core(source);
876    model.replace_facts(crate::rules::semantic::collect_facts(source, &model));
877    diagnostics.extend(analyze_warnings(source_file, levels));
878    diagnostics.extend(crate::rules::analyze_semantic(source, &model, None, levels));
879    Recovered::new(model, diagnostics)
880}
881
882/// One resolved module edge supplied by the project loader.
883#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
884pub struct ResolvedModuleEdge {
885    pub from: SourceId,
886    pub specifier: NodeId,
887    pub to: SourceId,
888}
889
890/// Borrowed input for a linked multi-file checker run.
891#[derive(Clone, Copy)]
892pub struct ProgramCheckInput<'a> {
893    pub files: &'a [Recovered<SourceFile>],
894    pub edges: &'a [ResolvedModuleEdge],
895}
896
897/// Immutable linked checker product.
898#[derive(Clone, Debug)]
899pub struct ProgramSemanticModel {
900    files: BTreeMap<SourceId, SemanticModel>,
901    edges: Box<[ResolvedModuleEdge]>,
902}
903
904impl ProgramSemanticModel {
905    #[must_use]
906    pub fn file(&self, source_id: SourceId) -> Option<&SemanticModel> {
907        self.files.get(&source_id)
908    }
909
910    #[must_use]
911    pub fn edges(&self) -> &[ResolvedModuleEdge] {
912        &self.edges
913    }
914}
915
916/// Checks a set of loaded files after module resolution.
917#[must_use]
918pub fn check_program(
919    input: ProgramCheckInput<'_>,
920    levels: &LintTable,
921) -> Recovered<ProgramSemanticModel> {
922    let mut files = BTreeMap::new();
923    let mut diagnostics = Vec::new();
924    for recovered in input.files {
925        let source = recovered.product();
926        let (mut model, core_diagnostics) = check_core(source);
927        model.replace_facts(crate::rules::semantic::collect_facts(source, &model));
928        diagnostics.extend(core_diagnostics);
929        diagnostics.extend(analyze_warnings(recovered, levels));
930        files.insert(source.source_id(), model);
931    }
932    crate::rules::semantic::collect_program_facts(input.files, input.edges, &mut files);
933    let program = ProgramSemanticModel {
934        files,
935        edges: input.edges.into(),
936    };
937    for recovered in input.files {
938        let source = recovered.product();
939        let model = program
940            .file(source.source_id())
941            .expect("program model contains every input source");
942        diagnostics.extend(crate::rules::analyze_semantic(
943            source,
944            model,
945            Some(&program),
946            levels,
947        ));
948    }
949    Recovered::new(program, diagnostics)
950}
951
952fn check_core(source: &SourceFile) -> (SemanticModel, Vec<Diagnostic>) {
953    let mut checker = Checker::new(source);
954    checker.run();
955    checker.finish()
956}
957
958/// Lazy resolution state for a type-declaring symbol.
959#[derive(Clone, Copy)]
960enum TypeState {
961    Unresolved,
962    InProgress,
963    Done(TypeId),
964}
965
966/// A named type definition kept by reference for lazy, memoized resolution.
967#[derive(Clone, Copy)]
968enum TypeDef<'src> {
969    Alias {
970        scope: ScopeId,
971        type_parameters: Option<&'src crate::syntax::TypeParameterList>,
972        node: &'src Ty,
973    },
974    Interface {
975        scope: ScopeId,
976        type_parameters: Option<&'src crate::syntax::TypeParameterList>,
977        extends: &'src [TypeReference],
978        members: &'src [crate::syntax::TypeMemberNode],
979    },
980}
981
982struct Checker<'src> {
983    source: &'src SourceFile,
984    intrinsics: GlobalEnvironment,
985    scopes: Vec<Scope>,
986    symbols: Vec<Symbol>,
987    symbol_types: Vec<TypeId>,
988    type_state: Vec<TypeState>,
989    type_defs: HashMap<SymbolId, TypeDef<'src>>,
990    references: HashMap<NodeId, SymbolId>,
991    diagnostics: Vec<Diagnostic>,
992    types: TypeTable,
993    module_scope: ScopeId,
994}
995
996impl<'src> Checker<'src> {
997    fn new(source: &'src SourceFile) -> Self {
998        let mut checker = Self {
999            source,
1000            intrinsics: GlobalEnvironment::standard(),
1001            scopes: Vec::new(),
1002            symbols: Vec::new(),
1003            symbol_types: Vec::new(),
1004            type_state: Vec::new(),
1005            type_defs: HashMap::new(),
1006            references: HashMap::new(),
1007            diagnostics: Vec::new(),
1008            types: TypeTable::new(),
1009            module_scope: ScopeId(0),
1010        };
1011        let global_scope = checker.new_scope(ScopeKind::Global, None);
1012        checker.module_scope = checker.new_scope(ScopeKind::Module, Some(global_scope));
1013        checker.bind_intrinsic_environment(global_scope);
1014        checker
1015    }
1016
1017    fn bind_intrinsic_environment(&mut self, scope: ScopeId) {
1018        for name in self.intrinsics.values() {
1019            self.declare(
1020                name,
1021                SymbolKind::IntrinsicValue,
1022                scope,
1023                NodeId::default(),
1024                NodeId::default_range(),
1025            );
1026        }
1027        for name in self.intrinsics.types() {
1028            self.declare(
1029                name,
1030                SymbolKind::IntrinsicType,
1031                scope,
1032                NodeId::default(),
1033                NodeId::default_range(),
1034            );
1035        }
1036    }
1037
1038    fn run(&mut self) {
1039        let statements = self.source.statements();
1040        let scope = self.module_scope;
1041        self.bind_statements(statements, scope);
1042        self.bind_hoisted_statements(statements, scope);
1043        self.resolve_statements(statements, scope);
1044    }
1045
1046    fn finish(self) -> (SemanticModel, Vec<Diagnostic>) {
1047        let model = SemanticModel {
1048            scopes: self.scopes,
1049            symbols: self.symbols,
1050            symbol_types: self.symbol_types,
1051            references: self.references,
1052            types: self.types,
1053            module_scope: self.module_scope,
1054            facts: AnalysisFacts::default(),
1055        };
1056        (model, self.diagnostics)
1057    }
1058
1059    // -- text and scope helpers ------------------------------------------------
1060
1061    fn text(&self, token: &Token) -> &'src str {
1062        self.source.token_text(token).unwrap_or("")
1063    }
1064
1065    fn identifier_text(&self, identifier: &IdentifierNode) -> &'src str {
1066        self.text(identifier.data().token())
1067    }
1068
1069    fn new_scope(&mut self, kind: ScopeKind, parent: Option<ScopeId>) -> ScopeId {
1070        let id = ScopeId(u32::try_from(self.scopes.len()).expect("scope count fits in u32"));
1071        self.scopes.push(Scope {
1072            kind,
1073            parent,
1074            values: BTreeMap::new(),
1075            types: BTreeMap::new(),
1076        });
1077        id
1078    }
1079
1080    /// Walks outward from `scope` to the nearest Function or Module scope, the
1081    /// declaration target for JS-hoisted `var` and function bindings. Stopping
1082    /// at the first such scope keeps inner-function `var`s from escaping into an
1083    /// outer function; the Module scope has no parent and terminates the walk.
1084    fn value_hoist_scope(&self, scope: ScopeId) -> ScopeId {
1085        let mut current = scope;
1086        loop {
1087            let node = &self.scopes[current.0 as usize];
1088            if matches!(node.kind, ScopeKind::Function | ScopeKind::Module) {
1089                return current;
1090            }
1091            match node.parent {
1092                Some(parent) => current = parent,
1093                None => return current,
1094            }
1095        }
1096    }
1097
1098    fn emit(&mut self, code: DiagnosticCode, range: TextRange, message: &'static str) {
1099        self.diagnostics.push(Diagnostic::error(
1100            code,
1101            self.source.source_id(),
1102            range,
1103            message,
1104        ));
1105    }
1106
1107    // -- declaration binding ---------------------------------------------------
1108
1109    fn declare(
1110        &mut self,
1111        name: &str,
1112        kind: SymbolKind,
1113        scope: ScopeId,
1114        declaration: NodeId,
1115        range: TextRange,
1116    ) -> SymbolId {
1117        // `var` and function declarations are hoisted to the nearest Function or
1118        // Module scope, so a binding textually nested in a block, `for`, or
1119        // `catch` scope is owned by its enclosing function. `let`/`const` and all
1120        // other kinds stay in the scope they were written in.
1121        let scope = if matches!(
1122            kind,
1123            SymbolKind::Variable(VariableKind::Var) | SymbolKind::Function
1124        ) {
1125            self.value_hoist_scope(scope)
1126        } else {
1127            scope
1128        };
1129        if kind.occupies_value()
1130            && let Some(existing) = self.scopes[scope.0 as usize].values.get(name)
1131            && kind.value_mergeable()
1132            && self.symbols[existing.get() as usize].kind.value_mergeable()
1133        {
1134            return *existing;
1135        }
1136        let id = SymbolId(u32::try_from(self.symbols.len()).expect("symbol count fits in u32"));
1137        self.symbols.push(Symbol {
1138            name: name.to_owned(),
1139            kind,
1140            scope,
1141            declaration,
1142            range,
1143        });
1144        self.symbol_types.push(self.types.any());
1145        self.type_state.push(TypeState::Unresolved);
1146
1147        let mut conflict = false;
1148        if kind.occupies_value() {
1149            conflict |= self.insert_value(scope, name, id, kind);
1150        }
1151        if kind.occupies_type() {
1152            conflict |= self.insert_type(scope, name, id, kind);
1153        }
1154        if conflict {
1155            self.emit(DUPLICATE_DECLARATION, range, DUPLICATE_MESSAGE);
1156        }
1157        id
1158    }
1159
1160    fn insert_value(&mut self, scope: ScopeId, name: &str, id: SymbolId, kind: SymbolKind) -> bool {
1161        match self.scopes[scope.0 as usize].values.get(name) {
1162            None => {
1163                self.scopes[scope.0 as usize]
1164                    .values
1165                    .insert(name.to_owned(), id);
1166                false
1167            }
1168            Some(existing) => {
1169                let existing_kind = self.symbols[existing.get() as usize].kind;
1170                !(kind.value_mergeable() && existing_kind.value_mergeable())
1171            }
1172        }
1173    }
1174
1175    fn insert_type(&mut self, scope: ScopeId, name: &str, id: SymbolId, kind: SymbolKind) -> bool {
1176        match self.scopes[scope.0 as usize].types.get(name) {
1177            None => {
1178                self.scopes[scope.0 as usize]
1179                    .types
1180                    .insert(name.to_owned(), id);
1181                false
1182            }
1183            Some(existing) => {
1184                let existing_kind = self.symbols[existing.get() as usize].kind;
1185                !(kind.type_mergeable() && existing_kind.type_mergeable())
1186            }
1187        }
1188    }
1189
1190    fn bind_statements(&mut self, statements: &'src [crate::syntax::Stmt], scope: ScopeId) {
1191        for statement in statements {
1192            self.bind_statement(statement, scope);
1193        }
1194    }
1195
1196    /// Pre-binds `var` and function names that occur beneath lexical child
1197    /// scopes. The traversal never enters a function body: its own call to this
1198    /// pass supplies the correct function hoist target.
1199    fn bind_hoisted_statements(&mut self, statements: &'src [crate::syntax::Stmt], scope: ScopeId) {
1200        for statement in statements {
1201            self.bind_hoisted_statement(statement, scope);
1202        }
1203    }
1204
1205    fn bind_hoisted_statement(&mut self, statement: &'src crate::syntax::Stmt, scope: ScopeId) {
1206        match statement.data() {
1207            Statement::Variable(variable) if variable.kind == VariableKind::Var => {
1208                self.bind_variable(variable, scope, statement.id());
1209            }
1210            Statement::Function(function) => {
1211                if let Some(name) = &function.function.name {
1212                    self.declare(
1213                        self.identifier_text(name),
1214                        SymbolKind::Function,
1215                        scope,
1216                        statement.id(),
1217                        name.range(),
1218                    );
1219                }
1220            }
1221            Statement::Block(block) => {
1222                self.bind_hoisted_statements(&block.data().statements, scope)
1223            }
1224            Statement::If(statement) => {
1225                self.bind_hoisted_statement(&statement.consequent, scope);
1226                if let Some(alternate) = &statement.alternate {
1227                    self.bind_hoisted_statement(alternate, scope);
1228                }
1229            }
1230            Statement::Switch(statement) => {
1231                for case in &statement.cases {
1232                    self.bind_hoisted_statements(&case.data().consequent, scope);
1233                }
1234            }
1235            Statement::For(for_statement) => {
1236                if let Some(ForInitializer::Variable(variable)) = &for_statement.initializer
1237                    && variable.kind == VariableKind::Var
1238                {
1239                    self.bind_variable(variable, scope, NodeId::default());
1240                }
1241                self.bind_hoisted_statement(&for_statement.body, scope);
1242            }
1243            Statement::ForIn(for_statement) => {
1244                if let ForBinding::Variable(variable) = &for_statement.binding
1245                    && variable.kind == VariableKind::Var
1246                {
1247                    self.bind_variable(variable, scope, NodeId::default());
1248                }
1249                self.bind_hoisted_statement(&for_statement.body, scope);
1250            }
1251            Statement::ForOf(for_statement) => {
1252                if let ForBinding::Variable(variable) = &for_statement.binding
1253                    && variable.kind == VariableKind::Var
1254                {
1255                    self.bind_variable(variable, scope, NodeId::default());
1256                }
1257                self.bind_hoisted_statement(&for_statement.body, scope);
1258            }
1259            Statement::While(statement) => self.bind_hoisted_statement(&statement.body, scope),
1260            Statement::DoWhile(statement) => self.bind_hoisted_statement(&statement.body, scope),
1261            Statement::Try(statement) => {
1262                self.bind_hoisted_statements(&statement.block.data().statements, scope);
1263                if let Some(handler) = &statement.handler {
1264                    self.bind_hoisted_statements(&handler.data().body.data().statements, scope);
1265                }
1266                if let Some(finalizer) = &statement.finalizer {
1267                    self.bind_hoisted_statements(&finalizer.data().statements, scope);
1268                }
1269            }
1270            Statement::With(statement) => self.bind_hoisted_statement(&statement.body, scope),
1271            Statement::Labeled(statement) => self.bind_hoisted_statement(&statement.body, scope),
1272            Statement::Namespace(namespace) => {
1273                self.bind_hoisted_statements(&namespace.body.data().statements, scope);
1274            }
1275            Statement::Declare(inner) => self.bind_hoisted_statement(inner, scope),
1276            Statement::Export(crate::syntax::ExportDeclaration::Named(
1277                crate::syntax::ExportNamedDeclaration::Declaration(inner),
1278            )) => self.bind_hoisted_statement(inner, scope),
1279            _ => {}
1280        }
1281    }
1282
1283    fn bind_statement(&mut self, statement: &'src crate::syntax::Stmt, scope: ScopeId) {
1284        let declaration = statement.id();
1285        match statement.data() {
1286            Statement::Variable(variable) => self.bind_variable(variable, scope, declaration),
1287            Statement::Function(function) => {
1288                if let Some(name) = &function.function.name {
1289                    self.declare(
1290                        self.identifier_text(name),
1291                        SymbolKind::Function,
1292                        scope,
1293                        declaration,
1294                        name.range(),
1295                    );
1296                }
1297            }
1298            Statement::Class(class) => {
1299                if let Some(name) = &class.name {
1300                    self.declare(
1301                        self.identifier_text(name),
1302                        SymbolKind::Class,
1303                        scope,
1304                        declaration,
1305                        name.range(),
1306                    );
1307                }
1308            }
1309            Statement::Interface(interface) => self.bind_interface(interface, scope, declaration),
1310            Statement::TypeAlias(alias) => self.bind_type_alias(alias, scope, declaration),
1311            Statement::Enum(declaration_node) => {
1312                self.declare(
1313                    self.identifier_text(&declaration_node.name),
1314                    SymbolKind::Enum,
1315                    scope,
1316                    declaration,
1317                    declaration_node.name.range(),
1318                );
1319            }
1320            Statement::Namespace(namespace) => {
1321                self.declare(
1322                    self.identifier_text(&namespace.name),
1323                    SymbolKind::Namespace,
1324                    scope,
1325                    declaration,
1326                    namespace.name.range(),
1327                );
1328            }
1329            Statement::Import(import) => self.bind_import(import, scope, declaration),
1330            Statement::ImportEquals(import) => {
1331                self.declare(
1332                    self.identifier_text(&import.local),
1333                    SymbolKind::Import,
1334                    scope,
1335                    declaration,
1336                    import.local.range(),
1337                );
1338            }
1339            Statement::Declare(inner) => self.bind_statement(inner, scope),
1340            Statement::Export(crate::syntax::ExportDeclaration::Named(
1341                crate::syntax::ExportNamedDeclaration::Declaration(inner),
1342            )) => {
1343                self.bind_statement(inner, scope);
1344            }
1345            _ => {}
1346        }
1347    }
1348
1349    fn bind_variable(
1350        &mut self,
1351        variable: &'src VariableDeclaration,
1352        scope: ScopeId,
1353        declaration: NodeId,
1354    ) {
1355        for declarator in &variable.declarations {
1356            self.bind_pattern(
1357                &declarator.data().binding,
1358                variable.kind,
1359                scope,
1360                declaration,
1361            );
1362        }
1363    }
1364
1365    fn bind_pattern(
1366        &mut self,
1367        pattern: &'src crate::syntax::Pattern,
1368        kind: VariableKind,
1369        scope: ScopeId,
1370        declaration: NodeId,
1371    ) {
1372        match pattern.data() {
1373            BindingPattern::Identifier(name) => {
1374                self.declare(
1375                    self.identifier_text(name),
1376                    SymbolKind::Variable(kind),
1377                    scope,
1378                    declaration,
1379                    name.range(),
1380                );
1381            }
1382            BindingPattern::Object(object) => {
1383                for property in &object.properties {
1384                    self.bind_pattern(&property.binding, kind, scope, declaration);
1385                }
1386            }
1387            BindingPattern::Array(array) => {
1388                for element in &array.elements {
1389                    if let crate::syntax::ArrayBindingElement::Binding(inner) = element {
1390                        self.bind_pattern(inner, kind, scope, declaration);
1391                    }
1392                }
1393            }
1394            BindingPattern::Rest(rest) => {
1395                self.bind_pattern(&rest.argument, kind, scope, declaration);
1396            }
1397            BindingPattern::Assignment(assignment) => {
1398                self.bind_pattern(&assignment.left, kind, scope, declaration);
1399            }
1400            BindingPattern::Missing(_) => {}
1401        }
1402    }
1403
1404    fn bind_interface(
1405        &mut self,
1406        interface: &'src InterfaceDeclaration,
1407        scope: ScopeId,
1408        declaration: NodeId,
1409    ) {
1410        let id = self.declare(
1411            self.identifier_text(&interface.name),
1412            SymbolKind::Interface,
1413            scope,
1414            declaration,
1415            interface.name.range(),
1416        );
1417        let type_scope = self.new_scope(ScopeKind::Block, Some(scope));
1418        self.bind_type_parameter_names(interface.type_parameters.as_ref(), type_scope);
1419        // Only the first interface of a mergeable set owns the definition slot;
1420        // later merges keep their symbol but reuse the representative's shape.
1421        self.type_defs.entry(id).or_insert(TypeDef::Interface {
1422            scope: type_scope,
1423            type_parameters: interface.type_parameters.as_ref(),
1424            extends: &interface.extends,
1425            members: &interface.members,
1426        });
1427    }
1428
1429    fn bind_type_alias(
1430        &mut self,
1431        alias: &'src TypeAliasDeclaration,
1432        scope: ScopeId,
1433        declaration: NodeId,
1434    ) {
1435        let id = self.declare(
1436            self.identifier_text(&alias.name),
1437            SymbolKind::TypeAlias,
1438            scope,
1439            declaration,
1440            alias.name.range(),
1441        );
1442        let type_scope = self.new_scope(ScopeKind::Block, Some(scope));
1443        self.bind_type_parameter_names(alias.type_parameters.as_ref(), type_scope);
1444        self.type_defs.insert(
1445            id,
1446            TypeDef::Alias {
1447                scope: type_scope,
1448                type_parameters: alias.type_parameters.as_ref(),
1449                node: &alias.type_node,
1450            },
1451        );
1452    }
1453
1454    fn bind_import(
1455        &mut self,
1456        import: &'src crate::syntax::ImportDeclaration,
1457        scope: ScopeId,
1458        declaration: NodeId,
1459    ) {
1460        let Some(clause) = &import.clause else {
1461            return;
1462        };
1463        if let Some(default) = &clause.default {
1464            self.declare(
1465                self.identifier_text(default),
1466                SymbolKind::Import,
1467                scope,
1468                declaration,
1469                default.range(),
1470            );
1471        }
1472        match &clause.binding {
1473            Some(ImportBinding::Namespace(name)) => {
1474                self.declare(
1475                    self.identifier_text(name),
1476                    SymbolKind::Import,
1477                    scope,
1478                    declaration,
1479                    name.range(),
1480                );
1481            }
1482            Some(ImportBinding::Named(specifiers)) => {
1483                for specifier in specifiers {
1484                    let local = &specifier.data().local;
1485                    self.declare(
1486                        self.identifier_text(local),
1487                        SymbolKind::Import,
1488                        scope,
1489                        declaration,
1490                        local.range(),
1491                    );
1492                }
1493            }
1494            None => {}
1495        }
1496    }
1497
1498    // -- reference resolution and assignability --------------------------------
1499
1500    fn resolve_statements(&mut self, statements: &'src [crate::syntax::Stmt], scope: ScopeId) {
1501        for statement in statements {
1502            self.resolve_statement(statement, scope);
1503        }
1504    }
1505
1506    fn resolve_statement(&mut self, statement: &'src crate::syntax::Stmt, scope: ScopeId) {
1507        match statement.data() {
1508            Statement::Variable(variable) => self.resolve_variable(variable, scope),
1509            Statement::Function(function) => self.resolve_function(&function.function, scope),
1510            Statement::Class(class) => self.resolve_class(class, scope),
1511            Statement::Interface(interface) => {
1512                if let Some(id) = self.scopes[scope.0 as usize]
1513                    .types
1514                    .get(self.identifier_text(&interface.name))
1515                    .copied()
1516                {
1517                    let _ = self.resolve_type_symbol(id);
1518                }
1519            }
1520            Statement::TypeAlias(alias) => {
1521                if let Some(id) = self.scopes[scope.0 as usize]
1522                    .types
1523                    .get(self.identifier_text(&alias.name))
1524                    .copied()
1525                {
1526                    let _ = self.resolve_type_symbol(id);
1527                }
1528            }
1529            Statement::Block(block) => {
1530                let child = self.new_scope(ScopeKind::Block, Some(scope));
1531                self.bind_statements(&block.data().statements, child);
1532                self.resolve_statements(&block.data().statements, child);
1533            }
1534            Statement::Expression(statement) => self.resolve_expr(&statement.expression, scope),
1535            Statement::If(statement) => {
1536                self.resolve_expr(&statement.test, scope);
1537                self.resolve_statement(&statement.consequent, scope);
1538                if let Some(alternate) = &statement.alternate {
1539                    self.resolve_statement(alternate, scope);
1540                }
1541            }
1542            Statement::Switch(statement) => {
1543                self.resolve_expr(&statement.discriminant, scope);
1544                let child = self.new_scope(ScopeKind::Block, Some(scope));
1545                for case in &statement.cases {
1546                    if let Some(test) = &case.data().test {
1547                        self.resolve_expr(test, child);
1548                    }
1549                    self.bind_statements(&case.data().consequent, child);
1550                }
1551                for case in &statement.cases {
1552                    self.resolve_statements(&case.data().consequent, child);
1553                }
1554            }
1555            Statement::For(for_statement) => {
1556                let child = self.new_scope(ScopeKind::For, Some(scope));
1557                if let Some(initializer) = &for_statement.initializer {
1558                    self.resolve_for_initializer(initializer, child);
1559                }
1560                if let Some(test) = &for_statement.test {
1561                    self.resolve_expr(test, child);
1562                }
1563                if let Some(update) = &for_statement.update {
1564                    self.resolve_expr(update, child);
1565                }
1566                self.resolve_statement(&for_statement.body, child);
1567            }
1568            Statement::ForIn(for_statement) => {
1569                let child = self.new_scope(ScopeKind::For, Some(scope));
1570                self.resolve_for_binding(&for_statement.binding, child);
1571                self.resolve_expr(&for_statement.object, child);
1572                self.resolve_statement(&for_statement.body, child);
1573            }
1574            Statement::ForOf(for_statement) => {
1575                let child = self.new_scope(ScopeKind::For, Some(scope));
1576                self.resolve_for_binding(&for_statement.binding, child);
1577                self.resolve_expr(&for_statement.iterable, child);
1578                self.resolve_statement(&for_statement.body, child);
1579            }
1580            Statement::While(statement) => {
1581                self.resolve_expr(&statement.test, scope);
1582                self.resolve_statement(&statement.body, scope);
1583            }
1584            Statement::DoWhile(statement) => {
1585                self.resolve_statement(&statement.body, scope);
1586                self.resolve_expr(&statement.test, scope);
1587            }
1588            Statement::Try(statement) => {
1589                let block = &statement.block;
1590                let try_scope = self.new_scope(ScopeKind::Block, Some(scope));
1591                self.bind_statements(&block.data().statements, try_scope);
1592                self.resolve_statements(&block.data().statements, try_scope);
1593                if let Some(handler) = &statement.handler {
1594                    let catch_scope = self.new_scope(ScopeKind::Catch, Some(scope));
1595                    if let Some(binding) = &handler.data().binding {
1596                        self.bind_pattern(binding, VariableKind::Let, catch_scope, handler.id());
1597                    }
1598                    let body = &handler.data().body;
1599                    self.bind_statements(&body.data().statements, catch_scope);
1600                    self.resolve_statements(&body.data().statements, catch_scope);
1601                }
1602                if let Some(finalizer) = &statement.finalizer {
1603                    let finally_scope = self.new_scope(ScopeKind::Block, Some(scope));
1604                    self.bind_statements(&finalizer.data().statements, finally_scope);
1605                    self.resolve_statements(&finalizer.data().statements, finally_scope);
1606                }
1607            }
1608            Statement::With(statement) => {
1609                self.resolve_expr(&statement.object, scope);
1610                self.resolve_statement(&statement.body, scope);
1611            }
1612            Statement::Labeled(statement) => self.resolve_statement(&statement.body, scope),
1613            Statement::Return(statement) => {
1614                if let Some(argument) = &statement.argument {
1615                    self.resolve_expr(argument, scope);
1616                }
1617            }
1618            Statement::Throw(statement) => self.resolve_expr(&statement.argument, scope),
1619            Statement::Enum(declaration) => {
1620                for member in &declaration.members {
1621                    if let Some(initializer) = &member.data().initializer {
1622                        self.resolve_expr(initializer, scope);
1623                    }
1624                }
1625            }
1626            Statement::Namespace(namespace) => {
1627                let child = self.new_scope(ScopeKind::Block, Some(scope));
1628                let body = &namespace.body;
1629                self.bind_statements(&body.data().statements, child);
1630                self.resolve_statements(&body.data().statements, child);
1631            }
1632            Statement::Declare(inner) => self.resolve_statement(inner, scope),
1633            Statement::Export(export) => self.resolve_export(export, scope),
1634            _ => {}
1635        }
1636    }
1637
1638    fn resolve_export(&mut self, export: &'src crate::syntax::ExportDeclaration, scope: ScopeId) {
1639        match export {
1640            crate::syntax::ExportDeclaration::Named(
1641                crate::syntax::ExportNamedDeclaration::Declaration(inner),
1642            ) => self.resolve_statement(inner, scope),
1643            crate::syntax::ExportDeclaration::Default(default) => match &default.value {
1644                crate::syntax::ExportDefaultValue::Function(function) => {
1645                    self.resolve_function(function, scope);
1646                }
1647                crate::syntax::ExportDefaultValue::Class(class) => self.resolve_class(class, scope),
1648                crate::syntax::ExportDefaultValue::Expression(expression) => {
1649                    self.resolve_expr(expression, scope);
1650                }
1651                crate::syntax::ExportDefaultValue::Missing(_) => {}
1652            },
1653            crate::syntax::ExportDeclaration::Assignment(expression) => {
1654                self.resolve_expr(expression, scope);
1655            }
1656            _ => {}
1657        }
1658    }
1659
1660    fn resolve_for_initializer(&mut self, initializer: &'src ForInitializer, scope: ScopeId) {
1661        match initializer {
1662            ForInitializer::Variable(variable) => {
1663                self.bind_variable(variable, scope, NodeId::default());
1664                self.resolve_variable(variable, scope);
1665            }
1666            ForInitializer::Expression(expression) => self.resolve_expr(expression, scope),
1667        }
1668    }
1669
1670    fn resolve_for_binding(&mut self, binding: &'src ForBinding, scope: ScopeId) {
1671        match binding {
1672            ForBinding::Variable(variable) => {
1673                self.bind_variable(variable, scope, NodeId::default());
1674                self.resolve_variable(variable, scope);
1675            }
1676            ForBinding::Target(target) => self.resolve_assignment_target(target, scope),
1677        }
1678    }
1679
1680    fn resolve_variable(&mut self, variable: &'src VariableDeclaration, scope: ScopeId) {
1681        for declarator in &variable.declarations {
1682            let declarator = declarator.data();
1683            if let Some(initializer) = &declarator.initializer {
1684                self.resolve_expr(initializer, scope);
1685            }
1686            let annotation = declarator
1687                .type_annotation
1688                .as_ref()
1689                .map(|annotation| self.resolve_type(&annotation.data().type_node, scope));
1690            let initializer_type = declarator
1691                .initializer
1692                .as_ref()
1693                .map(|initializer| self.type_of_expr(initializer, scope));
1694
1695            // Only a plain identifier binding carries a checkable declared type.
1696            if let BindingPattern::Identifier(name) = declarator.binding.data() {
1697                let declared = annotation
1698                    .or(initializer_type)
1699                    .unwrap_or_else(|| self.types.any());
1700                if let Some(symbol) = self.lookup_value(scope, self.identifier_text(name)) {
1701                    self.symbol_types[symbol.get() as usize] = declared;
1702                }
1703                if let (Some(target), Some(source)) = (annotation, initializer_type)
1704                    && !self.types.assignable(source, target)
1705                {
1706                    let range = declarator
1707                        .initializer
1708                        .as_ref()
1709                        .map_or_else(|| name.range(), |initializer| initializer.range());
1710                    self.emit(TYPE_NOT_ASSIGNABLE, range, NOT_ASSIGNABLE_MESSAGE);
1711                }
1712            }
1713        }
1714    }
1715
1716    fn resolve_function(&mut self, function: &'src FunctionLike, parent: ScopeId) {
1717        let scope = self.new_scope(ScopeKind::Function, Some(parent));
1718        for name in ["arguments", "this"] {
1719            let explicitly_bound = function.parameters.iter().any(|parameter| {
1720                matches!(
1721                    parameter.data().binding.data(),
1722                    BindingPattern::Identifier(identifier)
1723                        if self.identifier_text(identifier) == name
1724                )
1725            });
1726            if !explicitly_bound {
1727                self.declare(
1728                    name,
1729                    SymbolKind::Parameter,
1730                    scope,
1731                    NodeId::default(),
1732                    NodeId::default_range(),
1733                );
1734            }
1735        }
1736        if let Some(name) = &function.name {
1737            self.declare(
1738                self.identifier_text(name),
1739                SymbolKind::Function,
1740                scope,
1741                name.id(),
1742                name.range(),
1743            );
1744        }
1745        self.bind_type_parameters(function.type_parameters.as_ref(), scope);
1746        for parameter in &function.parameters {
1747            self.resolve_parameter(parameter, scope);
1748        }
1749        if let Some(return_type) = &function.return_type {
1750            let _ = self.resolve_type(&return_type.data().type_node, scope);
1751        }
1752        match &function.body {
1753            Some(FunctionBody::Block(block)) => {
1754                self.bind_statements(&block.data().statements, scope);
1755                self.bind_hoisted_statements(&block.data().statements, scope);
1756                self.resolve_statements(&block.data().statements, scope);
1757            }
1758            Some(FunctionBody::Expression(expression)) => self.resolve_expr(expression, scope),
1759            _ => {}
1760        }
1761    }
1762
1763    fn bind_type_parameters(
1764        &mut self,
1765        list: Option<&'src crate::syntax::TypeParameterList>,
1766        scope: ScopeId,
1767    ) {
1768        self.bind_type_parameter_names(list, scope);
1769        self.resolve_type_parameter_bounds(list, scope);
1770    }
1771
1772    fn bind_type_parameter_names(
1773        &mut self,
1774        list: Option<&'src crate::syntax::TypeParameterList>,
1775        scope: ScopeId,
1776    ) {
1777        let Some(list) = list else {
1778            return;
1779        };
1780        for parameter in &list.parameters {
1781            let data = parameter.data();
1782            self.declare(
1783                self.identifier_text(&data.name),
1784                SymbolKind::TypeParameter,
1785                scope,
1786                parameter.id(),
1787                data.name.range(),
1788            );
1789        }
1790    }
1791
1792    fn resolve_type_parameter_bounds(
1793        &mut self,
1794        list: Option<&'src crate::syntax::TypeParameterList>,
1795        scope: ScopeId,
1796    ) {
1797        let Some(list) = list else {
1798            return;
1799        };
1800        for parameter in &list.parameters {
1801            let data = parameter.data();
1802            if let Some(constraint) = &data.constraint {
1803                let _ = self.resolve_type(constraint, scope);
1804            }
1805            if let Some(default) = &data.default {
1806                let _ = self.resolve_type(default, scope);
1807            }
1808        }
1809    }
1810
1811    fn resolve_parameter(&mut self, parameter: &'src crate::syntax::ParameterNode, scope: ScopeId) {
1812        let data = parameter.data();
1813        self.bind_pattern(&data.binding, VariableKind::Let, scope, parameter.id());
1814        if let (BindingPattern::Identifier(name), Some(annotation)) =
1815            (data.binding.data(), &data.type_annotation)
1816        {
1817            let resolved = self.resolve_type(&annotation.data().type_node, scope);
1818            if let Some(symbol) = self.scopes[scope.0 as usize]
1819                .values
1820                .get(self.identifier_text(name))
1821                .copied()
1822            {
1823                self.symbol_types[symbol.get() as usize] = resolved;
1824            }
1825        } else if let Some(annotation) = &data.type_annotation {
1826            let _ = self.resolve_type(&annotation.data().type_node, scope);
1827        }
1828        if let Some(initializer) = &data.initializer {
1829            self.resolve_expr(initializer, scope);
1830        }
1831    }
1832
1833    fn resolve_class(&mut self, class: &'src ClassDeclaration, parent: ScopeId) {
1834        let scope = self.new_scope(ScopeKind::Class, Some(parent));
1835        self.bind_type_parameters(class.type_parameters.as_ref(), scope);
1836        if let Some(heritage) = &class.extends {
1837            self.resolve_expr(&heritage.expression, parent);
1838        }
1839        for implemented in &class.implements {
1840            let _ = self.resolve_type(implemented, scope);
1841        }
1842        for member in &class.members {
1843            self.resolve_class_member(member.data(), scope);
1844        }
1845    }
1846
1847    fn resolve_class_member(&mut self, member: &'src ClassMember, scope: ScopeId) {
1848        match member {
1849            ClassMember::Method(method) => {
1850                self.resolve_property_name(&method.name, scope);
1851                self.resolve_function(&method.function, scope);
1852            }
1853            ClassMember::Constructor(constructor) => {
1854                let child = self.new_scope(ScopeKind::Function, Some(scope));
1855                for name in ["arguments", "this"] {
1856                    let explicitly_bound = constructor.parameters.iter().any(|parameter| {
1857                        matches!(
1858                            parameter.data().binding.data(),
1859                            BindingPattern::Identifier(identifier)
1860                                if self.identifier_text(identifier) == name
1861                        )
1862                    });
1863                    if !explicitly_bound {
1864                        self.declare(
1865                            name,
1866                            SymbolKind::Parameter,
1867                            child,
1868                            NodeId::default(),
1869                            NodeId::default_range(),
1870                        );
1871                    }
1872                }
1873                for parameter in &constructor.parameters {
1874                    self.resolve_parameter(parameter, child);
1875                }
1876                self.bind_statements(&constructor.body.data().statements, child);
1877                self.resolve_statements(&constructor.body.data().statements, child);
1878            }
1879            ClassMember::Property(property) => {
1880                self.resolve_property_name(&property.name, scope);
1881                if let Some(annotation) = &property.type_annotation {
1882                    let _ = self.resolve_type(&annotation.data().type_node, scope);
1883                }
1884                if let Some(initializer) = &property.initializer {
1885                    self.resolve_expr(initializer, scope);
1886                }
1887            }
1888            ClassMember::AutoAccessor(accessor) => {
1889                self.resolve_property_name(&accessor.name, scope);
1890                if let Some(initializer) = &accessor.initializer {
1891                    self.resolve_expr(initializer, scope);
1892                }
1893            }
1894            ClassMember::StaticBlock(block) => {
1895                let child = self.new_scope(ScopeKind::Block, Some(scope));
1896                self.bind_statements(&block.data().statements, child);
1897                self.resolve_statements(&block.data().statements, child);
1898            }
1899            _ => {}
1900        }
1901    }
1902
1903    fn resolve_property_name(&mut self, name: &'src PropertyName, scope: ScopeId) {
1904        if let PropertyName::Computed(expression) = name {
1905            self.resolve_expr(expression, scope);
1906        }
1907    }
1908
1909    fn resolve_expr(&mut self, expression: &'src Expr, scope: ScopeId) {
1910        match expression.data() {
1911            Expression::Identifier(identifier) => self.resolve_value(identifier, scope),
1912            Expression::Array(array) => {
1913                for element in &array.elements {
1914                    match element {
1915                        ArrayElement::Expression(inner) => self.resolve_expr(inner, scope),
1916                        ArrayElement::Spread(spread) => self.resolve_expr(&spread.argument, scope),
1917                        _ => {}
1918                    }
1919                }
1920            }
1921            Expression::Object(object) => {
1922                for member in &object.members {
1923                    self.resolve_object_member(member.data(), scope);
1924                }
1925            }
1926            Expression::Function(function) => self.resolve_function(&function.function, scope),
1927            Expression::Class(class) => self.resolve_class(&class.class, scope),
1928            Expression::Arrow(arrow) => {
1929                let child = self.new_scope(ScopeKind::Function, Some(scope));
1930                self.bind_type_parameters(arrow.type_parameters.as_ref(), child);
1931                for parameter in &arrow.parameters {
1932                    self.resolve_parameter(parameter, child);
1933                }
1934                if let Some(return_type) = &arrow.return_type {
1935                    let _ = self.resolve_type(&return_type.data().type_node, child);
1936                }
1937                match &arrow.body {
1938                    FunctionBody::Block(block) => {
1939                        self.bind_statements(&block.data().statements, child);
1940                        self.resolve_statements(&block.data().statements, child);
1941                    }
1942                    FunctionBody::Expression(inner) => self.resolve_expr(inner, child),
1943                    FunctionBody::Missing(_) => {}
1944                }
1945            }
1946            Expression::Call(call) => {
1947                self.resolve_expr(&call.callee, scope);
1948                self.resolve_type_arguments(call.type_arguments.as_ref(), scope);
1949                self.resolve_arguments(&call.arguments, scope);
1950            }
1951            Expression::New(new) => {
1952                self.resolve_expr(&new.callee, scope);
1953                self.resolve_type_arguments(new.type_arguments.as_ref(), scope);
1954                self.resolve_arguments(&new.arguments, scope);
1955            }
1956            Expression::Member(member) => {
1957                self.resolve_expr(&member.object, scope);
1958                if let MemberProperty::Computed(inner) = &member.property {
1959                    self.resolve_expr(inner, scope);
1960                }
1961            }
1962            Expression::Await(await_expression) => {
1963                self.resolve_expr(&await_expression.argument, scope);
1964            }
1965            Expression::Yield(yield_expression) => {
1966                if let Some(argument) = &yield_expression.argument {
1967                    self.resolve_expr(argument, scope);
1968                }
1969            }
1970            Expression::Unary(unary) => self.resolve_expr(&unary.argument, scope),
1971            Expression::Update(update) => self.resolve_assignment_target(&update.argument, scope),
1972            Expression::Binary(binary) => {
1973                self.resolve_expr(&binary.left, scope);
1974                self.resolve_expr(&binary.right, scope);
1975            }
1976            Expression::Logical(logical) => {
1977                self.resolve_expr(&logical.left, scope);
1978                self.resolve_expr(&logical.right, scope);
1979            }
1980            Expression::Conditional(conditional) => {
1981                self.resolve_expr(&conditional.test, scope);
1982                self.resolve_expr(&conditional.consequent, scope);
1983                self.resolve_expr(&conditional.alternate, scope);
1984            }
1985            Expression::Assignment(assignment) => {
1986                self.resolve_assignment_target(&assignment.left, scope);
1987                self.resolve_expr(&assignment.right, scope);
1988            }
1989            Expression::Sequence(sequence) => {
1990                for inner in &sequence.expressions {
1991                    self.resolve_expr(inner, scope);
1992                }
1993            }
1994            Expression::Parenthesized(inner) => self.resolve_expr(inner, scope),
1995            Expression::As(cast) => {
1996                self.resolve_expr(&cast.expression, scope);
1997                if let Some(type_node) = &cast.type_node {
1998                    let _ = self.resolve_type(type_node, scope);
1999                }
2000            }
2001            Expression::Satisfies(satisfies) => {
2002                self.resolve_expr(&satisfies.expression, scope);
2003                let _ = self.resolve_type(&satisfies.type_node, scope);
2004            }
2005            Expression::TypeAssertion(assertion) => {
2006                self.resolve_expr(&assertion.expression, scope);
2007                let _ = self.resolve_type(&assertion.type_node, scope);
2008            }
2009            Expression::NonNull(non_null) => self.resolve_expr(&non_null.expression, scope),
2010            Expression::TaggedTemplate(tagged) => {
2011                self.resolve_expr(&tagged.tag, scope);
2012                for inner in &tagged.template.expressions {
2013                    self.resolve_expr(inner, scope);
2014                }
2015            }
2016            Expression::Template(template) => {
2017                for inner in &template.expressions {
2018                    self.resolve_expr(inner, scope);
2019                }
2020            }
2021            Expression::Import(import) => {
2022                self.resolve_expr(&import.source, scope);
2023                if let Some(options) = &import.options {
2024                    self.resolve_expr(options, scope);
2025                }
2026            }
2027            _ => {}
2028        }
2029    }
2030
2031    fn resolve_object_member(&mut self, member: &'src ObjectMember, scope: ScopeId) {
2032        match member {
2033            ObjectMember::Property(property) => {
2034                self.resolve_property_name(&property.name, scope);
2035                self.resolve_expr(&property.value, scope);
2036            }
2037            ObjectMember::Method(method) => {
2038                self.resolve_property_name(&method.name, scope);
2039                self.resolve_function(&method.function, scope);
2040            }
2041            ObjectMember::Spread(spread) => self.resolve_expr(&spread.argument, scope),
2042            ObjectMember::Missing(_) => {}
2043        }
2044    }
2045
2046    fn resolve_arguments(&mut self, arguments: &'src [CallArgument], scope: ScopeId) {
2047        for argument in arguments {
2048            match argument {
2049                CallArgument::Expression(inner) => self.resolve_expr(inner, scope),
2050                CallArgument::Spread(spread) => self.resolve_expr(&spread.argument, scope),
2051                CallArgument::Missing(_) => {}
2052            }
2053        }
2054    }
2055
2056    fn resolve_type_arguments(
2057        &mut self,
2058        arguments: Option<&'src crate::syntax::TypeArgumentList>,
2059        scope: ScopeId,
2060    ) {
2061        if let Some(list) = arguments {
2062            for argument in &list.arguments {
2063                let _ = self.resolve_type(argument, scope);
2064            }
2065        }
2066    }
2067
2068    fn resolve_assignment_target(
2069        &mut self,
2070        target: &'src crate::syntax::AssignmentTargetNode,
2071        scope: ScopeId,
2072    ) {
2073        match target.data() {
2074            AssignmentTarget::Identifier(identifier) => self.resolve_value(identifier, scope),
2075            AssignmentTarget::Member(member) => {
2076                self.resolve_expr(&member.object, scope);
2077                if let MemberProperty::Computed(inner) = &member.property {
2078                    self.resolve_expr(inner, scope);
2079                }
2080            }
2081            AssignmentTarget::Object(object) => {
2082                for property in &object.properties {
2083                    self.resolve_property_name(&property.name, scope);
2084                    self.resolve_assignment_target(&property.target, scope);
2085                    if let Some(initializer) = &property.initializer {
2086                        self.resolve_expr(initializer, scope);
2087                    }
2088                }
2089            }
2090            AssignmentTarget::Array(array) => {
2091                for element in &array.elements {
2092                    if let crate::syntax::AssignmentArrayElement::Target(inner) = element {
2093                        self.resolve_assignment_target(inner, scope);
2094                    }
2095                }
2096            }
2097            AssignmentTarget::Missing(_) => {}
2098        }
2099    }
2100
2101    fn resolve_value(&mut self, identifier: &IdentifierNode, scope: ScopeId) {
2102        let name = self.identifier_text(identifier);
2103        if name.is_empty() {
2104            return;
2105        }
2106        if let Some(symbol) = self.lookup_value(scope, name) {
2107            self.references.insert(identifier.id(), symbol);
2108        } else {
2109            self.emit(
2110                CANNOT_FIND_NAME,
2111                identifier.range(),
2112                CANNOT_FIND_NAME_MESSAGE,
2113            );
2114        }
2115    }
2116
2117    fn lookup_value(&self, scope: ScopeId, name: &str) -> Option<SymbolId> {
2118        let mut current = Some(scope);
2119        while let Some(id) = current {
2120            let scope = &self.scopes[id.0 as usize];
2121            if let Some(symbol) = scope.values.get(name) {
2122                return Some(*symbol);
2123            }
2124            current = scope.parent;
2125        }
2126        None
2127    }
2128
2129    fn lookup_type(&self, scope: ScopeId, name: &str) -> Option<SymbolId> {
2130        let mut current = Some(scope);
2131        while let Some(id) = current {
2132            let scope = &self.scopes[id.0 as usize];
2133            if let Some(symbol) = scope.types.get(name) {
2134                return Some(*symbol);
2135            }
2136            current = scope.parent;
2137        }
2138        None
2139    }
2140
2141    // -- the named type algebra ------------------------------------------------
2142
2143    fn resolve_type(&mut self, node: &'src Ty, scope: ScopeId) -> TypeId {
2144        match node.data() {
2145            TypeNode::Keyword(keyword) => self.keyword_type(*keyword),
2146            TypeNode::Literal(literal) => self.literal_type(literal),
2147            TypeNode::Reference(reference) => {
2148                self.resolve_type_reference(reference, scope, node.range())
2149            }
2150            TypeNode::Union(members) => {
2151                let resolved: Vec<TypeId> = members
2152                    .iter()
2153                    .map(|member| self.resolve_type(member, scope))
2154                    .collect();
2155                self.types.union(&resolved)
2156            }
2157            TypeNode::Array(element) => {
2158                let resolved = self.resolve_type(element, scope);
2159                self.types.array(resolved)
2160            }
2161            TypeNode::Object(object) => self.resolve_object_type(&object.members, scope),
2162            TypeNode::Function(function) => self.resolve_function_type(function, scope),
2163            TypeNode::Parenthesized(inner) => self.resolve_type(inner, scope),
2164            TypeNode::Tuple(tuple) => {
2165                let element_types: Vec<TypeId> = tuple
2166                    .elements
2167                    .iter()
2168                    .map(|element| self.resolve_type(&element.type_node, scope))
2169                    .collect();
2170                let element = self.types.union(&element_types);
2171                self.types.array(element)
2172            }
2173            _ => self.types.error_type(),
2174        }
2175    }
2176
2177    fn keyword_type(&self, keyword: KeywordType) -> TypeId {
2178        match keyword {
2179            KeywordType::Any => self.types.any(),
2180            KeywordType::Unknown => self.types.unknown(),
2181            KeywordType::Never => self.types.never(),
2182            KeywordType::Void => self.types.void(),
2183            KeywordType::Undefined => self.types.undefined_type(),
2184            KeywordType::Null => self.types.null_type(),
2185            KeywordType::Boolean => self.types.boolean(),
2186            KeywordType::Number => self.types.number(),
2187            KeywordType::BigInt => self.types.bigint(),
2188            KeywordType::String => self.types.string(),
2189            KeywordType::Symbol => self.types.symbol_type(),
2190            KeywordType::Object => self.types.object(),
2191            KeywordType::Intrinsic => self.types.error_type(),
2192        }
2193    }
2194
2195    fn literal_type(&mut self, literal: &TypeLiteral) -> TypeId {
2196        match literal {
2197            TypeLiteral::String(token) => {
2198                let text = self.text(token.data().token());
2199                self.types.string_literal(text)
2200            }
2201            TypeLiteral::Number(token) => {
2202                let text = self.text(token.data().token());
2203                self.types.number_literal(text)
2204            }
2205            TypeLiteral::BigInt(token) => {
2206                let text = self.text(token.data().token());
2207                self.types.bigint_literal(text)
2208            }
2209            TypeLiteral::Boolean(token) => {
2210                let value = self.text(token.data().token()) == "true";
2211                self.types.boolean_literal(value)
2212            }
2213            TypeLiteral::Null(_) => self.types.null_type(),
2214            TypeLiteral::Unary { .. } => self.types.number(),
2215        }
2216    }
2217
2218    fn resolve_type_reference(
2219        &mut self,
2220        reference: &'src TypeReference,
2221        scope: ScopeId,
2222        range: TextRange,
2223    ) -> TypeId {
2224        if let Some(argument_list) = &reference.type_arguments {
2225            for argument in &argument_list.arguments {
2226                let _ = self.resolve_type(argument, scope);
2227            }
2228        }
2229        let EntityName::Identifier(identifier) = &reference.name else {
2230            // Qualified and missing names are opaque in this slice.
2231            return self.types.error_type();
2232        };
2233        let name = self.identifier_text(identifier);
2234        match self.lookup_type(scope, name) {
2235            Some(symbol) => match self.symbols[symbol.get() as usize].kind {
2236                SymbolKind::Interface | SymbolKind::TypeAlias => self.resolve_type_symbol(symbol),
2237                SymbolKind::Class | SymbolKind::Enum | SymbolKind::TypeParameter => {
2238                    self.types.named(symbol)
2239                }
2240                _ => self.types.error_type(),
2241            },
2242            None => {
2243                self.emit(CANNOT_FIND_TYPE, range, CANNOT_FIND_TYPE_MESSAGE);
2244                self.types.error_type()
2245            }
2246        }
2247    }
2248
2249    fn resolve_type_symbol(&mut self, symbol: SymbolId) -> TypeId {
2250        match self.type_state[symbol.get() as usize] {
2251            TypeState::Done(id) => return id,
2252            TypeState::InProgress => return self.types.error_type(),
2253            TypeState::Unresolved => {}
2254        }
2255        let Some(definition) = self.type_defs.get(&symbol).copied() else {
2256            let id = self.types.error_type();
2257            self.type_state[symbol.get() as usize] = TypeState::Done(id);
2258            return id;
2259        };
2260        self.type_state[symbol.get() as usize] = TypeState::InProgress;
2261        let resolved = match definition {
2262            TypeDef::Alias {
2263                scope,
2264                type_parameters,
2265                node,
2266            } => {
2267                self.resolve_type_parameter_bounds(type_parameters, scope);
2268                self.resolve_type(node, scope)
2269            }
2270            TypeDef::Interface {
2271                scope,
2272                type_parameters,
2273                extends,
2274                members,
2275            } => {
2276                self.resolve_type_parameter_bounds(type_parameters, scope);
2277                self.resolve_interface_type(scope, extends, members)
2278            }
2279        };
2280        self.type_state[symbol.get() as usize] = TypeState::Done(resolved);
2281        resolved
2282    }
2283
2284    fn resolve_interface_type(
2285        &mut self,
2286        scope: ScopeId,
2287        extends: &'src [TypeReference],
2288        members: &'src [crate::syntax::TypeMemberNode],
2289    ) -> TypeId {
2290        let mut properties = self.type_member_properties(members, scope);
2291        for base in extends {
2292            let base_type = self.resolve_type_reference(base, scope, NodeId::default_range());
2293            if let Type::ObjectType(base_props) = self.types.get(base_type) {
2294                for base_prop in base_props.clone() {
2295                    if !properties.iter().any(|prop| prop.name == base_prop.name) {
2296                        properties.push(base_prop);
2297                    }
2298                }
2299            }
2300        }
2301        self.types.object_type(properties)
2302    }
2303
2304    fn resolve_object_type(
2305        &mut self,
2306        members: &'src [crate::syntax::TypeMemberNode],
2307        scope: ScopeId,
2308    ) -> TypeId {
2309        let properties = self.type_member_properties(members, scope);
2310        self.types.object_type(properties)
2311    }
2312
2313    fn type_member_properties(
2314        &mut self,
2315        members: &'src [crate::syntax::TypeMemberNode],
2316        scope: ScopeId,
2317    ) -> Vec<PropertyType> {
2318        let mut properties = Vec::new();
2319        for member in members {
2320            match member.data() {
2321                TypeMember::Property(property) => {
2322                    if let Some(name) = self.property_key(&property.name) {
2323                        let type_id = match &property.type_annotation {
2324                            Some(annotation) => {
2325                                self.resolve_type(&annotation.data().type_node, scope)
2326                            }
2327                            None => self.types.any(),
2328                        };
2329                        properties.push(PropertyType::new(name, property.optional, type_id));
2330                    }
2331                }
2332                TypeMember::Method(method) => {
2333                    if let Some(name) = self.property_key(&method.name) {
2334                        let type_id = self.resolve_function_type(&method.function, scope);
2335                        properties.push(PropertyType::new(name, method.optional, type_id));
2336                    }
2337                }
2338                _ => {}
2339            }
2340        }
2341        properties
2342    }
2343
2344    fn resolve_function_type(&mut self, function: &'src FunctionType, scope: ScopeId) -> TypeId {
2345        let child = self.new_scope(ScopeKind::Function, Some(scope));
2346        self.bind_type_parameters(function.type_parameters.as_ref(), child);
2347        let parameters: Vec<TypeId> = function
2348            .parameters
2349            .iter()
2350            .map(|parameter| self.resolve_type(&parameter.type_annotation.data().type_node, child))
2351            .collect();
2352        let return_type = self.resolve_type(&function.return_type, child);
2353        self.types.function(parameters, return_type)
2354    }
2355
2356    fn property_key(&self, name: &PropertyName) -> Option<String> {
2357        match name {
2358            PropertyName::Identifier(identifier) => {
2359                Some(self.identifier_text(identifier).to_owned())
2360            }
2361            PropertyName::String(string) => {
2362                let text = self.text(string.data().token());
2363                Some(
2364                    text.trim_matches(|c| c == '"' || c == '\'' || c == '`')
2365                        .to_owned(),
2366                )
2367            }
2368            PropertyName::Number(number) => Some(self.text(number.data().token()).to_owned()),
2369            _ => None,
2370        }
2371    }
2372
2373    // -- expression typing (bounded, permissive) -------------------------------
2374
2375    fn type_of_expr(&mut self, expression: &'src Expr, scope: ScopeId) -> TypeId {
2376        match expression.data() {
2377            Expression::Identifier(identifier) => {
2378                self.references.get(&identifier.id()).map_or_else(
2379                    || self.types.any(),
2380                    |symbol| self.symbol_types[symbol.get() as usize],
2381                )
2382            }
2383            Expression::Literal(literal) => self.type_of_literal(literal),
2384            Expression::Parenthesized(inner) => self.type_of_expr(inner, scope),
2385            Expression::NonNull(non_null) => self.type_of_expr(&non_null.expression, scope),
2386            Expression::As(cast) => match &cast.type_node {
2387                Some(type_node) => self.resolve_type(type_node, scope),
2388                None => self.type_of_expr(&cast.expression, scope),
2389            },
2390            Expression::TypeAssertion(assertion) => self.resolve_type(&assertion.type_node, scope),
2391            Expression::Array(array) => {
2392                let mut element_types = Vec::new();
2393                for element in &array.elements {
2394                    if let ArrayElement::Expression(inner) = element {
2395                        let inner_type = self.type_of_expr(inner, scope);
2396                        element_types.push(inner_type);
2397                    }
2398                }
2399                let element = if element_types.is_empty() {
2400                    self.types.never()
2401                } else {
2402                    self.types.union(&element_types)
2403                };
2404                self.types.array(element)
2405            }
2406            Expression::Object(object) => {
2407                let mut properties = Vec::new();
2408                for member in &object.members {
2409                    match member.data() {
2410                        ObjectMember::Property(property) => {
2411                            if let Some(name) = self.property_key(&property.name) {
2412                                let value_type = self.type_of_expr(&property.value, scope);
2413                                properties.push(PropertyType::new(name, false, value_type));
2414                            }
2415                        }
2416                        ObjectMember::Method(method) => {
2417                            if let Some(name) = self.property_key(&method.name) {
2418                                let method_type =
2419                                    self.type_of_function_like(&method.function, scope);
2420                                properties.push(PropertyType::new(name, false, method_type));
2421                            }
2422                        }
2423                        _ => {}
2424                    }
2425                }
2426                self.types.object_type(properties)
2427            }
2428            _ => self.types.any(),
2429        }
2430    }
2431
2432    fn type_of_function_like(&mut self, function: &'src FunctionLike, parent: ScopeId) -> TypeId {
2433        let scope = self.new_scope(ScopeKind::Function, Some(parent));
2434        self.bind_type_parameters(function.type_parameters.as_ref(), scope);
2435        let mut parameters = Vec::with_capacity(function.parameters.len());
2436        for parameter in &function.parameters {
2437            let parameter_type = match &parameter.data().type_annotation {
2438                Some(annotation) => self.resolve_type(&annotation.data().type_node, scope),
2439                None => self.types.any(),
2440            };
2441            parameters.push(parameter_type);
2442        }
2443        let return_type = match &function.return_type {
2444            Some(annotation) => self.resolve_type(&annotation.data().type_node, scope),
2445            None => self.types.any(),
2446        };
2447        self.types.function(parameters, return_type)
2448    }
2449
2450    fn type_of_literal(&mut self, literal: &Literal) -> TypeId {
2451        match literal {
2452            Literal::String(token) => {
2453                let text = self.text(token.data().token());
2454                self.types.string_literal(text)
2455            }
2456            Literal::Number(token) => {
2457                let text = self.text(token.data().token());
2458                self.types.number_literal(text)
2459            }
2460            Literal::BigInt(token) => {
2461                let text = self.text(token.data().token());
2462                self.types.bigint_literal(text)
2463            }
2464            Literal::Boolean(token) => {
2465                let value = self.text(token.data().token()) == "true";
2466                self.types.boolean_literal(value)
2467            }
2468            Literal::Null(_) => self.types.null_type(),
2469            Literal::Regex(_) => self.types.object(),
2470        }
2471    }
2472}
2473
2474/// A default zero range for synthesized diagnostics anchored on missing syntax.
2475trait DefaultRange {
2476    fn default_range() -> TextRange;
2477}
2478
2479impl DefaultRange for NodeId {
2480    fn default_range() -> TextRange {
2481        use crate::source::Utf16Pos;
2482        TextRange::new(Utf16Pos::ZERO, Utf16Pos::ZERO).expect("zero range is ordered")
2483    }
2484}
2485
2486#[cfg(test)]
2487mod tests {
2488    use super::{
2489        CANNOT_FIND_NAME, CANNOT_FIND_TYPE, DUPLICATE_DECLARATION, PropertyType, ScopeKind,
2490        SymbolKind, TYPE_NOT_ASSIGNABLE, TypeTable, check,
2491    };
2492    use crate::diagnostic::{DiagnosticSeverity, Recovered};
2493    use crate::source::{ScriptKind, SourceId, SourceText, TextRange, Utf16Pos};
2494    use crate::syntax::{
2495        ArrowFunction, BindingPattern, Block, EntityName, Expr, Expression, ExpressionStatement,
2496        FunctionBody, Identifier, IdentifierNode, KeywordType, Literal, MissingNode, Node, NodeId,
2497        NodeKind, NumericLiteral, Parameter, ParameterNode, SourceFile, Statement, Stmt,
2498        StringLiteral, Token, TokenKind, TypeAnnotation, TypeNode,
2499    };
2500    use crate::{parser, scanner};
2501    use std::sync::Arc;
2502
2503    // ---- direct algebra tests -------------------------------------------------
2504
2505    #[test]
2506    fn top_and_bottom_types_bound_the_lattice() {
2507        let table = TypeTable::new();
2508        // never flows into everything; nothing else flows into never.
2509        assert!(table.assignable(table.never(), table.number()));
2510        assert!(!table.assignable(table.number(), table.never()));
2511        // unknown is the top: everything in, nothing out.
2512        assert!(table.assignable(table.number(), table.unknown()));
2513        assert!(!table.assignable(table.unknown(), table.number()));
2514        // any is the escape hatch both directions.
2515        assert!(table.assignable(table.any(), table.number()));
2516        assert!(table.assignable(table.number(), table.any()));
2517    }
2518
2519    #[test]
2520    fn literals_widen_to_their_base_primitive_only() {
2521        let mut table = TypeTable::new();
2522        let one = table.number_literal("1");
2523        assert!(table.assignable(one, table.number()));
2524        assert!(!table.assignable(table.number(), one));
2525        assert!(!table.assignable(one, table.string()));
2526    }
2527
2528    #[test]
2529    fn union_source_requires_all_members_target_requires_one() {
2530        let mut table = TypeTable::new();
2531        let number_or_string = table.union(&[table.number(), table.string()]);
2532        assert!(table.assignable(table.number(), number_or_string));
2533        assert!(!table.assignable(table.boolean(), number_or_string));
2534        assert!(table.assignable(number_or_string, table.unknown()));
2535        assert!(!table.assignable(number_or_string, table.number()));
2536    }
2537
2538    #[test]
2539    fn union_normalizes_absorption_and_duplicates() {
2540        let mut table = TypeTable::new();
2541        assert_eq!(
2542            table.union(&[table.number(), table.number()]),
2543            table.number()
2544        );
2545        assert_eq!(
2546            table.union(&[table.number(), table.never()]),
2547            table.number()
2548        );
2549        assert_eq!(table.union(&[table.number(), table.any()]), table.any());
2550    }
2551
2552    #[test]
2553    fn arrays_are_covariant_in_their_element() {
2554        let mut table = TypeTable::new();
2555        let number_literal = table.number_literal("1");
2556        let literal_array = table.array(number_literal);
2557        let number_array = table.array(table.number());
2558        assert!(table.assignable(literal_array, number_array));
2559        assert!(!table.assignable(number_array, literal_array));
2560    }
2561
2562    #[test]
2563    fn objects_are_structural_with_optional_and_excess_rules() {
2564        let mut table = TypeTable::new();
2565        let required = table.object_type(vec![PropertyType::new("x", false, table.number())]);
2566        let with_excess = table.object_type(vec![
2567            PropertyType::new("x", false, table.number()),
2568            PropertyType::new("y", false, table.string()),
2569        ]);
2570        let missing = table.object_type(vec![PropertyType::new("y", false, table.string())]);
2571        let optional = table.object_type(vec![PropertyType::new("x", true, table.number())]);
2572        let empty = table.object_type(vec![]);
2573
2574        // Excess source properties are allowed structurally.
2575        assert!(table.assignable(with_excess, required));
2576        // A missing required property is rejected.
2577        assert!(!table.assignable(missing, required));
2578        // An optional target property may be absent.
2579        assert!(table.assignable(empty, optional));
2580    }
2581
2582    #[test]
2583    fn functions_are_contravariant_in_params_covariant_in_return() {
2584        let mut table = TypeTable::new();
2585        let animal = table.named(super::SymbolId::new(100));
2586        let dog = table.named(super::SymbolId::new(101));
2587        // fn(animal) -> dog  <:  fn(dog) -> animal  is unrelated nominally, but
2588        // the variance shape is what we assert here.
2589        let number = table.number();
2590        let takes_number = table.function(vec![number], table.void());
2591        let number_literal = table.number_literal("1");
2592        let takes_number_literal = table.function(vec![number_literal], table.void());
2593        // target param number must be assignable to source param numberLiteral?
2594        // No: number is not a number literal, so it is rejected (contravariant).
2595        assert!(!table.assignable(takes_number_literal, takes_number));
2596        // Fewer source params is fine.
2597        let takes_none = table.function(vec![], table.void());
2598        assert!(table.assignable(takes_none, takes_number));
2599        // Return void absorbs any source return.
2600        let returns_number = table.function(vec![], table.number());
2601        assert!(table.assignable(returns_number, takes_none));
2602        // Silence unused nominal helpers when variance path above suffices.
2603        assert_ne!(animal, dog);
2604    }
2605
2606    #[test]
2607    fn relation_retains_optional_callback_void_and_enum_hazards() {
2608        let mut table = TypeTable::new();
2609        let source_object =
2610            table.object_type(vec![PropertyType::new("x", false, table.undefined_type())]);
2611        let target_object = table.object_type(vec![PropertyType::new("x", true, table.number())]);
2612        let optional = table.relation(source_object, target_object);
2613        assert!(optional.compatible());
2614        assert!(
2615            optional
2616                .hazards()
2617                .contains(&super::RelationHazard::ExplicitUndefinedForOptional)
2618        );
2619
2620        let source_function = table.function(Vec::new(), table.number());
2621        let target_function = table.function(vec![table.number()], table.void());
2622        let callback = table.relation(source_function, target_function);
2623        assert!(
2624            callback
2625                .hazards()
2626                .contains(&super::RelationHazard::FewerCallbackParameters)
2627        );
2628        assert!(
2629            callback
2630                .hazards()
2631                .contains(&super::RelationHazard::ValueReturnedToVoid)
2632        );
2633
2634        let enum_type = table.numeric_enum(super::SymbolId::new(200));
2635        let enum_boundary = table.relation(enum_type, table.number());
2636        assert!(enum_boundary.compatible());
2637        assert!(
2638            enum_boundary
2639                .hazards()
2640                .contains(&super::RelationHazard::NumericEnumNumber)
2641        );
2642    }
2643
2644    // ---- checker behavior tests ----------------------------------------------
2645
2646    fn source(text: &str) -> Arc<SourceText> {
2647        Arc::new(SourceText::new(text))
2648    }
2649
2650    fn check_text(text: &str) -> Recovered<super::SemanticModel> {
2651        let parsed = parser::parse(scanner::scan(
2652            SourceId::new(0),
2653            ScriptKind::TypeScript,
2654            source(text),
2655        ));
2656        check(&parsed)
2657    }
2658
2659    fn checker_codes(result: &Recovered<super::SemanticModel>) -> Vec<&'static str> {
2660        result
2661            .diagnostics()
2662            .iter()
2663            .map(|diagnostic| diagnostic.code().as_str())
2664            .filter(|code| code.starts_with("BAMTS-C"))
2665            .collect()
2666    }
2667
2668    fn range(start: usize, end: usize) -> TextRange {
2669        TextRange::new(Utf16Pos::new(start), Utf16Pos::new(end)).expect("ordered range")
2670    }
2671
2672    fn identifier(id: u32, name: &str, start: usize) -> IdentifierNode {
2673        let end = start + name.len();
2674        Node::new(
2675            NodeId::new(id),
2676            range(start, end),
2677            Identifier::new(Token::new(TokenKind::Identifier, range(start, end))),
2678        )
2679    }
2680
2681    /// Builds a `SourceFile` whose statements are supplied directly, so binding
2682    /// and typing can be exercised without depending on a parser.
2683    fn file(text: &str, statements: Vec<Stmt>) -> Recovered<SourceFile> {
2684        let source = source(text);
2685        let end = source.len_utf16().get();
2686        let eof = Token::new(TokenKind::EndOfFile, range(end, end));
2687        let file = SourceFile::new(
2688            NodeId::new(0),
2689            SourceId::new(1),
2690            ScriptKind::TypeScript,
2691            range(0, end),
2692            source,
2693            Vec::new(),
2694            statements,
2695            eof,
2696            Vec::new(),
2697        );
2698        Recovered::clean(file)
2699    }
2700
2701    fn keyword_annotation(
2702        id: u32,
2703        keyword: KeywordType,
2704        start: usize,
2705        end: usize,
2706    ) -> Node<TypeAnnotation> {
2707        let type_node = Node::new(
2708            NodeId::new(id),
2709            range(start, end),
2710            TypeNode::Keyword(keyword),
2711        );
2712        Node::new(
2713            NodeId::new(id + 1),
2714            range(start, end),
2715            TypeAnnotation {
2716                type_node: Box::new(type_node),
2717            },
2718        )
2719    }
2720
2721    fn variable(
2722        id: u32,
2723        text: &str,
2724        name: &str,
2725        name_start: usize,
2726        annotation: Option<Node<TypeAnnotation>>,
2727        initializer: Option<Box<Expr>>,
2728    ) -> Stmt {
2729        let name_node = identifier(id + 1, name, name_start);
2730        let binding = Node::new(
2731            NodeId::new(id + 2),
2732            name_node.range(),
2733            BindingPattern::Identifier(name_node),
2734        );
2735        let declarator = Node::new(
2736            NodeId::new(id + 3),
2737            range(0, text.len()),
2738            crate::syntax::VariableDeclarator {
2739                binding,
2740                definite: false,
2741                type_annotation: annotation,
2742                initializer,
2743            },
2744        );
2745        Node::new(
2746            NodeId::new(id),
2747            range(0, text.len()),
2748            Statement::Variable(crate::syntax::VariableDeclaration {
2749                kind: crate::syntax::VariableKind::Const,
2750                declarations: vec![declarator],
2751            }),
2752        )
2753    }
2754
2755    fn number_expr(id: u32, text: &str, start: usize) -> Box<Expr> {
2756        let end = start + text.len();
2757        let literal = Node::new(
2758            NodeId::new(id + 1),
2759            range(start, end),
2760            NumericLiteral::new(Token::new(TokenKind::NumericLiteral, range(start, end))),
2761        );
2762        Box::new(Node::new(
2763            NodeId::new(id),
2764            range(start, end),
2765            Expression::Literal(Literal::Number(literal)),
2766        ))
2767    }
2768
2769    fn string_expr(id: u32, text: &str, start: usize) -> Box<Expr> {
2770        let end = start + text.len();
2771        let literal = Node::new(
2772            NodeId::new(id + 1),
2773            range(start, end),
2774            StringLiteral::new(Token::new(TokenKind::StringLiteral, range(start, end))),
2775        );
2776        Box::new(Node::new(
2777            NodeId::new(id),
2778            range(start, end),
2779            Expression::Literal(Literal::String(literal)),
2780        ))
2781    }
2782
2783    fn identifier_expr(id: u32, name: &str, start: usize) -> Box<Expr> {
2784        Box::new(Node::new(
2785            NodeId::new(id),
2786            range(start, start + name.len()),
2787            Expression::Identifier(identifier(id + 1, name, start)),
2788        ))
2789    }
2790
2791    fn expression_statement(id: u32, expression: Box<Expr>) -> Stmt {
2792        Node::new(
2793            NodeId::new(id),
2794            expression.range(),
2795            Statement::Expression(ExpressionStatement { expression }),
2796        )
2797    }
2798
2799    fn semantic_codes(model: &Recovered<super::SemanticModel>) -> Vec<&'static str> {
2800        model
2801            .diagnostics()
2802            .iter()
2803            .map(|diagnostic| diagnostic.code().as_str())
2804            .collect()
2805    }
2806
2807    #[test]
2808    fn binds_a_variable_and_resolves_its_later_reference() {
2809        let statements = vec![
2810            variable(
2811                10,
2812                "const a = 1;",
2813                "a",
2814                6,
2815                None,
2816                Some(number_expr(20, "1", 10)),
2817            ),
2818            expression_statement(30, identifier_expr(31, "a", 13)),
2819        ];
2820        let result = check(&file("const a = 1; a;", statements));
2821        assert!(semantic_codes(&result).is_empty());
2822        let model = result.product();
2823        let symbol = model
2824            .lookup_value(model.module_scope(), "a")
2825            .expect("a is bound");
2826        assert!(matches!(
2827            model.symbol(symbol).kind(),
2828            SymbolKind::Variable(_)
2829        ));
2830        assert_eq!(model.resolved_reference_count(), 1);
2831        assert_eq!(model.scope(model.module_scope()).kind(), ScopeKind::Module);
2832    }
2833
2834    #[test]
2835    fn reports_an_unresolved_local_value_reference() {
2836        let statements = vec![expression_statement(30, identifier_expr(31, "missing", 0))];
2837        let result = check(&file("missing;", statements));
2838        assert_eq!(semantic_codes(&result), [CANNOT_FIND_NAME.as_str()]);
2839    }
2840
2841    #[test]
2842    fn a_global_value_reference_is_not_unresolved() {
2843        let statements = vec![expression_statement(30, identifier_expr(31, "console", 0))];
2844        let result = check(&file("console;", statements));
2845        assert!(semantic_codes(&result).is_empty());
2846    }
2847
2848    #[test]
2849    fn standard_global_families_bind_as_intrinsics() {
2850        let names = [
2851            // ECMAScript values and constructors.
2852            "JSON",
2853            "Math",
2854            "Object",
2855            "Array",
2856            "Promise",
2857            "Error",
2858            "TypeError",
2859            "escape",
2860            "unescape",
2861            // Collections, reflection, shared-memory, and typed-array families.
2862            "Map",
2863            "Set",
2864            "Symbol",
2865            "Reflect",
2866            "Atomics",
2867            "Int8Array",
2868            "BigUint64Array",
2869            // Timers and URL/text host APIs.
2870            "setTimeout",
2871            "clearInterval",
2872            "queueMicrotask",
2873            "URL",
2874            "URLSearchParams",
2875            "TextEncoder",
2876            "TextDecoder",
2877            // Node host globals that the runtime installs.
2878            "console",
2879            "process",
2880            "globalThis",
2881        ];
2882        let text = names.join(";");
2883        let mut start = 0;
2884        let statements = names
2885            .iter()
2886            .enumerate()
2887            .map(|(index, name)| {
2888                let statement = expression_statement(
2889                    u32::try_from(index * 2 + 30).expect("test node id fits u32"),
2890                    identifier_expr(
2891                        u32::try_from(index * 2 + 31).expect("test node id fits u32"),
2892                        name,
2893                        start,
2894                    ),
2895                );
2896                start += name.len() + 1;
2897                statement
2898            })
2899            .collect();
2900        let result = check(&file(&text, statements));
2901        assert!(
2902            semantic_codes(&result).is_empty(),
2903            "intrinsic diagnostics: {:?}",
2904            result.diagnostics()
2905        );
2906        assert_eq!(result.product().resolved_reference_count(), names.len());
2907    }
2908
2909    #[test]
2910    fn local_bindings_shadow_intrinsics() {
2911        let statements = vec![
2912            variable(
2913                10,
2914                "const console = 1;",
2915                "console",
2916                6,
2917                None,
2918                Some(number_expr(20, "1", 16)),
2919            ),
2920            expression_statement(30, identifier_expr(31, "console", 19)),
2921        ];
2922        let result = check(&file("const console = 1; console;", statements));
2923        assert!(semantic_codes(&result).is_empty());
2924        let model = result.product();
2925        let local = model
2926            .lookup_value(model.module_scope(), "console")
2927            .expect("local console binding exists");
2928        assert_eq!(model.reference(NodeId::new(32)), Some(local));
2929    }
2930
2931    #[test]
2932    fn reports_an_unknown_name_even_with_intrinsics() {
2933        let statements = vec![expression_statement(
2934            30,
2935            identifier_expr(31, "notAGlobal", 0),
2936        )];
2937        let result = check(&file("notAGlobal;", statements));
2938        assert_eq!(semantic_codes(&result), [CANNOT_FIND_NAME.as_str()]);
2939    }
2940
2941    #[test]
2942    fn reports_a_duplicate_block_scoped_declaration() {
2943        let statements = vec![
2944            variable(
2945                10,
2946                "const a = 1;",
2947                "a",
2948                6,
2949                None,
2950                Some(number_expr(20, "1", 10)),
2951            ),
2952            variable(
2953                40,
2954                "const a = 2;",
2955                "a",
2956                19,
2957                None,
2958                Some(number_expr(50, "2", 23)),
2959            ),
2960        ];
2961        let result = check(&file("const a = 1; const a = 2;", statements));
2962        assert_eq!(semantic_codes(&result), [DUPLICATE_DECLARATION.as_str()]);
2963    }
2964
2965    #[test]
2966    fn a_shadowing_binding_in_a_nested_block_is_not_a_duplicate() {
2967        let inner = variable(
2968            40,
2969            "const a = 2;",
2970            "a",
2971            21,
2972            None,
2973            Some(number_expr(50, "2", 25)),
2974        );
2975        let block = Node::new(
2976            NodeId::new(60),
2977            range(13, 29),
2978            Statement::Block(Node::new(
2979                NodeId::new(61),
2980                range(13, 29),
2981                Block {
2982                    statements: vec![inner],
2983                },
2984            )),
2985        );
2986        let statements = vec![
2987            variable(
2988                10,
2989                "const a = 1;",
2990                "a",
2991                6,
2992                None,
2993                Some(number_expr(20, "1", 10)),
2994            ),
2995            block,
2996        ];
2997        let result = check(&file("const a = 1; { const a = 2; }", statements));
2998        assert!(semantic_codes(&result).is_empty());
2999    }
3000
3001    #[test]
3002    fn a_number_literal_is_not_assignable_to_a_string_annotation() {
3003        let annotation = keyword_annotation(70, KeywordType::String, 9, 15);
3004        let statements = vec![variable(
3005            10,
3006            "const x: string = 1;",
3007            "x",
3008            6,
3009            Some(annotation),
3010            Some(number_expr(20, "1", 18)),
3011        )];
3012        let result = check(&file("const x: string = 1;", statements));
3013        assert_eq!(semantic_codes(&result), [TYPE_NOT_ASSIGNABLE.as_str()]);
3014    }
3015
3016    #[test]
3017    fn a_matching_literal_initializer_is_accepted() {
3018        let annotation = keyword_annotation(70, KeywordType::Number, 9, 15);
3019        let statements = vec![variable(
3020            10,
3021            "const x: number = 1;",
3022            "x",
3023            6,
3024            Some(annotation),
3025            Some(number_expr(20, "1", 18)),
3026        )];
3027        let result = check(&file("const x: number = 1;", statements));
3028        assert!(semantic_codes(&result).is_empty());
3029    }
3030
3031    #[test]
3032    fn an_unresolved_type_annotation_reports_cannot_find_type() {
3033        let reference = crate::syntax::TypeReference {
3034            name: EntityName::Identifier(identifier(71, "Foo", 9)),
3035            type_arguments: None,
3036        };
3037        let type_node = Node::new(
3038            NodeId::new(72),
3039            range(9, 12),
3040            TypeNode::Reference(reference),
3041        );
3042        let annotation = Node::new(
3043            NodeId::new(73),
3044            range(9, 12),
3045            TypeAnnotation {
3046                type_node: Box::new(type_node),
3047            },
3048        );
3049        let statements = vec![variable(
3050            10,
3051            "const x: Foo;",
3052            "x",
3053            6,
3054            Some(annotation),
3055            None,
3056        )];
3057        let result = check(&file("const x: Foo;", statements));
3058        assert_eq!(semantic_codes(&result), [CANNOT_FIND_TYPE.as_str()]);
3059    }
3060
3061    #[test]
3062    fn generic_declarations_bind_their_type_parameters() {
3063        let result = check_text(
3064            "type Box<T> = { value: T };\
3065             interface Pair<T> { left: T; map<U>(value: U): T; }\
3066             class Store<T> { value: T; method<U>(value: U): T { return this.value; } }",
3067        );
3068        assert!(checker_codes(&result).is_empty());
3069    }
3070
3071    #[test]
3072    fn imported_names_bind_in_the_type_namespace_through_exports() {
3073        let result = check_text(
3074            "import type { Remote } from './remote.ts';\
3075             export type Local<T> = Remote;\
3076             export interface Public<T> { value: Local<T>; remote: Remote; }",
3077        );
3078        assert!(checker_codes(&result).is_empty());
3079    }
3080
3081    #[test]
3082    fn standard_iterator_and_generator_interfaces_are_bound() {
3083        let result = check_text(
3084            "declare let iterator: IterableIterator<number>;\
3085             async function* values(): AsyncGenerator<number> { yield 1; }",
3086        );
3087        assert!(checker_codes(&result).is_empty());
3088    }
3089
3090    #[test]
3091    fn functions_bind_arguments_this_and_their_local_name() {
3092        let result = check_text(
3093            "const recursive = function self(this: void) { arguments; return self; };\
3094             class C { method() { arguments; return this; } }",
3095        );
3096        assert!(checker_codes(&result).is_empty());
3097    }
3098
3099    #[test]
3100    fn ambient_declarations_bind_before_their_uses() {
3101        let result = check_text(
3102            "const before: Box<number> = make<number>();\
3103             declare interface Box<T> { value: T; }\
3104             declare function make<T>(): Box<T>;",
3105        );
3106        assert!(checker_codes(&result).is_empty());
3107    }
3108
3109    #[test]
3110    fn local_generic_casts_resolve_in_the_enclosing_function() {
3111        let result = check_text(
3112            "function copy<T>(value: T): T { const result = value as T; return result; }",
3113        );
3114        assert!(checker_codes(&result).is_empty());
3115    }
3116
3117    #[test]
3118    fn const_assertions_preserve_literal_expression_types() {
3119        let result = check_text("const state: \"ready\" = \"ready\" as const;");
3120        assert!(checker_codes(&result).is_empty());
3121    }
3122
3123    #[test]
3124    fn object_methods_satisfy_structural_function_members() {
3125        let result = check_text(
3126            "interface Service { compute(value: number): Promise<number>; }\
3127             const service: Service = { async compute(value: number) { return value; } };",
3128        );
3129        assert!(checker_codes(&result).is_empty());
3130    }
3131
3132    #[test]
3133    fn unknown_names_and_real_initializer_mismatches_remain_errors() {
3134        let result =
3135            check_text("missingValue; let missing: MissingType; const count: number = 'wrong';");
3136        assert_eq!(
3137            checker_codes(&result),
3138            [
3139                CANNOT_FIND_NAME.as_str(),
3140                CANNOT_FIND_TYPE.as_str(),
3141                TYPE_NOT_ASSIGNABLE.as_str(),
3142            ]
3143        );
3144    }
3145
3146    #[test]
3147    fn hard_warnings_merge_into_ordered_diagnostics() {
3148        // A single-string source that triggers hard-warning W005 plus an
3149        // unresolved reference should yield both, canonically ordered.
3150        let text = "try {} catch (error) { error.message; }";
3151        // Reference `nope` (unresolved) placed before via an expression stmt with
3152        // an earlier range so ordering is observable.
3153        let statements = vec![expression_statement(30, identifier_expr(31, "nope", 0))];
3154        let result = check(&file(text, statements));
3155        let diagnostics = result.diagnostics();
3156        // Both a semantic error and a hard warning are present.
3157        assert!(
3158            diagnostics
3159                .iter()
3160                .any(|diagnostic| diagnostic.code() == CANNOT_FIND_NAME)
3161        );
3162        assert!(
3163            diagnostics
3164                .iter()
3165                .any(|diagnostic| diagnostic.severity() == DiagnosticSeverity::Warning)
3166        );
3167        // Diagnostics are in canonical (sorted) order.
3168        let mut sorted = diagnostics.to_vec();
3169        sorted.sort();
3170        assert_eq!(diagnostics, sorted.as_slice());
3171    }
3172
3173    #[test]
3174    fn a_parameter_reference_resolves_within_its_function_scope() {
3175        let parameter_name = identifier(81, "p", 11);
3176        let binding = Node::new(
3177            NodeId::new(82),
3178            parameter_name.range(),
3179            BindingPattern::Identifier(parameter_name),
3180        );
3181        let parameter: ParameterNode = Node::new(
3182            NodeId::new(83),
3183            range(11, 12),
3184            Parameter {
3185                decorators: Vec::new(),
3186                modifiers: crate::syntax::ParameterModifiers::default(),
3187                binding,
3188                optional: false,
3189                type_annotation: None,
3190                initializer: None,
3191            },
3192        );
3193        let body = identifier_expr(90, "p", 17);
3194        let arrow = Node::new(
3195            NodeId::new(80),
3196            range(10, 18),
3197            Expression::Arrow(ArrowFunction {
3198                is_async: false,
3199                type_parameters: None,
3200                parameters: vec![parameter],
3201                return_type: None,
3202                body: FunctionBody::Expression(body),
3203            }),
3204        );
3205        let statements = vec![variable(
3206            10,
3207            "const f = (p) => p;",
3208            "f",
3209            6,
3210            None,
3211            Some(Box::new(arrow)),
3212        )];
3213        let result = check(&file("const f = (p) => p;", statements));
3214        assert!(semantic_codes(&result).is_empty());
3215        // Scope tree contains a function scope for the arrow.
3216        let model = result.product();
3217        assert!(
3218            model
3219                .scopes()
3220                .iter()
3221                .any(|scope| scope.kind() == ScopeKind::Function)
3222        );
3223    }
3224
3225    #[test]
3226    fn a_string_initializer_matches_a_string_annotation() {
3227        let annotation = keyword_annotation(70, KeywordType::String, 9, 15);
3228        let statements = vec![variable(
3229            10,
3230            "const s: string = \"ok\";",
3231            "s",
3232            6,
3233            Some(annotation),
3234            Some(string_expr(20, "\"ok\"", 18)),
3235        )];
3236        let result = check(&file("const s: string = \"ok\";", statements));
3237        assert!(semantic_codes(&result).is_empty());
3238        let model = result.product();
3239        let symbol = model
3240            .lookup_value(model.module_scope(), "s")
3241            .expect("s is bound");
3242        assert_eq!(model.symbol_type(symbol), model.types().string());
3243    }
3244
3245    #[test]
3246    fn missing_identifiers_never_panic_the_checker() {
3247        // An identifier with an empty lexeme must be ignored, not reported.
3248        let missing = Node::new(
3249            NodeId::new(31),
3250            range(0, 0),
3251            Expression::Missing(MissingNode::new(NodeKind::IdentifierExpression)),
3252        );
3253        let statements = vec![expression_statement(30, Box::new(missing))];
3254        let result = check(&file("", statements));
3255        assert!(semantic_codes(&result).is_empty());
3256    }
3257
3258    // ---- var hoisting regression tests ---------------------------------------
3259
3260    fn var_declaration(
3261        kind: crate::syntax::VariableKind,
3262        id: u32,
3263        name: &str,
3264        name_start: usize,
3265        initializer: Option<Box<Expr>>,
3266    ) -> crate::syntax::VariableDeclaration {
3267        let name_node = identifier(id + 1, name, name_start);
3268        let binding = Node::new(
3269            NodeId::new(id + 2),
3270            name_node.range(),
3271            BindingPattern::Identifier(name_node),
3272        );
3273        let declarator = Node::new(
3274            NodeId::new(id + 3),
3275            range(name_start, name_start + name.len()),
3276            crate::syntax::VariableDeclarator {
3277                binding,
3278                definite: false,
3279                type_annotation: None,
3280                initializer,
3281            },
3282        );
3283        crate::syntax::VariableDeclaration {
3284            kind,
3285            declarations: vec![declarator],
3286        }
3287    }
3288
3289    fn variable_kind(
3290        kind: crate::syntax::VariableKind,
3291        id: u32,
3292        text: &str,
3293        name: &str,
3294        name_start: usize,
3295        initializer: Option<Box<Expr>>,
3296    ) -> Stmt {
3297        Node::new(
3298            NodeId::new(id),
3299            range(0, text.len()),
3300            Statement::Variable(var_declaration(kind, id, name, name_start, initializer)),
3301        )
3302    }
3303
3304    fn block_statement(id: u32, statements: Vec<Stmt>) -> Stmt {
3305        Node::new(
3306            NodeId::new(id),
3307            range(0, 1),
3308            Statement::Block(Node::new(
3309                NodeId::new(id + 1),
3310                range(0, 1),
3311                Block { statements },
3312            )),
3313        )
3314    }
3315
3316    #[test]
3317    fn a_var_in_a_block_hoists_to_the_module_and_resolves_outside() {
3318        let inner = variable_kind(
3319            crate::syntax::VariableKind::Var,
3320            40,
3321            "var a = 1;",
3322            "a",
3323            6,
3324            Some(number_expr(50, "1", 10)),
3325        );
3326        let block = block_statement(60, vec![inner]);
3327        let statements = vec![
3328            block,
3329            expression_statement(70, identifier_expr(71, "a", 15)),
3330        ];
3331        let result = check(&file("{ var a = 1; } a;", statements));
3332        assert!(semantic_codes(&result).is_empty());
3333        let model = result.product();
3334        let symbol = model
3335            .lookup_value(model.module_scope(), "a")
3336            .expect("var a hoists to the module scope");
3337        assert!(matches!(
3338            model.symbol(symbol).kind(),
3339            SymbolKind::Variable(crate::syntax::VariableKind::Var)
3340        ));
3341        assert_eq!(model.resolved_reference_count(), 1);
3342    }
3343
3344    #[test]
3345    fn a_var_in_a_nested_block_binds_before_its_declaration() {
3346        let inner = variable_kind(
3347            crate::syntax::VariableKind::Var,
3348            40,
3349            "var a = 1;",
3350            "a",
3351            9,
3352            Some(number_expr(50, "1", 13)),
3353        );
3354        let statements = vec![
3355            expression_statement(30, identifier_expr(31, "a", 0)),
3356            block_statement(60, vec![inner]),
3357        ];
3358        let result = check(&file("a; { var a = 1; }", statements));
3359        assert!(semantic_codes(&result).is_empty());
3360        assert_eq!(result.product().resolved_reference_count(), 1);
3361    }
3362
3363    #[test]
3364    fn a_for_initializer_var_hoists_to_the_module() {
3365        let for_stmt = Node::new(
3366            NodeId::new(60),
3367            range(0, 1),
3368            Statement::For(crate::syntax::ForStatement {
3369                initializer: Some(crate::syntax::ForInitializer::Variable(var_declaration(
3370                    crate::syntax::VariableKind::Var,
3371                    40,
3372                    "i",
3373                    9,
3374                    Some(number_expr(50, "0", 13)),
3375                ))),
3376                test: None,
3377                update: None,
3378                body: Box::new(block_statement(80, vec![])),
3379            }),
3380        );
3381        let statements = vec![
3382            for_stmt,
3383            expression_statement(90, identifier_expr(91, "i", 23)),
3384        ];
3385        let result = check(&file("for (var i = 0; ; ) {} i;", statements));
3386        assert!(semantic_codes(&result).is_empty());
3387        let model = result.product();
3388        assert!(
3389            model.lookup_value(model.module_scope(), "i").is_some(),
3390            "for-initializer var hoists out of the for scope"
3391        );
3392        assert_eq!(model.resolved_reference_count(), 1);
3393    }
3394
3395    #[test]
3396    fn a_var_in_a_nested_function_does_not_escape_to_the_outer_scope() {
3397        let inner = variable_kind(
3398            crate::syntax::VariableKind::Var,
3399            40,
3400            "var x = 1;",
3401            "x",
3402            15,
3403            Some(number_expr(50, "1", 19)),
3404        );
3405        let body = Node::new(
3406            NodeId::new(70),
3407            range(0, 1),
3408            Block {
3409                statements: vec![inner],
3410            },
3411        );
3412        let function = crate::syntax::FunctionLike {
3413            decorators: Vec::new(),
3414            name: Some(identifier(81, "f", 9)),
3415            is_async: false,
3416            is_generator: false,
3417            type_parameters: None,
3418            parameters: Vec::new(),
3419            return_type: None,
3420            body: Some(FunctionBody::Block(body)),
3421        };
3422        let fn_stmt = Node::new(
3423            NodeId::new(80),
3424            range(0, 1),
3425            Statement::Function(crate::syntax::FunctionDeclaration { function }),
3426        );
3427        let result = check(&file("function f() { var x = 1; }", vec![fn_stmt]));
3428        assert!(semantic_codes(&result).is_empty());
3429        let model = result.product();
3430        assert!(
3431            model.lookup_value(model.module_scope(), "f").is_some(),
3432            "the function declaration binds at the module scope"
3433        );
3434        assert!(
3435            model.lookup_value(model.module_scope(), "x").is_none(),
3436            "the inner var stays inside its own function scope"
3437        );
3438    }
3439
3440    #[test]
3441    fn a_function_declaration_in_a_block_hoists_to_the_module() {
3442        let function = crate::syntax::FunctionLike {
3443            decorators: Vec::new(),
3444            name: Some(identifier(81, "g", 14)),
3445            is_async: false,
3446            is_generator: false,
3447            type_parameters: None,
3448            parameters: Vec::new(),
3449            return_type: None,
3450            body: Some(FunctionBody::Block(Node::new(
3451                NodeId::new(82),
3452                range(15, 17),
3453                Block {
3454                    statements: Vec::new(),
3455                },
3456            ))),
3457        };
3458        let declaration = Node::new(
3459            NodeId::new(80),
3460            range(2, 17),
3461            Statement::Function(crate::syntax::FunctionDeclaration { function }),
3462        );
3463        let statements = vec![
3464            expression_statement(70, identifier_expr(71, "g", 0)),
3465            block_statement(90, vec![declaration]),
3466        ];
3467        let result = check(&file("g; { function g() {} }", statements));
3468        assert!(semantic_codes(&result).is_empty());
3469        let model = result.product();
3470        assert!(
3471            model.lookup_value(model.module_scope(), "g").is_some(),
3472            "block function declaration hoists to the module scope"
3473        );
3474        assert_eq!(model.resolved_reference_count(), 1);
3475    }
3476
3477    #[test]
3478    fn a_let_in_a_block_does_not_hoist_and_is_unresolved_outside() {
3479        let inner = variable_kind(
3480            crate::syntax::VariableKind::Let,
3481            40,
3482            "let b = 1;",
3483            "b",
3484            6,
3485            Some(number_expr(50, "1", 10)),
3486        );
3487        let block = block_statement(60, vec![inner]);
3488        let statements = vec![
3489            block,
3490            expression_statement(70, identifier_expr(71, "b", 15)),
3491        ];
3492        let result = check(&file("{ let b = 1; } b;", statements));
3493        assert_eq!(semantic_codes(&result), [CANNOT_FIND_NAME.as_str()]);
3494        let model = result.product();
3495        assert!(
3496            model.lookup_value(model.module_scope(), "b").is_none(),
3497            "let stays block-scoped and never reaches the module scope"
3498        );
3499    }
3500}