Skip to content

Commit 6961413

Browse files
Correctly handle stability when #![feature(staged_api)] is used
1 parent 0f51bb2 commit 6961413

File tree

2 files changed

+126
-84
lines changed

2 files changed

+126
-84
lines changed

src/librustdoc/doctest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ fn run_test(
785785
if doctest.no_run && !langstr.compile_fail && rustdoc_options.persist_doctests.is_none() {
786786
// FIXME: why does this code check if it *shouldn't* persist doctests
787787
// -- shouldn't it be the negation?
788-
compiler_args.push("--emit=metadata".to_owned());
788+
compiler.arg("--emit=metadata".to_owned());
789789
}
790790
}
791791

@@ -811,7 +811,7 @@ fn run_test(
811811
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
812812
stdin.write_all(doctest.full_test_code.as_bytes()).expect("could write out test sources");
813813

814-
if !langstr.compile_fail && langstr.should_panic {
814+
if is_should_panic {
815815
match compile_merged_doctest_and_caller_binary(
816816
child,
817817
&doctest,

src/librustdoc/doctest/make.rs

Lines changed: 124 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ struct ParseSourceInfo {
3434
crates: String,
3535
crate_attrs: String,
3636
maybe_crate_attrs: String,
37+
need_stability_attr: bool,
3738
}
3839

3940
/// Builder type for `DocTestBuilder`.
@@ -147,6 +148,7 @@ impl<'a> BuildDocTestBuilder<'a> {
147148
crates,
148149
crate_attrs,
149150
maybe_crate_attrs,
151+
need_stability_attr,
150152
})) = result
151153
else {
152154
// If the AST returned an error, we don't want this doctest to be merged with the
@@ -184,6 +186,7 @@ impl<'a> BuildDocTestBuilder<'a> {
184186
test_id,
185187
invalid_ast: false,
186188
can_be_merged,
189+
need_stability_attr,
187190
}
188191
}
189192
}
@@ -204,6 +207,7 @@ pub(crate) struct DocTestBuilder {
204207
pub(crate) test_id: Option<String>,
205208
pub(crate) invalid_ast: bool,
206209
pub(crate) can_be_merged: bool,
210+
need_stability_attr: bool,
207211
}
208212

209213
/// Contains needed information for doctest to be correctly generated with expected "wrapping".
@@ -301,6 +305,7 @@ impl DocTestBuilder {
301305
test_id,
302306
invalid_ast: true,
303307
can_be_merged: false,
308+
need_stability_attr: false,
304309
}
305310
}
306311

@@ -379,53 +384,58 @@ impl DocTestBuilder {
379384
}
380385

381386
// FIXME: This code cannot yet handle no_std test cases yet
382-
let wrapper = if dont_insert_main
383-
|| self.has_main_fn
384-
|| crate_level_code.contains("![no_std]")
385-
{
386-
None
387-
} else {
388-
let returns_result = processed_code.ends_with("(())");
389-
// Give each doctest main function a unique name.
390-
// This is for example needed for the tooling around `-C instrument-coverage`.
391-
let inner_fn_name = if let Some(ref test_id) = self.test_id {
392-
format!("_doctest_main_{test_id}")
387+
let wrapper =
388+
if dont_insert_main || self.has_main_fn || crate_level_code.contains("![no_std]") {
389+
None
393390
} else {
394-
"_inner".into()
395-
};
396-
let inner_attr = if self.test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
397-
let (main_pre, main_post) = if returns_result {
398-
(
399-
format!(
400-
"pub fn main() {{ {inner_attr}fn {inner_fn_name}() -> core::result::Result<(), impl core::fmt::Debug> {{\n",
401-
),
402-
format!("\n}} {inner_fn_name}().unwrap() }}"),
403-
)
404-
} else if self.test_id.is_some() {
405-
(
406-
format!("pub fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n",),
407-
format!("\n}} {inner_fn_name}() }}"),
408-
)
409-
} else {
410-
("pub fn main() {\n".into(), "\n}".into())
391+
let extra = if self.need_stability_attr {
392+
"#[stable(feature = \"doctest\", since = \"1.0.0\")]\n"
393+
} else {
394+
""
395+
};
396+
let returns_result = processed_code.ends_with("(())");
397+
// Give each doctest main function a unique name.
398+
// This is for example needed for the tooling around `-C instrument-coverage`.
399+
let inner_fn_name = if let Some(ref test_id) = self.test_id {
400+
format!("_doctest_main_{test_id}")
401+
} else {
402+
"_inner".into()
403+
};
404+
let inner_attr =
405+
if self.test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
406+
let (main_pre, main_post) = if returns_result {
407+
(
408+
format!(
409+
"{extra}pub fn main() {{ {inner_attr}fn {inner_fn_name}() \
410+
-> core::result::Result<(), impl core::fmt::Debug> {{\n",
411+
),
412+
format!("\n}} {inner_fn_name}().unwrap() }}"),
413+
)
414+
} else if self.test_id.is_some() {
415+
(
416+
format!("{extra}pub fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n",),
417+
format!("\n}} {inner_fn_name}() }}"),
418+
)
419+
} else {
420+
(format!("{extra}pub fn main() {{\n"), "\n}".into())
421+
};
422+
// Note on newlines: We insert a line/newline *before*, and *after*
423+
// the doctest and adjust the `line_offset` accordingly.
424+
// In the case of `-C instrument-coverage`, this means that the generated
425+
// inner `main` function spans from the doctest opening codeblock to the
426+
// closing one. For example
427+
// /// ``` <- start of the inner main
428+
// /// <- code under doctest
429+
// /// ``` <- end of the inner main
430+
line_offset += 1;
431+
432+
Some(WrapperInfo {
433+
before: main_pre,
434+
after: main_post,
435+
returns_result,
436+
insert_indent_space: opts.insert_indent_space,
437+
})
411438
};
412-
// Note on newlines: We insert a line/newline *before*, and *after*
413-
// the doctest and adjust the `line_offset` accordingly.
414-
// In the case of `-C instrument-coverage`, this means that the generated
415-
// inner `main` function spans from the doctest opening codeblock to the
416-
// closing one. For example
417-
// /// ``` <- start of the inner main
418-
// /// <- code under doctest
419-
// /// ``` <- end of the inner main
420-
line_offset += 1;
421-
422-
Some(WrapperInfo {
423-
before: main_pre,
424-
after: main_post,
425-
returns_result,
426-
insert_indent_space: opts.insert_indent_space,
427-
})
428-
};
429439

430440
(
431441
DocTestWrapResult::Valid {
@@ -575,6 +585,7 @@ fn parse_source(
575585
let mut prev_span_hi = 0;
576586
let not_crate_attrs = &[sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect];
577587
let parsed = parser.parse_item(rustc_parse::parser::ForceCollect::No);
588+
let mut need_crate_stability_attr = false;
578589

579590
let result = match parsed {
580591
Ok(Some(ref item))
@@ -600,58 +611,76 @@ fn parse_source(
600611
);
601612
}
602613
} else {
614+
if !info.need_stability_attr
615+
&& attr.has_name(sym::feature)
616+
&& let Some(sub_attrs) = attr.meta_item_list()
617+
&& sub_attrs.iter().any(|attr| {
618+
if let ast::MetaItemInner::MetaItem(attr) = attr {
619+
matches!(attr.kind, ast::MetaItemKind::Word)
620+
&& attr.has_name(sym::staged_api)
621+
} else {
622+
false
623+
}
624+
})
625+
{
626+
info.need_stability_attr = true;
627+
}
628+
if attr.has_any_name(&[sym::stable, sym::unstable]) {
629+
need_crate_stability_attr = false;
630+
}
603631
push_to_s(&mut info.crate_attrs, source, attr.span, &mut prev_span_hi);
604632
}
605633
}
606634
let mut has_non_items = false;
635+
let mut fn_main_pos = None;
607636
for stmt in &body.stmts {
608637
let mut is_extern_crate = false;
609638
match stmt.kind {
610639
StmtKind::Item(ref item) => {
611640
let (found_is_extern_crate, found_main) =
612641
check_item(item, &mut info, crate_name);
613642
is_extern_crate = found_is_extern_crate;
614-
if found_main
615-
&& should_panic
616-
&& !matches!(item.vis.kind, VisibilityKind::Public)
617-
{
618-
if matches!(item.vis.kind, VisibilityKind::Inherited) {
619-
push_code(
620-
stmt,
621-
is_extern_crate,
622-
&mut info,
623-
source,
624-
&mut prev_span_hi,
625-
Some(item.span.lo()),
626-
);
627-
} else {
628-
push_code(
629-
stmt,
630-
is_extern_crate,
631-
&mut info,
643+
if found_main {
644+
fn_main_pos = Some(info.everything_else.len());
645+
if !matches!(item.vis.kind, VisibilityKind::Public) && should_panic {
646+
if matches!(item.vis.kind, VisibilityKind::Inherited) {
647+
push_code(
648+
stmt,
649+
is_extern_crate,
650+
&mut info,
651+
source,
652+
&mut prev_span_hi,
653+
Some(item.span.lo()),
654+
);
655+
} else {
656+
push_code(
657+
stmt,
658+
is_extern_crate,
659+
&mut info,
660+
source,
661+
&mut prev_span_hi,
662+
Some(item.vis.span.lo()),
663+
);
664+
prev_span_hi +=
665+
(item.vis.span.hi().0 - item.vis.span.lo().0) as usize;
666+
};
667+
if !info
668+
.everything_else
669+
.chars()
670+
.last()
671+
.is_some_and(|c| c.is_whitespace())
672+
{
673+
info.everything_else.push(' ');
674+
}
675+
info.everything_else.push_str("pub ");
676+
push_to_s(
677+
&mut info.everything_else,
632678
source,
679+
item.span,
633680
&mut prev_span_hi,
634-
Some(item.vis.span.lo()),
635681
);
636-
prev_span_hi +=
637-
(item.vis.span.hi().0 - item.vis.span.lo().0) as usize;
638-
};
639-
if !info
640-
.everything_else
641-
.chars()
642-
.last()
643-
.is_some_and(|c| c.is_whitespace())
644-
{
645-
info.everything_else.push(' ');
682+
continue;
646683
}
647-
info.everything_else.push_str("pub ");
648-
push_to_s(
649-
&mut info.everything_else,
650-
source,
651-
item.span,
652-
&mut prev_span_hi,
653-
);
654-
continue;
655684
}
656685
}
657686
// We assume that the macro calls will expand to item(s) even though they could
@@ -690,6 +719,19 @@ fn parse_source(
690719
}
691720
push_code(stmt, is_extern_crate, &mut info, source, &mut prev_span_hi, None);
692721
}
722+
if info.need_stability_attr {
723+
if let Some(fn_main_pos) = fn_main_pos {
724+
info.everything_else.insert_str(
725+
fn_main_pos,
726+
"#[stable(feature = \"doctest\", since = \"1.0.0\")]\n",
727+
);
728+
info.need_stability_attr = false;
729+
}
730+
if need_crate_stability_attr {
731+
info.crate_attrs
732+
.push_str("#![stable(feature = \"doctest\", since = \"1.0.0\")]\n");
733+
}
734+
}
693735
if has_non_items {
694736
if info.has_main_fn
695737
&& let Some(dcx) = parent_dcx

0 commit comments

Comments
 (0)