aiken_lang/tipo/
fields.rs

1use super::error::{Error, UnknownLabels};
2use crate::ast::{CallArg, Span};
3use itertools::Itertools;
4use std::collections::{HashMap, HashSet};
5
6#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7pub struct FieldMap {
8    pub arity: usize,
9    pub fields: HashMap<String, (usize, Span)>,
10    pub is_function: bool,
11}
12
13impl FieldMap {
14    pub fn new(arity: usize, is_function: bool) -> Self {
15        Self {
16            arity,
17            fields: HashMap::new(),
18            is_function,
19        }
20    }
21
22    #[allow(clippy::result_large_err)]
23    pub fn insert(&mut self, label: String, index: usize, location: &Span) -> Result<(), Error> {
24        match self.fields.insert(label.clone(), (index, *location)) {
25            Some((_, location_other)) => {
26                if self.is_function {
27                    Err(Error::DuplicateArgument {
28                        label,
29                        location: *location,
30                        duplicate_location: location_other,
31                    })
32                } else {
33                    Err(Error::DuplicateField {
34                        label,
35                        location: *location,
36                        duplicate_location: location_other,
37                    })
38                }
39            }
40            None => Ok(()),
41        }
42    }
43
44    pub fn into_option(self) -> Option<Self> {
45        if self.fields.is_empty() {
46            None
47        } else {
48            Some(self)
49        }
50    }
51
52    /// Reorder an argument list so that labelled fields supplied out-of-order are
53    /// in the correct order.
54    #[allow(clippy::result_large_err)]
55    pub fn reorder<A>(&self, args: &mut [CallArg<A>], location: Span) -> Result<(), Error> {
56        let mut last_labeled_arguments_given: Option<&CallArg<A>> = None;
57        let mut seen_labels = std::collections::HashSet::new();
58        let mut unknown_labels = Vec::new();
59
60        if self.arity != args.len() {
61            return Err(Error::IncorrectFieldsArity {
62                labels: self.incorrect_arity_labels(args),
63                location,
64                expected: self.arity,
65                given: args.len(),
66            });
67        }
68
69        for arg in args.iter() {
70            match &arg.label {
71                Some(_) => {
72                    last_labeled_arguments_given = Some(arg);
73                }
74
75                None => {
76                    if let Some(label) = last_labeled_arguments_given {
77                        return Err(Error::PositionalArgumentAfterLabeled {
78                            location: arg.location,
79                            labeled_arg_location: label.location,
80                        });
81                    }
82                }
83            }
84        }
85
86        let mut i = 0;
87        while i < args.len() {
88            let label = &args.get(i).expect("Field indexing to get label").label;
89
90            let (label, &location) = match label {
91                // A labelled argument, we may need to reposition it in the array vector
92                Some(l) => (
93                    l,
94                    &args
95                        .get(i)
96                        .expect("Indexing in labelled field reordering")
97                        .location,
98                ),
99
100                // Not a labelled argument
101                None => {
102                    i += 1;
103                    continue;
104                }
105            };
106
107            let (position, duplicate_location) = match self.fields.get(label) {
108                None => {
109                    unknown_labels.push(location);
110                    i += 1;
111                    continue;
112                }
113                Some(&p) => p,
114            };
115
116            // If the argument is already in the right place
117            if position == i {
118                seen_labels.insert(label.clone());
119                i += 1;
120            } else {
121                if seen_labels.contains(label) {
122                    return Err(Error::DuplicateArgument {
123                        location,
124                        duplicate_location,
125                        label: label.to_string(),
126                    });
127                }
128
129                seen_labels.insert(label.clone());
130
131                args.swap(position, i);
132            }
133        }
134
135        if unknown_labels.is_empty() {
136            Ok(())
137        } else {
138            let valid = self.fields.keys().map(|t| t.to_string()).sorted().collect();
139
140            Err(Error::UnknownLabels(vec![UnknownLabels {
141                valid,
142                unknown: unknown_labels,
143                supplied: seen_labels.into_iter().collect(),
144            }]))
145        }
146    }
147
148    pub fn incorrect_arity_labels<A>(&self, args: &[CallArg<A>]) -> Vec<String> {
149        let given: HashSet<_> = args.iter().filter_map(|arg| arg.label.as_ref()).collect();
150
151        self.fields
152            .keys()
153            .filter(|f| !given.contains(f))
154            .sorted()
155            .cloned()
156            .collect()
157    }
158}