⭐️ A friendly language for building type-safe, scalable systems!
1

Configure Feed

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

Support list prepending in constants

authored by

Gears and committed by
Louis Pilfold
(Mar 19, 2026, 12:09 PM UTC) b5ebd9dd 505b260c

+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 + }