Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions compiler/rustc_mir_build/src/thir/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,8 +1100,7 @@ fn report_arm_reachability<'p, 'tcx>(
let arm_span = cx.tcx.hir_span(hir_id);
let whole_arm_span = if is_match_arm {
// If the arm is followed by a comma, extend the span to include it.
let with_whitespace = sm.span_extend_while_whitespace(arm_span);
if let Some(comma) = sm.span_look_ahead(with_whitespace, ",", Some(1)) {
if let Some(comma) = sm.span_followed_by(arm_span, ",") {
Some(arm_span.to(comma))
} else {
Some(arm_span)
Expand Down
8 changes: 3 additions & 5 deletions compiler/rustc_resolve/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1467,11 +1467,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
// `const name: Ty = expr;`. This is a heuristic, it will
// break down in the presence of macros.
let sm = self.tcx.sess.source_map();
let type_span = match sm.span_look_ahead(
original_rib_ident_def.span,
":",
None,
) {
let type_span = match sm
.span_followed_by(original_rib_ident_def.span, ":")
{
None => {
Some(original_rib_ident_def.span.shrink_to_hi())
}
Expand Down
17 changes: 15 additions & 2 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1987,10 +1987,23 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
// where a brace being opened means a block is being started. Look
// ahead for the next text to see if `span` is followed by a `{`.
let sm = self.r.tcx.sess.source_map();
if let Some(followed_brace_span) = sm.span_look_ahead(span, "{", Some(50)) {
if let Some(open_brace_span) = sm.span_followed_by(span, "{") {
// In case this could be a struct literal that needs to be surrounded
// by parentheses, find the appropriate span.
let close_brace_span = sm.span_look_ahead(followed_brace_span, "}", Some(50));
let close_brace_span =
Copy link
Copy Markdown
Member Author

@chenyukang chenyukang Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can not use span_look_ahead here, because we only want to make sure there is a } in 50 range.

otherwise we can not handle the scenario like U { /* keep comment here */ }, since there are some non whitespace chars inside { }.

https://github.com/rust-lang/rust/pull/154745/changes#diff-68dd28577bfdbdba4a4c4ee9d20797f2f0e3939d926d4bdcbe13b1117a2dbe32R35-R38 is the test case for this.

sm.span_to_next_source(open_brace_span).ok().and_then(|next_source| {
let offset = next_source.find('}')?;
if next_source[..offset].chars().count() >= 50 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment explaining what this >= 50 check is doing, and why 50 was chosen.

return None;
}
let start = open_brace_span.hi() + rustc_span::BytePos(offset as u32);
Some(Span::new(
start,
start + rustc_span::BytePos(1_u32),
open_brace_span.ctxt(),
None,
))
});
let closing_brace = close_brace_span.map(|sp| span.to(sp));
(true, closing_brace)
} else {
Expand Down
22 changes: 7 additions & 15 deletions compiler/rustc_span/src/source_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -964,21 +964,13 @@ impl SourceMap {
Span::new(BytePos(start_of_next_point), end_of_next_point, sp.ctxt, None)
}

/// Check whether span is followed by some specified expected string in limit scope
pub fn span_look_ahead(&self, span: Span, expect: &str, limit: Option<usize>) -> Option<Span> {
let mut sp = span;
for _ in 0..limit.unwrap_or(100_usize) {
sp = self.next_point(sp);
if let Ok(ref snippet) = self.span_to_snippet(sp) {
if snippet == expect {
return Some(sp);
}
if snippet.chars().any(|c| !c.is_whitespace()) {
break;
}
}
}
None
/// Check whether span is followed by some specified target string, ignoring whitespace.
/// *Only suitable for diagnostics.*
pub fn span_followed_by(&self, span: Span, target: &str) -> Option<Span> {
let span = self.span_extend_while_whitespace(span);
self.span_to_next_source(span).ok()?.strip_prefix(target).map(|_| {
Span::new(span.hi(), span.hi() + BytePos(target.len() as u32), span.ctxt(), None)
})
}

/// Finds the width of the character, either before or after the end of provided span,
Expand Down
19 changes: 19 additions & 0 deletions compiler/rustc_span/src/source_map/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,25 @@ fn test_next_point() {
assert!(sm.span_to_snippet(span).is_err());
}

#[test]
fn test_span_followed_by_stops_at_end_of_file() {
let sm = SourceMap::new(FilePathMapping::empty());
sm.new_source_file(filename(&sm, "example.rs"), "x".to_string());

let span = Span::with_root_ctxt(BytePos(0), BytePos(1));
assert_eq!(sm.span_followed_by(span, "y"), None);
}

#[test]
fn test_span_followed_by_skips_whitespace() {
let sm = SourceMap::new(FilePathMapping::empty());
sm.new_source_file(filename(&sm, "example.rs"), "x \n yz".to_string());

let span = Span::with_root_ctxt(BytePos(0), BytePos(1));
let span = sm.span_followed_by(span, "yz").unwrap();
assert_eq!(sm.span_to_snippet(span), Ok("yz".to_string()));
}

#[cfg(target_os = "linux")]
#[test]
fn read_binary_file_handles_lying_stat() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
(cand.self_ty().kind(), main_trait_predicate.self_ty().skip_binder().kind())
{
// Wrap method receivers and `&`-references in parens
let suggestion = if self.tcx.sess.source_map().span_look_ahead(span, ".", Some(50)).is_some() {
let suggestion = if self.tcx.sess.source_map().span_followed_by(span, ".").is_some() {
vec![
(span.shrink_to_lo(), format!("(")),
(span.shrink_to_hi(), format!(" as {})", cand.self_ty())),
Expand Down
18 changes: 18 additions & 0 deletions tests/ui/error-codes/E0423-struct-literal-comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#[derive(PartialEq)]
struct T { pub x: i32 }

#[derive(PartialEq)]
struct U { }

fn main() {
// Parser will report an error here
if T { x: 10 } == T {} {}
//~^ ERROR struct literals are not allowed here
//~| ERROR expected value, found struct `T`

// Regression test for the `followed_by_brace` helper:
// comments inside the braces should not suppress the parenthesized struct literal suggestion.
if U { /* keep comment here */ } == U {}
//~^ ERROR E0423
//~| ERROR expected expression, found `==`
}
42 changes: 42 additions & 0 deletions tests/ui/error-codes/E0423-struct-literal-comment.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
error: struct literals are not allowed here
--> $DIR/E0423-struct-literal-comment.rs:9:8
|
LL | if T { x: 10 } == T {} {}
| ^^^^^^^^^^^
|
help: surround the struct literal with parentheses
|
LL | if (T { x: 10 }) == T {} {}
| + +

error: expected expression, found `==`
--> $DIR/E0423-struct-literal-comment.rs:15:38
|
LL | if U { /* keep comment here */ } == U {}
| ^^ expected expression

error[E0423]: expected value, found struct `T`
--> $DIR/E0423-struct-literal-comment.rs:9:23
|
LL | if T { x: 10 } == T {} {}
| ^ not a value
|
help: surround the struct literal with parentheses
|
LL | if T { x: 10 } == (T {}) {}
| + +

error[E0423]: expected value, found struct `U`
--> $DIR/E0423-struct-literal-comment.rs:15:8
|
LL | if U { /* keep comment here */ } == U {}
| ^ not a value
|
help: surround the struct literal with parentheses
|
LL | if (U { /* keep comment here */ }) == U {}
| + +

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0423`.
Loading