Skip to main content

tsz_solver/relations/
relation_queries.rs

1//! Unified relation query entrypoints.
2//!
3//! This module centralizes common relation checks (assignability, subtype,
4//! overlap) behind one API so checker code can call Solver queries instead
5//! of wiring checker internals directly to concrete checker engines.
6
7use crate::TypeDatabase;
8use crate::caches::db::QueryDatabase;
9use crate::classes::inheritance::InheritanceGraph;
10use crate::operations::AssignabilityChecker;
11use crate::relations::compat::{
12    AssignabilityOverrideProvider, CompatChecker, NoopOverrideProvider,
13};
14use crate::relations::subtype::{AnyPropagationMode, NoopResolver, SubtypeChecker, TypeResolver};
15use crate::types::{RelationCacheKey, SymbolRef, TypeId};
16
17/// Relation categories supported by the unified query API.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum RelationKind {
20    /// TypeScript assignability (Lawyer layer).
21    Assignable,
22    /// Assignability with bivariant callback parameters.
23    AssignableBivariantCallbacks,
24    /// Structural subtyping (Judge layer).
25    Subtype,
26    /// Type overlap check used by TS2367-style diagnostics.
27    Overlap,
28    /// Type identity used for variable redeclaration compatibility.
29    RedeclarationIdentical,
30}
31
32/// Policy knobs for relation checks.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct RelationPolicy {
35    /// Packed relation flags (same layout as `RelationCacheKey.flags`).
36    pub flags: u16,
37    /// Enables additional strictness in the compatibility layer.
38    pub strict_subtype_checking: bool,
39    /// Disables `any`-suppression in compatibility fast paths.
40    pub strict_any_propagation: bool,
41    /// Controls how `SubtypeChecker` treats `any`.
42    pub any_propagation_mode: AnyPropagationMode,
43}
44
45impl Default for RelationPolicy {
46    fn default() -> Self {
47        Self {
48            flags: RelationCacheKey::FLAG_STRICT_NULL_CHECKS,
49            strict_subtype_checking: false,
50            strict_any_propagation: false,
51            any_propagation_mode: AnyPropagationMode::All,
52        }
53    }
54}
55
56impl RelationPolicy {
57    pub const fn from_flags(flags: u16) -> Self {
58        use crate::RelationCacheKey;
59        let strict_any = (flags & RelationCacheKey::FLAG_STRICT_FUNCTION_TYPES) != 0;
60        Self {
61            flags,
62            strict_subtype_checking: false,
63            strict_any_propagation: strict_any,
64            any_propagation_mode: if strict_any {
65                AnyPropagationMode::TopLevelOnly
66            } else {
67                AnyPropagationMode::All
68            },
69        }
70    }
71
72    pub const fn with_strict_subtype_checking(mut self, strict: bool) -> Self {
73        self.strict_subtype_checking = strict;
74        self
75    }
76
77    pub const fn with_strict_any_propagation(mut self, strict: bool) -> Self {
78        self.strict_any_propagation = strict;
79        self
80    }
81
82    pub const fn with_any_propagation_mode(mut self, mode: AnyPropagationMode) -> Self {
83        self.any_propagation_mode = mode;
84        self
85    }
86}
87
88/// Optional shared context needed by relation engines.
89#[derive(Clone, Copy, Default)]
90pub struct RelationContext<'a> {
91    pub query_db: Option<&'a dyn QueryDatabase>,
92    pub inheritance_graph: Option<&'a InheritanceGraph>,
93    pub class_check: Option<&'a dyn Fn(SymbolRef) -> bool>,
94}
95
96/// Result of a relation check.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct RelationResult {
99    pub kind: RelationKind,
100    pub related: bool,
101    pub depth_exceeded: bool,
102}
103
104impl RelationResult {
105    #[inline]
106    pub const fn is_related(self) -> bool {
107        self.related
108    }
109}
110
111/// Structured failure details for assignability diagnostics.
112#[derive(Debug, Clone)]
113pub struct AssignabilityFailureAnalysis {
114    pub weak_union_violation: bool,
115    pub failure_reason: Option<crate::SubtypeFailureReason>,
116}
117
118/// Analyze assignability failure details using a configured compat checker.
119pub fn analyze_assignability_failure_with_resolver<'a, R: TypeResolver, F>(
120    interner: &'a dyn TypeDatabase,
121    resolver: &'a R,
122    source: TypeId,
123    target: TypeId,
124    configure: F,
125) -> AssignabilityFailureAnalysis
126where
127    F: FnOnce(&mut CompatChecker<'a, R>),
128{
129    let mut checker = CompatChecker::with_resolver(interner, resolver);
130    configure(&mut checker);
131    AssignabilityFailureAnalysis {
132        weak_union_violation: checker.is_weak_union_violation(source, target),
133        failure_reason: checker.explain_failure(source, target),
134    }
135}
136
137/// Query a relation using a no-op resolver and no overrides.
138pub fn query_relation(
139    interner: &dyn TypeDatabase,
140    source: TypeId,
141    target: TypeId,
142    kind: RelationKind,
143    policy: RelationPolicy,
144    context: RelationContext<'_>,
145) -> RelationResult {
146    let resolver = NoopResolver;
147    query_relation_with_resolver(interner, &resolver, source, target, kind, policy, context)
148}
149
150/// Query a relation using a custom resolver and no checker overrides.
151pub fn query_relation_with_resolver<'a, R: TypeResolver>(
152    interner: &'a dyn TypeDatabase,
153    resolver: &'a R,
154    source: TypeId,
155    target: TypeId,
156    kind: RelationKind,
157    policy: RelationPolicy,
158    context: RelationContext<'a>,
159) -> RelationResult {
160    let overrides = NoopOverrideProvider;
161    query_relation_with_overrides(RelationQueryInputs {
162        interner,
163        resolver,
164        source,
165        target,
166        kind,
167        policy,
168        context,
169        overrides: &overrides,
170    })
171}
172
173/// Query a relation using a custom resolver and checker-provided overrides.
174pub fn query_relation_with_overrides<
175    'a,
176    R: TypeResolver,
177    P: AssignabilityOverrideProvider + ?Sized,
178>(
179    RelationQueryInputs {
180        interner,
181        resolver,
182        source,
183        target,
184        kind,
185        policy,
186        context,
187        overrides,
188    }: RelationQueryInputs<'a, R, P>,
189) -> RelationResult {
190    let (related, depth_exceeded) = match kind {
191        RelationKind::Assignable => {
192            let mut checker = configured_compat_checker(interner, resolver, policy, context);
193            (
194                checker.is_assignable_with_overrides(source, target, overrides),
195                false,
196            )
197        }
198        RelationKind::AssignableBivariantCallbacks => {
199            let mut checker = configured_compat_checker(interner, resolver, policy, context);
200            let _ = overrides;
201            (
202                checker.is_assignable_to_bivariant_callback(source, target),
203                false,
204            )
205        }
206        RelationKind::Subtype => {
207            let mut checker = configured_subtype_checker(interner, resolver, policy, context);
208            let related = checker.is_subtype_of(source, target);
209            (related, checker.depth_exceeded())
210        }
211        RelationKind::Overlap => {
212            let checker = configured_subtype_checker(interner, resolver, policy, context);
213            (checker.are_types_overlapping(source, target), false)
214        }
215        RelationKind::RedeclarationIdentical => {
216            let mut checker = configured_compat_checker(interner, resolver, policy, context);
217            (
218                checker.are_types_identical_for_redeclaration(source, target),
219                false,
220            )
221        }
222    };
223
224    RelationResult {
225        kind,
226        related,
227        depth_exceeded,
228    }
229}
230
231/// Bundled inputs for relation queries.
232pub struct RelationQueryInputs<'a, R: TypeResolver, P: AssignabilityOverrideProvider + ?Sized> {
233    pub interner: &'a dyn TypeDatabase,
234    pub resolver: &'a R,
235    pub source: TypeId,
236    pub target: TypeId,
237    pub kind: RelationKind,
238    pub policy: RelationPolicy,
239    pub context: RelationContext<'a>,
240    pub overrides: &'a P,
241}
242
243fn configured_compat_checker<'a, R: TypeResolver>(
244    interner: &'a dyn TypeDatabase,
245    resolver: &'a R,
246    policy: RelationPolicy,
247    context: RelationContext<'a>,
248) -> CompatChecker<'a, R> {
249    let mut checker = CompatChecker::with_resolver(interner, resolver);
250    checker.apply_flags(policy.flags);
251    checker.set_inheritance_graph(context.inheritance_graph);
252    checker.set_strict_subtype_checking(policy.strict_subtype_checking);
253    checker.set_strict_any_propagation(policy.strict_any_propagation);
254    if let Some(query_db) = context.query_db {
255        checker.set_query_db(query_db);
256    }
257    checker
258}
259
260fn configured_subtype_checker<'a, R: TypeResolver>(
261    interner: &'a dyn TypeDatabase,
262    resolver: &'a R,
263    policy: RelationPolicy,
264    context: RelationContext<'a>,
265) -> SubtypeChecker<'a, R> {
266    let mut checker = SubtypeChecker::with_resolver(interner, resolver)
267        .apply_flags(policy.flags)
268        .with_any_propagation_mode(policy.any_propagation_mode);
269    if let Some(query_db) = context.query_db {
270        checker = checker.with_query_db(query_db);
271    }
272    if let Some(inheritance_graph) = context.inheritance_graph {
273        checker = checker.with_inheritance_graph(inheritance_graph);
274    }
275    if let Some(class_check) = context.class_check {
276        checker = checker.with_class_check(class_check);
277    }
278    checker
279}
280
281#[cfg(test)]
282#[path = "../../tests/relation_queries_tests.rs"]
283mod tests;