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

Configure Feed

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

allow negative numbers in different bases

authored by

Giacomo Cavalieri and committed by
Louis Pilfold
(Jul 15, 2026, 10:58 AM +0100) dfe7dd95 ea9c598a

+323 -30
+4
CHANGELOG.md
··· 555 555 when finding the Gleam files of a package. 556 556 ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 557 557 558 + - Fixed a bug where the compiler would not be able to correctly parse negative 559 + numbers written in binary, octal, or hexadecimal base. 560 + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 561 + 558 562 - The formatter now properly formats binary operations in bit array size 559 563 segments. 560 564 ([Andrey Kozhev](https://github.com/ankddev))
+61 -30
compiler-core/src/parse/lexer.rs
··· 8 8 use crate::parse::error::{LexicalError, LexicalErrorType}; 9 9 use crate::parse::token::Token; 10 10 use std::char; 11 + use std::ops::Neg; 11 12 12 13 use super::error::InvalidUnicodeEscapeError; 13 14 ··· 879 880 880 881 fn lex_number(&mut self) -> LexResult { 881 882 let start_pos = self.get_pos(); 883 + 884 + // We call this function after making sure that what comes next starts 885 + // with what seems to be a valid number. If we see that it starts with 886 + // `-` we consume the token and record that the number is negative. 887 + let is_negative = if self.chr0 == Some('-') { 888 + let _ = self.next_char(); 889 + true 890 + } else { 891 + false 892 + }; 893 + 882 894 let num = if self.chr0 == Some('0') { 883 - if self.chr1 == Some('x') || self.chr1 == Some('X') { 884 - // Hex! 885 - let _ = self.next_char(); 886 - let _ = self.next_char(); 887 - self.lex_number_radix(start_pos, 16, "0x")? 888 - } else if self.chr1 == Some('o') || self.chr1 == Some('O') { 889 - // Octal! 890 - let _ = self.next_char(); 891 - let _ = self.next_char(); 892 - self.lex_number_radix(start_pos, 8, "0o")? 893 - } else if self.chr1 == Some('b') || self.chr1 == Some('B') { 894 - // Binary! 895 - let _ = self.next_char(); 896 - let _ = self.next_char(); 897 - self.lex_number_radix(start_pos, 2, "0b")? 898 - } else { 899 - self.lex_decimal_number()? 895 + match self.chr1 { 896 + Some('x' | 'X') => { 897 + // Hex! 898 + let _ = self.next_char(); 899 + let _ = self.next_char(); 900 + self.lex_number_radix(start_pos, 16, is_negative, "0x")? 901 + } 902 + Some('o' | 'O') => { 903 + // Octal! 904 + let _ = self.next_char(); 905 + let _ = self.next_char(); 906 + self.lex_number_radix(start_pos, 8, is_negative, "0o")? 907 + } 908 + Some('b' | 'B') => { 909 + // Binary! 910 + let _ = self.next_char(); 911 + let _ = self.next_char(); 912 + self.lex_number_radix(start_pos, 2, is_negative, "0b")? 913 + } 914 + _ => self.lex_decimal_number(start_pos, is_negative)?, 900 915 } 901 916 } else { 902 - self.lex_decimal_number()? 917 + self.lex_decimal_number(start_pos, is_negative)? 903 918 }; 904 919 905 920 if Some('_') == self.chr0 { ··· 917 932 } 918 933 919 934 // Lex a hex/octal/decimal/binary number without a decimal point. 920 - fn lex_number_radix(&mut self, start_pos: u32, radix: u32, prefix: &str) -> LexResult { 935 + fn lex_number_radix( 936 + &mut self, 937 + start_pos: u32, 938 + radix: u32, 939 + is_negative: bool, 940 + prefix: &str, 941 + ) -> LexResult { 921 942 let num = self.radix_run(radix); 922 943 if num.is_empty() { 923 944 let location = self.get_pos() - 1; ··· 941 962 let value = format!("{prefix}{num}"); 942 963 let int_value = super::parse_int_value(&value).expect("int value to parse as bigint"); 943 964 let end_pos = self.get_pos(); 965 + 966 + let (value, int_value) = if is_negative { 967 + (format!("-{value}"), int_value.neg()) 968 + } else { 969 + (value, int_value) 970 + }; 971 + 944 972 Ok(( 945 973 start_pos, 946 974 Token::Int { ··· 954 982 955 983 // Lex a normal number, that is, no octal, hex or binary number. 956 984 // This function cannot be reached without the head of the stream being either 0-9 or '-', 0-9 957 - fn lex_decimal_number(&mut self) -> LexResult { 958 - self.lex_decimal_or_int_number(true) 985 + fn lex_decimal_number(&mut self, start_pos: u32, is_negative: bool) -> LexResult { 986 + self.lex_decimal_or_int_number(start_pos, is_negative, true) 959 987 } 960 988 961 - fn lex_int_number(&mut self) -> LexResult { 962 - self.lex_decimal_or_int_number(false) 989 + fn lex_int_number(&mut self, start_pos: u32, is_negative: bool) -> LexResult { 990 + self.lex_decimal_or_int_number(start_pos, is_negative, false) 963 991 } 964 992 965 - fn lex_decimal_or_int_number(&mut self, can_lex_decimal: bool) -> LexResult { 966 - let start_pos = self.get_pos(); 993 + fn lex_decimal_or_int_number( 994 + &mut self, 995 + start_pos: u32, 996 + is_negative: bool, 997 + can_lex_decimal: bool, 998 + ) -> LexResult { 967 999 let mut value = String::new(); 968 - // consume negative sign 969 - if self.chr0 == Some('-') { 970 - value.push(self.next_char().expect("lex_normal_number negative")); 971 - } 1000 + if is_negative { 1001 + value.push('-') 1002 + }; 972 1003 // consume first run of digits 973 1004 value.push_str(&self.radix_run(10)); 974 1005 ··· 1025 1056 // It can be nested like: `tuple.1.2.3.4` 1026 1057 loop { 1027 1058 if matches!(self.chr0, Some('0'..='9')) { 1028 - let number = self.lex_int_number()?; 1059 + let number = self.lex_int_number(self.get_pos(), false)?; 1029 1060 self.emit(number); 1030 1061 } else { 1031 1062 break;
+81
compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_binary_number.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "pub fn main() { -0b101 }" 4 + --- 5 + Parsed { 6 + module: Module { 7 + name: "", 8 + documentation: [], 9 + type_info: (), 10 + definitions: [ 11 + TargetedDefinition { 12 + definition: Function( 13 + Function { 14 + location: SrcSpan { 15 + start: 0, 16 + end: 13, 17 + }, 18 + body_start: Some( 19 + 14, 20 + ), 21 + end_position: 24, 22 + name: Some( 23 + ( 24 + SrcSpan { 25 + start: 7, 26 + end: 11, 27 + }, 28 + "main", 29 + ), 30 + ), 31 + arguments: [], 32 + body: [ 33 + Expression( 34 + Int { 35 + location: SrcSpan { 36 + start: 16, 37 + end: 22, 38 + }, 39 + value: "-0b101", 40 + int_value: -5, 41 + }, 42 + ), 43 + ], 44 + publicity: Public, 45 + deprecation: NotDeprecated, 46 + return_annotation: None, 47 + return_type: (), 48 + documentation: None, 49 + external_erlang: None, 50 + external_javascript: None, 51 + implementations: Implementations { 52 + gleam: true, 53 + can_run_on_erlang: true, 54 + can_run_on_javascript: true, 55 + uses_erlang_externals: false, 56 + uses_javascript_externals: false, 57 + }, 58 + purity: Pure, 59 + }, 60 + ), 61 + target: None, 62 + }, 63 + ], 64 + names: Names { 65 + local_types: {}, 66 + imported_modules: {}, 67 + type_variables: {}, 68 + local_value_constructors: {}, 69 + reexport_aliases: {}, 70 + }, 71 + unused_definition_positions: {}, 72 + }, 73 + extra: ModuleExtra { 74 + module_comments: [], 75 + doc_comments: [], 76 + comments: [], 77 + empty_lines: [], 78 + new_lines: [], 79 + trailing_commas: [], 80 + }, 81 + }
+81
compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_hexadecimal_number.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "pub fn main() { -0x6f }" 4 + --- 5 + Parsed { 6 + module: Module { 7 + name: "", 8 + documentation: [], 9 + type_info: (), 10 + definitions: [ 11 + TargetedDefinition { 12 + definition: Function( 13 + Function { 14 + location: SrcSpan { 15 + start: 0, 16 + end: 13, 17 + }, 18 + body_start: Some( 19 + 14, 20 + ), 21 + end_position: 23, 22 + name: Some( 23 + ( 24 + SrcSpan { 25 + start: 7, 26 + end: 11, 27 + }, 28 + "main", 29 + ), 30 + ), 31 + arguments: [], 32 + body: [ 33 + Expression( 34 + Int { 35 + location: SrcSpan { 36 + start: 16, 37 + end: 21, 38 + }, 39 + value: "-0x6f", 40 + int_value: -111, 41 + }, 42 + ), 43 + ], 44 + publicity: Public, 45 + deprecation: NotDeprecated, 46 + return_annotation: None, 47 + return_type: (), 48 + documentation: None, 49 + external_erlang: None, 50 + external_javascript: None, 51 + implementations: Implementations { 52 + gleam: true, 53 + can_run_on_erlang: true, 54 + can_run_on_javascript: true, 55 + uses_erlang_externals: false, 56 + uses_javascript_externals: false, 57 + }, 58 + purity: Pure, 59 + }, 60 + ), 61 + target: None, 62 + }, 63 + ], 64 + names: Names { 65 + local_types: {}, 66 + imported_modules: {}, 67 + type_variables: {}, 68 + local_value_constructors: {}, 69 + reexport_aliases: {}, 70 + }, 71 + unused_definition_positions: {}, 72 + }, 73 + extra: ModuleExtra { 74 + module_comments: [], 75 + doc_comments: [], 76 + comments: [], 77 + empty_lines: [], 78 + new_lines: [], 79 + trailing_commas: [], 80 + }, 81 + }
+81
compiler-core/src/parse/snapshots/gleam_core__parse__tests__parse_negative_octal_number.snap
··· 1 + --- 2 + source: compiler-core/src/parse/tests.rs 3 + expression: "pub fn main() { -0o157 }" 4 + --- 5 + Parsed { 6 + module: Module { 7 + name: "", 8 + documentation: [], 9 + type_info: (), 10 + definitions: [ 11 + TargetedDefinition { 12 + definition: Function( 13 + Function { 14 + location: SrcSpan { 15 + start: 0, 16 + end: 13, 17 + }, 18 + body_start: Some( 19 + 14, 20 + ), 21 + end_position: 24, 22 + name: Some( 23 + ( 24 + SrcSpan { 25 + start: 7, 26 + end: 11, 27 + }, 28 + "main", 29 + ), 30 + ), 31 + arguments: [], 32 + body: [ 33 + Expression( 34 + Int { 35 + location: SrcSpan { 36 + start: 16, 37 + end: 22, 38 + }, 39 + value: "-0o157", 40 + int_value: -111, 41 + }, 42 + ), 43 + ], 44 + publicity: Public, 45 + deprecation: NotDeprecated, 46 + return_annotation: None, 47 + return_type: (), 48 + documentation: None, 49 + external_erlang: None, 50 + external_javascript: None, 51 + implementations: Implementations { 52 + gleam: true, 53 + can_run_on_erlang: true, 54 + can_run_on_javascript: true, 55 + uses_erlang_externals: false, 56 + uses_javascript_externals: false, 57 + }, 58 + purity: Pure, 59 + }, 60 + ), 61 + target: None, 62 + }, 63 + ], 64 + names: Names { 65 + local_types: {}, 66 + imported_modules: {}, 67 + type_variables: {}, 68 + local_value_constructors: {}, 69 + reexport_aliases: {}, 70 + }, 71 + unused_definition_positions: {}, 72 + }, 73 + extra: ModuleExtra { 74 + module_comments: [], 75 + doc_comments: [], 76 + comments: [], 77 + empty_lines: [], 78 + new_lines: [], 79 + trailing_commas: [], 80 + }, 81 + }
+15
compiler-core/src/parse/tests.rs
··· 2220 2220 } 2221 2221 2222 2222 #[test] 2223 + fn parse_negative_binary_number() { 2224 + assert_parse_module!("pub fn main() { -0b101 }"); 2225 + } 2226 + 2227 + #[test] 2228 + fn parse_negative_octal_number() { 2229 + assert_parse_module!("pub fn main() { -0o157 }"); 2230 + } 2231 + 2232 + #[test] 2233 + fn parse_negative_hexadecimal_number() { 2234 + assert_parse_module!("pub fn main() { -0x6f }"); 2235 + } 2236 + 2237 + #[test] 2223 2238 fn append_to_const_list() { 2224 2239 assert_module_error!( 2225 2240 "