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

Configure Feed

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

improve error message for invalid names

author
Giacomo Cavalieri
committer
Louis Pilfold
date (Jun 4, 2026, 1:49 PM +0100) commit 7de91758 parent 874b0bb6 change-id uppmsnvo
+207 -57
+21 -14
CHANGELOG.md
··· 4 4 5 5 ### Compiler 6 6 7 - - The compiler now issues a friendlier error when attempting to pattern match 8 - on both the prefix and suffix of a string: 7 + - The compiler now issues a friendlier error when attempting to pattern match 8 + on both the prefix and suffix of a string: 9 9 10 - ``` 11 - error: Syntax error 12 - ┌─ /src/parse/error.gleam:2:23 13 - 14 - 2 │ "prefix" <> infix <> "suffix" -> infix 15 - │ ^^^^^^^^^^^ This pattern is not allowed 10 + ``` 11 + error: Syntax error 12 + ┌─ /src/parse/error.gleam:2:23 13 + 14 + 2 │ "prefix" <> infix <> "suffix" -> infix 15 + │ ^^^^^^^^^^^ This pattern is not allowed 16 + 17 + A string pattern can only match on a literal string prefix. 16 18 17 - A string pattern can only match on a literal string prefix. 19 + Matching on a literal suffix is not possible, because `infix` would have an 20 + unknown size. 21 + ``` 18 22 19 - Matching on a literal suffix is not possible, because `infix` would have an 20 - unknown size. 21 - ``` 23 + ([Gavin Morrow](https://github.com/gavinmorrow)) 22 24 23 - ([Gavin Morrow](https://github.com/gavinmorrow)) 25 + - Improved the error message shown when using an invalid discard name for 26 + functions, constants, module names, and `as` patterns. 27 + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 24 28 25 29 ### Build tool 26 30 ··· 38 42 function, the return value of the extracted function is respected. 39 43 40 44 For example, 45 + 41 46 ```gleam 42 47 fn wibble() { 43 48 let wobble = fn() { ··· 46 51 } 47 52 } 48 53 ``` 54 + 49 55 is turned into 56 + 50 57 ```gleam 51 58 fn wibble() { 52 59 let wobble = fn() { ··· 60 67 } 61 68 ``` 62 69 63 - [Gavin Morrow](https://github.com/gavinmorrow) 70 + ([Gavin Morrow](https://github.com/gavinmorrow))
+39 -22
compiler-core/src/parse.rs
··· 71 71 use crate::build::Target; 72 72 use crate::error::wrap; 73 73 use crate::exhaustiveness::CompiledCase; 74 + use crate::parse::error::IncorrectNamePosition; 74 75 use crate::parse::extra::ModuleExtra; 75 76 use crate::type_::Deprecation; 76 77 use crate::type_::error::{VariableDeclaration, VariableOrigin, VariableSyntax}; ··· 1135 1136 })?; 1136 1137 let value = self.parse_expression_inner(true)?.ok_or(match self.tok0 { 1137 1138 Some((start, Token::DiscardName { .. }, end)) => ParseError { 1138 - error: ParseErrorType::IncorrectName, 1139 + error: ParseErrorType::IncorrectName { 1140 + kind: IncorrectNamePosition::Variable, 1141 + }, 1139 1142 location: SrcSpan { start, end }, 1140 1143 }, 1141 1144 ··· 1353 1356 // or a full string matching: "Hello, World!" as greeting -> ... 1354 1357 Some((_, Token::As, _)) => { 1355 1358 self.advance(); 1356 - let (name_start, name, name_end) = self.expect_name()?; 1359 + let (name_start, name, name_end) = 1360 + self.expect_name(IncorrectNamePosition::AsPattern)?; 1357 1361 let name_span = SrcSpan { 1358 1362 start: name_start, 1359 1363 end: name_end, ··· 1639 1643 match self.tok0 { 1640 1644 Some((_, Token::As, _)) => { 1641 1645 self.advance(); 1642 - let (start, name, end) = self.expect_name()?; 1646 + let (start, name, end) = self.expect_name(IncorrectNamePosition::AsPattern)?; 1643 1647 Ok(Some(Pattern::Assign { 1644 1648 name, 1645 1649 location: SrcSpan { start, end }, ··· 1695 1699 guard, 1696 1700 then, 1697 1701 })), 1698 - _ => match self.tok0 { 1699 - Some((start, Token::DiscardName { .. }, end)) => { 1700 - parse_error(ParseErrorType::IncorrectName, SrcSpan { start, end }) 1701 - } 1702 + None => match self.tok0 { 1703 + Some((start, Token::DiscardName { .. }, end)) => parse_error( 1704 + ParseErrorType::IncorrectName { 1705 + kind: IncorrectNamePosition::Variable, 1706 + }, 1707 + SrcSpan { start, end }, 1708 + ), 1702 1709 _ => parse_error( 1703 1710 ParseErrorType::ExpectedExpr, 1704 1711 SrcSpan { ··· 1925 1932 1926 1933 Some((start, _, end)) => { 1927 1934 return parse_error( 1928 - ParseErrorType::IncorrectName, 1935 + ParseErrorType::IncorrectName { 1936 + kind: IncorrectNamePosition::Variable, 1937 + }, 1929 1938 SrcSpan { start, end }, 1930 1939 ); 1931 1940 } ··· 2184 2193 }; 2185 2194 let mut name = None; 2186 2195 if !is_anon { 2187 - let (name_start, n, name_end) = self.expect_name()?; 2196 + let (name_start, n, name_end) = self.expect_name(IncorrectNamePosition::Function)?; 2188 2197 name = Some(( 2189 2198 SrcSpan { 2190 2199 start: name_start, ··· 2999 3008 3000 3009 // Gather module names 3001 3010 loop { 3002 - let (s, name, e) = self.expect_name()?; 3011 + let (s, name, e) = self.expect_name(IncorrectNamePosition::Module)?; 3003 3012 if module.is_empty() { 3004 3013 start = s; 3005 3014 } else { ··· 3115 3124 as_name: None, 3116 3125 }; 3117 3126 if self.maybe_one(&Token::As).is_some() { 3118 - let (_, as_name, end) = self.expect_name()?; 3127 + let (_, as_name, end) = 3128 + self.expect_name(IncorrectNamePosition::AsPattern)?; 3119 3129 import.as_name = Some(as_name); 3120 3130 import.location.end = end; 3121 3131 } ··· 3187 3197 public: bool, 3188 3198 attributes: &Attributes, 3189 3199 ) -> Result<Option<UntypedDefinition>, ParseError> { 3190 - let (name_start, name, name_end) = self.expect_name()?; 3200 + let (name_start, name, name_end) = self.expect_name(IncorrectNamePosition::Constant)?; 3191 3201 let documentation = self.take_documentation(name_start); 3192 3202 3193 3203 let annotation = self.parse_type_annotation(&Token::Colon)?; ··· 4094 4104 } 4095 4105 4096 4106 // Expect a Name else a token dependent helpful error 4097 - fn expect_name(&mut self) -> Result<(u32, EcoString, u32), ParseError> { 4107 + fn expect_name( 4108 + &mut self, 4109 + kind: IncorrectNamePosition, 4110 + ) -> Result<(u32, EcoString, u32), ParseError> { 4098 4111 let (start, token, end) = self.expect_assign_name()?; 4099 4112 match token { 4100 4113 AssignName::Variable(name) => Ok((start, name, end)), 4101 - AssignName::Discard(_) => { 4102 - parse_error(ParseErrorType::IncorrectName, SrcSpan { start, end }) 4103 - } 4114 + AssignName::Discard(_) => parse_error( 4115 + ParseErrorType::IncorrectName { kind }, 4116 + SrcSpan { start, end }, 4117 + ), 4104 4118 } 4105 4119 } 4106 4120 ··· 4110 4124 Some((start, tok, end)) => match tok { 4111 4125 Token::Name { name } => Ok((start, AssignName::Variable(name), end)), 4112 4126 Token::DiscardName { name, .. } => Ok((start, AssignName::Discard(name), end)), 4113 - Token::UpName { .. } => { 4114 - parse_error(ParseErrorType::IncorrectName, SrcSpan { start, end }) 4115 - } 4127 + Token::UpName { .. } => parse_error( 4128 + ParseErrorType::IncorrectName { 4129 + kind: IncorrectNamePosition::Variable, 4130 + }, 4131 + SrcSpan { start, end }, 4132 + ), 4116 4133 _ if tok.is_reserved_word() => parse_error( 4117 4134 ParseErrorType::UnexpectedReservedWord, 4118 4135 SrcSpan { start, end }, ··· 4571 4588 ) -> Result<u32, ParseError> { 4572 4589 // Parse the name of the attribute. 4573 4590 4574 - let (_, name, end) = self.expect_name()?; 4591 + let (_, name, end) = self.expect_name(IncorrectNamePosition::Attribute)?; 4575 4592 4576 4593 let end = match name.as_str() { 4577 4594 "external" => { ··· 4612 4629 end: u32, 4613 4630 attributes: &mut Attributes, 4614 4631 ) -> Result<u32, ParseError> { 4615 - let (_, name, _) = self.expect_name()?; 4632 + let (_, target, _) = self.expect_name(IncorrectNamePosition::Target)?; 4616 4633 4617 - let target = match name.as_str() { 4634 + let target = match target.as_str() { 4618 4635 "erlang" => Target::Erlang, 4619 4636 "javascript" => Target::JavaScript, 4620 4637 _ => return parse_error(ParseErrorType::UnknownTarget, SrcSpan::new(start, end)),
+49 -15
compiler-core/src/parse/error.rs
··· 58 58 pub location: SrcSpan, 59 59 } 60 60 61 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 62 + /// Where we found an incorrect name. 63 + pub enum IncorrectNamePosition { 64 + /// After `as`. For example: `as _wibble`. 65 + AsPattern, 66 + /// As a module in an import: `import _wibble` 67 + Module, 68 + /// As a function name: `fn _wibble()` 69 + Function, 70 + /// As an attribute name: `@_wibble` 71 + Attribute, 72 + /// As a constant name: `const _wibble = ...` 73 + Constant, 74 + /// As a target name: `@external(_wibble, ...)` 75 + Target, 76 + /// Used as a variable expression: `let a = _wibble`, `let a = _wibble(10)` 77 + Variable, 78 + } 79 + 61 80 #[derive(Debug, Clone, PartialEq, Eq)] 62 81 pub enum ParseErrorType { 63 82 ExpectedEqual, // expect "=" ··· 73 92 ExpectedTargetName, // after "@target(" 74 93 ExprLparStart, // it seems "(" was used to start an expression 75 94 ExtraSeparator, // #(1,,) <- the 2nd comma is an extra separator 76 - IncorrectName, // UpName or DiscardName used when Name was expected 77 - IncorrectUpName, // Name or DiscardName used when UpName was expected 78 - InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment 79 - InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256 80 - InvalidTailPattern, // only name and _name are allowed after ".." in list pattern 81 - InvalidTupleAccess, // only positive int literals for tuple access 95 + // UpName or DiscardName used when Name was expected 96 + IncorrectName { 97 + // Where we found the incorrect name. 98 + kind: IncorrectNamePosition, 99 + }, 100 + IncorrectUpName, // Name or DiscardName used when UpName was expected 101 + InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment 102 + InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256 103 + InvalidTailPattern, // only name and _name are allowed after ".." in list pattern 104 + InvalidTupleAccess, // only positive int literals for tuple access 82 105 LexError { 83 106 error: LexicalError, 84 107 }, ··· 260 283 extra_labels: vec![], 261 284 }, 262 285 263 - ParseErrorType::IncorrectName => ParseErrorDetails { 264 - text: "".into(), 265 - hint: Some(wrap( 266 - "Variable and module names start with a lowercase letter, \ 267 - and can contain a-z, 0-9, or _.", 268 - )), 269 - label_text: "I'm expecting a lowercase name here".into(), 270 - extra_labels: vec![], 271 - }, 286 + ParseErrorType::IncorrectName { kind } => { 287 + let subject = match kind { 288 + IncorrectNamePosition::AsPattern => "A name after `as`", 289 + IncorrectNamePosition::Module => "A module name", 290 + IncorrectNamePosition::Function => "A function name", 291 + IncorrectNamePosition::Attribute => "An attribute name", 292 + IncorrectNamePosition::Constant => "A constant name", 293 + IncorrectNamePosition::Target => "A target name", 294 + IncorrectNamePosition::Variable => "A variable name", 295 + }; 296 + ParseErrorDetails { 297 + text: wrap_format!( 298 + "{subject} must start with a lowercase letter, and can \ 299 + contain a-z, 0-9, or _.", 300 + ), 301 + hint: None, 302 + label_text: "I'm expecting a lowercase name here".into(), 303 + extra_labels: vec![], 304 + } 305 + } 272 306 273 307 ParseErrorType::IncorrectUpName => ParseErrorDetails { 274 308 text: "".into(),
+2 -3
compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore.snap
··· 15 15 3 │ let val = _func_starting_with_underscore(1) 16 16 │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ I'm expecting a lowercase name here 17 17 18 - Hint: Variable and module names start with a lowercase letter, and can 19 - contain 20 - a-z, 0-9, or _. 18 + A variable name must start with a lowercase letter, and can contain a-z, 19 + 0-9, or _.
+2 -3
compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore2.snap
··· 17 17 4 │ 1 -> _with_underscore(1) 18 18 │ ^^^^^^^^^^^^^^^^ I'm expecting a lowercase name here 19 19 20 - Hint: Variable and module names start with a lowercase letter, and can 21 - contain 22 - a-z, 0-9, or _. 20 + A variable name must start with a lowercase letter, and can contain a-z, 21 + 0-9, or _.
+20
compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_when_using_discard_pattern_for_as_pattern.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "pub fn main() {\n case todo {\n wibble as _wobble -> todo\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + pub fn main() { 7 + case todo { 8 + wibble as _wobble -> todo 9 + } 10 + } 11 + 12 + ----- ERROR 13 + error: Syntax error 14 + ┌─ /src/parse/error.gleam:3:15 15 + 16 + 3 │ wibble as _wobble -> todo 17 + │ ^^^^^^^ I'm expecting a lowercase name here 18 + 19 + A name after `as` must start with a lowercase letter, and can contain a-z, 20 + 0-9, or _.
+16
compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_when_using_discard_pattern_for_constant_name.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: const _wibble = 1 4 + --- 5 + ----- SOURCE CODE 6 + const _wibble = 1 7 + 8 + ----- ERROR 9 + error: Syntax error 10 + ┌─ /src/parse/error.gleam:1:7 11 + 12 + 1 │ const _wibble = 1 13 + │ ^^^^^^^ I'm expecting a lowercase name here 14 + 15 + A constant name must start with a lowercase letter, and can contain a-z, 16 + 0-9, or _.
+16
compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_when_using_discard_pattern_for_function_name.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: fn _wibble() -> Nil 4 + --- 5 + ----- SOURCE CODE 6 + fn _wibble() -> Nil 7 + 8 + ----- ERROR 9 + error: Syntax error 10 + ┌─ /src/parse/error.gleam:1:4 11 + 12 + 1 │ fn _wibble() -> Nil 13 + │ ^^^^^^^ I'm expecting a lowercase name here 14 + 15 + A function name must start with a lowercase letter, and can contain a-z, 16 + 0-9, or _.
+16
compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_when_using_discard_pattern_for_module_name.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: import _wibble 4 + --- 5 + ----- SOURCE CODE 6 + import _wibble 7 + 8 + ----- ERROR 9 + error: Syntax error 10 + ┌─ /src/parse/error.gleam:1:8 11 + 12 + 1 │ import _wibble 13 + │ ^^^^^^^ I'm expecting a lowercase name here 14 + 15 + A module name must start with a lowercase letter, and can contain a-z, 0-9, 16 + or _.
+26
compiler-core/src/parse/tests.rs
··· 2470 2470 "# 2471 2471 ); 2472 2472 } 2473 + 2474 + #[test] 2475 + fn error_message_when_using_discard_pattern_for_function_name() { 2476 + assert_module_error!("fn _wibble() -> Nil"); 2477 + } 2478 + 2479 + #[test] 2480 + fn error_message_when_using_discard_pattern_for_constant_name() { 2481 + assert_module_error!("const _wibble = 1"); 2482 + } 2483 + 2484 + #[test] 2485 + fn error_message_when_using_discard_pattern_for_module_name() { 2486 + assert_module_error!("import _wibble"); 2487 + } 2488 + 2489 + #[test] 2490 + fn error_message_when_using_discard_pattern_for_as_pattern() { 2491 + assert_module_error!( 2492 + "pub fn main() { 2493 + case todo { 2494 + wibble as _wobble -> todo 2495 + } 2496 + }" 2497 + ); 2498 + }