Auto merge of #95590 - GuillaumeGomez:multi-line-attr-handling-doctest, r=notriddle
Fix multiline attributes handling in doctests Fixes #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`
This commit is contained in:
commit
c1550e3f8c
4 changed files with 84 additions and 8 deletions
|
@ -210,6 +210,10 @@ impl<'a> Parser<'a> {
|
||||||
self.unclosed_delims.extend(snapshot.unclosed_delims.clone());
|
self.unclosed_delims.extend(snapshot.unclosed_delims.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unclosed_delims(&self) -> &[UnmatchedBrace] {
|
||||||
|
&self.unclosed_delims
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a snapshot of the `Parser`.
|
/// Create a snapshot of the `Parser`.
|
||||||
pub(super) fn create_snapshot_for_diagnostic(&self) -> SnapshotParser<'a> {
|
pub(super) fn create_snapshot_for_diagnostic(&self) -> SnapshotParser<'a> {
|
||||||
let mut snapshot = self.clone();
|
let mut snapshot = self.clone();
|
||||||
|
|
|
@ -10,7 +10,10 @@ use rustc_interface::interface;
|
||||||
use rustc_middle::hir::map::Map;
|
use rustc_middle::hir::map::Map;
|
||||||
use rustc_middle::hir::nested_filter;
|
use rustc_middle::hir::nested_filter;
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
|
use rustc_parse::maybe_new_parser_from_source_str;
|
||||||
|
use rustc_parse::parser::attr::InnerAttrPolicy;
|
||||||
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
use rustc_session::config::{self, CrateType, ErrorOutputType};
|
||||||
|
use rustc_session::parse::ParseSess;
|
||||||
use rustc_session::{lint, DiagnosticOutput, Session};
|
use rustc_session::{lint, DiagnosticOutput, Session};
|
||||||
use rustc_span::edition::Edition;
|
use rustc_span::edition::Edition;
|
||||||
use rustc_span::source_map::SourceMap;
|
use rustc_span::source_map::SourceMap;
|
||||||
|
@ -493,7 +496,7 @@ crate fn make_test(
|
||||||
edition: Edition,
|
edition: Edition,
|
||||||
test_id: Option<&str>,
|
test_id: Option<&str>,
|
||||||
) -> (String, usize, bool) {
|
) -> (String, usize, bool) {
|
||||||
let (crate_attrs, everything_else, crates) = partition_source(s);
|
let (crate_attrs, everything_else, crates) = partition_source(s, edition);
|
||||||
let everything_else = everything_else.trim();
|
let everything_else = everything_else.trim();
|
||||||
let mut line_offset = 0;
|
let mut line_offset = 0;
|
||||||
let mut prog = String::new();
|
let mut prog = String::new();
|
||||||
|
@ -525,9 +528,7 @@ crate fn make_test(
|
||||||
rustc_span::create_session_if_not_set_then(edition, |_| {
|
rustc_span::create_session_if_not_set_then(edition, |_| {
|
||||||
use rustc_errors::emitter::{Emitter, EmitterWriter};
|
use rustc_errors::emitter::{Emitter, EmitterWriter};
|
||||||
use rustc_errors::Handler;
|
use rustc_errors::Handler;
|
||||||
use rustc_parse::maybe_new_parser_from_source_str;
|
|
||||||
use rustc_parse::parser::ForceCollect;
|
use rustc_parse::parser::ForceCollect;
|
||||||
use rustc_session::parse::ParseSess;
|
|
||||||
use rustc_span::source_map::FilePathMapping;
|
use rustc_span::source_map::FilePathMapping;
|
||||||
|
|
||||||
let filename = FileName::anon_source_code(s);
|
let filename = FileName::anon_source_code(s);
|
||||||
|
@ -697,8 +698,39 @@ crate fn make_test(
|
||||||
(prog, line_offset, supports_color)
|
(prog, line_offset, supports_color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(aburka): use a real parser to deal with multiline attributes
|
fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool {
|
||||||
fn partition_source(s: &str) -> (String, String, String) {
|
if source.is_empty() {
|
||||||
|
// Empty content so nothing to check in here...
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
rustc_span::create_session_if_not_set_then(edition, |_| {
|
||||||
|
let filename = FileName::anon_source_code(source);
|
||||||
|
let sess = ParseSess::with_silent_emitter(None);
|
||||||
|
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source.to_owned())
|
||||||
|
{
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => {
|
||||||
|
debug!("Cannot build a parser to check mod attr so skipping...");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// If a parsing error happened, it's very likely that the attribute is incomplete.
|
||||||
|
if !parser.parse_attribute(InnerAttrPolicy::Permitted).is_ok() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// We now check if there is an unclosed delimiter for the attribute. To do so, we look at
|
||||||
|
// the `unclosed_delims` and see if the opening square bracket was closed.
|
||||||
|
parser
|
||||||
|
.unclosed_delims()
|
||||||
|
.get(0)
|
||||||
|
.map(|unclosed| {
|
||||||
|
unclosed.unclosed_span.map(|s| s.lo()).unwrap_or(BytePos(0)) != BytePos(2)
|
||||||
|
})
|
||||||
|
.unwrap_or(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
enum PartitionState {
|
enum PartitionState {
|
||||||
Attrs,
|
Attrs,
|
||||||
|
@ -710,6 +742,8 @@ fn partition_source(s: &str) -> (String, String, String) {
|
||||||
let mut crates = String::new();
|
let mut crates = String::new();
|
||||||
let mut after = String::new();
|
let mut after = String::new();
|
||||||
|
|
||||||
|
let mut mod_attr_pending = String::new();
|
||||||
|
|
||||||
for line in s.lines() {
|
for line in s.lines() {
|
||||||
let trimline = line.trim();
|
let trimline = line.trim();
|
||||||
|
|
||||||
|
@ -717,8 +751,14 @@ fn partition_source(s: &str) -> (String, String, String) {
|
||||||
// shunted into "everything else"
|
// shunted into "everything else"
|
||||||
match state {
|
match state {
|
||||||
PartitionState::Attrs => {
|
PartitionState::Attrs => {
|
||||||
state = if trimline.starts_with("#![")
|
state = if trimline.starts_with("#![") {
|
||||||
|| trimline.chars().all(|c| c.is_whitespace())
|
if !check_if_attr_is_complete(line, edition) {
|
||||||
|
mod_attr_pending = line.to_owned();
|
||||||
|
} else {
|
||||||
|
mod_attr_pending.clear();
|
||||||
|
}
|
||||||
|
PartitionState::Attrs
|
||||||
|
} else if trimline.chars().all(|c| c.is_whitespace())
|
||||||
|| (trimline.starts_with("//") && !trimline.starts_with("///"))
|
|| (trimline.starts_with("//") && !trimline.starts_with("///"))
|
||||||
{
|
{
|
||||||
PartitionState::Attrs
|
PartitionState::Attrs
|
||||||
|
@ -726,8 +766,22 @@ fn partition_source(s: &str) -> (String, String, String) {
|
||||||
|| trimline.starts_with("#[macro_use] extern crate")
|
|| trimline.starts_with("#[macro_use] extern crate")
|
||||||
{
|
{
|
||||||
PartitionState::Crates
|
PartitionState::Crates
|
||||||
|
} else {
|
||||||
|
// First we check if the previous attribute was "complete"...
|
||||||
|
if !mod_attr_pending.is_empty() {
|
||||||
|
// If not, then we append the new line into the pending attribute to check
|
||||||
|
// if this time it's complete...
|
||||||
|
mod_attr_pending.push_str(line);
|
||||||
|
if !trimline.is_empty() && check_if_attr_is_complete(line, edition) {
|
||||||
|
// If it's complete, then we can clear the pending content.
|
||||||
|
mod_attr_pending.clear();
|
||||||
|
}
|
||||||
|
// In any case, this is considered as `PartitionState::Attrs` so it's
|
||||||
|
// prepended before rustdoc's inserts.
|
||||||
|
PartitionState::Attrs
|
||||||
} else {
|
} else {
|
||||||
PartitionState::Other
|
PartitionState::Other
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
PartitionState::Crates => {
|
PartitionState::Crates => {
|
||||||
|
|
12
src/test/rustdoc-ui/doc-comment-multi-line-cfg-attr.rs
Normal file
12
src/test/rustdoc-ui/doc-comment-multi-line-cfg-attr.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// compile-flags:--test
|
||||||
|
// normalize-stdout-test: "src/test/rustdoc-ui" -> "$$DIR"
|
||||||
|
// normalize-stdout-test "finished in \d+\.\d+s" -> "finished in $$TIME"
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// # #![cfg_attr(not(dox), deny(missing_abi,
|
||||||
|
/// # non_ascii_idents))]
|
||||||
|
///
|
||||||
|
/// pub struct Bar;
|
||||||
|
/// ```
|
||||||
|
pub struct Bar;
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
test $DIR/doc-comment-multi-line-cfg-attr.rs - Bar (line 6) ... ok
|
||||||
|
|
||||||
|
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue