From b9a1601ef9ebd3b15c4a7739109038ab021dd485 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 9 Dec 2025 14:56:56 -0600 Subject: [PATCH 1/5] test(frontmatter): Show behavior for straw cr --- tests/ui/frontmatter/content-cr.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/ui/frontmatter/content-cr.rs diff --git a/tests/ui/frontmatter/content-cr.rs b/tests/ui/frontmatter/content-cr.rs new file mode 100644 index 0000000000000..768282fc2b937 --- /dev/null +++ b/tests/ui/frontmatter/content-cr.rs @@ -0,0 +1,11 @@ +--- +package.name = " " +package.description = "é" +--- + +// ignore-tidy-cr +//@ check-pass + +#![feature(frontmatter)] + +pub fn main() {} From 6838b54d2acc2add4c4fd1cb8d857e01731cd86a Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 9 Dec 2025 15:56:10 -0600 Subject: [PATCH 2/5] refactor(parse): Be consistent in naming --- compiler/rustc_parse/src/lexer/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index c62c8acced700..339b4064283e1 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -595,9 +595,9 @@ impl<'psess, 'src> Lexer<'psess, 'src> { let s = self.str_from(start); let real_start = s.find("---").unwrap(); let frontmatter_opening_pos = BytePos(real_start as u32) + start; - let s_new = &s[real_start..]; - let within = s_new.trim_start_matches('-'); - let len_opening = s_new.len() - within.len(); + let real_s = &s[real_start..]; + let within = real_s.trim_start_matches('-'); + let len_opening = real_s.len() - within.len(); let frontmatter_opening_end_pos = frontmatter_opening_pos + BytePos(len_opening as u32); if has_invalid_preceding_whitespace { From 63b09bff6256dabc888b513cc9b7691e8ac26239 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 9 Dec 2025 16:02:32 -0600 Subject: [PATCH 3/5] refactor(parse): Consistently use real_s --- compiler/rustc_parse/src/lexer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 339b4064283e1..d8970dfd79779 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -612,7 +612,7 @@ impl<'psess, 'src> Lexer<'psess, 'src> { } if invalid_infostring { - let line_end = s[real_start..].find('\n').unwrap_or(s[real_start..].len()); + let line_end = real_s.find('\n').unwrap_or(real_s.len()); let span = self.mk_sp( frontmatter_opening_end_pos, frontmatter_opening_pos + BytePos(line_end as u32), From d08b23b4694bbc742d27a78c7cd05836bf49b40e Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 9 Dec 2025 16:11:43 -0600 Subject: [PATCH 4/5] refactor(parse): Use a common frame of reference --- compiler/rustc_parse/src/lexer/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index d8970dfd79779..5195e66ce3728 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -620,10 +620,10 @@ impl<'psess, 'src> Lexer<'psess, 'src> { self.dcx().emit_err(errors::FrontmatterInvalidInfostring { span }); } - let last_line_start = within.rfind('\n').map_or(0, |i| i + 1); - let last_line = &within[last_line_start..]; + let last_line_start = real_s.rfind('\n').map_or(0, |i| i + 1); + let last_line = &real_s[last_line_start..]; let last_line_trimmed = last_line.trim_start_matches(is_horizontal_whitespace); - let last_line_start_pos = frontmatter_opening_end_pos + BytePos(last_line_start as u32); + let last_line_start_pos = frontmatter_opening_pos + BytePos(last_line_start as u32); let frontmatter_span = self.mk_sp(frontmatter_opening_pos, self.pos); self.psess.gated_spans.gate(sym::frontmatter, frontmatter_span); From 79212bae5b8c9f21be2eb3c86ea8eb7a29368778 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 9 Dec 2025 15:54:26 -0600 Subject: [PATCH 5/5] fix(parser): Disallow CR in frontmatter T-lang came back on the stabilization PR asking for CR to be disallowed with the intention of preventing confusing situations where a stray CR looks like a line break. If need arises for it, this could always be turned into a lint at a later point. --- compiler/rustc_parse/messages.ftl | 3 ++- compiler/rustc_parse/src/errors.rs | 7 +++++++ compiler/rustc_parse/src/lexer/mod.rs | 10 +++++++++- tests/ui/frontmatter/content-cr.rs | 3 +-- tests/ui/frontmatter/content-cr.stderr | 8 ++++++++ 5 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/ui/frontmatter/content-cr.stderr diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 87d1173c0d486..6dfb002a19eea 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -98,6 +98,8 @@ parse_bare_cr = {$double_quotes -> } .escape = escape the character +parse_bare_cr_in_frontmatter = bare CR not allowed in frontmatter + parse_bare_cr_in_raw_string = bare CR not allowed in raw string parse_binder_and_polarity = `for<...>` binder not allowed with `{$polarity}` trait polarity modifier @@ -350,7 +352,6 @@ parse_frontmatter_length_mismatch = frontmatter close does not match the opening parse_frontmatter_too_many_dashes = too many `-` symbols: frontmatter openings may be delimited by up to 255 `-` symbols, but found {$len_opening} parse_frontmatter_unclosed = unclosed frontmatter .note = frontmatter opening here was not closed - parse_function_body_equals_expr = function body cannot be `= expression;` .suggestion = surround the expression with `{"{"}` and `{"}"}` instead of `=` and `;` diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 698d8f76aaa64..a06614d83d4cc 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -829,6 +829,13 @@ pub(crate) struct FrontmatterTooManyDashes { pub len_opening: usize, } +#[derive(Diagnostic)] +#[diag(parse_bare_cr_in_frontmatter)] +pub(crate) struct BareCrFrontmatter { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(parse_leading_plus_not_supported)] pub(crate) struct LeadingPlusNotSupported { diff --git a/compiler/rustc_parse/src/lexer/mod.rs b/compiler/rustc_parse/src/lexer/mod.rs index 5195e66ce3728..e18acdb203bd3 100644 --- a/compiler/rustc_parse/src/lexer/mod.rs +++ b/compiler/rustc_parse/src/lexer/mod.rs @@ -611,8 +611,8 @@ impl<'psess, 'src> Lexer<'psess, 'src> { }); } + let line_end = real_s.find('\n').unwrap_or(real_s.len()); if invalid_infostring { - let line_end = real_s.find('\n').unwrap_or(real_s.len()); let span = self.mk_sp( frontmatter_opening_end_pos, frontmatter_opening_pos + BytePos(line_end as u32), @@ -621,6 +621,14 @@ impl<'psess, 'src> Lexer<'psess, 'src> { } let last_line_start = real_s.rfind('\n').map_or(0, |i| i + 1); + + let content = &real_s[line_end..last_line_start]; + if let Some(cr_offset) = content.find('\r') { + let cr_pos = start + BytePos((real_start + line_end + cr_offset) as u32); + let span = self.mk_sp(cr_pos, cr_pos + BytePos(1 as u32)); + self.dcx().emit_err(errors::BareCrFrontmatter { span }); + } + let last_line = &real_s[last_line_start..]; let last_line_trimmed = last_line.trim_start_matches(is_horizontal_whitespace); let last_line_start_pos = frontmatter_opening_pos + BytePos(last_line_start as u32); diff --git a/tests/ui/frontmatter/content-cr.rs b/tests/ui/frontmatter/content-cr.rs index 768282fc2b937..71bede928f377 100644 --- a/tests/ui/frontmatter/content-cr.rs +++ b/tests/ui/frontmatter/content-cr.rs @@ -1,10 +1,9 @@ --- -package.name = " " +package.name = " " # //~ ERROR bare CR not allowed in frontmatter package.description = "é" --- // ignore-tidy-cr -//@ check-pass #![feature(frontmatter)] diff --git a/tests/ui/frontmatter/content-cr.stderr b/tests/ui/frontmatter/content-cr.stderr new file mode 100644 index 0000000000000..d59b899cc4c39 --- /dev/null +++ b/tests/ui/frontmatter/content-cr.stderr @@ -0,0 +1,8 @@ +error: bare CR not allowed in frontmatter + --> $DIR/content-cr.rs:2:17 + | +LL | package.name = "␍" # + | ^ + +error: aborting due to 1 previous error +