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