Skip to main content

microcad_lang/ty/
tuple_type.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Tuple type syntax element
5
6use crate::ty::*;
7
8use microcad_lang_base::Identifier;
9
10/// (Partially named) tuple (e.g. `(n: Scalar, m: String, Integer)`)
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct TupleType {
13    /// Named fields
14    pub named: microcad_core::hash::HashMap<Identifier, Type>,
15    /// Unnamed fields
16    pub unnamed: microcad_core::hash::HashSet<Type>,
17}
18
19impl TupleType {
20    /// Create new Vec2 type.
21    pub fn new_vec2() -> Self {
22        [("x", Type::scalar()), ("y", Type::scalar())]
23            .into_iter()
24            .collect()
25    }
26
27    /// Create new Vec3 type.
28    pub fn new_vec3() -> Self {
29        [
30            ("x", Type::scalar()),
31            ("y", Type::scalar()),
32            ("z", Type::scalar()),
33        ]
34        .into_iter()
35        .collect()
36    }
37
38    /// Create new Color type.
39    pub fn new_color() -> Self {
40        [
41            ("r", Type::scalar()),
42            ("g", Type::scalar()),
43            ("b", Type::scalar()),
44            ("a", Type::scalar()),
45        ]
46        .into_iter()
47        .collect()
48    }
49
50    /// Create new Size2 type.
51    pub fn new_size2() -> Self {
52        [("width", Type::length()), ("height", Type::length())]
53            .into_iter()
54            .collect()
55    }
56
57    /// Match tuples by id.
58    pub(crate) fn is_matching(&self, params: &TupleType) -> bool {
59        if self == params {
60            true
61        } else if self.unnamed.is_empty()
62            && params.unnamed.is_empty()
63            && self.named.len() == params.named.len()
64        {
65            self.named.iter().all(|arg| {
66                if let Some(ty) = params.named.get(arg.0) {
67                    arg.1 == ty || arg.1.is_array_of(ty)
68                } else {
69                    false
70                }
71            })
72        } else {
73            false
74        }
75    }
76
77    /// Test if the named tuple has exactly all the given keys
78    fn matches_keys(&self, keys: &[&str]) -> bool {
79        if !self.unnamed.is_empty() || self.named.len() != keys.len() {
80            return false;
81        }
82        keys.iter()
83            .all(|k| self.named.contains_key(&Identifier::no_ref(k)))
84    }
85
86    /// Checks if the named tuple type only holds scalar values.
87    fn is_scalar_only(&self) -> bool {
88        self.common_type().is_some_and(|ty| *ty == Type::scalar())
89    }
90
91    /// Checks if the named tuple type only holds length values.
92    fn is_length_only(&self) -> bool {
93        self.common_type().is_some_and(|ty| *ty == Type::length())
94    }
95
96    /// Test if all fields have a common type.
97    pub(crate) fn common_type(&self) -> Option<&Type> {
98        let mut iter = self.unnamed.iter().chain(self.named.values());
99        if let Some(first) = iter.next() {
100            if iter.all(|x| x == first) {
101                return Some(first);
102            }
103        }
104        None
105    }
106
107    /// Check if the named tuple is a [`Color`].
108    pub(crate) fn is_color(&self) -> bool {
109        self.is_scalar_only() && self.matches_keys(&["r", "g", "b", "a"])
110    }
111
112    /// Check if the named tuple is a [`Vec2`].
113    pub(crate) fn is_vec2(&self) -> bool {
114        self.is_scalar_only() && self.matches_keys(&["x", "y"])
115    }
116
117    /// Check if the named tuple is a [`Vec3`].
118    pub(crate) fn is_vec3(&self) -> bool {
119        self.is_scalar_only() && self.matches_keys(&["x", "y", "z"])
120    }
121
122    /// Check if the named tuple is a [`Size2`]
123    pub(crate) fn is_size2(&self) -> bool {
124        self.is_length_only() && self.matches_keys(&["width", "height"])
125    }
126}
127
128impl std::hash::Hash for TupleType {
129    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
130        self.named.iter().for_each(|(id, ty)| {
131            id.hash(state);
132            ty.hash(state)
133        });
134        self.unnamed.iter().for_each(|ty| ty.hash(state));
135    }
136}
137
138impl FromIterator<(Identifier, Type)> for TupleType {
139    fn from_iter<T: IntoIterator<Item = (Identifier, Type)>>(iter: T) -> Self {
140        let (unnamed, named) = iter.into_iter().partition(|(id, _)| id.is_empty());
141        Self {
142            named,
143            unnamed: unnamed.into_values().collect(),
144        }
145    }
146}
147
148impl<'a> FromIterator<(&'a str, Type)> for TupleType {
149    fn from_iter<T: IntoIterator<Item = (&'a str, Type)>>(iter: T) -> Self {
150        let (unnamed, named) = iter
151            .into_iter()
152            .map(|(id, ty)| (Identifier::no_ref(id), ty))
153            .partition(|(id, _)| id.is_empty());
154        Self {
155            named,
156            unnamed: unnamed.into_values().collect(),
157        }
158    }
159}
160
161impl std::fmt::Display for TupleType {
162    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
163        if self.is_color() {
164            return write!(f, "Color");
165        }
166        if self.is_vec2() {
167            return write!(f, "Vec2");
168        }
169        if self.is_vec3() {
170            return write!(f, "Vec3");
171        }
172        if self.is_size2() {
173            return write!(f, "Size2");
174        }
175
176        write!(f, "({})", {
177            let mut types = self
178                .named
179                .iter()
180                .map(|(id, ty)| format!("{id}: {ty}"))
181                .chain(self.unnamed.iter().map(|ty| ty.to_string()))
182                .collect::<Vec<_>>();
183
184            types.sort();
185            types.join(", ")
186        })
187    }
188}
189
190#[test]
191fn test_tuple_type_eq() {
192    assert_eq!(TupleType::new_color(), TupleType::new_color());
193}
194
195#[test]
196fn test_tuple_type_match() {
197    let args = TupleType {
198        named: [
199            (Identifier::no_ref("x"), Type::Integer),
200            (
201                Identifier::no_ref("y"),
202                Type::Array(Box::new(Type::Integer)),
203            ),
204        ]
205        .into_iter()
206        .collect(),
207        unnamed: Default::default(),
208    };
209    let params = TupleType {
210        named: [
211            (Identifier::no_ref("x"), Type::Integer),
212            (Identifier::no_ref("y"), Type::Integer),
213        ]
214        .into_iter()
215        .collect(),
216        unnamed: Default::default(),
217    };
218    assert!(args.is_matching(&params));
219}