Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

Support list prepending in constants

+735 -26
+10
CHANGELOG.md
··· 4 4 5 5 ### Compiler 6 6 7 + - The compiler now supports list prepending in constants. For example: 8 + 9 + ```gleam 10 + pub const viviparous_mammals = ["dog", "cat", "human"] 11 + 12 + pub const all_mammals = ["platypus", "echidna", ..viviparous_mammals] 13 + ``` 14 + 15 + ([Surya Rose](https://github.com/GearsDatapacks)) 16 + 7 17 ### Build tool 8 18 9 19 - When publishing, the package manager now uses the full term instead of the
+43 -1
compiler-core/src/ast/constant.rs
··· 35 35 location: SrcSpan, 36 36 elements: Vec<Self>, 37 37 type_: T, 38 + tail: Option<Box<Self>>, 38 39 }, 39 40 40 41 Record { ··· 138 139 ValueConstructorVariant::LocalVariable { .. } => Located::Constant(self), 139 140 }, 140 141 Constant::Var { .. } => Located::Constant(self), 141 - Constant::Tuple { elements, .. } | Constant::List { elements, .. } => elements 142 + Constant::Tuple { elements, .. } => elements 143 + .iter() 144 + .find_map(|element| element.find_node(byte_index)) 145 + .unwrap_or(Located::Constant(self)), 146 + Constant::List { elements, tail, .. } => elements 142 147 .iter() 143 148 .find_map(|element| element.find_node(byte_index)) 149 + .or_else(|| tail.as_deref().and_then(|tail| tail.find_node(byte_index))) 144 150 .unwrap_or(Located::Constant(self)), 145 151 Constant::Record { arguments, .. } => arguments 146 152 .iter() ··· 376 382 (Constant::StringConcatenation { .. }, _) => false, 377 383 378 384 (Constant::Invalid { .. }, _) => false, 385 + } 386 + } 387 + 388 + pub fn list_elements(&self) -> Option<Vec<&Self>> { 389 + match self { 390 + Constant::List { elements, tail, .. } => Some( 391 + elements 392 + .iter() 393 + .chain( 394 + tail.as_deref() 395 + .and_then(|tail| tail.list_elements()) 396 + .unwrap_or_default() 397 + .into_iter(), 398 + ) 399 + .collect(), 400 + ), 401 + Constant::Var { 402 + constructor: Some(constructor), 403 + .. 404 + } => match &constructor.variant { 405 + ValueConstructorVariant::ModuleConstant { literal, .. } => literal.list_elements(), 406 + ValueConstructorVariant::LocalVariable { .. } 407 + | ValueConstructorVariant::ModuleFn { .. } 408 + | ValueConstructorVariant::Record { .. } => None, 409 + }, 410 + 411 + Constant::Int { .. } 412 + | Constant::Float { .. } 413 + | Constant::String { .. } 414 + | Constant::Tuple { .. } 415 + | Constant::Record { .. } 416 + | Constant::RecordUpdate { .. } 417 + | Constant::BitArray { .. } 418 + | Constant::StringConcatenation { .. } 419 + | Constant::Var { .. } 420 + | Constant::Invalid { .. } => None, 379 421 } 380 422 } 381 423 }
+8 -2
compiler-core/src/ast/visit.rs
··· 705 705 location: &'ast SrcSpan, 706 706 elements: &'ast Vec<TypedConstant>, 707 707 type_: &'ast Arc<Type>, 708 + tail: &'ast Option<Box<TypedConstant>>, 708 709 ) { 709 - visit_typed_constant_list(self, location, elements, type_); 710 + visit_typed_constant_list(self, location, elements, type_, tail); 710 711 } 711 712 712 713 #[allow(clippy::too_many_arguments)] ··· 878 879 _location: &'a SrcSpan, 879 880 elements: &'a Vec<TypedConstant>, 880 881 _type_: &'a Arc<Type>, 882 + tail: &'a Option<Box<TypedConstant>>, 881 883 ) { 882 884 for element in elements { 883 885 v.visit_typed_constant(element) 886 + } 887 + if let Some(tail) = tail { 888 + v.visit_typed_constant(tail); 884 889 } 885 890 } 886 891 ··· 1133 1138 location, 1134 1139 elements, 1135 1140 type_, 1136 - } => v.visit_typed_constant_list(location, elements, type_), 1141 + tail, 1142 + } => v.visit_typed_constant_list(location, elements, type_, tail), 1137 1143 super::Constant::Record { 1138 1144 location, 1139 1145 module,
+7 -1
compiler-core/src/ast_folder.rs
··· 969 969 location, 970 970 elements, 971 971 type_: (), 972 - } => self.fold_constant_list(location, elements), 972 + tail, 973 + } => self.fold_constant_list(location, elements, tail), 973 974 974 975 Constant::Record { 975 976 location, ··· 1073 1074 &mut self, 1074 1075 location: SrcSpan, 1075 1076 elements: Vec<UntypedConstant>, 1077 + tail: Option<Box<UntypedConstant>>, 1076 1078 ) -> UntypedConstant { 1077 1079 Constant::List { 1078 1080 location, 1079 1081 elements, 1080 1082 type_: (), 1083 + tail, 1081 1084 } 1082 1085 } 1083 1086 ··· 1184 1187 location, 1185 1188 elements, 1186 1189 type_, 1190 + tail, 1187 1191 } => { 1188 1192 let elements = elements 1189 1193 .into_iter() 1190 1194 .map(|element| self.fold_constant(element)) 1191 1195 .collect(); 1196 + let tail = tail.map(|tail| Box::new(self.fold_constant(*tail))); 1192 1197 Constant::List { 1193 1198 location, 1194 1199 elements, 1195 1200 type_, 1201 + tail, 1196 1202 } 1197 1203 } 1198 1204
+17 -7
compiler-core/src/erlang.rs
··· 1544 1544 tuple(elements.iter().map(|element| const_inline(element, env))) 1545 1545 } 1546 1546 1547 - Constant::List { elements, .. } => join( 1548 - elements.iter().map(|element| const_inline(element, env)), 1549 - break_(",", ", "), 1550 - ) 1551 - .nest(INDENT) 1552 - .surround("[", "]") 1553 - .group(), 1547 + Constant::List { elements, tail, .. } => { 1548 + let tail_elements = tail 1549 + .as_deref() 1550 + .and_then(|tail| tail.list_elements()) 1551 + .unwrap_or_default(); 1552 + 1553 + join( 1554 + elements 1555 + .iter() 1556 + .chain(tail_elements.into_iter()) 1557 + .map(|element| const_inline(element, env)), 1558 + break_(",", ", "), 1559 + ) 1560 + .nest(INDENT) 1561 + .surround("[", "]") 1562 + .group() 1563 + } 1554 1564 1555 1565 Constant::BitArray { segments, .. } => bit_array( 1556 1566 segments
+43
compiler-core/src/erlang/tests/consts.rs
··· 206 206 "# 207 207 ) 208 208 } 209 + 210 + #[test] 211 + fn list_prepend() { 212 + assert_erl!( 213 + " 214 + const wibble = [2, 3, 4] 215 + pub const wobble = [0, 1, ..wibble] 216 + 217 + pub fn main() { 218 + wobble 219 + } 220 + " 221 + ); 222 + } 223 + 224 + #[test] 225 + fn list_prepend_from_other_module() { 226 + assert_erl!( 227 + ("thepackage", "mod", "pub const wibble = [2, 3, 4]"), 228 + " 229 + import mod 230 + 231 + pub const wobble = [0, 1, ..mod.wibble] 232 + 233 + pub fn main() { 234 + wobble 235 + } 236 + " 237 + ); 238 + } 239 + 240 + #[test] 241 + fn list_prepend_literal() { 242 + assert_erl!( 243 + " 244 + pub const wibble = [0, 1, ..[2, 3, 4]] 245 + 246 + pub fn main() { 247 + wibble 248 + } 249 + " 250 + ); 251 + }
+24
compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__list_prepend.snap
··· 1 + --- 2 + source: compiler-core/src/erlang/tests/consts.rs 3 + expression: "\nconst wibble = [2, 3, 4]\npub const wobble = [0, 1, ..wibble]\n\npub fn main() {\n wobble\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const wibble = [2, 3, 4] 8 + pub const wobble = [0, 1, ..wibble] 9 + 10 + pub fn main() { 11 + wobble 12 + } 13 + 14 + 15 + ----- COMPILED ERLANG 16 + -module(my@mod). 17 + -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). 18 + -define(FILEPATH, "project/test/my/mod.gleam"). 19 + -export([main/0]). 20 + 21 + -file("project/test/my/mod.gleam", 5). 22 + -spec main() -> list(integer()). 23 + main() -> 24 + [0, 1, 2, 3, 4].
+25
compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__list_prepend_from_other_module.snap
··· 1 + --- 2 + source: compiler-core/src/erlang/tests/consts.rs 3 + expression: "\nimport mod\n\npub const wobble = [0, 1, ..mod.wibble]\n\npub fn main() {\n wobble\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + import mod 8 + 9 + pub const wobble = [0, 1, ..mod.wibble] 10 + 11 + pub fn main() { 12 + wobble 13 + } 14 + 15 + 16 + ----- COMPILED ERLANG 17 + -module(my@mod). 18 + -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). 19 + -define(FILEPATH, "project/test/my/mod.gleam"). 20 + -export([main/0]). 21 + 22 + -file("project/test/my/mod.gleam", 6). 23 + -spec main() -> list(integer()). 24 + main() -> 25 + [0, 1, 2, 3, 4].
+23
compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__list_prepend_literal.snap
··· 1 + --- 2 + source: compiler-core/src/erlang/tests/consts.rs 3 + expression: "\npub const wibble = [0, 1, ..[2, 3, 4]]\n\npub fn main() {\n wibble\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub const wibble = [0, 1, ..[2, 3, 4]] 8 + 9 + pub fn main() { 10 + wibble 11 + } 12 + 13 + 14 + ----- COMPILED ERLANG 15 + -module(my@mod). 16 + -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). 17 + -define(FILEPATH, "project/test/my/mod.gleam"). 18 + -export([main/0]). 19 + 20 + -file("project/test/my/mod.gleam", 4). 21 + -spec main() -> list(integer()). 22 + main() -> 23 + [0, 1, 2, 3, 4].
+22 -6
compiler-core/src/format.rs
··· 479 479 Constant::String { value, .. } => self.string(value), 480 480 481 481 Constant::List { 482 - elements, location, .. 483 - } => self.const_list(elements, location), 482 + elements, 483 + location, 484 + tail, 485 + .. 486 + } => self.const_list(elements, location, tail), 484 487 485 488 Constant::Tuple { 486 489 elements, location, .. ··· 587 590 &mut self, 588 591 elements: &'a [Constant<A, B>], 589 592 location: &SrcSpan, 593 + tail: &'a Option<Box<Constant<A, B>>>, 590 594 ) -> Document<'a> { 591 595 if elements.is_empty() { 592 596 // We take all comments that come _before_ the end of the list, ··· 609 613 610 614 let list_packing = self.items_sequence_packing( 611 615 elements, 612 - None, 616 + tail.as_deref(), 613 617 |element| element.can_have_multiple_per_line(), 614 618 *location, 615 619 ); ··· 642 646 } 643 647 elements_doc = elements_doc.next_break_fits(NextBreakFitsMode::Disabled); 644 648 645 - let doc = break_("[", "[").append(elements_doc).nest(INDENT); 649 + let doc = break_("[", "[").append(elements_doc); 650 + let (doc, final_break) = match tail { 651 + None => (doc.nest(INDENT), break_(",", "")), 652 + Some(tail) => { 653 + let comments = self.pop_comments(tail.location().start); 654 + 655 + let tail = commented(docvec!["..", self.const_expr(tail)], comments); 656 + ( 657 + doc.append(break_(",", ", ")).append(tail).nest(INDENT), 658 + break_("", ""), 659 + ) 660 + } 661 + }; 646 662 647 663 // We get all remaining comments that come before the list's closing 648 664 // square bracket. ··· 651 667 // Otherwise those would be moved out of the list. 652 668 let comments = self.pop_comments(location.end); 653 669 let doc = match printed_comments(comments, false) { 654 - None => doc.append(break_(",", "")).append("]"), 670 + None => doc.append(final_break).append("]"), 655 671 Some(comment) => doc 656 - .append(break_(",", "").nest(INDENT)) 672 + .append(final_break.nest(INDENT)) 657 673 // ^ See how here we're adding the missing indentation to the 658 674 // final break so that the final comment is as indented as the 659 675 // list's items.
+41
compiler-core/src/format/tests/constant.rs
··· 54 54 "# 55 55 ); 56 56 } 57 + 58 + #[test] 59 + fn const_list_prepend() { 60 + assert_format!( 61 + "pub const wibble = [2, 3, 4] 62 + 63 + pub const wobble = [0, 1, ..wibble] 64 + " 65 + ); 66 + } 67 + 68 + #[test] 69 + fn const_list_prepend_multiline() { 70 + assert_format!( 71 + r#"pub const wibble = ["Hello", "world"] 72 + 73 + pub const wobble = [ 74 + "Some really long message that causes the line to wrap", 75 + "Something else", 76 + ..wibble 77 + ] 78 + "# 79 + ); 80 + } 81 + 82 + #[test] 83 + fn const_list_prepend_multiline_with_comments() { 84 + assert_format!( 85 + r#"pub const wibble = ["Hello", "world"] 86 + 87 + pub const wobble = [ 88 + "Some really long message that causes the line to wrap", 89 + // Some comment 90 + "Something else", 91 + // Prepend the values to `wibble` 92 + ..wibble 93 + // End of list 94 + ] 95 + "# 96 + ); 97 + }
+9 -2
compiler-core/src/javascript/expression.rs
··· 1969 1969 .map(|element| self.constant_expression(context, element)), 1970 1970 ), 1971 1971 1972 - Constant::List { elements, .. } => { 1972 + Constant::List { elements, tail, .. } => { 1973 1973 self.tracker.list_used = true; 1974 + 1975 + let tail_elements = tail 1976 + .as_deref() 1977 + .and_then(|tail| tail.list_elements()) 1978 + .unwrap_or_default(); 1979 + 1974 1980 let list = list( 1975 1981 elements 1976 1982 .iter() 1983 + .chain(tail_elements.into_iter()) 1977 1984 .map(|element| self.constant_expression(context, element)), 1978 1985 ); 1979 1986 ··· 2605 2612 2606 2613 pub(crate) fn list<'a, I: IntoIterator<Item = Document<'a>>>(elements: I) -> Document<'a> 2607 2614 where 2608 - I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 2615 + I::IntoIter: DoubleEndedIterator, 2609 2616 { 2610 2617 let array = array(elements); 2611 2618 docvec!["toList(", array, ")"]
+31
compiler-core/src/javascript/tests/consts.rs
··· 136 136 " 137 137 ); 138 138 } 139 + 140 + #[test] 141 + fn list_prepend() { 142 + assert_js!( 143 + " 144 + const wibble = [2, 3, 4] 145 + pub const wobble = [0, 1, ..wibble] 146 + " 147 + ); 148 + } 149 + 150 + #[test] 151 + fn list_prepend_from_other_module() { 152 + assert_js!( 153 + ("mod", "pub const wibble = [2, 3, 4]"), 154 + " 155 + import mod 156 + 157 + pub const wobble = [0, 1, ..mod.wibble] 158 + " 159 + ); 160 + } 161 + 162 + #[test] 163 + fn list_prepend_literal() { 164 + assert_js!( 165 + " 166 + pub const wibble = [0, 1, ..[2, 3, 4]] 167 + " 168 + ); 169 + }
+16
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__list_prepend.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/consts.rs 3 + expression: "\nconst wibble = [2, 3, 4]\npub const wobble = [0, 1, ..wibble]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const wibble = [2, 3, 4] 8 + pub const wobble = [0, 1, ..wibble] 9 + 10 + 11 + ----- COMPILED JAVASCRIPT 12 + import { toList } from "../gleam.mjs"; 13 + 14 + const wibble = /* @__PURE__ */ toList([2, 3, 4]); 15 + 16 + export const wobble = /* @__PURE__ */ toList([0, 1, 2, 3, 4]);
+20
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__list_prepend_from_other_module.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/consts.rs 3 + expression: "\nimport mod\n\npub const wobble = [0, 1, ..mod.wibble]\n" 4 + --- 5 + ----- SOURCE CODE 6 + -- mod.gleam 7 + pub const wibble = [2, 3, 4] 8 + 9 + -- main.gleam 10 + 11 + import mod 12 + 13 + pub const wobble = [0, 1, ..mod.wibble] 14 + 15 + 16 + ----- COMPILED JAVASCRIPT 17 + import { toList } from "../gleam.mjs"; 18 + import * as $mod from "../mod.mjs"; 19 + 20 + export const wobble = /* @__PURE__ */ toList([0, 1, 2, 3, 4]);
+13
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__list_prepend_literal.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/consts.rs 3 + expression: "\npub const wibble = [0, 1, ..[2, 3, 4]]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub const wibble = [0, 1, ..[2, 3, 4]] 8 + 9 + 10 + ----- COMPILED JAVASCRIPT 11 + import { toList } from "../gleam.mjs"; 12 + 13 + export const wibble = /* @__PURE__ */ toList([0, 1, 2, 3, 4]);
+2
compiler-core/src/metadata.rs
··· 320 320 location, 321 321 elements, 322 322 type_, 323 + tail, 323 324 } => Constant::List { 324 325 location, 325 326 elements: elements ··· 327 328 .map(|element| self.constant(element)) 328 329 .collect(), 329 330 type_: self.type_(type_), 331 + tail: tail.map(|tail| Box::new(self.constant(*tail))), 330 332 }, 331 333 Constant::Record { 332 334 location,
+17
compiler-core/src/metadata/tests.rs
··· 1141 1141 int_value: 3.into(), 1142 1142 }, 1143 1143 ], 1144 + tail: Some(Box::new(Constant::List { 1145 + location: Default::default(), 1146 + type_: type_::list(type_::int()), 1147 + elements: vec![ 1148 + Constant::Int { 1149 + location: Default::default(), 1150 + value: "4".into(), 1151 + int_value: 4.into(), 1152 + }, 1153 + Constant::Int { 1154 + location: Default::default(), 1155 + value: "5".into(), 1156 + int_value: 5.into(), 1157 + }, 1158 + ], 1159 + tail: None, 1160 + })), 1144 1161 }); 1145 1162 1146 1163 assert_eq!(roundtrip(&module), module);
+93 -2
compiler-core/src/parse.rs
··· 3195 3195 3196 3196 Some((start, Token::LeftSquare, _)) => { 3197 3197 self.advance(); 3198 - let elements = 3199 - Parser::series_of(self, &Parser::parse_const_value, Some(&Token::Comma))?; 3198 + 3199 + let (elements, elements_end_with_comma) = self.series_of_has_trailing_separator( 3200 + &Parser::parse_const_value, 3201 + Some(&Token::Comma), 3202 + )?; 3203 + 3204 + // Parse an optional tail 3205 + let mut tail = None; 3206 + let mut elements_after_tail = None; 3207 + let mut dot_dot_location = None; 3208 + 3209 + // If there are no elements, we still want to parse a tail so 3210 + // that we can report a better error message. 3211 + if (elements_end_with_comma || elements.is_empty()) 3212 + && let Some((start, end)) = self.maybe_one(&Token::DotDot) 3213 + { 3214 + dot_dot_location = Some((start, end)); 3215 + tail = self.parse_const_value()?.map(Box::new); 3216 + if self.maybe_one(&Token::Comma).is_some() { 3217 + // See if there's a list of items after the tail, 3218 + // like `[..wibble, wobble, wabble]` 3219 + let elements = 3220 + self.series_of(&Parser::parse_const_value, Some(&Token::Comma)); 3221 + match elements { 3222 + Err(_) => {} 3223 + Ok(elements) => { 3224 + elements_after_tail = Some(elements); 3225 + } 3226 + }; 3227 + }; 3228 + 3229 + if tail.is_some() { 3230 + // Give a better error when there are two lists being 3231 + // concatenated like `[..wibble, ..wabble, woo]`, or if 3232 + // there are elements after the tail. 3233 + if let Some((second_start, second_end)) = self.maybe_one(&Token::DotDot) { 3234 + let _second_tail = self.parse_const_value(); 3235 + 3236 + if elements_after_tail.is_none() 3237 + || elements_after_tail 3238 + .as_ref() 3239 + .is_some_and(|vec| vec.is_empty()) 3240 + { 3241 + return parse_error( 3242 + ParseErrorType::ListSpreadWithAnotherSpread { 3243 + first_spread_location: SrcSpan { start, end }, 3244 + }, 3245 + SrcSpan { 3246 + start: second_start, 3247 + end: second_end, 3248 + }, 3249 + ); 3250 + } 3251 + } 3252 + } 3253 + } 3254 + 3200 3255 let (_, end) = 3201 3256 self.expect_one_following_series(&Token::RightSquare, "a constant value")?; 3257 + 3258 + // Return errors for malformed lists 3259 + match dot_dot_location { 3260 + Some((start, end)) if tail.is_none() => { 3261 + return parse_error( 3262 + ParseErrorType::ListSpreadWithoutTail, 3263 + SrcSpan { start, end }, 3264 + ); 3265 + } 3266 + _ => {} 3267 + } 3268 + if tail.is_some() 3269 + && elements.is_empty() 3270 + && elements_after_tail.as_ref().is_none_or(|e| e.is_empty()) 3271 + { 3272 + return parse_error( 3273 + ParseErrorType::ListSpreadWithoutElements, 3274 + SrcSpan { start, end }, 3275 + ); 3276 + } 3277 + 3278 + match elements_after_tail { 3279 + Some(elements) if !elements.is_empty() => { 3280 + let (start, end) = match (dot_dot_location, tail) { 3281 + (Some((start, _)), Some(tail)) => (start, tail.location().end), 3282 + (_, _) => (start, end), 3283 + }; 3284 + return parse_error( 3285 + ParseErrorType::ListSpreadFollowedByElements, 3286 + SrcSpan { start, end }, 3287 + ); 3288 + } 3289 + _ => {} 3290 + } 3291 + 3202 3292 Ok(Some(Constant::List { 3203 3293 elements, 3204 3294 location: SrcSpan { start, end }, 3205 3295 type_: (), 3296 + tail, 3206 3297 })) 3207 3298 } 3208 3299 // BitArray
+22
compiler-core/src/parse/snapshots/gleam_core__parse__tests__append_to_const_list.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "\nconst wibble = [2, 3]\nconst wobble = [..wibble, 4, 5]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const wibble = [2, 3] 8 + const wobble = [..wibble, 4, 5] 9 + 10 + 11 + ----- ERROR 12 + error: Syntax error 13 + ┌─ /src/parse/error.gleam:3:17 14 + 15 + 3 │ const wobble = [..wibble, 4, 5] 16 + │ ^^^^^^^^ I wasn't expecting elements after this 17 + 18 + Lists are immutable and singly-linked, so to append items to them 19 + all the elements of a list would need to be copied into a new list. 20 + This would be slow, so there is no built-in syntax for it. 21 + 22 + Hint: Prepend items to the list and then reverse it once you are done.
+19
compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_no_elements_to_const_list.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "\nconst wibble = [2, 3]\nconst wobble = [..wibble]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const wibble = [2, 3] 8 + const wobble = [..wibble] 9 + 10 + 11 + ----- ERROR 12 + error: Syntax error 13 + ┌─ /src/parse/error.gleam:3:16 14 + 15 + 3 │ const wobble = [..wibble] 16 + │ ^^^^^^^^^^ This spread does nothing 17 + 18 + See: https://tour.gleam.run/basics/lists/ 19 + Hint: Try prepending some elements [1, 2, ..list].
+23
compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_to_const_list_with_multiple_spreads.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "\nconst wibble = [2, 3]\nconst wobble = [0, 1]\nconst wubble = [..wobble, ..wibble]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const wibble = [2, 3] 8 + const wobble = [0, 1] 9 + const wubble = [..wobble, ..wibble] 10 + 11 + 12 + ----- ERROR 13 + error: Syntax error 14 + ┌─ /src/parse/error.gleam:4:27 15 + 16 + 4 │ const wubble = [..wobble, ..wibble] 17 + │ -- ^^ I wasn't expecting a second list here 18 + │ │ 19 + │ You're using a list here 20 + 21 + Lists are immutable and singly-linked, so to join two or more lists 22 + all the elements of the lists would need to be copied into a new list. 23 + This would be slow, so there is no built-in syntax for it.
+17
compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_to_const_list_with_no_tail.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "\nconst wibble = [1, 2, ..]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const wibble = [1, 2, ..] 8 + 9 + 10 + ----- ERROR 11 + error: Syntax error 12 + ┌─ /src/parse/error.gleam:2:23 13 + 14 + 2 │ const wibble = [1, 2, ..] 15 + │ ^^ I was expecting a value after this spread 16 + 17 + If a list expression has a spread then a tail must also be given.
+20
compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_to_const_list_without_comma.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "\nconst wibble = [2, 3]\nconst wobble = [1 ..wibble]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const wibble = [2, 3] 8 + const wobble = [1 ..wibble] 9 + 10 + 11 + ----- ERROR 12 + error: Syntax error 13 + ┌─ /src/parse/error.gleam:3:19 14 + 15 + 3 │ const wobble = [1 ..wibble] 16 + │ ^^ I was not expecting this 17 + 18 + Found `..`, expected one of: 19 + - `]` 20 + - a constant value
+53
compiler-core/src/parse/tests.rs
··· 2106 2106 " 2107 2107 ); 2108 2108 } 2109 + 2110 + #[test] 2111 + fn prepend_to_const_list_without_comma() { 2112 + // While this is valid (but deprecated) syntax for expressions, prepending to 2113 + // constant lists wasn't added until after the deprecation, so it was never 2114 + // valid in the first place. 2115 + assert_module_error!( 2116 + " 2117 + const wibble = [2, 3] 2118 + const wobble = [1 ..wibble] 2119 + " 2120 + ); 2121 + } 2122 + 2123 + #[test] 2124 + fn prepend_to_const_list_with_multiple_spreads() { 2125 + assert_module_error!( 2126 + " 2127 + const wibble = [2, 3] 2128 + const wobble = [0, 1] 2129 + const wubble = [..wobble, ..wibble] 2130 + " 2131 + ); 2132 + } 2133 + 2134 + #[test] 2135 + fn prepend_to_const_list_with_no_tail() { 2136 + assert_module_error!( 2137 + " 2138 + const wibble = [1, 2, ..] 2139 + " 2140 + ); 2141 + } 2142 + 2143 + #[test] 2144 + fn prepend_no_elements_to_const_list() { 2145 + assert_module_error!( 2146 + " 2147 + const wibble = [2, 3] 2148 + const wobble = [..wibble] 2149 + " 2150 + ); 2151 + } 2152 + 2153 + #[test] 2154 + fn append_to_const_list() { 2155 + assert_module_error!( 2156 + " 2157 + const wibble = [2, 3] 2158 + const wobble = [..wibble, 4, 5] 2159 + " 2160 + ); 2161 + }
+25 -5
compiler-core/src/type_/expression.rs
··· 3747 3747 } => self.infer_const_tuple(elements, location), 3748 3748 3749 3749 Constant::List { 3750 - elements, location, .. 3751 - } => self.infer_const_list(elements, location), 3750 + elements, 3751 + location, 3752 + tail, 3753 + .. 3754 + } => self.infer_const_list(elements, location, tail), 3752 3755 3753 3756 Constant::BitArray { location, segments } => { 3754 3757 match self.infer_constant_bit_array(segments, location) { ··· 4384 4387 &mut self, 4385 4388 untyped_elements: Vec<UntypedConstant>, 4386 4389 location: SrcSpan, 4390 + tail: Option<Box<UntypedConstant>>, 4387 4391 ) -> TypedConstant { 4388 - let type_ = self.new_unbound_var(); 4392 + let element_type = self.new_unbound_var(); 4389 4393 let mut elements = Vec::with_capacity(untyped_elements.len()); 4390 4394 4391 4395 for element in untyped_elements { 4392 4396 let element = self.infer_const(&None, element); 4393 - if let Err(error) = unify(type_.clone(), element.type_()) { 4397 + if let Err(error) = unify(element_type.clone(), element.type_()) { 4394 4398 self.problems 4395 4399 .error(convert_unify_error(error, element.location())); 4396 4400 } ··· 4398 4402 elements.push(element); 4399 4403 } 4400 4404 4405 + let type_ = list(element_type); 4406 + 4407 + let tail = if let Some(tail) = tail { 4408 + let tail = self.infer_const(&None, *tail); 4409 + if let Err(error) = unify(type_.clone(), tail.type_()) { 4410 + self.problems 4411 + .error(convert_unify_error(error, tail.location())) 4412 + } 4413 + Some(Box::new(tail)) 4414 + } else { 4415 + None 4416 + }; 4417 + 4401 4418 Constant::List { 4402 4419 elements, 4403 4420 location, 4404 - type_: list(type_), 4421 + type_, 4422 + tail, 4405 4423 } 4406 4424 } 4407 4425 ··· 5292 5310 location, 5293 5311 elements, 5294 5312 type_: _, 5313 + tail, 5295 5314 } => Constant::List { 5296 5315 location, 5297 5316 elements, 5298 5317 type_: new_type, 5318 + tail, 5299 5319 }, 5300 5320 5301 5321 Constant::Record {
+24
compiler-core/src/type_/snapshots/gleam_core__type___tests__prepend_constant_list_wrong_element_type.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests.rs 3 + expression: "\nconst list = [3, 4, 5]\npub const full_list = [1.0, 2.0, ..list]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const list = [3, 4, 5] 8 + pub const full_list = [1.0, 2.0, ..list] 9 + 10 + 11 + ----- ERROR 12 + error: Type mismatch 13 + ┌─ /src/one/two.gleam:3:36 14 + 15 + 3 │ pub const full_list = [1.0, 2.0, ..list] 16 + │ ^^^^ 17 + 18 + Expected type: 19 + 20 + List(Float) 21 + 22 + Found type: 23 + 24 + List(Int)
+24
compiler-core/src/type_/snapshots/gleam_core__type___tests__prepend_constant_list_wrong_type.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests.rs 3 + expression: "\nconst pi = 3.14\npub const full_list = [1.0, 2.0, ..pi]\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + const pi = 3.14 8 + pub const full_list = [1.0, 2.0, ..pi] 9 + 10 + 11 + ----- ERROR 12 + error: Type mismatch 13 + ┌─ /src/one/two.gleam:3:36 14 + 15 + 3 │ pub const full_list = [1.0, 2.0, ..pi] 16 + │ ^^ 17 + 18 + Expected type: 19 + 20 + List(Float) 21 + 22 + Found type: 23 + 24 + Float
+44
compiler-core/src/type_/tests.rs
··· 3503 3503 "# 3504 3504 ); 3505 3505 } 3506 + 3507 + #[test] 3508 + fn prepend_constant_list() { 3509 + assert_module_infer!( 3510 + " 3511 + const list = [3, 4, 5] 3512 + pub const full_list = [1, 2, ..list] 3513 + ", 3514 + vec![("full_list", "List(Int)")], 3515 + ); 3516 + } 3517 + 3518 + #[test] 3519 + fn prepend_constant_list_from_other_module() { 3520 + assert_module_infer!( 3521 + ("mod", "pub const list = [3, 4, 5]"), 3522 + " 3523 + import mod 3524 + 3525 + pub const full_list = [1, 2, ..mod.list] 3526 + ", 3527 + vec![("full_list", "List(Int)")], 3528 + ); 3529 + } 3530 + 3531 + #[test] 3532 + fn prepend_constant_list_wrong_type() { 3533 + assert_module_error!( 3534 + " 3535 + const pi = 3.14 3536 + pub const full_list = [1.0, 2.0, ..pi] 3537 + " 3538 + ); 3539 + } 3540 + 3541 + #[test] 3542 + fn prepend_constant_list_wrong_element_type() { 3543 + assert_module_error!( 3544 + " 3545 + const list = [3, 4, 5] 3546 + pub const full_list = [1.0, 2.0, ..list] 3547 + " 3548 + ); 3549 + }