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

Configure Feed

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

bit array patterns matching on impossible int segments are marked as unreachable

fixes #4959

author
Giacomo Cavalieri
committer
Louis Pilfold
date (Sep 26, 2025, 12:33 PM +0100) commit 1071b491 parent a820a0ac change-id oxmwmokw
+630 -57
+34
CHANGELOG.md
··· 29 29 30 30 ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 31 31 32 + - The compiler now raises a warning for unreachable branches that are matching 33 + on bit array segments that could never match. Consider this example: 34 + 35 + ```gleam 36 + pub fn get_payload(packet: BitArray) -> Result(BitArray, Nil) { 37 + case packet { 38 + <<200, payload:bytes>> -> Ok(payload) 39 + <<404, _:bits>> -> Error(Nil) 40 + _ -> Ok(packet) 41 + } 42 + } 43 + ``` 44 + 45 + There's a subtle bug here. The second branch can never match since it's 46 + impossible for the first byte of the bit array to have the value `404`. 47 + The new error explains this nicely: 48 + 49 + ```text 50 + warning: Unreachable pattern 51 + ┌─ /src.gleam:4:5 52 + 53 + 4 │ <<404, _:bits>> -> Error(Nil) 54 + │ ^^^^^^^^^^^^^^^ 55 + │ │ 56 + │ A 1 byte unsigned integer will never match this value 57 + 58 + This pattern cannot be reached as it contains segments that will never 59 + match. 60 + 61 + Hint: It can be safely removed. 62 + ``` 63 + 64 + ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 65 + 32 66 - The compiler now emits a better error message for private types marked as 33 67 opaque. For example, the following piece of code: 34 68
+232 -11
compiler-core/src/exhaustiveness.rs
··· 65 65 66 66 use crate::{ 67 67 ast::{ 68 - self, AssignName, BitArraySize, Endianness, IntOperator, TypedBitArraySize, TypedClause, 69 - TypedPattern, TypedPatternBitArraySegment, 68 + self, AssignName, BitArraySize, Endianness, IntOperator, SrcSpan, TypedBitArraySize, 69 + TypedClause, TypedPattern, TypedPatternBitArraySegment, 70 70 }, 71 71 strings::{ 72 72 convert_string_escape_chars, length_utf16, length_utf32, string_to_utf16_bytes, ··· 410 410 fn assign_segment_constant_value(&mut self, name: EcoString, value: &BitArrayMatchedValue) { 411 411 let value = match value { 412 412 BitArrayMatchedValue::LiteralFloat(value) => BoundValue::LiteralFloat(value.clone()), 413 - BitArrayMatchedValue::LiteralInt(value) => BoundValue::LiteralInt(value.clone()), 413 + BitArrayMatchedValue::LiteralInt { value, .. } => BoundValue::LiteralInt(value.clone()), 414 414 BitArrayMatchedValue::LiteralString { value, .. } => { 415 415 BoundValue::LiteralString(value.clone()) 416 416 } ··· 525 525 }, 526 526 ) if index != variant => true, 527 527 _ => false, 528 + } 529 + } 530 + 531 + fn is_matching_on_impossible_segment(&self) -> Option<Vec<ImpossibleBitArraySegmentPattern>> { 532 + match self { 533 + Self::BitArray { tests } => { 534 + let impossible_segments = tests 535 + .iter() 536 + .filter_map(|test| match test { 537 + BitArrayTest::Size(_) 538 + | BitArrayTest::CatchAllIsBytes { .. } 539 + | BitArrayTest::ReadSizeIsNotNegative { .. } 540 + | BitArrayTest::SegmentIsFiniteFloat { .. } => None, 541 + 542 + BitArrayTest::Match(MatchTest { value, read_action }) => { 543 + value.is_impossible_segment(read_action) 544 + } 545 + }) 546 + .collect_vec(); 547 + 548 + if impossible_segments.is_empty() { 549 + None 550 + } else { 551 + Some(impossible_segments) 552 + } 553 + } 554 + _ => None, 528 555 } 529 556 } 530 557 } ··· 1159 1186 /// the possible patterns: it can only contain literal floats, ints, strings, 1160 1187 /// a variable name, and a discard. 1161 1188 /// 1162 - #[derive(Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] 1189 + #[derive(Clone, Eq, Debug, serde::Serialize, serde::Deserialize)] 1163 1190 pub enum BitArrayMatchedValue { 1164 1191 LiteralFloat(EcoString), 1165 - LiteralInt(BigInt), 1192 + LiteralInt { 1193 + value: BigInt, 1194 + /// This is carried around for better error reporting in case a segment 1195 + /// is deemed unreachable: it is the location this literal value comes 1196 + /// from in the whole pattern. 1197 + location: SrcSpan, 1198 + }, 1166 1199 LiteralString { 1167 1200 value: EcoString, 1168 1201 encoding: StringEncoding, ··· 1178 1211 }, 1179 1212 } 1180 1213 1214 + impl PartialEq for BitArrayMatchedValue { 1215 + fn eq(&self, other: &Self) -> bool { 1216 + match (self, other) { 1217 + // Ints need special care: they're carrying around information about 1218 + // where they come from. But as for equality is concerned for the 1219 + // pattern match compilation, they are considered equal as long as 1220 + // the two values are equal, no matter where the values come from. 1221 + (Self::LiteralInt { value: one, .. }, Self::LiteralInt { value: other, .. }) => { 1222 + one == other 1223 + } 1224 + (Self::LiteralInt { .. }, _) => false, 1225 + 1226 + // All the other cases follow the standard equality implementation. 1227 + (Self::LiteralFloat(one), Self::LiteralFloat(other)) => one == other, 1228 + (Self::LiteralFloat(_), _) => false, 1229 + 1230 + // Two literal string matches are the same if they match for the 1231 + // same bytes. No matter the original starting string and encoding. 1232 + (Self::LiteralString { bytes: one, .. }, Self::LiteralString { bytes: other, .. }) => { 1233 + one == other 1234 + } 1235 + (Self::LiteralString { .. }, _) => false, 1236 + 1237 + (Self::Variable(one), Self::Variable(other)) => one == other, 1238 + (Self::Variable(_), _) => false, 1239 + 1240 + (Self::Discard(one), Self::Discard(other)) => one == other, 1241 + (Self::Discard(_), _) => false, 1242 + 1243 + ( 1244 + Self::Assign { 1245 + name: name_one, 1246 + value: one, 1247 + }, 1248 + Self::Assign { 1249 + name: name_other, 1250 + value: other, 1251 + }, 1252 + ) => name_one == name_other && one == other, 1253 + (Self::Assign { .. }, _) => false, 1254 + } 1255 + } 1256 + } 1257 + 1181 1258 impl BitArrayMatchedValue { 1182 1259 pub(crate) fn is_literal(&self) -> bool { 1183 1260 match self { 1184 1261 BitArrayMatchedValue::LiteralFloat(_) 1185 - | BitArrayMatchedValue::LiteralInt(_) 1262 + | BitArrayMatchedValue::LiteralInt { .. } 1186 1263 | BitArrayMatchedValue::LiteralString { .. } => true, 1187 1264 BitArrayMatchedValue::Variable(..) | BitArrayMatchedValue::Discard(..) => false, 1188 1265 BitArrayMatchedValue::Assign { value, .. } => value.is_literal(), ··· 1201 1278 // TODO: We could also implement the interfering optimisation for 1202 1279 // literal ints as well, but that will be a bit trickier than 1203 1280 // strings. 1204 - BitArrayMatchedValue::LiteralInt(_) 1281 + BitArrayMatchedValue::LiteralInt { .. } 1205 1282 | BitArrayMatchedValue::LiteralFloat(_) 1206 1283 | BitArrayMatchedValue::Variable(_) 1207 1284 | BitArrayMatchedValue::Discard(_) => None, 1208 1285 } 1209 1286 } 1287 + 1288 + /// If we can statically tell the segment will never match, this will return 1289 + /// the reason why it can't match. Otherwise it returns `None`. 1290 + /// 1291 + fn is_impossible_segment( 1292 + &self, 1293 + read_action: &ReadAction, 1294 + ) -> Option<ImpossibleBitArraySegmentPattern> { 1295 + match self { 1296 + BitArrayMatchedValue::Assign { value, .. } => value.is_impossible_segment(read_action), 1297 + BitArrayMatchedValue::LiteralInt { value, location } => { 1298 + let size = read_action.size.constant_bits()?.to_u32()?; 1299 + if representable_with_bits(value.clone(), size, read_action.signed) { 1300 + None 1301 + } else { 1302 + Some(ImpossibleBitArraySegmentPattern::UnrepresentableInteger { 1303 + value: value.clone(), 1304 + size, 1305 + location: *location, 1306 + signed: read_action.signed, 1307 + }) 1308 + } 1309 + } 1310 + 1311 + BitArrayMatchedValue::LiteralFloat(_) 1312 + | BitArrayMatchedValue::LiteralString { .. } 1313 + | BitArrayMatchedValue::Variable(_) 1314 + | BitArrayMatchedValue::Discard(_) => None, 1315 + } 1316 + } 1210 1317 } 1211 1318 1212 1319 #[derive(Clone, Copy, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] ··· 1221 1328 match self { 1222 1329 BitArrayMatchedValue::Discard(_) => true, 1223 1330 BitArrayMatchedValue::LiteralFloat(_) 1224 - | BitArrayMatchedValue::LiteralInt(_) 1331 + | BitArrayMatchedValue::LiteralInt { .. } 1225 1332 | BitArrayMatchedValue::LiteralString { .. } 1226 1333 | BitArrayMatchedValue::Variable(_) 1227 1334 | BitArrayMatchedValue::Assign { .. } => false, ··· 1904 2011 .contains(&(clause, pattern_index)) 1905 2012 { 1906 2013 Reachability::Unreachable(UnreachablePatternReason::ImpossibleVariant) 2014 + } else if let Some(segments) = self 2015 + .diagnostics 2016 + .match_impossible_segments 2017 + .get(&(clause, pattern_index)) 2018 + { 2019 + Reachability::Unreachable(UnreachablePatternReason::ImpossibleSegments( 2020 + segments.clone(), 2021 + )) 1907 2022 } else { 1908 2023 Reachability::Unreachable(UnreachablePatternReason::DuplicatePattern) 1909 2024 } ··· 1946 2061 1947 2062 /// Whether a pattern is reachable, or why it is unreachable. 1948 2063 /// 1949 - #[derive(Debug, Clone, Copy, PartialEq, Eq)] 2064 + #[derive(Debug, Clone, PartialEq, Eq)] 1950 2065 pub enum Reachability { 1951 2066 Reachable, 1952 2067 Unreachable(UnreachablePatternReason), ··· 1983 2098 /// See `reachable` for an explanation of its structure. 1984 2099 /// 1985 2100 pub match_impossible_variants: HashSet<(usize, usize)>, 2101 + 2102 + /// Patterns that are matching on bit array segments the compiler can tell 2103 + /// for sure will never match. 2104 + /// 2105 + pub match_impossible_segments: HashMap<(usize, usize), Vec<ImpossibleBitArraySegmentPattern>>, 2106 + } 2107 + 2108 + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 2109 + pub enum ImpossibleBitArraySegmentPattern { 2110 + UnrepresentableInteger { 2111 + value: BigInt, 2112 + size: u32, 2113 + location: SrcSpan, 2114 + signed: bool, 2115 + }, 2116 + } 2117 + 2118 + impl ImpossibleBitArraySegmentPattern { 2119 + pub fn location(&self) -> SrcSpan { 2120 + match self { 2121 + ImpossibleBitArraySegmentPattern::UnrepresentableInteger { location, .. } => *location, 2122 + } 2123 + } 1986 2124 } 1987 2125 1988 2126 impl<'a> Compiler<'a> { ··· 1995 2133 missing: false, 1996 2134 reachable: HashSet::new(), 1997 2135 match_impossible_variants: HashSet::new(), 2136 + match_impossible_segments: HashMap::new(), 1998 2137 }, 1999 2138 } 2000 2139 } ··· 2030 2169 .diagnostics 2031 2170 .match_impossible_variants 2032 2171 .insert((branch.clause_index, branch.alternative_index)); 2172 + } 2173 + 2174 + fn mark_as_matching_impossible_segment( 2175 + &mut self, 2176 + branch: &Branch, 2177 + segments: Vec<ImpossibleBitArraySegmentPattern>, 2178 + ) { 2179 + let _ = self 2180 + .diagnostics 2181 + .reachable 2182 + .remove(&(branch.clause_index, branch.alternative_index)); 2183 + let _ = self 2184 + .diagnostics 2185 + .match_impossible_segments 2186 + .insert((branch.clause_index, branch.alternative_index), segments); 2033 2187 } 2034 2188 2035 2189 fn compile(&mut self, mut branches: VecDeque<Branch>) -> Decision { ··· 2173 2327 }; 2174 2328 2175 2329 let checked_pattern = self.pattern(pattern_check.pattern); 2330 + 2176 2331 if checked_pattern.is_matching_on_unreachable_variant(branch_mode) { 2177 2332 self.mark_as_matching_impossible_variant(&branch); 2333 + continue; 2334 + } else if let Some(unreachable_segments) = 2335 + checked_pattern.is_matching_on_impossible_segment() 2336 + { 2337 + self.mark_as_matching_impossible_segment(&branch, unreachable_segments); 2178 2338 continue; 2179 2339 } 2180 2340 ··· 3231 3391 // following read actions as a valid size. 3232 3392 match &value { 3233 3393 BitArrayMatchedValue::LiteralFloat(_) 3234 - | BitArrayMatchedValue::LiteralInt(_) 3394 + | BitArrayMatchedValue::LiteralInt { .. } 3235 3395 | BitArrayMatchedValue::LiteralString { .. } 3236 3396 | BitArrayMatchedValue::Discard(_) => {} 3237 3397 BitArrayMatchedValue::Variable(name) ··· 3267 3427 ) -> BitArrayMatchedValue { 3268 3428 let pattern = pattern.unwrap_or(&segment.value); 3269 3429 match pattern { 3270 - ast::Pattern::Int { int_value, .. } => BitArrayMatchedValue::LiteralInt(int_value.clone()), 3430 + ast::Pattern::Int { 3431 + int_value, 3432 + location, 3433 + .. 3434 + } => BitArrayMatchedValue::LiteralInt { 3435 + value: int_value.clone(), 3436 + location: *location, 3437 + }, 3271 3438 ast::Pattern::Float { value, .. } => BitArrayMatchedValue::LiteralFloat(value.clone()), 3272 3439 ast::Pattern::String { value, .. } if segment.has_utf16_option() => { 3273 3440 BitArrayMatchedValue::LiteralString { ··· 3419 3586 None => false, 3420 3587 }) 3421 3588 } 3589 + 3590 + #[must_use] 3591 + fn representable_with_bits(value: BigInt, bits: u32, signed: bool) -> bool { 3592 + // No number is representable in 0 bits. 3593 + if bits == 0 { 3594 + return false; 3595 + }; 3596 + if signed { 3597 + // Signed numbers range in [-2^(bits-1), 2^(bits-1)[ 3598 + let power = BigInt::from(2).pow(bits - 1); 3599 + -&power <= value && value < power 3600 + } else { 3601 + // Unsigned numbers range in [0, 2^bits[ 3602 + BigInt::from(0) <= value && value < BigInt::from(2).pow(bits) 3603 + } 3604 + } 3605 + 3606 + #[cfg(test)] 3607 + mod representable_with_bits_test { 3608 + use crate::exhaustiveness::representable_with_bits; 3609 + use num_bigint::BigInt; 3610 + 3611 + #[test] 3612 + fn positive_number_representable_with_bits_test() { 3613 + // 9 can be represented as a >=4 bits unsigned number. 3614 + assert!(representable_with_bits(9.into(), 4, false)); 3615 + assert!(representable_with_bits(9.into(), 5, false)); 3616 + // But not in <=3 bits as an unsigned number. 3617 + assert!(!representable_with_bits(9.into(), 3, false)); 3618 + assert!(!representable_with_bits(9.into(), 2, false)); 3619 + 3620 + // It can be represented as a >=5 bit signed number. 3621 + assert!(representable_with_bits(9.into(), 5, true)); 3622 + assert!(representable_with_bits(9.into(), 6, true)); 3623 + // But not in <= 4 bits as a signed number. 3624 + assert!(!representable_with_bits(9.into(), 4, true)); 3625 + assert!(!representable_with_bits(9.into(), 3, true)); 3626 + } 3627 + 3628 + #[test] 3629 + fn negative_number_representable_with_bits_test() { 3630 + // A negative number will never be representable as an unsigned number, 3631 + // no matter the number of bits! 3632 + assert!(!representable_with_bits(BigInt::from(-9), 1, false)); 3633 + assert!(!representable_with_bits(BigInt::from(-9), 500, false)); 3634 + 3635 + // -9 can be represented in >=5 bits as a signed number. 3636 + assert!(representable_with_bits(BigInt::from(-9), 5, true)); 3637 + assert!(representable_with_bits(BigInt::from(-9), 6, true)); 3638 + // But not in <= 4 bits as a signed number. 3639 + assert!(!representable_with_bits(BigInt::from(-9), 4, true)); 3640 + assert!(!representable_with_bits(BigInt::from(-9), 3, true)); 3641 + } 3642 + }
+3 -3
compiler-core/src/javascript/decision.rs
··· 1088 1088 BitArrayMatchedValue::LiteralFloat(expected) => { 1089 1089 self.literal_float_segment_bytes_check(value, expected, read_action) 1090 1090 } 1091 - BitArrayMatchedValue::LiteralInt(expected) => { 1092 - self.literal_int_segment_bytes_check(value, expected.clone(), read_action) 1093 - } 1091 + BitArrayMatchedValue::LiteralInt { 1092 + value: expected, .. 1093 + } => self.literal_int_segment_bytes_check(value, expected.clone(), read_action), 1094 1094 BitArrayMatchedValue::Variable(..) 1095 1095 | BitArrayMatchedValue::Discard(..) 1096 1096 | BitArrayMatchedValue::Assign { .. } => {
+1 -7
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_unsigned_constant_pattern.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/bit_arrays.rs 3 - assertion_line: 575 4 3 expression: "\npub fn go(x) {\n case x {\n <<-2:unsigned>> -> 1\n _ -> 2\n }\n}\n" 5 - snapshot_kind: text 6 4 --- 7 5 ----- SOURCE CODE 8 6 ··· 16 14 17 15 ----- COMPILED JAVASCRIPT 18 16 export function go(x) { 19 - if (x.bitSize === 8 && x.byteAt(0) === 254) { 20 - return 1; 21 - } else { 22 - return 2; 23 - } 17 + return 2; 24 18 }
+9 -11
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_unsigned_constant_pattern.snap
··· 15 15 const FILEPATH = "src/module.gleam"; 16 16 17 17 export function go(x) { 18 - if (!(x.bitSize === 8 && x.byteAt(0) === 254)) { 19 - throw makeError( 20 - "let_assert", 21 - FILEPATH, 22 - "my/mod", 23 - 3, 24 - "go", 25 - "Pattern match failed, no pattern matched the value.", 26 - { value: x, start: 18, end: 48, pattern_start: 29, pattern_end: 44 } 27 - ) 28 - } 18 + throw makeError( 19 + "let_assert", 20 + FILEPATH, 21 + "my/mod", 22 + 3, 23 + "go", 24 + "Pattern match failed, no pattern matched the value.", 25 + { value: x, start: 18, end: 48, pattern_start: 29, pattern_end: 44 } 26 + ) 29 27 return x; 30 28 }
+31 -3
compiler-core/src/type_/error.rs
··· 5 5 use crate::{ 6 6 ast::{BinOp, BitArraySegmentTruncation, Layer, SrcSpan, TodoKind}, 7 7 build::Target, 8 + exhaustiveness::ImpossibleBitArraySegmentPattern, 8 9 type_::{Type, expression::ComparisonOutcome}, 9 10 }; 10 11 ··· 969 970 location: SrcSpan, 970 971 }, 971 972 972 - AssertAssignmentOnInferredVariant { 973 + AssertAssignmentOnImpossiblePattern { 973 974 location: SrcSpan, 975 + reason: AssertImpossiblePattern, 974 976 }, 975 977 976 978 /// When a `todo` or `panic` is used as a function instead of providing the ··· 1075 1077 }, 1076 1078 } 1077 1079 1080 + #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] 1081 + pub enum AssertImpossiblePattern { 1082 + /// When `let assert`-ing on a variant that's different from the inferred 1083 + /// one. 1084 + /// 1085 + /// ```gleam 1086 + /// let assert Error(_) = Ok(_) 1087 + /// ``` 1088 + /// 1089 + InferredVariant, 1090 + 1091 + /// When `let assert`-ing on a pattern that will never match because it's 1092 + /// matching on impossible segment(s). 1093 + /// 1094 + /// ```gleam 1095 + /// let assert <<-2:unsigned>> = bit_array 1096 + /// ``` 1097 + /// 1098 + ImpossibleSegments { 1099 + segments: Vec<ImpossibleBitArraySegmentPattern>, 1100 + }, 1101 + } 1102 + 1078 1103 #[derive(Debug, Eq, Copy, PartialEq, Clone, serde::Serialize, serde::Deserialize)] 1079 1104 pub enum FeatureKind { 1080 1105 LabelShorthandSyntax, ··· 1147 1172 Panic, 1148 1173 } 1149 1174 1150 - #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1175 + #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 1151 1176 pub enum UnreachablePatternReason { 1152 1177 /// The clause is unreachable because a previous pattern 1153 1178 /// matches the same case. ··· 1156 1181 /// of the custom type that we are matching on, and this matches 1157 1182 /// against one of the variants we know it isn't. 1158 1183 ImpossibleVariant, 1184 + /// The clause is unreachable because it is matching on a pattern segment 1185 + /// that we could tell is never going to match 1186 + ImpossibleSegments(Vec<ImpossibleBitArraySegmentPattern>), 1159 1187 } 1160 1188 1161 1189 impl Error { ··· 1292 1320 | Warning::OpaqueExternalType { location, .. } 1293 1321 | Warning::InternalTypeLeak { location, .. } 1294 1322 | Warning::RedundantAssertAssignment { location, .. } 1295 - | Warning::AssertAssignmentOnInferredVariant { location, .. } 1323 + | Warning::AssertAssignmentOnImpossiblePattern { location, .. } 1296 1324 | Warning::TodoOrPanicUsedAsFunction { location, .. } 1297 1325 | Warning::UnreachableCodeAfterPanic { location, .. } 1298 1326 | Warning::RedundantPipeFunctionCapture { location, .. }
+11 -1
compiler-core/src/type_/expression.rs
··· 2039 2039 match output.is_reachable(0, 0) { 2040 2040 Reachability::Unreachable(UnreachablePatternReason::ImpossibleVariant) => self 2041 2041 .problems 2042 - .warning(Warning::AssertAssignmentOnInferredVariant { 2042 + .warning(Warning::AssertAssignmentOnImpossiblePattern { 2043 2043 location: pattern.location(), 2044 + reason: AssertImpossiblePattern::InferredVariant, 2045 + }), 2046 + 2047 + Reachability::Unreachable(UnreachablePatternReason::ImpossibleSegments( 2048 + segments, 2049 + )) => self 2050 + .problems 2051 + .warning(Warning::AssertAssignmentOnImpossiblePattern { 2052 + location: pattern.location(), 2053 + reason: AssertImpossiblePattern::ImpossibleSegments { segments }, 2044 2054 }), 2045 2055 // A duplicate pattern warning should not happen, since there is only one pattern. 2046 2056 Reachability::Reachable
+24
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__assert_on_impossible_to_reach_integer_segment.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn main(x) {\n let assert <<1, -1, 2, -3>> = x\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main(x) { 8 + let assert <<1, -1, 2, -3>> = x 9 + } 10 + 11 + ----- WARNING 12 + warning: Assertion that will always fail 13 + ┌─ /src/warning/wrn.gleam:3:14 14 + 15 + 3 │ let assert <<1, -1, 2, -3>> = x 16 + │ ^^^^^^^^^^^^^^^^ 17 + │ │ │ 18 + │ │ A 1 byte unsigned integer will never match this value 19 + │ A 1 byte unsigned integer will never match this value 20 + 21 + We can tell from the code above that the value will never match this 22 + pattern and that this code will always crash. 23 + 24 + Either change the pattern or use `panic` to unconditionally fail.
+26
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn main(x) {\n case x {\n <<9:size(2)>> -> True\n _ -> False\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main(x) { 8 + case x { 9 + <<9:size(2)>> -> True 10 + _ -> False 11 + } 12 + } 13 + 14 + ----- WARNING 15 + warning: Unreachable pattern 16 + ┌─ /src/warning/wrn.gleam:4:5 17 + 18 + 4 │ <<9:size(2)>> -> True 19 + │ ^^^^^^^^^^^^^ 20 + │ │ 21 + │ A 2 bits unsigned integer will never match this value 22 + 23 + This pattern cannot be reached as it contains segments that will never 24 + match. 25 + 26 + Hint: It can be safely removed.
+26
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_2.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn main(x) {\n case x {\n <<-9:unsigned>> -> True\n _ -> False\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main(x) { 8 + case x { 9 + <<-9:unsigned>> -> True 10 + _ -> False 11 + } 12 + } 13 + 14 + ----- WARNING 15 + warning: Unreachable pattern 16 + ┌─ /src/warning/wrn.gleam:4:5 17 + 18 + 4 │ <<-9:unsigned>> -> True 19 + │ ^^^^^^^^^^^^^^^ 20 + │ │ 21 + │ A 1 byte unsigned integer will never match this value 22 + 23 + This pattern cannot be reached as it contains segments that will never 24 + match. 25 + 26 + Hint: It can be safely removed.
+26
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_3.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn main(x) {\n case x {\n <<312>> -> True\n _ -> False\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main(x) { 8 + case x { 9 + <<312>> -> True 10 + _ -> False 11 + } 12 + } 13 + 14 + ----- WARNING 15 + warning: Unreachable pattern 16 + ┌─ /src/warning/wrn.gleam:4:5 17 + 18 + 4 │ <<312>> -> True 19 + │ ^^^^^^^ 20 + │ │ 21 + │ A 1 byte unsigned integer will never match this value 22 + 23 + This pattern cannot be reached as it contains segments that will never 24 + match. 25 + 26 + Hint: It can be safely removed.
+26
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_4.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn main(x) {\n case x {\n <<-1>> -> True\n _ -> False\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main(x) { 8 + case x { 9 + <<-1>> -> True 10 + _ -> False 11 + } 12 + } 13 + 14 + ----- WARNING 15 + warning: Unreachable pattern 16 + ┌─ /src/warning/wrn.gleam:4:5 17 + 18 + 4 │ <<-1>> -> True 19 + │ ^^^^^^ 20 + │ │ 21 + │ A 1 byte unsigned integer will never match this value 22 + 23 + This pattern cannot be reached as it contains segments that will never 24 + match. 25 + 26 + Hint: It can be safely removed.
+27
compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__multiple_impossible_to_reach_integer_segments.snap
··· 1 + --- 2 + source: compiler-core/src/type_/tests/warnings.rs 3 + expression: "\npub fn main(x) {\n case x {\n <<1, -1, 2, -3>> -> True\n _ -> False\n }\n}" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main(x) { 8 + case x { 9 + <<1, -1, 2, -3>> -> True 10 + _ -> False 11 + } 12 + } 13 + 14 + ----- WARNING 15 + warning: Unreachable pattern 16 + ┌─ /src/warning/wrn.gleam:4:5 17 + 18 + 4 │ <<1, -1, 2, -3>> -> True 19 + │ ^^^^^^^^^^^^^^^^ 20 + │ │ │ 21 + │ │ A 1 byte unsigned integer will never match this value 22 + │ A 1 byte unsigned integer will never match this value 23 + 24 + This pattern cannot be reached as it contains segments that will never 25 + match. 26 + 27 + Hint: It can be safely removed.
+75
compiler-core/src/type_/tests/warnings.rs
··· 4353 4353 }" 4354 4354 ); 4355 4355 } 4356 + 4357 + #[test] 4358 + fn impossible_to_reach_integer_segment() { 4359 + assert_warning!( 4360 + " 4361 + pub fn main(x) { 4362 + case x { 4363 + <<9:size(2)>> -> True 4364 + _ -> False 4365 + } 4366 + }" 4367 + ); 4368 + } 4369 + 4370 + #[test] 4371 + fn impossible_to_reach_integer_segment_2() { 4372 + assert_warning!( 4373 + " 4374 + pub fn main(x) { 4375 + case x { 4376 + <<-9:unsigned>> -> True 4377 + _ -> False 4378 + } 4379 + }" 4380 + ); 4381 + } 4382 + 4383 + #[test] 4384 + fn impossible_to_reach_integer_segment_3() { 4385 + assert_warning!( 4386 + " 4387 + pub fn main(x) { 4388 + case x { 4389 + <<312>> -> True 4390 + _ -> False 4391 + } 4392 + }" 4393 + ); 4394 + } 4395 + 4396 + #[test] 4397 + fn impossible_to_reach_integer_segment_4() { 4398 + assert_warning!( 4399 + " 4400 + pub fn main(x) { 4401 + case x { 4402 + <<-1>> -> True 4403 + _ -> False 4404 + } 4405 + }" 4406 + ); 4407 + } 4408 + 4409 + #[test] 4410 + fn multiple_impossible_to_reach_integer_segments() { 4411 + assert_warning!( 4412 + " 4413 + pub fn main(x) { 4414 + case x { 4415 + <<1, -1, 2, -3>> -> True 4416 + _ -> False 4417 + } 4418 + }" 4419 + ); 4420 + } 4421 + 4422 + #[test] 4423 + fn assert_on_impossible_to_reach_integer_segment() { 4424 + assert_warning!( 4425 + " 4426 + pub fn main(x) { 4427 + let assert <<1, -1, 2, -3>> = x 4428 + }" 4429 + ); 4430 + }
+79 -21
compiler-core/src/warning.rs
··· 3 3 build::Target, 4 4 diagnostic::{self, Diagnostic, ExtraLabel, Location}, 5 5 error::wrap, 6 + exhaustiveness::ImpossibleBitArraySegmentPattern, 6 7 type_::{ 7 8 self, 8 9 error::{ 9 - FeatureKind, LiteralCollectionKind, PanicPosition, TodoOrPanic, 10 - UnreachablePatternReason, 10 + AssertImpossiblePattern, FeatureKind, LiteralCollectionKind, PanicPosition, 11 + TodoOrPanic, UnreachablePatternReason, 11 12 }, 12 13 expression::ComparisonOutcome, 13 14 pretty::Printer, ··· 16 17 use camino::Utf8PathBuf; 17 18 use debug_ignore::DebugIgnore; 18 19 use ecow::EcoString; 20 + use itertools::Itertools; 19 21 use std::{ 20 22 io::Write, 21 23 sync::{Arc, atomic::Ordering}, ··· 838 840 } 839 841 840 842 type_::Warning::UnreachableCasePattern { location, reason } => { 841 - let text: String = match reason { 843 + let text = match reason { 842 844 UnreachablePatternReason::DuplicatePattern => wrap( 843 845 "This pattern cannot be reached as a previous \ 844 846 pattern matches the same values.\n", ··· 846 848 UnreachablePatternReason::ImpossibleVariant => wrap( 847 849 "This pattern cannot be reached as it matches on \ 848 850 a variant of a type which is never present.\n", 851 + ), 852 + UnreachablePatternReason::ImpossibleSegments(_) => wrap( 853 + "This pattern cannot be reached as it contains \ 854 + segments that will never match.\n", 849 855 ), 850 856 }; 857 + 858 + let extra_labels = match reason { 859 + UnreachablePatternReason::DuplicatePattern 860 + | UnreachablePatternReason::ImpossibleVariant => vec![], 861 + UnreachablePatternReason::ImpossibleSegments(segments) => segments 862 + .iter() 863 + .map(|segment| ExtraLabel { 864 + src_info: None, 865 + label: diagnostic::Label { 866 + text: Some(explain_impossible_segment(segment)), 867 + span: segment.location(), 868 + }, 869 + }) 870 + .collect_vec(), 871 + }; 872 + 851 873 Diagnostic { 852 874 title: "Unreachable pattern".into(), 853 875 text, ··· 860 882 text: None, 861 883 span: *location, 862 884 }, 863 - extra_labels: Vec::new(), 885 + extra_labels, 864 886 }), 865 887 } 866 888 } ··· 1007 1029 }), 1008 1030 }, 1009 1031 1010 - type_::Warning::AssertAssignmentOnInferredVariant { location } => Diagnostic { 1011 - title: "Assertion that will always fail".into(), 1012 - text: wrap( 1013 - "We can tell from the code above that the value will never match \ 1032 + type_::Warning::AssertAssignmentOnImpossiblePattern { location, reason } => { 1033 + let extra_labels = match reason { 1034 + AssertImpossiblePattern::InferredVariant => vec![], 1035 + AssertImpossiblePattern::ImpossibleSegments { segments } => segments 1036 + .iter() 1037 + .map(|segment| ExtraLabel { 1038 + src_info: None, 1039 + label: diagnostic::Label { 1040 + text: Some(explain_impossible_segment(segment)), 1041 + span: segment.location(), 1042 + }, 1043 + }) 1044 + .collect_vec(), 1045 + }; 1046 + 1047 + Diagnostic { 1048 + title: "Assertion that will always fail".into(), 1049 + text: wrap( 1050 + "We can tell from the code above that the value will never match \ 1014 1051 this pattern and that this code will always crash. 1015 1052 1016 1053 Either change the pattern or use `panic` to unconditionally fail.", 1017 - ), 1018 - hint: None, 1019 - level: diagnostic::Level::Warning, 1020 - location: Some(Location { 1021 - label: diagnostic::Label { 1022 - text: None, 1023 - span: *location, 1024 - }, 1025 - path: path.clone(), 1026 - src: src.clone(), 1027 - extra_labels: vec![], 1028 - }), 1029 - }, 1054 + ), 1055 + hint: None, 1056 + level: diagnostic::Level::Warning, 1057 + location: Some(Location { 1058 + label: diagnostic::Label { 1059 + text: None, 1060 + span: *location, 1061 + }, 1062 + path: path.clone(), 1063 + src: src.clone(), 1064 + extra_labels, 1065 + }), 1066 + } 1067 + } 1030 1068 1031 1069 type_::Warning::TodoOrPanicUsedAsFunction { 1032 1070 kind, ··· 1436 1474 let mut nocolor = Buffer::no_color(); 1437 1475 self.pretty(&mut nocolor); 1438 1476 String::from_utf8(nocolor.into_inner()).expect("Warning printing produced invalid utf8") 1477 + } 1478 + } 1479 + 1480 + fn explain_impossible_segment(segment: &ImpossibleBitArraySegmentPattern) -> String { 1481 + match segment { 1482 + ImpossibleBitArraySegmentPattern::UnrepresentableInteger { 1483 + size, 1484 + signed, 1485 + value: _, 1486 + location: _, 1487 + } => { 1488 + let human_readable_size = match size { 1489 + 1 => "1 bit".into(), 1490 + 8 => "1 byte".into(), 1491 + n if n % 8 == 0 => format!("{} bytes", n / 8), 1492 + n => format!("{n} bits"), 1493 + }; 1494 + let sign = if *signed { "signed" } else { "unsigned" }; 1495 + format!("A {human_readable_size} {sign} integer will never match this value") 1496 + } 1439 1497 } 1440 1498 } 1441 1499