Skip to main content

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