Skip to main content

hara_native/kernel/
schema.rs

1//! Canonical semantic representation of the portable HAL schema grammar.
2//!
3//! HALC keeps surface schemas as ordinary forms on the wire. This module is
4//! the first compiler-facing lowering step: it turns those forms into a typed
5//! graph without evaluating schema Vars or copying nested definitions.
6
7use 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
523/// Infers conservative function signatures from executable module forms.
524/// Declared schemas seed parameter types, but inferred results remain a
525/// separate table: annotations are contracts, while these are optimizer facts.
526pub 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}