Skip to content

Commit d2cfd24

Browse files
sobolevnlpil
authored andcommitted
fix(typing): do not report futher errors, after contructor arity is reported
1 parent cd3e808 commit d2cfd24

File tree

3 files changed

+139
-75
lines changed

3 files changed

+139
-75
lines changed

compiler-core/src/type_/pattern.rs

Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use super::*;
1111
use crate::{
1212
analyse::{self, Inferred, name::check_name_case},
1313
ast::{
14-
AssignName, BitArrayOption, BitArraySize, ImplicitCallArgOrigin, Layer, TypedBitArraySize,
15-
UntypedPatternBitArraySegment,
14+
AssignName, BitArrayOption, BitArraySize, ImplicitCallArgOrigin, Layer, Pattern,
15+
TypedBitArraySize, UntypedPatternBitArraySegment,
1616
},
1717
parse::PatternPosition,
1818
reference::ReferenceKind,
@@ -911,78 +911,11 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
911911
self.error_encountered = true;
912912
};
913913
}
914-
915-
// Insert discard variables to match the unspecified fields
916-
// In order to support both positional and labelled arguments we have to insert
917-
// them after all positional variables and before the labelled ones. This means
918-
// we have calculate that index and then insert() the discards. It would be faster
919-
// if we could put the discards anywhere which would let us use push().
920-
// Potential future optimisation.
921-
let index_of_first_labelled_arg = pattern_arguments
922-
.iter()
923-
.position(|argument| argument.label.is_some())
924-
.unwrap_or(pattern_arguments.len());
925-
926-
// In Gleam we can pass in positional unlabelled args to a constructor
927-
// even if the field was defined as labelled
928-
//
929-
// pub type Wibble {
930-
// Wibble(Int, two: Int, three: Int, four: Int)
931-
// }
932-
// Wibble(1, 2, 3, 4)
933-
//
934-
// When using `..` to ignore some fields the compiler needs to add a
935-
// placeholder implicit discard pattern for each one of the ignored
936-
// arguments. To give those discards the proper missing label we need to
937-
// know how many of the labelled fields were provided as unlabelled.
938-
//
939-
// That's why we want to keep track of the number of unlabelled argument
940-
// that have been supplied to the pattern and all the labels that have
941-
// been explicitly supplied.
942-
//
943-
// Wibble(a, b, four: c, ..)
944-
// ┬─── ┬──────
945-
// │ ╰ We supplied 1 labelled arg
946-
// ╰ We supplied 2 unlabelled args
947-
//
948-
let supplied_unlabelled_arguments = index_of_first_labelled_arg;
949-
let supplied_labelled_arguments = pattern_arguments
950-
.iter()
951-
.filter_map(|argument| argument.label.clone())
952-
.collect::<HashSet<_>>();
953-
let constructor_unlabelled_arguments =
954-
field_map.arity - field_map.fields.len() as u32;
955-
let labelled_arguments_supplied_as_unlabelled =
956-
supplied_unlabelled_arguments
957-
.saturating_sub(constructor_unlabelled_arguments as usize);
958-
959-
let mut missing_labels = field_map
960-
.fields
961-
.iter()
962-
// We take the labels in order of definition in the constructor...
963-
.sorted_by_key(|(_, position)| *position)
964-
.map(|(label, _)| label.clone())
965-
// ...and then remove the ones that were supplied as unlabelled
966-
// positional arguments...
967-
.skip(labelled_arguments_supplied_as_unlabelled)
968-
// ... lastly we still need to remove all those labels that
969-
// were explicitly supplied in the pattern.
970-
.filter(|label| !supplied_labelled_arguments.contains(label));
971-
972-
while pattern_arguments.len() < field_map.arity as usize {
973-
let new_call_arg = CallArg {
974-
value: Pattern::Discard {
975-
name: "_".into(),
976-
location: spread_location,
977-
type_: (),
978-
},
979-
location: spread_location,
980-
label: missing_labels.next(),
981-
implicit: Some(ImplicitCallArgOrigin::PatternFieldSpread),
982-
};
983-
984-
pattern_arguments.insert(index_of_first_labelled_arg, new_call_arg);
985-
}
914+
insert_spread_call_arguments(
915+
&mut pattern_arguments,
916+
field_map,
917+
spread_location,
918+
);
986919
}
987920

988921
if let Err(error) = field_map.reorder(
@@ -993,6 +926,13 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
993926
incorrect_arity_error = true;
994927
self.problems.error(error);
995928
self.error_encountered = true;
929+
// Since we reported the arity error now, we can pretend
930+
// that `..` is now added, so other errors will not be affected.
931+
insert_spread_call_arguments(
932+
&mut pattern_arguments,
933+
field_map,
934+
name_location,
935+
);
996936
}
997937
}
998938

@@ -1115,7 +1055,6 @@ impl<'a, 'b> PatternTyper<'a, 'b> {
11151055

11161056
let pattern_arguments =
11171057
self.infer_pattern_call_arguments(pattern_arguments, arguments);
1118-
11191058
Pattern::Constructor {
11201059
location,
11211060
name_location,
@@ -1381,3 +1320,80 @@ fn unify_constructor_variants(into: &mut Type, from: &Type) {
13811320
_ => {}
13821321
}
13831322
}
1323+
1324+
/// Inserts needed discard variables to Constructor::Pattern, when `spread` is used.
1325+
fn insert_spread_call_arguments(
1326+
pattern_arguments: &mut Vec<CallArg<Pattern<()>>>,
1327+
field_map: &FieldMap,
1328+
location: SrcSpan,
1329+
) {
1330+
// Insert discard variables to match the unspecified fields
1331+
// In order to support both positional and labelled arguments we have to insert
1332+
// them after all positional variables and before the labelled ones. This means
1333+
// we have calculate that index and then insert() the discards. It would be faster
1334+
// if we could put the discards anywhere which would let us use push().
1335+
// Potential future optimisation.
1336+
let index_of_first_labelled_arg = pattern_arguments
1337+
.iter()
1338+
.position(|argument| argument.label.is_some())
1339+
.unwrap_or(pattern_arguments.len());
1340+
1341+
// In Gleam we can pass in positional unlabelled args to a constructor
1342+
// even if the field was defined as labelled
1343+
//
1344+
// pub type Wibble {
1345+
// Wibble(Int, two: Int, three: Int, four: Int)
1346+
// }
1347+
// Wibble(1, 2, 3, 4)
1348+
//
1349+
// When using `..` to ignore some fields the compiler needs to add a
1350+
// placeholder implicit discard pattern for each one of the ignored
1351+
// arguments. To give those discards the proper missing label we need to
1352+
// know how many of the labelled fields were provided as unlabelled.
1353+
//
1354+
// That's why we want to keep track of the number of unlabelled argument
1355+
// that have been supplied to the pattern and all the labels that have
1356+
// been explicitly supplied.
1357+
//
1358+
// Wibble(a, b, four: c, ..)
1359+
// ┬─── ┬──────
1360+
// │ ╰ We supplied 1 labelled arg
1361+
// ╰ We supplied 2 unlabelled args
1362+
//
1363+
let supplied_unlabelled_arguments = index_of_first_labelled_arg;
1364+
let supplied_labelled_arguments = pattern_arguments
1365+
.iter()
1366+
.filter_map(|argument| argument.label.clone())
1367+
.collect::<HashSet<_>>();
1368+
let constructor_unlabelled_arguments = field_map.arity - field_map.fields.len() as u32;
1369+
let labelled_arguments_supplied_as_unlabelled =
1370+
supplied_unlabelled_arguments.saturating_sub(constructor_unlabelled_arguments as usize);
1371+
1372+
let mut missing_labels = field_map
1373+
.fields
1374+
.iter()
1375+
// We take the labels in order of definition in the constructor...
1376+
.sorted_by_key(|(_, position)| *position)
1377+
.map(|(label, _)| label.clone())
1378+
// ...and then remove the ones that were supplied as unlabelled
1379+
// positional arguments...
1380+
.skip(labelled_arguments_supplied_as_unlabelled)
1381+
// ... lastly we still need to remove all those labels that
1382+
// were explicitly supplied in the pattern.
1383+
.filter(|label| !supplied_labelled_arguments.contains(label));
1384+
1385+
while pattern_arguments.len() < field_map.arity as usize {
1386+
let new_call_arg = CallArg {
1387+
value: Pattern::Discard {
1388+
name: "_".into(),
1389+
location,
1390+
type_: (),
1391+
},
1392+
location,
1393+
label: missing_labels.next(),
1394+
implicit: Some(ImplicitCallArgOrigin::PatternFieldSpread),
1395+
};
1396+
1397+
pattern_arguments.insert(index_of_first_labelled_arg, new_call_arg);
1398+
}
1399+
}

compiler-core/src/type_/tests/custom_types.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,22 @@ pub fn main() {
182182
]
183183
);
184184
}
185+
186+
#[test]
187+
fn pattern_match_correct_labeled_field() {
188+
assert_module_error!(
189+
r#"
190+
type Fish {
191+
Starfish()
192+
Jellyfish(name: String, jiggly: Bool)
193+
}
194+
195+
fn handle_fish(fish: Fish) {
196+
case fish {
197+
Starfish() -> False
198+
Jellyfish(jiggly:) -> jiggly // <- error is here
199+
}
200+
}
201+
"#
202+
);
203+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
source: compiler-core/src/type_/tests/custom_types.rs
3+
expression: "\ntype Fish {\n Starfish()\n Jellyfish(name: String, jiggly: Bool)\n}\n\nfn handle_fish(fish: Fish) {\n case fish {\n Starfish() -> False\n Jellyfish(jiggly:) -> jiggly // <- error is here\n }\n}\n"
4+
---
5+
----- SOURCE CODE
6+
7+
type Fish {
8+
Starfish()
9+
Jellyfish(name: String, jiggly: Bool)
10+
}
11+
12+
fn handle_fish(fish: Fish) {
13+
case fish {
14+
Starfish() -> False
15+
Jellyfish(jiggly:) -> jiggly // <- error is here
16+
}
17+
}
18+
19+
20+
----- ERROR
21+
error: Incorrect arity
22+
┌─ /src/one/two.gleam:10:5
23+
24+
10Jellyfish(jiggly:) -> jiggly // <- error is here
25+
^^^^^^^^^^^^^^^^^^ Expected 2 arguments, got 1
26+
27+
This pattern accepts these additional labelled arguments:
28+
29+
- name

0 commit comments

Comments
 (0)