zerodds_types/
type_matcher.rs1use crate::assignability::{AssignabilityConfig, Assignable, is_assignable};
26use crate::qos::{TypeConsistencyEnforcement, TypeConsistencyKind};
27use crate::resolve::TypeRegistry;
28use crate::type_identifier::TypeIdentifier;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum TypeMatchResult {
35 Matches,
37 Incompatible {
39 reason: &'static str,
41 },
42}
43
44impl TypeMatchResult {
45 #[must_use]
47 pub const fn is_match(&self) -> bool {
48 matches!(self, Self::Matches)
49 }
50
51 fn from_assignable(a: Assignable) -> Self {
52 match a {
53 Assignable::Yes => Self::Matches,
54 Assignable::No(reason) => Self::Incompatible { reason },
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy)]
64pub struct TypeMatcher<'a> {
65 tce: &'a TypeConsistencyEnforcement,
66}
67
68impl<'a> TypeMatcher<'a> {
69 #[must_use]
71 pub const fn new(tce: &'a TypeConsistencyEnforcement) -> Self {
72 Self { tce }
73 }
74
75 #[must_use]
80 pub fn match_types(
81 &self,
82 writer: &TypeIdentifier,
83 reader: &TypeIdentifier,
84 registry: &TypeRegistry,
85 ) -> TypeMatchResult {
86 let cfg = self.build_config();
87 TypeMatchResult::from_assignable(is_assignable(writer, reader, registry, &cfg))
88 }
89
90 fn build_config(&self) -> AssignabilityConfig {
100 let coerce = matches!(self.tce.kind, TypeConsistencyKind::AllowTypeCoercion)
101 && !self.tce.prevent_type_widening;
102 AssignabilityConfig {
103 allow_type_coercion: if self.tce.force_type_validation {
104 false
105 } else {
106 coerce
107 },
108 ignore_sequence_bounds: self.tce.ignore_sequence_bounds,
109 ignore_string_bounds: self.tce.ignore_string_bounds,
110 ignore_member_names: self.tce.ignore_member_names,
111 ignore_literal_names: false,
112 max_depth: crate::resolve::DEFAULT_MAX_RESOLVE_DEPTH,
113 }
114 }
115}
116
117#[cfg(test)]
118#[allow(
119 clippy::unwrap_used,
120 clippy::panic,
121 clippy::field_reassign_with_default
122)]
123mod tests {
124 use super::*;
125 use crate::type_identifier::PrimitiveKind;
126
127 fn reg() -> TypeRegistry {
128 TypeRegistry::new()
129 }
130
131 #[test]
132 fn identical_primitive_matches() {
133 let tce = TypeConsistencyEnforcement::default();
134 let m = TypeMatcher::new(&tce);
135 let w = TypeIdentifier::Primitive(PrimitiveKind::Int32);
136 assert_eq!(m.match_types(&w, &w, ®()), TypeMatchResult::Matches);
137 }
138
139 #[test]
140 fn widening_allowed_by_default_tce() {
141 let tce = TypeConsistencyEnforcement::default();
143 let m = TypeMatcher::new(&tce);
144 let w = TypeIdentifier::Primitive(PrimitiveKind::Int16);
145 let r = TypeIdentifier::Primitive(PrimitiveKind::Int32);
146 assert!(m.match_types(&w, &r, ®()).is_match());
147 }
148
149 #[test]
150 fn widening_blocked_by_prevent_type_widening() {
151 let mut tce = TypeConsistencyEnforcement::default();
152 tce.prevent_type_widening = true;
153 let m = TypeMatcher::new(&tce);
154 let w = TypeIdentifier::Primitive(PrimitiveKind::Int16);
155 let r = TypeIdentifier::Primitive(PrimitiveKind::Int32);
156 assert!(!m.match_types(&w, &r, ®()).is_match());
157 }
158
159 #[test]
160 fn force_type_validation_blocks_coercion() {
161 let mut tce = TypeConsistencyEnforcement::default();
162 tce.force_type_validation = true;
163 let m = TypeMatcher::new(&tce);
164 let w = TypeIdentifier::Primitive(PrimitiveKind::Int16);
165 let r = TypeIdentifier::Primitive(PrimitiveKind::Int32);
166 assert!(!m.match_types(&w, &r, ®()).is_match());
167 }
168
169 #[test]
170 fn disallow_type_coercion_blocks_widening() {
171 let mut tce = TypeConsistencyEnforcement::default();
172 tce.kind = TypeConsistencyKind::DisallowTypeCoercion;
173 let m = TypeMatcher::new(&tce);
174 let w = TypeIdentifier::Primitive(PrimitiveKind::Int16);
175 let r = TypeIdentifier::Primitive(PrimitiveKind::Int32);
176 assert!(!m.match_types(&w, &r, ®()).is_match());
177 }
178
179 #[test]
180 fn incompatible_reports_reason() {
181 let tce = TypeConsistencyEnforcement::default();
182 let m = TypeMatcher::new(&tce);
183 let w = TypeIdentifier::Primitive(PrimitiveKind::Int64);
184 let r = TypeIdentifier::Primitive(PrimitiveKind::Int16);
185 match m.match_types(&w, &r, ®()) {
186 TypeMatchResult::Incompatible { reason } => {
187 assert!(!reason.is_empty(), "reason must be non-empty");
188 }
189 TypeMatchResult::Matches => {
190 panic!("narrowing i64→i16 must not match");
191 }
192 }
193 }
194}