Skip to content

Commit cd42365

Browse files
committed
library: core::str::lines: Fix handling of trailing bare CR
E.g., split "bare\r" into the single line "bare\r", not "bare". The documentation for this function says that only LF or CR-LF count as newlines. So a bare CR is not a line ending, and must not be stripped. This fix is a behavioural change, even though it brings the behaviour into line with the documentation, and into line with that of `std::io::BufRead:;lines()`. It seems unlikely that anyone is relying on this bug, but I'm not sure how to rule it out. Perhaps this should have an FCP or a crater run or something. It should definitely be in the release notes. This is an alternative to rust-lang#91051, which proposes to document rather than fix the behaviour. As for the implementation: the current version doesn't give the map closure the right information, so we need to use split_inclusive. After that, we can reuse the logic in the new `str::trim_newline`. Signed-off-by: Ian Jackson <[email protected]>
1 parent 825eab4 commit cd42365

File tree

2 files changed

+6
-8
lines changed

2 files changed

+6
-8
lines changed

library/core/src/str/iter.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use super::from_utf8_unchecked;
1313
use super::pattern::Pattern;
1414
use super::pattern::{DoubleEndedSearcher, ReverseSearcher, Searcher};
1515
use super::validations::{next_code_point, next_code_point_reverse, utf8_is_cont_byte};
16-
use super::LinesAnyMap;
16+
use super::LinesMap;
1717
use super::{BytesIsNotEmpty, UnsafeBytesToStr};
1818
use super::{CharEscapeDebugContinue, CharEscapeDefault, CharEscapeUnicode};
1919
use super::{IsAsciiWhitespace, IsNotEmpty, IsWhitespace};
@@ -1095,7 +1095,7 @@ generate_pattern_iterators! {
10951095
#[stable(feature = "rust1", since = "1.0.0")]
10961096
#[must_use = "iterators are lazy and do nothing unless consumed"]
10971097
#[derive(Clone, Debug)]
1098-
pub struct Lines<'a>(pub(super) Map<SplitTerminator<'a, char>, LinesAnyMap>);
1098+
pub struct Lines<'a>(pub(super) Map<SplitInclusive<'a, char>, LinesMap>);
10991099

11001100
#[stable(feature = "rust1", since = "1.0.0")]
11011101
impl<'a> Iterator for Lines<'a> {

library/core/src/str/mod.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -913,13 +913,13 @@ impl str {
913913
/// assert_splits_into("\n", &[""]);
914914
/// assert_splits_into("\n2nd", &["", "2nd"]);
915915
/// assert_splits_into("\r\n", &[""]);
916-
/// assert_splits_into("bare\r", &["bare"]); // should be "bare\r"
916+
/// assert_splits_into("bare\r", &["bare\r"]);
917917
/// assert_splits_into("bare\rcr", &["bare\rcr"]);
918918
/// ```
919919
#[stable(feature = "rust1", since = "1.0.0")]
920920
#[inline]
921921
pub fn lines(&self) -> Lines<'_> {
922-
Lines(self.split_terminator('\n').map(LinesAnyMap))
922+
Lines(self.split_inclusive('\n').map(LinesMap))
923923
}
924924

925925
/// An iterator over the lines of a string.
@@ -2558,10 +2558,8 @@ impl Default for &mut str {
25582558
impl_fn_for_zst! {
25592559
/// A nameable, cloneable fn type
25602560
#[derive(Clone)]
2561-
struct LinesAnyMap impl<'a> Fn = |line: &'a str| -> &'a str {
2562-
let l = line.len();
2563-
if l > 0 && line.as_bytes()[l - 1] == b'\r' { &line[0 .. l - 1] }
2564-
else { line }
2561+
struct LinesMap impl<'a> Fn = |line: &'a str| -> &'a str {
2562+
line.trim_newline()
25652563
};
25662564

25672565
#[derive(Clone)]

0 commit comments

Comments
 (0)