aiken_lang/tipo/
fields.rs1use 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 #[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 Some(l) => (
93 l,
94 &args
95 .get(i)
96 .expect("Indexing in labelled field reordering")
97 .location,
98 ),
99
100 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 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}