Merge from rustc

This commit is contained in:
The Miri Cronjob Bot 2025-05-27 05:02:07 +00:00
commit 1c73fb9fc4
274 changed files with 3618 additions and 2749 deletions

View file

@ -118,15 +118,25 @@ impl Step for Miri {
fn run(self, builder: &Builder<'_>) {
let host = builder.build.build;
let target = self.target;
let stage = builder.top_stage;
// `x run` uses stage 0 by default but miri does not work well with stage 0.
// Change the stage to 1 if it's not set explicitly.
let stage = if builder.config.is_explicit_stage() || builder.top_stage >= 1 {
builder.top_stage
} else {
1
};
if stage == 0 {
eprintln!("miri cannot be run at stage 0");
std::process::exit(1);
}
// This compiler runs on the host, we'll just use it for the target.
let target_compiler = builder.compiler(stage, host);
let host_compiler = tool::get_tool_rustc_compiler(builder, target_compiler);
let target_compiler = builder.compiler(stage, target);
let miri_build = builder.ensure(tool::Miri { compiler: target_compiler, target });
// Rustc tools are off by one stage, so use the build compiler to run miri.
let host_compiler = miri_build.build_compiler;
// Get a target sysroot for Miri.
let miri_sysroot = test::Miri::build_miri_sysroot(builder, target_compiler, target);

View file

@ -739,7 +739,7 @@ impl Step for Clippy {
const DEFAULT: bool = false;
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
run.path("src/tools/clippy")
run.suite_path("src/tools/clippy/tests").path("src/tools/clippy")
}
fn make_run(run: RunConfig<'_>) {
@ -783,6 +783,23 @@ impl Step for Clippy {
let host_libs = builder.stage_out(compiler, Mode::ToolRustc).join(builder.cargo_dir());
cargo.env("HOST_LIBS", host_libs);
// Collect paths of tests to run
'partially_test: {
let paths = &builder.config.paths[..];
let mut test_names = Vec::new();
for path in paths {
if let Some(path) =
helpers::is_valid_test_suite_arg(path, "src/tools/clippy/tests", builder)
{
test_names.push(path);
} else if path.ends_with("src/tools/clippy") {
// When src/tools/clippy is called directly, all tests should be run.
break 'partially_test;
}
}
cargo.env("TESTNAME", test_names.join(","));
}
cargo.add_rustc_lib_path(builder);
let cargo = prepare_cargo_test(cargo, &[], &[], host, builder);
@ -2947,7 +2964,14 @@ impl Step for Distcheck {
run.builder.ensure(Distcheck);
}
/// Runs "distcheck", a 'make check' from a tarball
/// Runs `distcheck`, which is a collection of smoke tests:
///
/// - Run `make check` from an unpacked dist tarball to make sure we can at the minimum run
/// check steps from those sources.
/// - Check that selected dist components (`rust-src` only at the moment) at least have expected
/// directory shape and crate manifests that cargo can generate a lockfile from.
///
/// FIXME(#136822): dist components are under-tested.
fn run(self, builder: &Builder<'_>) {
builder.info("Distcheck");
let dir = builder.tempdir().join("distcheck");

View file

@ -1,3 +1,15 @@
# Runs `distcheck`, which is a collection of smoke tests:
#
# - Run `make check` from an unpacked dist tarball to make sure we can at the
# minimum run check steps from those sources.
# - Check that selected dist components at least have expected directory shape
# and crate manifests that cargo can generate a lockfile from.
#
# Refer to `src/bootstrap/src/core/build_steps/test.rs` `Distcheck::run` for
# specifics.
#
# FIXME(#136822): dist components are generally under-tested.
FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive

View file

@ -123,7 +123,7 @@ pr:
DOCKER_SCRIPT: x86_64-gnu-llvm.sh
<<: *job-linux-16c
- name: x86_64-gnu-tools
<<: *job-linux-16c
<<: *job-linux-36c-codebuild
# Jobs that run when you perform a try build (@bors try)
# These jobs automatically inherit envs.try, to avoid repeating

View file

@ -0,0 +1,8 @@
# Unstable Book
These are the sources for <https://doc.rust-lang.org/nightly/unstable-book/>.
To generate them, run `./x doc unstable-book`, which will generate HTML files in `build/host/doc/unstable-book` using `src/tools/rustbook`.
If you need to change the overall structure, modify `src/tools/unstable-book-gen/src/SUMMARY.md`.
Note that most of this book is autogenerated by `unstable-book-gen`, with the exception of `compiler-flags` and `compiler-environment-variables`.
As a result, it does not integrate well with `mdbook`. Use `./x doc` instead.

View file

@ -0,0 +1 @@
# Compiler environment variables

View file

@ -14,7 +14,7 @@ Cargo disallows setting `cargo::rustc-env=RUSTC_BOOTSTRAP` in build scripts.
Build systems can limit the features they enable with [`-Z allow-features=feature1,feature2`][Z-allow-features].
Crates can fully opt out of unstable features by using [`#![forbid(unstable_features)]`][unstable-features] at the crate root (or any other way of enabling lints, such as `-F unstable-features`).
[Z-allow-features]: ./allow-features.html
[Z-allow-features]: ../compiler-flags/allow-features.html
[unstable-features]: ../../rustc/lints/listing/allowed-by-default.html#unstable-features
## Why does this environment variable exist?

View file

@ -11,4 +11,4 @@ Features are comma-separated, for example `-Z allow-features=ffi_pure,f16`.
If the flag is present, any feature listed will be allowed and any feature not listed will be disallowed.
Any unrecognized feature is ignored.
[`RUSTC_BOOTSTRAP`]: ./rustc-bootstrap.html
[`RUSTC_BOOTSTRAP`]: ../compiler-environment-variables/RUSTC_BOOTSTRAP.html

View file

@ -610,6 +610,9 @@ impl Item {
UnionItem(ref union_) => Some(union_.has_stripped_entries()),
EnumItem(ref enum_) => Some(enum_.has_stripped_entries()),
VariantItem(ref v) => v.has_stripped_entries(),
TypeAliasItem(ref type_alias) => {
type_alias.inner_type.as_ref().and_then(|t| t.has_stripped_entries())
}
_ => None,
}
}
@ -761,14 +764,11 @@ impl Item {
Some(tcx.visibility(def_id))
}
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Vec<String> {
pub(crate) fn attributes_without_repr(&self, tcx: TyCtxt<'_>, is_json: bool) -> Vec<String> {
const ALLOWED_ATTRIBUTES: &[Symbol] =
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
use rustc_abi::IntegerType;
let mut attrs: Vec<String> = self
.attrs
self.attrs
.other_attrs
.iter()
.filter_map(|attr| {
@ -796,74 +796,28 @@ impl Item {
None
}
})
.collect();
.collect()
}
// Add #[repr(...)]
if let Some(def_id) = self.def_id()
&& let ItemType::Struct | ItemType::Enum | ItemType::Union = self.type_()
{
let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new();
if repr.c() {
out.push("C");
}
if repr.transparent() {
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
// field is public in case all fields are 1-ZST fields.
let render_transparent = is_json
|| cache.document_private
|| adt
.all_fields()
.find(|field| {
let ty =
field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(
ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty),
)
.is_ok_and(|layout| !layout.is_1zst())
})
.map_or_else(
|| adt.all_fields().any(|field| field.vis.is_public()),
|field| field.vis.is_public(),
);
pub(crate) fn attributes_and_repr(
&self,
tcx: TyCtxt<'_>,
cache: &Cache,
is_json: bool,
) -> Vec<String> {
let mut attrs = self.attributes_without_repr(tcx, is_json);
if render_transparent {
out.push("transparent");
}
}
if repr.simd() {
out.push("simd");
}
let pack_s;
if let Some(pack) = repr.pack {
pack_s = format!("packed({})", pack.bytes());
out.push(&pack_s);
}
let align_s;
if let Some(align) = repr.align {
align_s = format!("align({})", align.bytes());
out.push(&align_s);
}
let int_s;
if let Some(int) = repr.int {
int_s = match int {
IntegerType::Pointer(is_signed) => {
format!("{}size", if is_signed { 'i' } else { 'u' })
}
IntegerType::Fixed(size, is_signed) => {
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
}
};
out.push(&int_s);
}
if !out.is_empty() {
attrs.push(format!("#[repr({})]", out.join(", ")));
}
if let Some(repr_attr) = self.repr(tcx, cache, is_json) {
attrs.push(repr_attr);
}
attrs
}
/// Returns a stringified `#[repr(...)]` attribute.
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json)
}
pub fn is_doc_hidden(&self) -> bool {
self.attrs.is_doc_hidden()
}
@ -873,6 +827,73 @@ impl Item {
}
}
pub(crate) fn repr_attributes(
tcx: TyCtxt<'_>,
cache: &Cache,
def_id: DefId,
item_type: ItemType,
is_json: bool,
) -> Option<String> {
use rustc_abi::IntegerType;
if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
return None;
}
let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new();
if repr.c() {
out.push("C");
}
if repr.transparent() {
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
// field is public in case all fields are 1-ZST fields.
let render_transparent = cache.document_private
|| is_json
|| adt
.all_fields()
.find(|field| {
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
.is_ok_and(|layout| !layout.is_1zst())
})
.map_or_else(
|| adt.all_fields().any(|field| field.vis.is_public()),
|field| field.vis.is_public(),
);
if render_transparent {
out.push("transparent");
}
}
if repr.simd() {
out.push("simd");
}
let pack_s;
if let Some(pack) = repr.pack {
pack_s = format!("packed({})", pack.bytes());
out.push(&pack_s);
}
let align_s;
if let Some(align) = repr.align {
align_s = format!("align({})", align.bytes());
out.push(&align_s);
}
let int_s;
if let Some(int) = repr.int {
int_s = match int {
IntegerType::Pointer(is_signed) => {
format!("{}size", if is_signed { 'i' } else { 'u' })
}
IntegerType::Fixed(size, is_signed) => {
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
}
};
out.push(&int_s);
}
if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
}
#[derive(Clone, Debug)]
pub(crate) enum ItemKind {
ExternCrateItem {
@ -2107,7 +2128,7 @@ impl Enum {
self.variants.iter().any(|f| f.is_stripped())
}
pub(crate) fn variants(&self) -> impl Iterator<Item = &Item> {
pub(crate) fn non_stripped_variants(&self) -> impl Iterator<Item = &Item> {
self.variants.iter().filter(|v| !v.is_stripped())
}
}
@ -2345,6 +2366,17 @@ pub(crate) enum TypeAliasInnerType {
Struct { ctor_kind: Option<CtorKind>, fields: Vec<Item> },
}
impl TypeAliasInnerType {
fn has_stripped_entries(&self) -> Option<bool> {
Some(match self {
Self::Enum { variants, .. } => variants.iter().any(|v| v.is_stripped()),
Self::Union { fields } | Self::Struct { fields, .. } => {
fields.iter().any(|f| f.is_stripped())
}
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct TypeAlias {
pub(crate) type_: Type,

View file

@ -1194,18 +1194,36 @@ fn render_assoc_item(
// a whitespace prefix and newline.
fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
fmt::from_fn(move |f| {
for a in it.attributes(cx.tcx(), cx.cache(), false) {
for a in it.attributes_and_repr(cx.tcx(), cx.cache(), false) {
writeln!(f, "{prefix}{a}")?;
}
Ok(())
})
}
struct CodeAttribute(String);
fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) {
write!(w, "<div class=\"code-attribute\">{}</div>", code_attr.0).unwrap();
}
// When an attribute is rendered inside a <code> tag, it is formatted using
// a div to produce a newline after it.
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
for attr in it.attributes(cx.tcx(), cx.cache(), false) {
write!(w, "<div class=\"code-attribute\">{attr}</div>").unwrap();
for attr in it.attributes_and_repr(cx.tcx(), cx.cache(), false) {
render_code_attribute(CodeAttribute(attr), w);
}
}
/// used for type aliases to only render their `repr` attribute.
fn render_repr_attributes_in_code(
w: &mut impl fmt::Write,
cx: &Context<'_>,
def_id: DefId,
item_type: ItemType,
) {
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type, false) {
render_code_attribute(CodeAttribute(repr), w);
}
}

View file

@ -20,7 +20,7 @@ use super::{
collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference,
item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls,
render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre,
render_impl, render_rightside, render_stability_since_raw,
render_impl, render_repr_attributes_in_code, render_rightside, render_stability_since_raw,
render_stability_since_raw_with_extra, write_section_heading,
};
use crate::clean;
@ -1278,94 +1278,58 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
match inner_type {
clean::TypeAliasInnerType::Enum { variants, is_non_exhaustive } => {
let variants_iter = || variants.iter().filter(|i| !i.is_stripped());
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let enum_def_id = ty.ty_adt_def().unwrap().did();
wrap_item(w, |w| {
let variants_len = variants.len();
let variants_count = variants_iter().count();
let has_stripped_entries = variants_len != variants_count;
write!(
w,
"enum {}{}{}",
it.name.unwrap(),
t.generics.print(cx),
render_enum_fields(
cx,
Some(&t.generics),
variants,
variants_count,
has_stripped_entries,
*is_non_exhaustive,
enum_def_id,
)
)
})?;
write!(w, "{}", item_variants(cx, it, variants, enum_def_id))?;
DisplayEnum {
variants,
generics: &t.generics,
is_non_exhaustive: *is_non_exhaustive,
def_id: enum_def_id,
}
.render_into(cx, it, true, w)?;
}
clean::TypeAliasInnerType::Union { fields } => {
wrap_item(w, |w| {
let fields_count = fields.iter().filter(|i| !i.is_stripped()).count();
let has_stripped_fields = fields.len() != fields_count;
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let union_def_id = ty.ty_adt_def().unwrap().did();
write!(
w,
"union {}{}{}",
it.name.unwrap(),
t.generics.print(cx),
render_struct_fields(
Some(&t.generics),
None,
fields,
"",
true,
has_stripped_fields,
cx,
),
)
})?;
write!(w, "{}", item_fields(cx, it, fields, None))?;
ItemUnion {
cx,
it,
fields,
generics: &t.generics,
is_type_alias: true,
def_id: union_def_id,
}
.render_into(w)?;
}
clean::TypeAliasInnerType::Struct { ctor_kind, fields } => {
wrap_item(w, |w| {
let fields_count = fields.iter().filter(|i| !i.is_stripped()).count();
let has_stripped_fields = fields.len() != fields_count;
let ty = cx.tcx().type_of(it.def_id().unwrap()).instantiate_identity();
let struct_def_id = ty.ty_adt_def().unwrap().did();
write!(
w,
"struct {}{}{}",
it.name.unwrap(),
t.generics.print(cx),
render_struct_fields(
Some(&t.generics),
*ctor_kind,
fields,
"",
true,
has_stripped_fields,
cx,
),
)
})?;
write!(w, "{}", item_fields(cx, it, fields, None))?;
DisplayStruct {
ctor_kind: *ctor_kind,
generics: &t.generics,
fields,
def_id: struct_def_id,
}
.render_into(cx, it, true, w)?;
}
}
} else {
let def_id = it.item_id.expect_def_id();
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
write!(
w,
"{}{}",
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id)
)?;
}
let def_id = it.item_id.expect_def_id();
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
// associated items from the aliased type (see discussion in #32077), but
// we need #14072 to make sense of the generics.
write!(
w,
"{}{}",
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id)
)?;
// [RUSTDOCIMPL] type.impl
//
// Include type definitions from the alias target type.
@ -1463,50 +1427,83 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) ->
})
}
fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
item_template!(
#[template(path = "item_union.html")]
struct ItemUnion<'a, 'cx> {
cx: &'a Context<'cx>,
it: &'a clean::Item,
s: &'a clean::Union,
},
methods = [document, document_type_layout, render_attributes_in_pre, render_assoc_items]
);
item_template!(
#[template(path = "item_union.html")]
struct ItemUnion<'a, 'cx> {
cx: &'a Context<'cx>,
it: &'a clean::Item,
fields: &'a [clean::Item],
generics: &'a clean::Generics,
is_type_alias: bool,
def_id: DefId,
},
methods = [document, document_type_layout, render_assoc_items]
);
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
fn render_union(&self) -> impl Display {
render_union(self.it, Some(&self.s.generics), &self.s.fields, self.cx)
}
fn document_field(&self, field: &'a clean::Item) -> impl Display {
document(self.cx, field, Some(self.it), HeadingOffset::H3)
}
fn stability_field(&self, field: &clean::Item) -> Option<String> {
field.stability_class(self.cx.tcx())
}
fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
ty.print(self.cx)
}
fn fields_iter(
&self,
) -> iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>> {
self.s
.fields
.iter()
.filter_map(|f| match f.kind {
clean::StructFieldItem(ref ty) => Some((f, ty)),
_ => None,
})
.peekable()
}
impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
fn render_union(&self) -> impl Display {
render_union(self.it, Some(&self.generics), &self.fields, self.cx)
}
fn document_field(&self, field: &'a clean::Item) -> impl Display {
document(self.cx, field, Some(self.it), HeadingOffset::H3)
}
fn stability_field(&self, field: &clean::Item) -> Option<String> {
field.stability_class(self.cx.tcx())
}
fn print_ty(&self, ty: &'a clean::Type) -> impl Display {
ty.print(self.cx)
}
// FIXME (GuillaumeGomez): When <https://github.com/askama-rs/askama/issues/452> is implemented,
// we can replace the returned value with:
//
// `iter::Peekable<impl Iterator<Item = (&'a clean::Item, &'a clean::Type)>>`
//
// And update `item_union.html`.
fn fields_iter(&self) -> impl Iterator<Item = (&'a clean::Item, &'a clean::Type)> {
self.fields.iter().filter_map(|f| match f.kind {
clean::StructFieldItem(ref ty) => Some((f, ty)),
_ => None,
})
}
fn render_attributes_in_pre(&self) -> impl fmt::Display {
fmt::from_fn(move |f| {
if self.is_type_alias {
// For now the only attributes we render for type aliases are `repr` attributes.
if let Some(repr) = clean::repr_attributes(
self.cx.tcx(),
self.cx.cache(),
self.def_id,
ItemType::Union,
false,
) {
writeln!(f, "{repr}")?;
};
} else {
for a in self.it.attributes_and_repr(self.cx.tcx(), self.cx.cache(), false) {
writeln!(f, "{a}")?;
}
}
Ok(())
})
}
}
fn item_union(cx: &Context<'_>, it: &clean::Item, s: &clean::Union) -> impl fmt::Display {
fmt::from_fn(|w| {
ItemUnion { cx, it, s }.render_into(w).unwrap();
ItemUnion {
cx,
it,
fields: &s.fields,
generics: &s.generics,
is_type_alias: false,
def_id: it.def_id().unwrap(),
}
.render_into(w)?;
Ok(())
})
}
@ -1533,41 +1530,81 @@ fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Displa
})
}
fn item_enum(cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) -> impl fmt::Display {
fmt::from_fn(|w| {
let count_variants = e.variants().count();
struct DisplayEnum<'clean> {
variants: &'clean IndexVec<VariantIdx, clean::Item>,
generics: &'clean clean::Generics,
is_non_exhaustive: bool,
def_id: DefId,
}
impl<'clean> DisplayEnum<'clean> {
fn render_into<W: fmt::Write>(
self,
cx: &Context<'_>,
it: &clean::Item,
is_type_alias: bool,
w: &mut W,
) -> fmt::Result {
let non_stripped_variant_count = self.variants.iter().filter(|i| !i.is_stripped()).count();
let variants_len = self.variants.len();
let has_stripped_entries = variants_len != non_stripped_variant_count;
wrap_item(w, |w| {
render_attributes_in_code(w, it, cx);
if is_type_alias {
// For now the only attributes we render for type aliases are `repr` attributes.
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Enum);
} else {
render_attributes_in_code(w, it, cx);
}
write!(
w,
"{}enum {}{}{}",
visibility_print_with_space(it, cx),
it.name.unwrap(),
e.generics.print(cx),
self.generics.print(cx),
render_enum_fields(
cx,
Some(&e.generics),
&e.variants,
count_variants,
e.has_stripped_entries(),
it.is_non_exhaustive(),
it.def_id().unwrap(),
Some(self.generics),
self.variants,
non_stripped_variant_count,
has_stripped_entries,
self.is_non_exhaustive,
self.def_id,
),
)
})?;
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
if count_variants != 0 {
write!(w, "{}", item_variants(cx, it, &e.variants, it.def_id().unwrap()))?;
}
let def_id = it.item_id.expect_def_id();
let layout_def_id = if is_type_alias {
self.def_id
} else {
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
// We don't return the same `DefId` since the layout size of the type alias might be
// different since we might have more information on the generics.
def_id
};
if non_stripped_variant_count != 0 {
write!(w, "{}", item_variants(cx, it, self.variants, self.def_id))?;
}
write!(
w,
"{}{}",
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id)
document_type_layout(cx, layout_def_id)
)
}
}
fn item_enum(cx: &Context<'_>, it: &clean::Item, e: &clean::Enum) -> impl fmt::Display {
fmt::from_fn(|w| {
DisplayEnum {
variants: &e.variants,
generics: &e.generics,
is_non_exhaustive: it.is_non_exhaustive(),
def_id: it.def_id().unwrap(),
}
.render_into(cx, it, false, w)
})
}
@ -1955,27 +1992,59 @@ fn item_constant(
})
}
fn item_struct(cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) -> impl fmt::Display {
fmt::from_fn(|w| {
struct DisplayStruct<'a> {
ctor_kind: Option<CtorKind>,
generics: &'a clean::Generics,
fields: &'a [clean::Item],
def_id: DefId,
}
impl<'a> DisplayStruct<'a> {
fn render_into<W: fmt::Write>(
self,
cx: &Context<'_>,
it: &clean::Item,
is_type_alias: bool,
w: &mut W,
) -> fmt::Result {
wrap_item(w, |w| {
render_attributes_in_code(w, it, cx);
if is_type_alias {
// For now the only attributes we render for type aliases are `repr` attributes.
render_repr_attributes_in_code(w, cx, self.def_id, ItemType::Struct);
} else {
render_attributes_in_code(w, it, cx);
}
write!(
w,
"{}",
render_struct(it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx)
render_struct(it, Some(self.generics), self.ctor_kind, self.fields, "", true, cx)
)
})?;
let def_id = it.item_id.expect_def_id();
if !is_type_alias {
write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?;
}
let def_id = it.item_id.expect_def_id();
write!(
w,
"{}{}{}{}",
document(cx, it, None, HeadingOffset::H2),
item_fields(cx, it, &s.fields, s.ctor_kind),
"{}{}{}",
item_fields(cx, it, self.fields, self.ctor_kind),
render_assoc_items(cx, it, def_id, AssocItemRender::All),
document_type_layout(cx, def_id),
)
}
}
fn item_struct(cx: &Context<'_>, it: &clean::Item, s: &clean::Struct) -> impl fmt::Display {
fmt::from_fn(|w| {
DisplayStruct {
ctor_kind: s.ctor_kind,
generics: &s.generics,
fields: s.fields.as_slice(),
def_id: it.def_id().unwrap(),
}
.render_into(cx, it, false, w)
})
}

View file

@ -599,7 +599,7 @@ fn sidebar_enum<'a>(
deref_id_map: &'a DefIdMap<String>,
) {
let mut variants = e
.variants()
.non_stripped_variants()
.filter_map(|v| v.name)
.map(|name| Link::new(format!("variant.{name}"), name.to_string()))
.collect::<Vec<_>>();

View file

@ -2527,9 +2527,12 @@ in src-script.js and main.js
z-index: 11;
/* Reduce height slightly to account for mobile topbar. */
height: calc(100vh - 45px);
width: 200px;
/* resize indicator: hide this when on touch or mobile */
border-right: none;
width: 100%;
}
.sidebar-elems .block li a {
white-space: wrap;
}
/* The source view uses a different design for the sidebar toggle, and doesn't have a topbar,

View file

@ -2,15 +2,16 @@
{{ self.render_attributes_in_pre()|safe }}
{{ self.render_union()|safe }}
</code></pre>
{{ self.document()|safe }}
{% if self.fields_iter().peek().is_some() %}
{% if !self.is_type_alias %}
{{ self.document()|safe }}
{% endif %}
{% if self.fields_iter().next().is_some() %}
<h2 id="fields" class="fields section-header"> {# #}
Fields<a href="#fields" class="anchor">§</a> {# #}
</h2>
{% for (field, ty) in self.fields_iter() %}
{% let name = field.name.expect("union field name") %}
<span id="structfield.{{ name }}" {#+ #}
class="{{ ItemType::StructField +}} section-header"> {# #}
<span id="structfield.{{ name }}" class="{{ ItemType::StructField +}} section-header"> {# #}
<a href="#structfield.{{ name }}" class="anchor field">§</a> {# #}
<code>{{ name }}: {{+ self.print_ty(ty)|safe }}</code> {# #}
</span>

View file

@ -40,7 +40,7 @@ impl JsonRenderer<'_> {
})
.collect();
let docs = item.opt_doc_value();
let attrs = item.attributes(self.tcx, self.cache(), true);
let attrs = item.attributes_and_repr(self.tcx, self.cache(), true);
let span = item.span(self.tcx);
let visibility = item.visibility(self.tcx);
let clean::ItemInner { name, item_id, .. } = *item.inner;

View file

@ -941,6 +941,8 @@ impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
ProjectionKind::Subslice |
// Doesn't have surface syntax. Only occurs in patterns.
ProjectionKind::OpaqueCast => (),
// Only occurs in closure captures.
ProjectionKind::UnwrapUnsafeBinder => (),
ProjectionKind::Deref => {
// Explicit derefs are typically handled later on, but
// some items do not need explicit deref, such as array accesses,

View file

@ -139,7 +139,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
AllocKind::LiveData => {
if memory_kind == MiriMemoryKind::Global.into() {
// For new global allocations, we always pre-allocate the memory to be able use the machine address directly.
let prepared_bytes = MiriAllocBytes::zeroed(info.size, info.align)
let prepared_bytes = MiriAllocBytes::zeroed(info.size, info.align, ())
.unwrap_or_else(|| {
panic!("Miri ran out of memory: cannot create allocation of {size:?} bytes", size = info.size)
});
@ -159,7 +159,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
AllocKind::Function | AllocKind::VTable => {
// Allocate some dummy memory to get a unique address for this function/vtable.
let alloc_bytes =
MiriAllocBytes::from_bytes(&[0u8; 1], Align::from_bytes(1).unwrap());
MiriAllocBytes::from_bytes(&[0u8; 1], Align::from_bytes(1).unwrap(), ());
let ptr = alloc_bytes.as_ptr();
// Leak the underlying memory to ensure it remains unique.
std::mem::forget(alloc_bytes);
@ -429,7 +429,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
prepared_alloc_bytes.copy_from_slice(bytes);
interp_ok(prepared_alloc_bytes)
} else {
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align))
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align, ()))
}
}

View file

@ -24,7 +24,7 @@ impl Clone for MiriAllocBytes {
fn clone(&self) -> Self {
let bytes: Cow<'_, [u8]> = Cow::Borrowed(self);
let align = Align::from_bytes(self.layout.align().to_u64()).unwrap();
MiriAllocBytes::from_bytes(bytes, align)
MiriAllocBytes::from_bytes(bytes, align, ())
}
}
@ -86,7 +86,10 @@ impl MiriAllocBytes {
}
impl AllocBytes for MiriAllocBytes {
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, align: Align) -> Self {
/// Placeholder!
type AllocParams = ();
fn from_bytes<'a>(slice: impl Into<Cow<'a, [u8]>>, align: Align, _params: ()) -> Self {
let slice = slice.into();
let size = slice.len();
let align = align.bytes();
@ -102,7 +105,7 @@ impl AllocBytes for MiriAllocBytes {
alloc_bytes
}
fn zeroed(size: Size, align: Align) -> Option<Self> {
fn zeroed(size: Size, align: Align, _params: ()) -> Option<Self> {
let size = size.bytes();
let align = align.bytes();
// SAFETY: `alloc_fn` will only be used with `size != 0`.

View file

@ -218,34 +218,37 @@ impl<'tcx> Thread<'tcx> {
}
}
/// Return the top user-relevant frame, if there is one.
/// Return the top user-relevant frame, if there is one. `skip` indicates how many top frames
/// should be skipped.
/// Note that the choice to return `None` here when there is no user-relevant frame is part of
/// justifying the optimization that only pushes of user-relevant frames require updating the
/// `top_user_relevant_frame` field.
fn compute_top_user_relevant_frame(&self) -> Option<usize> {
fn compute_top_user_relevant_frame(&self, skip: usize) -> Option<usize> {
self.stack
.iter()
.enumerate()
.rev()
.skip(skip)
.find_map(|(idx, frame)| if frame.extra.is_user_relevant { Some(idx) } else { None })
}
/// Re-compute the top user-relevant frame from scratch.
pub fn recompute_top_user_relevant_frame(&mut self) {
self.top_user_relevant_frame = self.compute_top_user_relevant_frame();
/// Re-compute the top user-relevant frame from scratch. `skip` indicates how many top frames
/// should be skipped.
pub fn recompute_top_user_relevant_frame(&mut self, skip: usize) {
self.top_user_relevant_frame = self.compute_top_user_relevant_frame(skip);
}
/// Set the top user-relevant frame to the given value. Must be equal to what
/// `get_top_user_relevant_frame` would return!
pub fn set_top_user_relevant_frame(&mut self, frame_idx: usize) {
debug_assert_eq!(Some(frame_idx), self.compute_top_user_relevant_frame());
debug_assert_eq!(Some(frame_idx), self.compute_top_user_relevant_frame(0));
self.top_user_relevant_frame = Some(frame_idx);
}
/// Returns the topmost frame that is considered user-relevant, or the
/// top of the stack if there is no such frame, or `None` if the stack is empty.
pub fn top_user_relevant_frame(&self) -> Option<usize> {
debug_assert_eq!(self.top_user_relevant_frame, self.compute_top_user_relevant_frame());
debug_assert_eq!(self.top_user_relevant_frame, self.compute_top_user_relevant_frame(0));
// This can be called upon creation of an allocation. We create allocations while setting up
// parts of the Rust runtime when we do not have any stack frames yet, so we need to handle
// empty stacks.
@ -899,7 +902,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let mut alloc = alloc.inner().adjust_from_tcx(
&this.tcx,
|bytes, align| {
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align))
interp_ok(MiriAllocBytes::from_bytes(std::borrow::Cow::Borrowed(bytes), align, ()))
},
|ptr| this.global_root_pointer(ptr),
)?;

View file

@ -354,11 +354,10 @@ pub fn create_ecx<'tcx>(
argvs.push(arg_place.to_ref(&ecx));
}
// Make an array with all these pointers, in the Miri memory.
let argvs_layout = ecx.layout_of(Ty::new_array(
tcx,
Ty::new_imm_ptr(tcx, tcx.types.u8),
u64::try_from(argvs.len()).unwrap(),
))?;
let u8_ptr_type = Ty::new_imm_ptr(tcx, tcx.types.u8);
let u8_ptr_ptr_type = Ty::new_imm_ptr(tcx, u8_ptr_type);
let argvs_layout =
ecx.layout_of(Ty::new_array(tcx, u8_ptr_type, u64::try_from(argvs.len()).unwrap()))?;
let argvs_place = ecx.allocate(argvs_layout, MiriMemoryKind::Machine.into())?;
for (idx, arg) in argvs.into_iter().enumerate() {
let place = ecx.project_field(&argvs_place, idx)?;
@ -373,10 +372,8 @@ pub fn create_ecx<'tcx>(
ecx.mark_immutable(&argc_place);
ecx.machine.argc = Some(argc_place.ptr());
let argv_place = ecx.allocate(
ecx.layout_of(Ty::new_imm_ptr(tcx, tcx.types.unit))?,
MiriMemoryKind::Machine.into(),
)?;
let argv_place =
ecx.allocate(ecx.layout_of(u8_ptr_ptr_type)?, MiriMemoryKind::Machine.into())?;
ecx.write_pointer(argvs_place.ptr(), &argv_place)?;
ecx.mark_immutable(&argv_place);
ecx.machine.argv = Some(argv_place.ptr());
@ -398,7 +395,9 @@ pub fn create_ecx<'tcx>(
}
ecx.mark_immutable(&cmd_place);
}
ecx.mplace_to_ref(&argvs_place)?
let imm = argvs_place.to_ref(&ecx);
let layout = ecx.layout_of(u8_ptr_ptr_type)?;
ImmTy::from_immediate(imm, layout)
};
// Return place (in static memory so that it does not count as leak).

View file

@ -470,7 +470,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
caller_fn_abi,
&args.iter().map(|a| FnArg::Copy(a.clone().into())).collect::<Vec<_>>(),
/*with_caller_location*/ false,
&dest,
&dest.into(),
stack_pop,
)
}

View file

@ -22,7 +22,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
&mut self,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
unwind: mir::UnwindAction,
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
@ -45,7 +45,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let intrinsic_name = this.tcx.item_name(instance.def_id());
let intrinsic_name = intrinsic_name.as_str();
match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest, ret)? {
// FIXME: avoid allocating memory
let dest = this.force_allocation(dest)?;
match this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, &dest, ret)? {
EmulateItemResult::NotSupported => {
// We haven't handled the intrinsic, let's see if we can use a fallback body.
if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {

View file

@ -1115,7 +1115,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
instance: ty::Instance<'tcx>,
abi: &FnAbi<'tcx, Ty<'tcx>>,
args: &[FnArg<'tcx, Provenance>],
dest: &MPlaceTy<'tcx>,
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
unwind: mir::UnwindAction,
) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
@ -1142,7 +1142,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
fn_val: DynSym,
abi: &FnAbi<'tcx, Ty<'tcx>>,
args: &[FnArg<'tcx, Provenance>],
dest: &MPlaceTy<'tcx>,
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
unwind: mir::UnwindAction,
) -> InterpResult<'tcx> {
@ -1155,7 +1155,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
ecx: &mut MiriInterpCx<'tcx>,
instance: ty::Instance<'tcx>,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
unwind: mir::UnwindAction,
) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
@ -1634,15 +1634,21 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
interp_ok(())
}
fn before_stack_pop(
ecx: &InterpCx<'tcx, Self>,
frame: &Frame<'tcx, Self::Provenance, Self::FrameExtra>,
) -> InterpResult<'tcx> {
fn before_stack_pop(ecx: &mut InterpCx<'tcx, Self>) -> InterpResult<'tcx> {
let frame = ecx.frame();
// We want this *before* the return value copy, because the return place itself is protected
// until we do `end_call` here.
if ecx.machine.borrow_tracker.is_some() {
ecx.on_stack_pop(frame)?;
}
if frame.extra.is_user_relevant {
// All that we store is whether or not the frame we just removed is local, so now we
// have no idea where the next topmost local frame is. So we recompute it.
// (If this ever becomes a bottleneck, we could have `push` store the previous
// user-relevant frame and restore that here.)
// We have to skip the frame that is just being popped.
ecx.active_thread_mut().recompute_top_user_relevant_frame(/* skip */ 1);
}
// tracing-tree can autoamtically annotate scope changes, but it gets very confused by our
// concurrency and what it prints is just plain wrong. So we print our own information
// instead. (Cc https://github.com/rust-lang/miri/issues/2266)
@ -1656,15 +1662,8 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
frame: Frame<'tcx, Provenance, FrameExtra<'tcx>>,
unwinding: bool,
) -> InterpResult<'tcx, ReturnAction> {
if frame.extra.is_user_relevant {
// All that we store is whether or not the frame we just removed is local, so now we
// have no idea where the next topmost local frame is. So we recompute it.
// (If this ever becomes a bottleneck, we could have `push` store the previous
// user-relevant frame and restore that here.)
ecx.active_thread_mut().recompute_top_user_relevant_frame();
}
let res = {
// Move `frame`` into a sub-scope so we control when it will be dropped.
// Move `frame` into a sub-scope so we control when it will be dropped.
let mut frame = frame;
let timing = frame.extra.timing.take();
let res = ecx.handle_stack_pop_unwind(frame.extra, unwinding);
@ -1804,6 +1803,9 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
) -> Cow<'e, RangeSet> {
Cow::Borrowed(ecx.machine.union_data_ranges.entry(ty).or_insert_with(compute_range))
}
/// Placeholder!
fn get_default_alloc_params(&self) -> <Self::Bytes as AllocBytes>::AllocParams { () }
}
/// Trait for callbacks handling asynchronous machine operations.

View file

@ -43,7 +43,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
link_name: Symbol,
abi: &FnAbi<'tcx, Ty<'tcx>>,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
unwind: mir::UnwindAction,
) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
@ -69,8 +69,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
_ => {}
}
// FIXME: avoid allocating memory
let dest = this.force_allocation(dest)?;
// The rest either implements the logic, or falls back to `lookup_exported_symbol`.
match this.emulate_foreign_item_inner(link_name, abi, args, dest)? {
match this.emulate_foreign_item_inner(link_name, abi, args, &dest)? {
EmulateItemResult::NeedsReturn => {
trace!("{:?}", this.dump_place(&dest.clone().into()));
this.return_to_block(ret)?;
@ -111,7 +114,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
sym: DynSym,
abi: &FnAbi<'tcx, Ty<'tcx>>,
args: &[OpTy<'tcx>],
dest: &MPlaceTy<'tcx>,
dest: &PlaceTy<'tcx>,
ret: Option<mir::BasicBlock>,
unwind: mir::UnwindAction,
) -> InterpResult<'tcx> {

View file

@ -8,7 +8,7 @@ struct MakeSend(*const i32);
unsafe impl Send for MakeSend {}
fn main() {
race(0);
race(0); //~ERROR: Data race detected between (1) non-atomic read on thread `unnamed-1` and (2) deallocation on thread `main`
}
// Using an argument for the ptr to point to, since those do not get StorageDead.
@ -22,5 +22,4 @@ fn race(local: i32) {
thread::yield_now();
// Deallocating the local (when `main` returns)
// races with the read in the other thread.
// Make sure the error points at this function's end, not just the call site.
} //~ERROR: Data race detected between (1) non-atomic read on thread `unnamed-1` and (2) deallocation on thread `main`
}

View file

@ -1,8 +1,8 @@
error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) deallocation on thread `main` at ALLOC. (2) just happened here
--> tests/fail/data_race/stack_pop_race.rs:LL:CC
|
LL | }
| ^ Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) deallocation on thread `main` at ALLOC. (2) just happened here
LL | race(0);
| ^^^^^^^ Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) deallocation on thread `main` at ALLOC. (2) just happened here
|
help: and (1) occurred earlier here
--> tests/fail/data_race/stack_pop_race.rs:LL:CC
@ -12,12 +12,7 @@ LL | let _val = unsafe { *ptr.0 };
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: BACKTRACE (of the first span):
= note: inside `race` at tests/fail/data_race/stack_pop_race.rs:LL:CC
note: inside `main`
--> tests/fail/data_race/stack_pop_race.rs:LL:CC
|
LL | race(0);
| ^^^^^^^
= note: inside `main` at tests/fail/data_race/stack_pop_race.rs:LL:CC
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

View file

@ -14,8 +14,8 @@ LL | let local = 0;
help: ALLOC was deallocated here:
--> tests/fail/tail_calls/dangling-local-var.rs:LL:CC
|
LL | become g(ptr)
| ^^^^^^^^^^^^^
LL | f(std::ptr::null());
| ^^^^^^^^^^^^^^^^^^^
= note: BACKTRACE (of the first span):
= note: inside `g` at tests/fail/tail_calls/dangling-local-var.rs:LL:CC
note: inside `main`

View file

@ -1,7 +1,7 @@
#![no_std]
#![no_main]
//@compile-flags: -Zmiri-track-alloc-id=21 -Zmiri-track-alloc-accesses -Cpanic=abort
//@normalize-stderr-test: "id 21" -> "id $$ALLOC"
//@compile-flags: -Zmiri-track-alloc-id=20 -Zmiri-track-alloc-accesses -Cpanic=abort
//@normalize-stderr-test: "id 20" -> "id $$ALLOC"
//@only-target: linux # alloc IDs differ between OSes (due to extern static allocations)
extern "Rust" {

View file

@ -200,6 +200,7 @@ const EXCEPTIONS_CRANELIFT: ExceptionList = &[
("cranelift-module", "Apache-2.0 WITH LLVM-exception"),
("cranelift-native", "Apache-2.0 WITH LLVM-exception"),
("cranelift-object", "Apache-2.0 WITH LLVM-exception"),
("cranelift-srcgen", "Apache-2.0 WITH LLVM-exception"),
("foldhash", "Zlib"),
("mach2", "BSD-2-Clause OR MIT OR Apache-2.0"),
("regalloc2", "Apache-2.0 WITH LLVM-exception"),
@ -525,6 +526,7 @@ const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
"cranelift-module",
"cranelift-native",
"cranelift-object",
"cranelift-srcgen",
"crc32fast",
"equivalent",
"fallible-iterator",

View file

@ -54,6 +54,7 @@ pub struct Feature {
pub tracking_issue: Option<NonZeroU32>,
pub file: PathBuf,
pub line: usize,
pub description: Option<String>,
}
impl Feature {
fn tracking_issue_display(&self) -> impl fmt::Display {
@ -296,6 +297,7 @@ fn collect_lang_features_in(features: &mut Features, base: &Path, file: &str, ba
let mut prev_names = vec![];
let lines = contents.lines().zip(1..);
let mut doc_comments: Vec<String> = Vec::new();
for (line, line_number) in lines {
let line = line.trim();
@ -332,6 +334,13 @@ fn collect_lang_features_in(features: &mut Features, base: &Path, file: &str, ba
continue;
}
if in_feature_group {
if let Some(doc_comment) = line.strip_prefix("///") {
doc_comments.push(doc_comment.trim().to_string());
continue;
}
}
let mut parts = line.split(',');
let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) {
Some("unstable") => Status::Unstable,
@ -438,9 +447,15 @@ fn collect_lang_features_in(features: &mut Features, base: &Path, file: &str, ba
tracking_issue,
file: path.to_path_buf(),
line: line_number,
description: if doc_comments.is_empty() {
None
} else {
Some(doc_comments.join(" "))
},
});
}
}
doc_comments.clear();
}
}
@ -564,6 +579,7 @@ fn map_lib_features(
tracking_issue: find_attr_val(line, "issue").and_then(handle_issue_none),
file: file.to_path_buf(),
line: i + 1,
description: None,
};
mf(Ok((feature_name, feature)), file, i + 1);
continue;
@ -600,6 +616,7 @@ fn map_lib_features(
tracking_issue,
file: file.to_path_buf(),
line: i + 1,
description: None,
};
if line.contains(']') {
mf(Ok((feature_name, feature)), file, i + 1);

View file

@ -1,5 +1,7 @@
[The Unstable Book](the-unstable-book.md)
- [Compiler environment variables](compiler-environment-variables.md)
{compiler_env_vars}
- [Compiler flags](compiler-flags.md)
{compiler_flags}
- [Language features](language-features.md)

View file

@ -12,13 +12,18 @@ use tidy::unstable_book::{
collect_unstable_feature_names,
};
fn generate_stub_issue(path: &Path, name: &str, issue: u32) {
let content = format!(include_str!("stub-issue.md"), name = name, issue = issue);
fn generate_stub_issue(path: &Path, name: &str, issue: u32, description: &str) {
let content = format!(
include_str!("stub-issue.md"),
name = name,
issue = issue,
description = description
);
t!(write(path, content), path);
}
fn generate_stub_no_issue(path: &Path, name: &str) {
let content = format!(include_str!("stub-no-issue.md"), name = name);
fn generate_stub_no_issue(path: &Path, name: &str, description: &str) {
let content = format!(include_str!("stub-no-issue.md"), name = name, description = description);
t!(write(path, content), path);
}
@ -30,8 +35,12 @@ fn set_to_summary_str(set: &BTreeSet<String>, dir: &str) -> String {
fn generate_summary(path: &Path, lang_features: &Features, lib_features: &Features) {
let compiler_flags = collect_unstable_book_section_file_names(&path.join("src/compiler-flags"));
let compiler_env_vars =
collect_unstable_book_section_file_names(&path.join("src/compiler-environment-variables"));
let compiler_flags_str = set_to_summary_str(&compiler_flags, "compiler-flags");
let compiler_env_vars_str =
set_to_summary_str(&compiler_env_vars, "compiler-environment-variables");
let unstable_lang_features = collect_unstable_feature_names(&lang_features);
let unstable_lib_features = collect_unstable_feature_names(&lib_features);
@ -42,6 +51,7 @@ fn generate_summary(path: &Path, lang_features: &Features, lib_features: &Featur
let summary_path = path.join("src/SUMMARY.md");
let content = format!(
include_str!("SUMMARY.md"),
compiler_env_vars = compiler_env_vars_str,
compiler_flags = compiler_flags_str,
language_features = lang_features_str,
library_features = lib_features_str
@ -58,11 +68,17 @@ fn generate_unstable_book_files(src: &Path, out: &Path, features: &Features) {
let file_name = format!("{feature_name}.md");
let out_file_path = out.join(&file_name);
let feature = &features[&feature_name_underscore];
let description = feature.description.as_deref().unwrap_or_default();
if let Some(issue) = feature.tracking_issue {
generate_stub_issue(&out_file_path, &feature_name_underscore, issue.get());
generate_stub_issue(
&out_file_path,
&feature_name_underscore,
issue.get(),
&description,
);
} else {
generate_stub_no_issue(&out_file_path, &feature_name_underscore);
generate_stub_no_issue(&out_file_path, &feature_name_underscore, &description);
}
}
}

View file

@ -1,5 +1,7 @@
# `{name}`
{description}
The tracking issue for this feature is: [#{issue}]
[#{issue}]: https://github.com/rust-lang/rust/issues/{issue}

View file

@ -1,5 +1,7 @@
# `{name}`
{description}
This feature has no tracking issue, and is therefore likely internal to the compiler, not being intended for general use.
------------------------