Skip to main content

maplibre_expr/
typ.rs

1//! The MapLibre expression type system: [`Type`], subtyping, and formatting.
2//!
3//! Used by the [`typecheck`](crate::typecheck) pass to infer each node's type
4//! and reject expressions the reference implementation rejects at compile time.
5
6use std::fmt;
7
8use crate::value::Value;
9
10/// A type in the MapLibre expression type lattice.
11#[derive(Debug, Clone, PartialEq)]
12pub enum Type {
13    Null,
14    Number,
15    String,
16    Boolean,
17    Color,
18    Object,
19    /// The top type — a supertype of every concrete type.
20    Value,
21    /// `array<itemType, N>`; `N` is `None` for unspecified length.
22    Array(Box<Type>, Option<usize>),
23    ProjectionDefinition,
24    Collator,
25    Formatted,
26    Padding,
27    NumberArray,
28    ColorArray,
29    ResolvedImage,
30    VariableAnchorOffsetCollection,
31}
32
33impl Type {
34    /// A fixed-length array type helper.
35    pub fn array(item: Type, n: Option<usize>) -> Type {
36        Type::Array(Box::new(item), n)
37    }
38
39    /// The bare kind name (`"number"`, `"array"`, ...).
40    pub fn kind(&self) -> &'static str {
41        match self {
42            Type::Null => "null",
43            Type::Number => "number",
44            Type::String => "string",
45            Type::Boolean => "boolean",
46            Type::Color => "color",
47            Type::Object => "object",
48            Type::Value => "value",
49            Type::Array(..) => "array",
50            Type::ProjectionDefinition => "projectionDefinition",
51            Type::Collator => "collator",
52            Type::Formatted => "formatted",
53            Type::Padding => "padding",
54            Type::NumberArray => "numberArray",
55            Type::ColorArray => "colorArray",
56            Type::ResolvedImage => "resolvedImage",
57            Type::VariableAnchorOffsetCollection => "variableAnchorOffsetCollection",
58        }
59    }
60
61    /// The type of a concrete runtime [`Value`] (`typeOf` in the spec).
62    pub fn of_value(v: &Value) -> Type {
63        match v {
64            Value::Null => Type::Null,
65            Value::Bool(_) => Type::Boolean,
66            Value::Number(_) => Type::Number,
67            Value::String(_) => Type::String,
68            Value::Color(_) => Type::Color,
69            Value::Object(_) => Type::Object,
70            Value::Image { .. } => Type::ResolvedImage,
71            Value::Formatted(_) => Type::Formatted,
72            Value::NumberArray(_) => Type::NumberArray,
73            Value::ColorArray(_) => Type::ColorArray,
74            Value::Padding(_) => Type::Padding,
75            Value::Projection(_) => Type::ProjectionDefinition,
76            Value::Collator { .. } => Type::Collator,
77            Value::Array(items) => {
78                let mut item_type: Option<Type> = None;
79                for it in items {
80                    let t = Type::of_value(it);
81                    match &item_type {
82                        None => item_type = Some(t),
83                        Some(existing) if *existing == t => {}
84                        Some(_) => {
85                            item_type = Some(Type::Value);
86                            break;
87                        }
88                    }
89                }
90                Type::array(item_type.unwrap_or(Type::Value), Some(items.len()))
91            }
92        }
93    }
94}
95
96impl fmt::Display for Type {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        match self {
99            Type::Array(item, Some(n)) => write!(f, "array<{item}, {n}>"),
100            Type::Array(item, None) => {
101                if matches!(**item, Type::Value) {
102                    write!(f, "array")
103                } else {
104                    write!(f, "array<{item}>")
105                }
106            }
107            other => write!(f, "{}", other.kind()),
108        }
109    }
110}
111
112/// Returns `true` if `t` is a subtype of `expected` (the spec's `checkSubtype`
113/// returning no error). `Value` is the top type; array subtyping compares item
114/// type and, when present, length.
115pub fn is_subtype(expected: &Type, t: &Type) -> bool {
116    if let Type::Array(exp_item, exp_n) = expected {
117        if let Type::Array(t_item, t_n) = t {
118            let item_ok = (*t_n == Some(0) && matches!(**t_item, Type::Value))
119                || is_subtype(exp_item, t_item);
120            let n_ok = exp_n.is_none() || exp_n == t_n;
121            return item_ok && n_ok;
122        }
123        return false;
124    }
125    if expected.kind() == t.kind() {
126        return true;
127    }
128    if matches!(expected, Type::Value) {
129        return is_value_member(t);
130    }
131    false
132}
133
134/// Whether `t` is a member of the `value` union (everything concrete except a
135/// bare collator).
136fn is_value_member(t: &Type) -> bool {
137    !matches!(t, Type::Collator)
138}