Skip to content

Commit c1550e3

Browse files
committed
Auto merge of rust-lang#95590 - GuillaumeGomez:multi-line-attr-handling-doctest, r=notriddle
Fix multiline attributes handling in doctests Fixes rust-lang#55713. I needed to have access to the `unclosed_delims` field in order to check that the attribute was completely parsed and didn't have missing parts, so I created a getter for it. r? `@notriddle`
2 parents 76d770a + 732ed2a commit c1550e3

File tree

4 files changed

+84
-8
lines changed

4 files changed

+84
-8
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

+4
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ impl<'a> Parser<'a> {
210210
self.unclosed_delims.extend(snapshot.unclosed_delims.clone());
211211
}
212212

213+
pub fn unclosed_delims(&self) -> &[UnmatchedBrace] {
214+
&self.unclosed_delims
215+
}
216+
213217
/// Create a snapshot of the `Parser`.
214218
pub(super) fn create_snapshot_for_diagnostic(&self) -> SnapshotParser<'a> {
215219
let mut snapshot = self.clone();

src/librustdoc/doctest.rs

+62-8
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use rustc_interface::interface;
1010
use rustc_middle::hir::map::Map;
1111
use rustc_middle::hir::nested_filter;
1212
use rustc_middle::ty::TyCtxt;
13+
use rustc_parse::maybe_new_parser_from_source_str;
14+
use rustc_parse::parser::attr::InnerAttrPolicy;
1315
use rustc_session::config::{self, CrateType, ErrorOutputType};
16+
use rustc_session::parse::ParseSess;
1417
use rustc_session::{lint, DiagnosticOutput, Session};
1518
use rustc_span::edition::Edition;
1619
use rustc_span::source_map::SourceMap;
@@ -493,7 +496,7 @@ crate fn make_test(
493496
edition: Edition,
494497
test_id: Option<&str>,
495498
) -> (String, usize, bool) {
496-
let (crate_attrs, everything_else, crates) = partition_source(s);
499+
let (crate_attrs, everything_else, crates) = partition_source(s, edition);
497500
let everything_else = everything_else.trim();
498501
let mut line_offset = 0;
499502
let mut prog = String::new();
@@ -525,9 +528,7 @@ crate fn make_test(
525528
rustc_span::create_session_if_not_set_then(edition, |_| {
526529
use rustc_errors::emitter::{Emitter, EmitterWriter};
527530
use rustc_errors::Handler;
528-
use rustc_parse::maybe_new_parser_from_source_str;
529531
use rustc_parse::parser::ForceCollect;
530-
use rustc_session::parse::ParseSess;
531532
use rustc_span::source_map::FilePathMapping;
532533

533534
let filename = FileName::anon_source_code(s);
@@ -697,8 +698,39 @@ crate fn make_test(
697698
(prog, line_offset, supports_color)
698699
}
699700

700-
// FIXME(aburka): use a real parser to deal with multiline attributes
701-
fn partition_source(s: &str) -> (String, String, String) {
701+
fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool {
702+
if source.is_empty() {
703+
// Empty content so nothing to check in here...
704+
return true;
705+
}
706+
rustc_span::create_session_if_not_set_then(edition, |_| {
707+
let filename = FileName::anon_source_code(source);
708+
let sess = ParseSess::with_silent_emitter(None);
709+
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source.to_owned())
710+
{
711+
Ok(p) => p,
712+
Err(_) => {
713+
debug!("Cannot build a parser to check mod attr so skipping...");
714+
return true;
715+
}
716+
};
717+
// If a parsing error happened, it's very likely that the attribute is incomplete.
718+
if !parser.parse_attribute(InnerAttrPolicy::Permitted).is_ok() {
719+
return false;
720+
}
721+
// We now check if there is an unclosed delimiter for the attribute. To do so, we look at
722+
// the `unclosed_delims` and see if the opening square bracket was closed.
723+
parser
724+
.unclosed_delims()
725+
.get(0)
726+
.map(|unclosed| {
727+
unclosed.unclosed_span.map(|s| s.lo()).unwrap_or(BytePos(0)) != BytePos(2)
728+
})
729+
.unwrap_or(true)
730+
})
731+
}
732+
733+
fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
702734
#[derive(Copy, Clone, PartialEq)]
703735
enum PartitionState {
704736
Attrs,
@@ -710,15 +742,23 @@ fn partition_source(s: &str) -> (String, String, String) {
710742
let mut crates = String::new();
711743
let mut after = String::new();
712744

745+
let mut mod_attr_pending = String::new();
746+
713747
for line in s.lines() {
714748
let trimline = line.trim();
715749

716750
// FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
717751
// shunted into "everything else"
718752
match state {
719753
PartitionState::Attrs => {
720-
state = if trimline.starts_with("#![")
721-
|| trimline.chars().all(|c| c.is_whitespace())
754+
state = if trimline.starts_with("#![") {
755+
if !check_if_attr_is_complete(line, edition) {
756+
mod_attr_pending = line.to_owned();
757+
} else {
758+
mod_attr_pending.clear();
759+
}
760+
PartitionState::Attrs
761+
} else if trimline.chars().all(|c| c.is_whitespace())
722762
|| (trimline.starts_with("//") && !trimline.starts_with("///"))
723763
{
724764
PartitionState::Attrs
@@ -727,7 +767,21 @@ fn partition_source(s: &str) -> (String, String, String) {
727767
{
728768
PartitionState::Crates
729769
} else {
730-
PartitionState::Other
770+
// First we check if the previous attribute was "complete"...
771+
if !mod_attr_pending.is_empty() {
772+
// If not, then we append the new line into the pending attribute to check
773+
// if this time it's complete...
774+
mod_attr_pending.push_str(line);
775+
if !trimline.is_empty() && check_if_attr_is_complete(line, edition) {
776+
// If it's complete, then we can clear the pending content.
777+
mod_attr_pending.clear();
778+
}
779+
// In any case, this is considered as `PartitionState::Attrs` so it's
780+
// prepended before rustdoc's inserts.
781+
PartitionState::Attrs
782+
} else {
783+
PartitionState::Other
784+
}
731785
};
732786
}
733787
PartitionState::Crates => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// compile-flags:--test
2+
// normalize-stdout-test: "src/test/rustdoc-ui" -> "$$DIR"
3+
// normalize-stdout-test "finished in \d+\.\d+s" -> "finished in $$TIME"
4+
// check-pass
5+
6+
/// ```
7+
/// # #![cfg_attr(not(dox), deny(missing_abi,
8+
/// # non_ascii_idents))]
9+
///
10+
/// pub struct Bar;
11+
/// ```
12+
pub struct Bar;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
running 1 test
3+
test $DIR/doc-comment-multi-line-cfg-attr.rs - Bar (line 6) ... ok
4+
5+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
6+

0 commit comments

Comments
 (0)