pub struct SubtypeChecker<'a, R: TypeResolver = NoopResolver> {Show 14 fields
pub strict_function_types: bool,
pub allow_void_return: bool,
pub allow_bivariant_rest: bool,
pub bypass_evaluation: bool,
pub max_depth: u32,
pub allow_bivariant_param_count: bool,
pub exact_optional_property_types: bool,
pub strict_null_checks: bool,
pub no_unchecked_indexed_access: bool,
pub disable_method_bivariance: bool,
pub inheritance_graph: Option<&'a InheritanceGraph>,
pub is_class_symbol: Option<&'a dyn Fn(SymbolRef) -> bool>,
pub any_propagation: AnyPropagationMode,
pub tracer: Option<&'a mut dyn DynSubtypeTracer>,
/* private fields */
}Expand description
Subtype checking context. Maintains the “seen” set for cycle detection.
Fields§
§strict_function_types: boolWhether to use strict function types (contravariant parameters). Default: true (sound, correct behavior)
allow_void_return: boolWhether to allow any return type when the target return is void.
allow_bivariant_rest: boolWhether rest parameters of any/unknown should be treated as bivariant. See https://github.com/microsoft/TypeScript/issues/20007.
bypass_evaluation: boolWhen true, skip the evaluate_type() call in check_subtype.
This prevents infinite recursion when TypeEvaluator calls SubtypeChecker
for simplification, since TypeEvaluator has already evaluated the types.
max_depth: u32Maximum recursion depth for subtype checking.
Used by TypeEvaluator simplification to prevent stack overflow.
Default: MAX_SUBTYPE_DEPTH (100)
allow_bivariant_param_count: boolWhether required parameter count mismatches are allowed for bivariant methods.
exact_optional_property_types: boolWhether optional properties are exact (exclude implicit undefined).
Default: false (legacy TS behavior).
strict_null_checks: boolWhether null/undefined are treated as separate types. Default: true (strict null checks).
no_unchecked_indexed_access: boolWhether indexed access includes undefined.
Default: false (legacy TS behavior).
disable_method_bivariance: bool§inheritance_graph: Option<&'a InheritanceGraph>Optional inheritance graph for O(1) nominal class subtype checking. When provided, enables fast nominal checks for class inheritance.
is_class_symbol: Option<&'a dyn Fn(SymbolRef) -> bool>Optional callback to check if a symbol is a class (for nominal subtyping). Returns true if the symbol has the CLASS flag set.
any_propagation: AnyPropagationModeControls how any is treated during subtype checks.
tracer: Option<&'a mut dyn DynSubtypeTracer>Optional tracer for collecting subtype failure diagnostics.
When Some, enables detailed failure reason collection for error messages.
When None, disables tracing for maximum performance (default).
Implementations§
Source§impl<'a> SubtypeChecker<'a, NoopResolver>
impl<'a> SubtypeChecker<'a, NoopResolver>
Sourcepub fn new(interner: &'a dyn TypeDatabase) -> Self
pub fn new(interner: &'a dyn TypeDatabase) -> Self
Create a new SubtypeChecker without a resolver (basic mode).
Source§impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
Sourcepub fn with_resolver(interner: &'a dyn TypeDatabase, resolver: &'a R) -> Self
pub fn with_resolver(interner: &'a dyn TypeDatabase, resolver: &'a R) -> Self
Create a new SubtypeChecker with a custom resolver.
Sourcepub const fn with_inheritance_graph(self, graph: &'a InheritanceGraph) -> Self
pub const fn with_inheritance_graph(self, graph: &'a InheritanceGraph) -> Self
Set the inheritance graph for O(1) nominal class subtype checking.
Sourcepub fn with_class_check(self, check: &'a dyn Fn(SymbolRef) -> bool) -> Self
pub fn with_class_check(self, check: &'a dyn Fn(SymbolRef) -> bool) -> Self
Set the callback to check if a symbol is a class.
Sourcepub const fn with_any_propagation_mode(self, mode: AnyPropagationMode) -> Self
pub const fn with_any_propagation_mode(self, mode: AnyPropagationMode) -> Self
Configure how any is treated during subtype checks.
Sourcepub fn with_tracer(self, tracer: &'a mut dyn DynSubtypeTracer) -> Self
pub fn with_tracer(self, tracer: &'a mut dyn DynSubtypeTracer) -> Self
Set the tracer for collecting subtype failure diagnostics. When set, enables detailed failure reason collection for error messages.
Sourcepub fn with_query_db(self, db: &'a dyn QueryDatabase) -> Self
pub fn with_query_db(self, db: &'a dyn QueryDatabase) -> Self
Set the query database for Salsa-backed memoization.
When set, routes evaluate_type and is_subtype_of through Salsa.
Sourcepub const fn with_strict_null_checks(self, strict_null_checks: bool) -> Self
pub const fn with_strict_null_checks(self, strict_null_checks: bool) -> Self
Set whether strict null checks are enabled. When false, null and undefined are assignable to any type.
Sourcepub fn reset(&mut self)
pub fn reset(&mut self)
Reset per-check state so this checker can be reused for another subtype check.
This clears cycle detection sets and counters while preserving configuration
(strict_function_types, allow_void_return, etc.) and borrowed references
(interner, resolver, inheritance_graph, etc.).
Uses .clear() instead of re-allocating, so hash set memory is reused.
Sourcepub const fn depth_exceeded(&self) -> bool
pub const fn depth_exceeded(&self) -> bool
Whether the recursion depth was exceeded during subtype checking.
Source§impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
Sourcepub fn check_subtype(&mut self, source: TypeId, target: TypeId) -> SubtypeResult
pub fn check_subtype(&mut self, source: TypeId, target: TypeId) -> SubtypeResult
When a cycle is detected, we return CycleDetected (coinductive semantics)
which implements greatest fixed point semantics - the correct behavior for
recursive type checking. When depth/iteration limits are exceeded, we return
DepthExceeded (conservative false) for soundness.
Source§impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
Sourcepub fn explain_failure(
&mut self,
source: TypeId,
target: TypeId,
) -> Option<SubtypeFailureReason>
pub fn explain_failure( &mut self, source: TypeId, target: TypeId, ) -> Option<SubtypeFailureReason>
Explain why source is not assignable to target.
This is the “slow path” - called only when is_assignable_to returns false
and we need to generate an error message. Re-runs the subtype logic with
tracing enabled to produce a structured failure reason.
Returns None if the types are actually compatible (shouldn’t happen
if called correctly after a failed check).
Sourcepub fn are_types_structurally_identical(&self, a: TypeId, b: TypeId) -> bool
pub fn are_types_structurally_identical(&self, a: TypeId, b: TypeId) -> bool
Check if two types are structurally identical using De Bruijn indices for cycles.
This is the O(1) alternative to bidirectional subtyping for identity checks. It transforms cyclic graphs into trees to solve the Graph Isomorphism problem.
Source§impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
Sourcepub fn is_subtype_of(&mut self, source: TypeId, target: TypeId) -> bool
pub fn is_subtype_of(&mut self, source: TypeId, target: TypeId) -> bool
Check if source is a subtype of target.
This is the main entry point for subtype checking.
When a QueryDatabase is available (via with_query_db), fast-path checks
(identity, any, unknown, never) are done locally, then the full structural
check is delegated to the internal check_subtype which may use Salsa
memoization for evaluate_type calls.
Sourcepub fn is_assignable_to(&mut self, source: TypeId, target: TypeId) -> bool
pub fn is_assignable_to(&mut self, source: TypeId, target: TypeId) -> bool
Check if source is assignable to target.
This is a strict structural check; use CompatChecker for TypeScript assignability rules.
Source§impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
impl<'a, R: TypeResolver> SubtypeChecker<'a, R>
Sourcepub fn are_types_overlapping(&self, a: TypeId, b: TypeId) -> bool
pub fn are_types_overlapping(&self, a: TypeId, b: TypeId) -> bool
Check if two types have any overlap (non-empty intersection).
This is used for TS2367: “This condition will always return ‘false’ since the types ‘X’ and ‘Y’ have no overlap.”
Returns true if there exists at least one type that is a subtype of both a and b.
Returns false if a & b would be the never type (zero overlap).
This catches OBVIOUS non-overlaps:
- Different primitives (string vs number, boolean vs bigint, etc.)
- Different literals of same primitive (“a” vs “b”, 1 vs 2)
- Object property type mismatches ({ a: string } vs { a: number })
For complex types (unions, intersections, generics), we conservatively return true to avoid false positives.
§Examples
are_types_overlapping(string, number)-> false (different primitives)are_types_overlapping(1, 2)-> false (different number literals)are_types_overlapping({ a: string }, { a: number })-> false (property type mismatch)are_types_overlapping({ a: 1 }, { b: 2 })-> true (can have { a: 1, b: 2 })are_types_overlapping(string, "hello")-> true (literal is subtype of primitive)
Trait Implementations§
Source§impl<'a, R: TypeResolver> AssignabilityChecker for SubtypeChecker<'a, R>
impl<'a, R: TypeResolver> AssignabilityChecker for SubtypeChecker<'a, R>
fn is_assignable_to(&mut self, source: TypeId, target: TypeId) -> bool
Source§fn is_assignable_to_bivariant_callback(
&mut self,
source: TypeId,
target: TypeId,
) -> bool
fn is_assignable_to_bivariant_callback( &mut self, source: TypeId, target: TypeId, ) -> bool
Source§fn evaluate_type(&mut self, type_id: TypeId) -> TypeId
fn evaluate_type(&mut self, type_id: TypeId) -> TypeId
Func<T> must be expanded to their structural form (e.g., a Callable).
The default implementation returns the type unchanged (no resolver available).