1use harn_parser::builtin_signatures::{BuiltinSignature, Ty};
9use harn_parser::TypeExpr;
10
11use crate::DataValue;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum RuntimeTypeKind {
15 Int,
16 Float,
17 Decimal,
18 String,
19 Bytes,
20 Bool,
21 Nil,
22 List,
23 Dict,
24 Closure,
25 Duration,
26 Enum,
27 Struct,
28 TaskHandle,
29 Channel,
30 Atomic,
31 Rng,
32 SyncPermit,
33 Resource,
34 ResourceGuard,
35 McpClient,
36 VerdictReceipt,
37 Set,
38 Generator,
39 Stream,
40 Range,
41 Iter,
42 Pair,
43 Harness,
44}
45
46pub trait TypeContractValue: Sized {
47 fn runtime_type_kind(&self) -> RuntimeTypeKind;
48 fn list_items(&self) -> Option<&[Self]> {
49 None
50 }
51 fn record_field(&self, _name: &str) -> Option<&Self> {
52 None
53 }
54 fn record_values_match(&self, _predicate: &mut dyn FnMut(&Self) -> bool) -> Option<bool> {
60 None
61 }
62 fn string_literal(&self) -> Option<&str> {
63 None
64 }
65 fn int_literal(&self) -> Option<i64> {
66 None
67 }
68 fn nominal_type_name(&self) -> Option<&str> {
69 None
70 }
71}
72
73pub fn matches_type<V: TypeContractValue>(
74 value: &V,
75 expected: &TypeExpr,
76 type_params: &[String],
77 nominal_type_names: &[String],
78) -> bool {
79 use RuntimeTypeKind as Kind;
80 match expected {
81 TypeExpr::Named(name) => match name.as_str() {
82 _ if type_params.iter().any(|param| param == name) => true,
83 "any" | "unknown" => true,
84 "int" => value.runtime_type_kind() == Kind::Int,
85 "float" | "number" => matches!(value.runtime_type_kind(), Kind::Float | Kind::Int),
86 "decimal" => value.runtime_type_kind() == Kind::Decimal,
87 "string" => value.runtime_type_kind() == Kind::String,
88 "bool" => value.runtime_type_kind() == Kind::Bool,
89 "nil" => value.runtime_type_kind() == Kind::Nil,
90 "list" => value.runtime_type_kind() == Kind::List,
91 "dict" | "record" => value.runtime_type_kind() == Kind::Dict,
92 "bytes" => value.runtime_type_kind() == Kind::Bytes,
93 "duration" => value.runtime_type_kind() == Kind::Duration,
94 "set" => value.runtime_type_kind() == Kind::Set,
95 "range" => value.runtime_type_kind() == Kind::Range,
96 "iter" => value.runtime_type_kind() == Kind::Iter,
97 "generator" | "Generator" => value.runtime_type_kind() == Kind::Generator,
98 "stream" | "Stream" => value.runtime_type_kind() == Kind::Stream,
99 "channel" => value.runtime_type_kind() == Kind::Channel,
100 "task_handle" => value.runtime_type_kind() == Kind::TaskHandle,
101 "atomic" => value.runtime_type_kind() == Kind::Atomic,
102 "rng" => value.runtime_type_kind() == Kind::Rng,
103 "sync_permit" => value.runtime_type_kind() == Kind::SyncPermit,
104 "resource" => value.runtime_type_kind() == Kind::Resource,
105 "resource_guard" => value.runtime_type_kind() == Kind::ResourceGuard,
106 "mcp_client" => value.runtime_type_kind() == Kind::McpClient,
107 "verdict_receipt" => value.runtime_type_kind() == Kind::VerdictReceipt,
108 "pair" => value.runtime_type_kind() == Kind::Pair,
109 "enum" => value.runtime_type_kind() == Kind::Enum,
110 "struct" => value.runtime_type_kind() == Kind::Struct,
111 "closure" => value.runtime_type_kind() == Kind::Closure,
112 _ if !nominal_type_names.iter().any(|ty| ty == name) => true,
113 _ => value
114 .nominal_type_name()
115 .is_some_and(|actual| actual == name),
116 },
117 TypeExpr::Union(members) => members
118 .iter()
119 .any(|member| matches_type(value, member, type_params, nominal_type_names)),
120 TypeExpr::Intersection(members) => members
121 .iter()
122 .all(|member| matches_type(value, member, type_params, nominal_type_names)),
123 TypeExpr::List(inner) => value.list_items().is_some_and(|items| {
124 items
125 .iter()
126 .all(|item| matches_type(item, inner, type_params, nominal_type_names))
127 }),
128 TypeExpr::Tuple(elements) => value.list_items().is_some_and(|items| {
129 items.len() == elements.len()
130 && items.iter().zip(elements).all(|(item, element)| {
131 matches_type(item, element, type_params, nominal_type_names)
132 })
133 }),
134 TypeExpr::DictType(_, value_type) => {
135 value.runtime_type_kind() == Kind::Dict
136 && record_values_match(value, value_type, type_params, nominal_type_names)
137 }
138 TypeExpr::Iter(_) | TypeExpr::Generator(_) | TypeExpr::Stream(_) => matches!(
139 value.runtime_type_kind(),
140 Kind::List | Kind::Generator | Kind::Stream
141 ),
142 TypeExpr::Shape(fields) | TypeExpr::OpenShape { fields, .. } => {
143 matches!(value.runtime_type_kind(), Kind::Dict | Kind::Struct)
144 && fields
145 .iter()
146 .all(|field| match value.record_field(&field.name) {
147 Some(field_value)
148 if field.optional && field_value.runtime_type_kind() == Kind::Nil =>
149 {
150 true
151 }
152 Some(field_value) => matches_type(
153 field_value,
154 &field.type_expr,
155 type_params,
156 nominal_type_names,
157 ),
158 None => field.optional,
159 })
160 }
161 TypeExpr::Applied { name, args } => match (name.as_str(), args.as_slice()) {
162 ("list" | "List", [inner]) => value.list_items().is_some_and(|items| {
163 items
164 .iter()
165 .all(|item| matches_type(item, inner, type_params, nominal_type_names))
166 }),
167 ("dict" | "Dict", [_, value_type]) => {
168 value.runtime_type_kind() == Kind::Dict
169 && record_values_match(value, value_type, type_params, nominal_type_names)
170 }
171 ("Option", [inner]) => {
172 value.runtime_type_kind() == Kind::Nil
173 || matches_type(value, inner, type_params, nominal_type_names)
174 }
175 _ => true,
176 },
177 TypeExpr::FnType { .. } => value.runtime_type_kind() == Kind::Closure,
178 TypeExpr::Never => false,
179 TypeExpr::LitString(expected) => value.string_literal() == Some(expected),
180 TypeExpr::LitInt(expected) => value.int_literal() == Some(*expected),
181 TypeExpr::Owned(inner) => matches_type(value, inner, type_params, nominal_type_names),
182 }
183}
184
185pub fn matches_manifest_type<V: TypeContractValue>(value: &V, expected: &Ty) -> bool {
191 let expected = harn_parser::builtin_signatures::ty_to_type_expr(expected);
192 matches_type(value, &expected, &[], &[])
193}
194
195pub fn manifest_type_is_portable(expected: &Ty) -> bool {
203 match expected {
204 Ty::Any | Ty::LitInt(_) | Ty::LitString(_) => true,
205 Ty::Named(name) => matches!(
206 *name,
207 "any"
208 | "unknown"
209 | "nil"
210 | "bool"
211 | "int"
212 | "float"
213 | "number"
214 | "string"
215 | "bytes"
216 | "list"
217 | "dict"
218 | "record"
219 ),
220 Ty::Optional(inner) => manifest_type_is_portable(inner),
221 Ty::Apply("list" | "List", [inner]) | Ty::Apply("Option", [inner]) => {
222 manifest_type_is_portable(inner)
223 }
224 Ty::Apply("dict" | "Dict", [key, value]) => {
225 manifest_dict_key_is_portable(key) && manifest_type_is_portable(value)
226 }
227 Ty::Union(members) => !members.is_empty() && members.iter().all(manifest_type_is_portable),
228 Ty::Shape(fields) => fields
229 .iter()
230 .all(|field| manifest_type_is_portable(&field.ty)),
231 Ty::Generic(_) | Ty::Apply(_, _) | Ty::Fn(_, _) | Ty::SchemaOf(_) | Ty::Never => false,
232 }
233}
234
235fn manifest_dict_key_is_portable(expected: &Ty) -> bool {
236 match expected {
237 Ty::Any | Ty::Named("any" | "unknown" | "string") | Ty::LitString(_) => true,
238 Ty::Union(members) => {
239 !members.is_empty() && members.iter().all(manifest_dict_key_is_portable)
240 }
241 _ => false,
242 }
243}
244
245pub fn manifest_signature_is_portable(signature: &BuiltinSignature) -> bool {
248 signature
249 .params
250 .iter()
251 .all(|parameter| manifest_type_is_portable(¶meter.ty))
252 && manifest_type_is_portable(&signature.returns)
253}
254
255fn record_values_match<V: TypeContractValue>(
256 value: &V,
257 value_type: &TypeExpr,
258 type_params: &[String],
259 nominal_type_names: &[String],
260) -> bool {
261 value
262 .record_values_match(&mut |field| {
263 matches_type(field, value_type, type_params, nominal_type_names)
264 })
265 .unwrap_or(false)
266}
267
268impl TypeContractValue for DataValue {
269 fn runtime_type_kind(&self) -> RuntimeTypeKind {
270 match self {
271 Self::Nil => RuntimeTypeKind::Nil,
272 Self::Bool(_) => RuntimeTypeKind::Bool,
273 Self::Int(_) => RuntimeTypeKind::Int,
274 Self::Float(_) => RuntimeTypeKind::Float,
275 Self::String(_) => RuntimeTypeKind::String,
276 Self::Bytes(_) => RuntimeTypeKind::Bytes,
277 Self::List(_) => RuntimeTypeKind::List,
278 Self::Record(_) => RuntimeTypeKind::Dict,
279 }
280 }
281
282 fn list_items(&self) -> Option<&[Self]> {
283 match self {
284 Self::List(items) => Some(items),
285 _ => None,
286 }
287 }
288
289 fn record_field(&self, name: &str) -> Option<&Self> {
290 match self {
291 Self::Record(fields) => fields.get(name),
292 _ => None,
293 }
294 }
295
296 fn record_values_match(&self, predicate: &mut dyn FnMut(&Self) -> bool) -> Option<bool> {
297 match self {
298 Self::Record(fields) => Some(fields.values().all(predicate)),
299 _ => None,
300 }
301 }
302
303 fn string_literal(&self) -> Option<&str> {
304 match self {
305 Self::String(value) => Some(value),
306 _ => None,
307 }
308 }
309
310 fn int_literal(&self) -> Option<i64> {
311 match self {
312 Self::Int(value) => Some(*value),
313 _ => None,
314 }
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use harn_parser::builtin_signatures::{ShapeFieldDescriptor, Ty};
321
322 use super::*;
323
324 const STRING: Ty = Ty::Named("string");
325 const STRING_LIST_ARGS: &[Ty] = &[STRING];
326 const RESULT_ARGS: &[Ty] = &[STRING, Ty::Named("dict")];
327 const INT_KEYED_DICT_ARGS: &[Ty] = &[Ty::Named("int"), STRING];
328 const RECORD_FIELDS: &[ShapeFieldDescriptor] = &[
329 ShapeFieldDescriptor::new("name", STRING),
330 ShapeFieldDescriptor::optional("note", STRING),
331 ];
332
333 #[test]
334 fn manifest_types_use_the_source_type_contract() {
335 let strings = DataValue::List(vec![DataValue::String("kernel".into())]);
336 assert!(matches_manifest_type(
337 &strings,
338 &Ty::Apply("list", STRING_LIST_ARGS)
339 ));
340 assert!(!matches_manifest_type(
341 &DataValue::List(vec![DataValue::Int(1)]),
342 &Ty::Apply("list", STRING_LIST_ARGS)
343 ));
344
345 let record = DataValue::Record(std::collections::BTreeMap::from([(
346 "name".to_string(),
347 DataValue::String("portable".into()),
348 )]));
349 assert!(matches_manifest_type(&record, &Ty::Shape(RECORD_FIELDS)));
350 }
351
352 #[test]
353 fn portable_manifest_types_are_exactly_data_value_types() {
354 assert!(manifest_type_is_portable(&Ty::Apply(
355 "list",
356 STRING_LIST_ARGS
357 )));
358 assert!(manifest_type_is_portable(&Ty::Shape(RECORD_FIELDS)));
359 assert!(!manifest_type_is_portable(&Ty::Apply(
360 "Result",
361 RESULT_ARGS
362 )));
363 assert!(!manifest_type_is_portable(&Ty::Named("channel")));
364 assert!(!manifest_type_is_portable(&Ty::Fn(&[], &STRING)));
365 assert!(!manifest_type_is_portable(&Ty::Generic("T")));
366 assert!(!manifest_type_is_portable(&Ty::Apply(
367 "dict",
368 INT_KEYED_DICT_ARGS
369 )));
370 }
371}