1use super::Form;
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct SchemaField {
12 pub name: Form,
13 pub properties: Option<Form>,
14 pub value_type: SchemaType,
15}
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct FunctionSchema {
19 pub fixed: Vec<SchemaType>,
20 pub rest: Option<Box<SchemaType>>,
21 pub output: Box<SchemaType>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum SchemaType {
26 Primitive(String),
27 Reference(String),
28 Union(Vec<SchemaType>),
29 Vector(Box<SchemaType>),
30 Set(Box<SchemaType>),
31 Tuple(Vec<SchemaType>),
32 Map(Vec<SchemaField>),
33 Struct {
34 name: String,
35 mutable: bool,
36 fields: Vec<SchemaField>,
37 },
38 WithProperties {
39 schema: Box<SchemaType>,
40 properties: Form,
41 },
42 Function(Vec<FunctionSchema>),
43 Enum(Vec<Form>),
44 Extension {
45 head: String,
46 arguments: Vec<Form>,
47 },
48 Unknown(Form),
49}
50
51impl crate::lang::protocol::IDeref for SchemaType {
52 type Output = Form;
53
54 fn deref(&self) -> Form {
55 schema_shorthand(self)
56 }
57}
58
59pub fn schema_shorthand(schema: &SchemaType) -> Form {
60 let nested = |value: &SchemaType| schema_shorthand(value);
61 match schema {
62 SchemaType::Primitive(name) => Form::Vector(vec![Form::Keyword(name.clone())]),
63 SchemaType::Reference(name) => Form::Vector(vec![Form::List(vec![
64 Form::Symbol("var".into()),
65 Form::Symbol(name.clone()),
66 ])]),
67 SchemaType::Union(types) => Form::Vector(
68 std::iter::once(Form::Keyword("or".into()))
69 .chain(types.iter().map(nested))
70 .collect(),
71 ),
72 SchemaType::Vector(item) => {
73 Form::Vector(vec![Form::Keyword("vector".into()), nested(item)])
74 }
75 SchemaType::Set(item) => Form::Vector(vec![Form::Keyword("set".into()), nested(item)]),
76 SchemaType::Tuple(items) => Form::Vector(
77 std::iter::once(Form::Keyword("tuple".into()))
78 .chain(items.iter().map(nested))
79 .collect(),
80 ),
81 SchemaType::Map(fields) => Form::Vector(
82 std::iter::once(Form::Keyword("map".into()))
83 .chain(fields.iter().map(|field| {
84 let mut pair = vec![field.name.clone()];
85 if let Some(properties) = &field.properties {
86 pair.push(properties.clone());
87 }
88 pair.push(nested(&field.value_type));
89 Form::Vector(pair)
90 }))
91 .collect(),
92 ),
93 SchemaType::Struct {
94 name,
95 mutable,
96 fields,
97 } => {
98 let mut values = vec![Form::Keyword("struct".into())];
99 if *mutable {
100 values.push(Form::Map(vec![(
101 Form::Keyword("mutable?".into()),
102 Form::Bool(true),
103 )]));
104 }
105 values.push(Form::List(vec![
106 Form::Symbol("var".into()),
107 Form::Symbol(name.clone()),
108 ]));
109 values.extend(fields.iter().map(|field| {
110 let mut pair = vec![field.name.clone()];
111 if let Some(properties) = &field.properties {
112 pair.push(properties.clone());
113 }
114 pair.push(nested(&field.value_type));
115 Form::Vector(pair)
116 }));
117 Form::Vector(values)
118 }
119 SchemaType::Function(arities) => {
120 let function = |arity: &FunctionSchema| {
121 let mut inputs = arity.fixed.iter().map(nested).collect::<Vec<_>>();
122 if let Some(rest) = &arity.rest {
123 inputs.push(Form::Symbol("&".into()));
124 inputs.push(nested(rest));
125 }
126 Form::Vector(vec![
127 Form::Keyword("fn".into()),
128 Form::Vector(inputs),
129 nested(&arity.output),
130 ])
131 };
132 if arities.len() == 1 {
133 function(&arities[0])
134 } else {
135 Form::Vector(
136 std::iter::once(Form::Keyword("function".into()))
137 .chain(arities.iter().map(function))
138 .collect(),
139 )
140 }
141 }
142 SchemaType::Enum(values) => Form::Vector(
143 std::iter::once(Form::Keyword("enum".into()))
144 .chain(values.iter().cloned())
145 .collect(),
146 ),
147 SchemaType::WithProperties { schema, properties } => {
148 let Form::Vector(mut values) = nested(schema) else {
149 return nested(schema);
150 };
151 values.insert(1, properties.clone());
152 Form::Vector(values)
153 }
154 SchemaType::Extension { head, arguments } => Form::Vector(
155 std::iter::once(Form::Keyword(head.clone()))
156 .chain(arguments.iter().cloned())
157 .collect(),
158 ),
159 SchemaType::Unknown(Form::Vector(values)) => Form::Vector(values.clone()),
160 SchemaType::Unknown(surface) => Form::Vector(vec![surface.clone()]),
161 }
162}
163
164pub fn normalize_schema(schema: &Form) -> Result<SchemaType, String> {
165 match schema {
166 Form::Keyword(name) if name == "integer" => Ok(integer_schema()),
167 Form::Keyword(name) => Ok(SchemaType::Primitive(name.clone())),
168 Form::List(reference)
169 if reference.len() == 2
170 && matches!(&reference[0], Form::Symbol(operator) if operator == "var") =>
171 {
172 match &reference[1] {
173 Form::Symbol(name) if name.contains('/') => Ok(SchemaType::Reference(name.clone())),
174 Form::Symbol(name) => Err(format!(
175 "named schema reference is not fully qualified: {name}"
176 )),
177 _ => Err("named schema reference must target a symbol".into()),
178 }
179 }
180 Form::Vector(items) if !items.is_empty() => normalize_composite(items),
181 Form::Map(entries) => normalize_longhand(entries),
182 other => Ok(SchemaType::Unknown(other.clone())),
183 }
184}
185
186fn longhand_value<'a>(entries: &'a [(Form, Form)], name: &str) -> Option<&'a Form> {
187 entries
188 .iter()
189 .find_map(|(key, value)| matches!(key, Form::Keyword(key) if key == name).then_some(value))
190}
191
192fn longhand_children(entries: &[(Form, Form)]) -> Result<&[Form], String> {
193 match longhand_value(entries, "children") {
194 Some(Form::Vector(values)) => Ok(values),
195 None => Ok(&[]),
196 _ => Err("schema :children must be a vector".into()),
197 }
198}
199
200fn longhand_sequence<'a>(
201 entries: &'a [(Form, Form)],
202 name: &str,
203 fallback: &'a [Form],
204) -> Result<&'a [Form], String> {
205 match longhand_value(entries, name) {
206 Some(Form::Vector(values)) => Ok(values),
207 Some(_) => Err(format!("schema :{name} must be a vector")),
208 None => Ok(fallback),
209 }
210}
211
212fn normalize_reference_name(value: &Form) -> Result<SchemaType, String> {
213 match value {
214 Form::Symbol(name) if name.contains('/') => Ok(SchemaType::Reference(name.clone())),
215 Form::Symbol(name) => Err(format!(
216 "named schema reference is not fully qualified: {name}"
217 )),
218 _ => Err("named schema reference must target a symbol".into()),
219 }
220}
221
222fn normalize_union_forms(values: &[Form]) -> Result<SchemaType, String> {
223 if values.is_empty() {
224 return Err(":or schema requires at least one member".into());
225 }
226 let mut members = Vec::new();
227 for value in values {
228 let normalized = normalize_schema(value)?;
229 match normalized {
230 SchemaType::Union(nested) => {
231 for member in nested {
232 push_unique(&mut members, member);
233 }
234 }
235 member => push_unique(&mut members, member),
236 }
237 }
238 Ok(if members.len() == 1 {
239 members.pop().unwrap()
240 } else {
241 SchemaType::Union(members)
242 })
243}
244
245fn normalize_longhand_field(field: &Form) -> Result<SchemaField, String> {
246 let Form::Map(entries) = field else {
247 return Err("map schema fields must be {:name name :type schema} maps".into());
248 };
249 let name = longhand_value(entries, "name")
250 .ok_or_else(|| "map schema field requires :name".to_string())?;
251 let value_type = longhand_value(entries, "type")
252 .ok_or_else(|| "map schema field requires :type".to_string())?;
253 let properties = match longhand_value(entries, "properties") {
254 None => None,
255 Some(Form::Map(values)) => Some(Form::Map(values.clone())),
256 Some(_) => return Err("map schema field :properties must be a map".into()),
257 };
258 Ok(SchemaField {
259 name: name.clone(),
260 properties,
261 value_type: normalize_schema(value_type)?,
262 })
263}
264
265fn normalize_struct_name(value: &Form) -> Result<String, String> {
266 match value {
267 Form::List(reference)
268 if reference.len() == 2
269 && matches!(&reference[0], Form::Symbol(operator) if operator == "var") =>
270 {
271 match &reference[1] {
272 Form::Symbol(name) if name.contains('/') => Ok(name.clone()),
273 Form::Symbol(name) => Err(format!(
274 "named struct schema reference is not fully qualified: {name}"
275 )),
276 _ => Err("named struct schema reference must target a symbol".into()),
277 }
278 }
279 Form::Symbol(name) if name.contains('/') => Ok(name.clone()),
280 Form::Symbol(name) => Err(format!(
281 "named struct schema reference is not fully qualified: {name}"
282 )),
283 _ => Err("struct schema name must be a qualified symbol or (var ...) reference".into()),
284 }
285}
286
287fn normalize_struct_field(argument: &Form) -> Result<SchemaField, String> {
288 let Form::Vector(pair) = argument else {
289 return Err(":struct schema fields must be [name type] or [name properties type]".into());
290 };
291 match pair.as_slice() {
292 [name, value_type] => Ok(SchemaField {
293 name: name.clone(),
294 properties: None,
295 value_type: normalize_schema(value_type)?,
296 }),
297 [name, Form::Map(properties), value_type] => Ok(SchemaField {
298 name: name.clone(),
299 properties: Some(Form::Map(properties.clone())),
300 value_type: normalize_schema(value_type)?,
301 }),
302 _ => Err(":struct schema fields must be [name type] or [name properties type]".into()),
303 }
304}
305
306fn normalize_struct_forms(arguments: &[Form], mutable: bool) -> Result<SchemaType, String> {
307 let Some(name) = arguments.first() else {
308 return Err(":struct schema requires a qualified name".into());
309 };
310 Ok(SchemaType::Struct {
311 name: normalize_struct_name(name)?,
312 mutable,
313 fields: arguments[1..]
314 .iter()
315 .map(normalize_struct_field)
316 .collect::<Result<Vec<_>, _>>()?,
317 })
318}
319
320fn struct_mutability(arguments: &[Form]) -> Result<(bool, &[Form]), String> {
321 let Some(Form::Map(properties)) = arguments.first() else {
322 return Ok((false, arguments));
323 };
324 let Some(value) = properties.iter().find_map(|(key, value)| {
325 matches!(key, Form::Keyword(key) if key == "mutable?").then_some(value)
326 }) else {
327 return Ok((false, arguments));
328 };
329 let Form::Bool(mutable) = value else {
330 return Err(":struct schema :mutable? must be boolean".into());
331 };
332 Ok((*mutable, &arguments[1..]))
333}
334
335fn normalize_function_inputs(
336 inputs: &Form,
337) -> Result<(Vec<SchemaType>, Option<Box<SchemaType>>), String> {
338 match inputs {
339 Form::Map(entries) => {
340 let fixed = match longhand_value(entries, "fixed") {
341 Some(Form::Vector(values)) => values
342 .iter()
343 .map(normalize_schema)
344 .collect::<Result<Vec<_>, _>>()?,
345 None => Vec::new(),
346 _ => return Err("function schema :fixed must be a vector".into()),
347 };
348 let rest = match longhand_value(entries, "rest") {
349 None | Some(Form::Nil) => None,
350 Some(value) => Some(Box::new(normalize_schema(value)?)),
351 };
352 Ok((fixed, rest))
353 }
354 Form::Vector(values) => {
355 let mut fixed = Vec::new();
356 let mut rest = None;
357 let mut index = 0;
358 while index < values.len() {
359 if matches!(&values[index], Form::Symbol(marker) if marker == "&") {
360 if rest.is_some() || index + 2 != values.len() {
361 return Err(":fn schema & must precede exactly one rest type".into());
362 }
363 rest = Some(Box::new(normalize_schema(&values[index + 1])?));
364 index += 2;
365 } else {
366 fixed.push(normalize_schema(&values[index])?);
367 index += 1;
368 }
369 }
370 Ok((fixed, rest))
371 }
372 _ => Err("function schema :inputs must be a vector or map".into()),
373 }
374}
375
376fn normalize_longhand_function(entries: &[(Form, Form)]) -> Result<FunctionSchema, String> {
377 let inputs = longhand_value(entries, "inputs")
378 .ok_or_else(|| "function schema requires :inputs".to_string())?;
379 let output = longhand_value(entries, "output")
380 .ok_or_else(|| "function schema requires :output".to_string())?;
381 let (fixed, rest) = normalize_function_inputs(inputs)?;
382 Ok(FunctionSchema {
383 fixed,
384 rest,
385 output: Box::new(normalize_schema(output)?),
386 })
387}
388
389fn normalize_longhand_functions(values: &[Form]) -> Result<SchemaType, String> {
390 if values.is_empty() {
391 return Err(":function schema requires at least one :fn schema".into());
392 }
393 let mut arities = Vec::new();
394 for value in values {
395 match value {
396 Form::Map(entries) if longhand_value(entries, "kind").is_none() => {
397 arities.push(normalize_longhand_function(entries)?);
398 }
399 _ => match normalize_schema(value)? {
400 SchemaType::Function(nested) => arities.extend(nested),
401 _ => return Err(":function members must be :fn schemas".into()),
402 },
403 }
404 }
405 Ok(SchemaType::Function(arities))
406}
407
408fn normalize_longhand(entries: &[(Form, Form)]) -> Result<SchemaType, String> {
409 let Some(Form::Keyword(kind)) = longhand_value(entries, "kind") else {
410 return Ok(SchemaType::Unknown(Form::Map(entries.to_vec())));
411 };
412 let children = longhand_children(entries)?;
413 let normalized = match kind.as_str() {
414 "primitive" => {
415 let value = longhand_value(entries, "name").or_else(|| children.first());
416 match value {
417 Some(Form::Keyword(name)) if name == "integer" => Ok(integer_schema()),
418 Some(Form::Keyword(name)) => Ok(SchemaType::Primitive(name.clone())),
419 _ => Err("primitive schema requires one keyword name".into()),
420 }
421 }
422 "reference" => {
423 let value = longhand_value(entries, "name").or_else(|| children.first());
424 value
425 .ok_or_else(|| "reference schema requires :name".to_string())
426 .and_then(normalize_reference_name)
427 }
428 "union" | "or" => normalize_union_forms(longhand_sequence(entries, "types", children)?),
429 "vector" => {
430 let value = longhand_value(entries, "item").or_else(|| children.first());
431 value
432 .ok_or_else(|| "vector schema requires :item".to_string())
433 .and_then(normalize_schema)
434 .map(|value| SchemaType::Vector(Box::new(value)))
435 }
436 "set" => {
437 let value = longhand_value(entries, "item").or_else(|| children.first());
438 value
439 .ok_or_else(|| "set schema requires :item".to_string())
440 .and_then(normalize_schema)
441 .map(|value| SchemaType::Set(Box::new(value)))
442 }
443 "tuple" => longhand_sequence(entries, "items", children)?
444 .iter()
445 .map(normalize_schema)
446 .collect::<Result<Vec<_>, _>>()
447 .map(SchemaType::Tuple),
448 "map" => {
449 if longhand_value(entries, "fields").is_some() {
450 longhand_sequence(entries, "fields", &[])?
451 .iter()
452 .map(normalize_longhand_field)
453 .collect::<Result<Vec<_>, _>>()
454 .map(SchemaType::Map)
455 } else {
456 children
457 .iter()
458 .map(normalize_map_field)
459 .collect::<Result<Vec<_>, _>>()
460 .map(SchemaType::Map)
461 }
462 }
463 "struct" => {
464 let mutable = match longhand_value(entries, "mutable?") {
465 None => false,
466 Some(Form::Bool(value)) => *value,
467 Some(_) => return Err("struct schema :mutable? must be boolean".into()),
468 };
469 let name = longhand_value(entries, "name").or_else(|| children.first())
470 .ok_or_else(|| ":struct schema requires a qualified name".to_string())?;
471 let field_fallback = if children.is_empty() {
472 &[][..]
473 } else {
474 &children[1..]
475 };
476 let fields = longhand_sequence(entries, "fields", field_fallback)?
477 .iter()
478 .map(normalize_struct_field)
479 .collect::<Result<Vec<_>, _>>()?;
480 Ok(SchemaType::Struct {
481 name: normalize_struct_name(name)?,
482 mutable,
483 fields,
484 })
485 }
486 "fn" => normalize_longhand_function(entries).map(|arity| SchemaType::Function(vec![arity])),
487 "function" => {
488 normalize_longhand_functions(longhand_sequence(entries, "arities", children)?)
489 }
490 "enum" => Ok(SchemaType::Enum(
491 longhand_sequence(entries, "values", children)?.to_vec(),
492 )),
493 "extension" => {
494 let head = longhand_value(entries, "head")
495 .or_else(|| longhand_value(entries, "name"))
496 .ok_or_else(|| "extension schema requires :head".to_string())?;
497 let Form::Keyword(head) = head else {
498 return Err("extension schema :head must be a keyword".into());
499 };
500 Ok(SchemaType::Extension {
501 head: head.clone(),
502 arguments: longhand_sequence(entries, "arguments", children)?.to_vec(),
503 })
504 }
505 "unknown" => Ok(SchemaType::Unknown(
506 longhand_value(entries, "surface")
507 .or_else(|| children.first())
508 .cloned()
509 .unwrap_or_else(|| Form::Map(entries.to_vec())),
510 )),
511 _ => Err(format!("unsupported longhand schema kind: {kind}")),
512 }?;
513 match longhand_value(entries, "properties") {
514 None => Ok(normalized),
515 Some(Form::Map(values)) => Ok(SchemaType::WithProperties {
516 schema: Box::new(normalized),
517 properties: Form::Map(values.clone()),
518 }),
519 Some(_) => Err("schema :properties must be a map".into()),
520 }
521}
522
523pub fn infer_function_types(
527 namespace: &str,
528 forms: &[Form],
529 declarations: &HashMap<String, SchemaType>,
530 definitions: &HashMap<String, SchemaType>,
531) -> HashMap<String, SchemaType> {
532 let mut inferred = HashMap::new();
533 for form in forms {
534 let Form::List(items) = super::super::core::form_without_metadata(form) else {
535 continue;
536 };
537 if !matches!(items.first(), Some(Form::Symbol(operator)) if operator == "defn") {
538 continue;
539 }
540 let Some(name) = items.get(1).and_then(binding_name) else {
541 continue;
542 };
543 let qualified = format!("{namespace}/{name}");
544 let parameters_at = items.iter().enumerate().skip(2).find_map(|(index, value)| {
545 matches!(
546 super::super::core::form_without_metadata(value),
547 Form::Vector(_)
548 )
549 .then_some(index)
550 });
551 let declared = declarations
552 .get(&qualified)
553 .and_then(|schema| resolve_type(schema, definitions));
554 let mut arities = Vec::new();
555 if let Some(parameters_at) = parameters_at {
556 let Form::Vector(parameters) =
557 super::super::core::form_without_metadata(&items[parameters_at])
558 else {
559 continue;
560 };
561 arities.push(infer_function_arity(
562 parameters,
563 &items[parameters_at + 1..],
564 declared,
565 ));
566 } else {
567 for clause in items.iter().skip(2) {
568 let Form::List(clause) = super::super::core::form_without_metadata(clause) else {
569 continue;
570 };
571 let Some(Form::Vector(parameters)) = clause.first() else {
572 continue;
573 };
574 arities.push(infer_function_arity(parameters, &clause[1..], declared));
575 }
576 }
577 if !arities.is_empty() {
578 inferred.insert(qualified, SchemaType::Function(arities));
579 }
580 }
581 inferred
582}
583
584fn infer_function_arity(
585 parameters: &[Form],
586 body: &[Form],
587 declared: Option<&SchemaType>,
588) -> FunctionSchema {
589 let declared_arity = match declared {
590 Some(SchemaType::Function(arities)) => arities.iter().find(|arity| {
591 arity.fixed.len()
592 == parameters
593 .iter()
594 .take_while(|form| !matches!(form, Form::Symbol(marker) if marker == "&"))
595 .count()
596 && arity.rest.is_some()
597 == parameters
598 .iter()
599 .any(|form| matches!(form, Form::Symbol(marker) if marker == "&"))
600 }),
601 _ => None,
602 };
603 let mut environment = HashMap::new();
604 let mut fixed = Vec::new();
605 let mut rest = None;
606 let mut parameter_index = 0;
607 let mut variadic = false;
608 for parameter in parameters {
609 if matches!(parameter, Form::Symbol(marker) if marker == "&") {
610 variadic = true;
611 continue;
612 }
613 let Some(parameter_name) = binding_name(parameter) else {
614 continue;
615 };
616 let parameter_type = if variadic {
617 declared_arity
618 .and_then(|arity| arity.rest.as_deref())
619 .cloned()
620 .unwrap_or_else(unknown_type)
621 } else {
622 declared_arity
623 .and_then(|arity| arity.fixed.get(parameter_index))
624 .cloned()
625 .unwrap_or_else(unknown_type)
626 };
627 environment.insert(parameter_name.to_owned(), parameter_type.clone());
628 if variadic {
629 rest = Some(Box::new(parameter_type));
630 } else {
631 fixed.push(parameter_type);
632 parameter_index += 1;
633 }
634 }
635 let output = body
636 .iter()
637 .map(|body| infer_expression(body, &mut environment))
638 .last()
639 .unwrap_or_else(|| SchemaType::Primitive("nil".into()));
640 FunctionSchema {
641 fixed,
642 rest,
643 output: Box::new(output),
644 }
645}
646
647fn binding_name(form: &Form) -> Option<&str> {
648 match form {
649 Form::Symbol(name) => Some(name),
650 Form::Metadata(_, value) => binding_name(value),
651 _ => None,
652 }
653}
654
655fn resolve_type<'a>(
656 schema: &'a SchemaType,
657 definitions: &'a HashMap<String, SchemaType>,
658) -> Option<&'a SchemaType> {
659 let mut current = schema;
660 let mut visited = std::collections::HashSet::new();
661 loop {
662 match current {
663 SchemaType::WithProperties { schema, .. } => current = schema,
664 SchemaType::Reference(name) => {
665 if !visited.insert(name) {
666 return Some(current);
667 }
668 current = definitions.get(name)?;
669 }
670 _ => return Some(current),
671 }
672 }
673}
674
675fn unknown_type() -> SchemaType {
676 SchemaType::Unknown(Form::Symbol("?".into()))
677}
678
679fn infer_expression(form: &Form, environment: &mut HashMap<String, SchemaType>) -> SchemaType {
680 match super::super::core::form_without_metadata(form) {
681 Form::Nil => SchemaType::Primitive("nil".into()),
682 Form::Bool(_) => SchemaType::Primitive("bool".into()),
683 Form::Number(_) => SchemaType::Primitive("long".into()),
684 Form::Float(_) => SchemaType::Primitive("float".into()),
685 Form::BigInteger(_) => SchemaType::Primitive("bigint".into()),
686 Form::Character(_) => SchemaType::Primitive("char".into()),
687 Form::Regex(_) => SchemaType::Primitive("regex".into()),
688 Form::String(_) => SchemaType::Primitive("str".into()),
689 Form::Keyword(_) => SchemaType::Primitive("keyword".into()),
690 Form::Symbol(name) => environment
691 .get(name)
692 .map(inference_type)
693 .unwrap_or_else(unknown_type),
694 Form::Vector(values) => SchemaType::Vector(Box::new(join_types(
695 values
696 .iter()
697 .map(|value| infer_expression(value, environment)),
698 ))),
699 Form::Map(entries) => SchemaType::Map(
700 entries
701 .iter()
702 .map(|(name, value)| SchemaField {
703 name: name.clone(),
704 properties: None,
705 value_type: infer_expression(value, environment),
706 })
707 .collect(),
708 ),
709 Form::Set(values) => SchemaType::Set(Box::new(join_types(
710 values
711 .iter()
712 .map(|value| infer_expression(value, environment)),
713 ))),
714 Form::List(items) if items.is_empty() => SchemaType::Extension {
715 head: "list".into(),
716 arguments: Vec::new(),
717 },
718 Form::List(items) => infer_list(items, environment),
719 Form::Tagged(_, value) => infer_expression(value, environment),
720 Form::Metadata(_, value) => infer_expression(value, environment),
721 }
722}
723
724fn infer_list(items: &[Form], environment: &mut HashMap<String, SchemaType>) -> SchemaType {
725 let Some(Form::Symbol(operator)) = items.first() else {
726 return unknown_type();
727 };
728 match operator.as_str() {
729 "do" => items[1..]
730 .iter()
731 .map(|value| infer_expression(value, environment))
732 .last()
733 .unwrap_or_else(|| SchemaType::Primitive("nil".into())),
734 "if" => join_types(
735 items[2..]
736 .iter()
737 .map(|value| infer_expression(value, environment)),
738 ),
739 "let" if items.len() >= 3 => {
740 let mut nested = environment.clone();
741 if let Form::Vector(bindings) = super::super::core::form_without_metadata(&items[1]) {
742 for pair in bindings.chunks(2) {
743 if let [name, value] = pair {
744 if let Some(name) = binding_name(name) {
745 let value_type = infer_expression(value, &mut nested);
746 nested.insert(name.to_owned(), value_type);
747 }
748 }
749 }
750 }
751 items[2..]
752 .iter()
753 .map(|value| infer_expression(value, &mut nested))
754 .last()
755 .unwrap_or_else(|| SchemaType::Primitive("nil".into()))
756 }
757 "+" | "-" | "*" | "mod" => {
758 let operands = join_types(
759 items[1..]
760 .iter()
761 .map(|value| infer_expression(value, environment)),
762 );
763 match operands {
764 SchemaType::Primitive(name)
765 if matches!(name.as_str(), "int" | "long" | "bigint" | "float") =>
766 {
767 SchemaType::Primitive(name)
768 }
769 SchemaType::Union(members) if members.iter().all(is_long_alias) => {
770 SchemaType::Primitive("long".into())
771 }
772 _ => SchemaType::Primitive("number".into()),
773 }
774 }
775 "/" => SchemaType::Primitive("number".into()),
776 "=" | "<" | "<=" | ">" | ">=" | "instance?" => SchemaType::Primitive("bool".into()),
777 "count" => SchemaType::Primitive("long".into()),
778 "vector" => SchemaType::Vector(Box::new(join_types(
779 items[1..]
780 .iter()
781 .map(|value| infer_expression(value, environment)),
782 ))),
783 _ => unknown_type(),
784 }
785}
786
787fn join_types(types: impl IntoIterator<Item = SchemaType>) -> SchemaType {
788 let mut members = Vec::new();
789 for value in types {
790 match value {
791 SchemaType::Union(nested) => {
792 for member in nested {
793 push_unique(&mut members, member);
794 }
795 }
796 member => push_unique(&mut members, member),
797 }
798 }
799 match members.len() {
800 0 => unknown_type(),
801 1 => members.pop().unwrap(),
802 _ => SchemaType::Union(members),
803 }
804}
805
806fn inference_type(schema: &SchemaType) -> SchemaType {
807 match schema {
808 SchemaType::Primitive(name) if name == "int" => SchemaType::Primitive("long".into()),
809 _ => schema.clone(),
810 }
811}
812
813fn is_long_alias(schema: &SchemaType) -> bool {
814 matches!(schema, SchemaType::Primitive(name) if name == "int" || name == "long")
815}
816
817fn normalize_map_field(argument: &Form) -> Result<SchemaField, String> {
818 let Form::Vector(pair) = argument else {
819 return Err(":map schema fields must be [name type] or [name properties type]".into());
820 };
821 match pair.as_slice() {
822 [name, value_type] => Ok(SchemaField {
823 name: name.clone(),
824 properties: None,
825 value_type: normalize_schema(value_type)?,
826 }),
827 [name, Form::Map(properties), value_type] => Ok(SchemaField {
828 name: name.clone(),
829 properties: Some(Form::Map(properties.clone())),
830 value_type: normalize_schema(value_type)?,
831 }),
832 _ => Err(":map schema fields must be [name type] or [name properties type]".into()),
833 }
834}
835
836fn supports_properties(head: &str) -> bool {
837 matches!(
838 head,
839 "str"
840 | "string"
841 | "keyword"
842 | "symbol"
843 | "list"
844 | "bytes"
845 | "int"
846 | "long"
847 | "bigint"
848 | "integer"
849 | "num"
850 | "number"
851 | "any"
852 | "vector"
853 | "set"
854 | "map"
855 )
856}
857
858fn normalize_composite(items: &[Form]) -> Result<SchemaType, String> {
859 let Form::Keyword(head) = &items[0] else {
860 return Ok(SchemaType::Unknown(Form::Vector(items.to_vec())));
861 };
862 let raw_arguments = &items[1..];
863 let (properties, arguments) = if supports_properties(head) {
864 match raw_arguments.first() {
865 Some(Form::Map(values)) => (Some(Form::Map(values.clone())), &raw_arguments[1..]),
866 _ => (None, raw_arguments),
867 }
868 } else {
869 (None, raw_arguments)
870 };
871 let normalized = match head.as_str() {
872 "or" => normalize_union_forms(arguments),
873 "integer" if arguments.is_empty() => Ok(integer_schema()),
874 "maybe" => {
875 require_count(head, arguments, 1)?;
876 let mut members = Vec::new();
877 push_unique(&mut members, normalize_schema(&arguments[0])?);
878 push_unique(&mut members, SchemaType::Primitive("nil".into()));
879 Ok(SchemaType::Union(members))
880 }
881 "vector" => {
882 require_count(head, arguments, 1)?;
883 Ok(SchemaType::Vector(Box::new(normalize_schema(
884 &arguments[0],
885 )?)))
886 }
887 "set" => {
888 require_count(head, arguments, 1)?;
889 Ok(SchemaType::Set(Box::new(normalize_schema(&arguments[0])?)))
890 }
891 "tuple" => arguments
892 .iter()
893 .map(normalize_schema)
894 .collect::<Result<Vec<_>, _>>()
895 .map(SchemaType::Tuple),
896 "map" => arguments
897 .iter()
898 .map(normalize_map_field)
899 .collect::<Result<Vec<_>, _>>()
900 .map(SchemaType::Map),
901 "struct" => {
902 let (mutable, arguments) = struct_mutability(arguments)?;
903 normalize_struct_forms(arguments, mutable)
904 }
905 "fn" => normalize_function(items).map(|arity| SchemaType::Function(vec![arity])),
906 "function" => {
907 if arguments.is_empty() {
908 return Err(":function schema requires at least one :fn schema".into());
909 }
910 arguments
911 .iter()
912 .map(|argument| {
913 let Form::Vector(function) = argument else {
914 return Err(":function members must be :fn schemas".into());
915 };
916 normalize_function(function)
917 })
918 .collect::<Result<Vec<_>, _>>()
919 .map(SchemaType::Function)
920 }
921 "enum" => Ok(SchemaType::Enum(arguments.to_vec())),
922 _ if arguments.is_empty() => Ok(SchemaType::Primitive(head.clone())),
923 _ => Ok(SchemaType::Extension {
924 head: head.clone(),
925 arguments: arguments.to_vec(),
926 }),
927 }?;
928 Ok(match properties {
929 Some(properties) => SchemaType::WithProperties {
930 schema: Box::new(normalized),
931 properties,
932 },
933 None => normalized,
934 })
935}
936
937fn integer_schema() -> SchemaType {
938 SchemaType::Union(vec![
939 SchemaType::Primitive("long".into()),
940 SchemaType::Primitive("bigint".into()),
941 ])
942}
943
944fn normalize_function(items: &[Form]) -> Result<FunctionSchema, String> {
945 if !matches!(items.first(), Some(Form::Keyword(head)) if head == "fn") || items.len() != 3 {
946 return Err(":fn schema must be [:fn [inputs ...] output]".into());
947 }
948 let Form::Vector(inputs) = &items[1] else {
949 return Err(":fn schema inputs must be a vector".into());
950 };
951 let mut fixed = Vec::new();
952 let mut rest = None;
953 let mut index = 0;
954 while index < inputs.len() {
955 if matches!(&inputs[index], Form::Symbol(marker) if marker == "&") {
956 if rest.is_some() || index + 2 != inputs.len() {
957 return Err(":fn schema & must precede exactly one rest type".into());
958 }
959 rest = Some(Box::new(normalize_schema(&inputs[index + 1])?));
960 index += 2;
961 } else {
962 fixed.push(normalize_schema(&inputs[index])?);
963 index += 1;
964 }
965 }
966 Ok(FunctionSchema {
967 fixed,
968 rest,
969 output: Box::new(normalize_schema(&items[2])?),
970 })
971}
972
973fn require_count(head: &str, arguments: &[Form], expected: usize) -> Result<(), String> {
974 if arguments.len() == expected {
975 Ok(())
976 } else {
977 Err(format!(
978 ":{head} schema expects {expected} argument{}, got {}",
979 if expected == 1 { "" } else { "s" },
980 arguments.len()
981 ))
982 }
983}
984
985fn push_unique(output: &mut Vec<SchemaType>, value: SchemaType) {
986 if !output.contains(&value) {
987 output.push(value);
988 }
989}
990
991#[cfg(test)]
992mod tests {
993 use super::*;
994 use crate::kernel::parse;
995
996 #[test]
997 fn normalizes_nested_named_function_schemas() {
998 assert_eq!(
999 normalize_schema(&parse("[:fn [#'demo/Customer & :int] [:maybe :str]]").unwrap())
1000 .unwrap(),
1001 SchemaType::Function(vec![FunctionSchema {
1002 fixed: vec![SchemaType::Reference("demo/Customer".into())],
1003 rest: Some(Box::new(SchemaType::Primitive("int".into()))),
1004 output: Box::new(SchemaType::Union(vec![
1005 SchemaType::Primitive("str".into()),
1006 SchemaType::Primitive("nil".into()),
1007 ])),
1008 }])
1009 );
1010 }
1011
1012 #[test]
1013 fn rejects_malformed_known_schema_forms() {
1014 assert!(normalize_schema(&parse("[:map [:name]]").unwrap()).is_err());
1015 assert!(normalize_schema(&parse("[:fn [:str & :int :bool] :str]").unwrap()).is_err());
1016 assert!(normalize_schema(&parse("[:maybe]").unwrap()).is_err());
1017 }
1018
1019 #[test]
1020 fn integer_schema_is_the_long_or_big_integer_union() {
1021 let expected = SchemaType::Union(vec![
1022 SchemaType::Primitive("long".into()),
1023 SchemaType::Primitive("bigint".into()),
1024 ]);
1025 assert_eq!(normalize_schema(&parse(":integer").unwrap()).unwrap(), expected);
1026 assert_eq!(
1027 normalize_schema(&parse("[:integer]").unwrap()).unwrap(),
1028 expected
1029 );
1030 }
1031
1032 #[test]
1033 fn struct_schema_is_a_first_class_normal_form() {
1034 let schema = normalize_schema(
1035 &parse(
1036 "[:struct {:mutable? true} (var demo/Cursor) \
1037 [:position :int] [:limit {:optional true} [:maybe :int]]]",
1038 )
1039 .unwrap(),
1040 )
1041 .unwrap();
1042 assert_eq!(
1043 schema,
1044 SchemaType::Struct {
1045 name: "demo/Cursor".into(),
1046 mutable: true,
1047 fields: vec![
1048 SchemaField {
1049 name: Form::Symbol("position".into()),
1050 properties: None,
1051 value_type: SchemaType::Primitive("int".into()),
1052 },
1053 SchemaField {
1054 name: Form::Symbol("limit".into()),
1055 properties: Some(Form::Map(vec![(
1056 Form::Keyword("optional".into()),
1057 Form::Bool(true),
1058 )])),
1059 value_type: SchemaType::Union(vec![
1060 SchemaType::Primitive("int".into()),
1061 SchemaType::Primitive("nil".into()),
1062 ]),
1063 },
1064 ],
1065 }
1066 );
1067 assert_eq!(normalize_schema(&schema_shorthand(&schema)).unwrap(), schema);
1068 }
1069
1070 #[test]
1071 fn struct_schema_requires_a_qualified_type_name() {
1072 assert!(normalize_schema(&parse("[:struct (var Cursor) [:value :any]]").unwrap()).is_err());
1073 assert!(normalize_schema(&parse("[:struct (var demo/Cursor) [:value]]").unwrap()).is_err());
1074 }
1075
1076 #[test]
1077 fn infers_body_results_without_replacing_declared_contracts() {
1078 let forms = crate::kernel::parse_forms(
1079 "(ns demo)\n\
1080 (def Unary [:fn [:int] :number])\n\
1081 (defn ^{:schema #'Unary} choose [value]\n\
1082 (let [next (+ value 1)] (if true next 0)))\n\
1083 (defn labels [] {:name \"Ada\" :active true})\n\
1084 (defn select ([value] value) ([left right] right))",
1085 )
1086 .unwrap();
1087 let declarations = HashMap::from([(
1088 "demo/choose".into(),
1089 SchemaType::Reference("demo/Unary".into()),
1090 )]);
1091 let definitions = HashMap::from([(
1092 "demo/Unary".into(),
1093 normalize_schema(&parse("[:fn [:int] :number]").unwrap()).unwrap(),
1094 )]);
1095 let inferred = infer_function_types("demo", &forms, &declarations, &definitions);
1096
1097 assert!(matches!(
1098 inferred.get("demo/choose"),
1099 Some(SchemaType::Function(arities))
1100 if arities[0].fixed == vec![SchemaType::Primitive("int".into())]
1101 && *arities[0].output == SchemaType::Primitive("long".into())
1102 ));
1103 assert!(matches!(
1104 inferred.get("demo/labels"),
1105 Some(SchemaType::Function(arities))
1106 if matches!(arities[0].output.as_ref(), SchemaType::Map(fields) if fields.len() == 2)
1107 ));
1108 assert!(matches!(
1109 inferred.get("demo/select"),
1110 Some(SchemaType::Function(arities)) if arities.len() == 2
1111 ));
1112 }
1113}