Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 13170cd

Browse files
committedDec 21, 2024
Auto merge of #134590 - matthiaskrgr:rollup-8lz2s62, r=matthiaskrgr
Rollup of 7 pull requests Successful merges: - #123604 (Abstract `ProcThreadAttributeList` into its own struct) - #128780 (Add `--doctest-compilation-args` option to add compilation flags to doctest compilation) - #133782 (Precedence improvements: closures and jumps) - #134509 (Arbitrary self types v2: niche deshadowing test) - #134524 (Arbitrary self types v2: no deshadow pre feature.) - #134539 (Restrict `#[non_exaustive]` on structs with default field values) - #134586 (Also lint on option of function pointer comparisons) r? `@ghost` `@rustbot` modify labels: rollup
2 parents 5f23ef7 + b7ac8d7 commit 13170cd

29 files changed

+779
-186
lines changed
 

‎compiler/rustc_ast/src/ast.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,11 +1322,15 @@ impl Expr {
13221322
}
13231323

13241324
pub fn precedence(&self) -> ExprPrecedence {
1325-
match self.kind {
1326-
ExprKind::Closure(..) => ExprPrecedence::Closure,
1325+
match &self.kind {
1326+
ExprKind::Closure(closure) => {
1327+
match closure.fn_decl.output {
1328+
FnRetTy::Default(_) => ExprPrecedence::Jump,
1329+
FnRetTy::Ty(_) => ExprPrecedence::Unambiguous,
1330+
}
1331+
}
13271332

13281333
ExprKind::Break(..)
1329-
| ExprKind::Continue(..)
13301334
| ExprKind::Ret(..)
13311335
| ExprKind::Yield(..)
13321336
| ExprKind::Yeet(..)
@@ -1360,6 +1364,7 @@ impl Expr {
13601364
| ExprKind::Block(..)
13611365
| ExprKind::Call(..)
13621366
| ExprKind::ConstBlock(_)
1367+
| ExprKind::Continue(..)
13631368
| ExprKind::Field(..)
13641369
| ExprKind::ForLoop { .. }
13651370
| ExprKind::FormatArgs(..)

‎compiler/rustc_ast/src/util/parser.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,7 @@ impl AssocOp {
231231

232232
#[derive(Clone, Copy, PartialEq, PartialOrd)]
233233
pub enum ExprPrecedence {
234-
Closure,
235-
// return, break, yield
234+
// return, break, yield, closures
236235
Jump,
237236
// = += -= *= /= %= &= |= ^= <<= >>=
238237
Assign,

‎compiler/rustc_hir/src/hir.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,11 +1943,15 @@ pub struct Expr<'hir> {
19431943

19441944
impl Expr<'_> {
19451945
pub fn precedence(&self) -> ExprPrecedence {
1946-
match self.kind {
1947-
ExprKind::Closure { .. } => ExprPrecedence::Closure,
1946+
match &self.kind {
1947+
ExprKind::Closure(closure) => {
1948+
match closure.fn_decl.output {
1949+
FnRetTy::DefaultReturn(_) => ExprPrecedence::Jump,
1950+
FnRetTy::Return(_) => ExprPrecedence::Unambiguous,
1951+
}
1952+
}
19481953

19491954
ExprKind::Break(..)
1950-
| ExprKind::Continue(..)
19511955
| ExprKind::Ret(..)
19521956
| ExprKind::Yield(..)
19531957
| ExprKind::Become(..) => ExprPrecedence::Jump,
@@ -1973,6 +1977,7 @@ impl Expr<'_> {
19731977
| ExprKind::Block(..)
19741978
| ExprKind::Call(..)
19751979
| ExprKind::ConstBlock(_)
1980+
| ExprKind::Continue(..)
19761981
| ExprKind::Field(..)
19771982
| ExprKind::If(..)
19781983
| ExprKind::Index(..)
@@ -1990,7 +1995,7 @@ impl Expr<'_> {
19901995
| ExprKind::UnsafeBinderCast(..)
19911996
| ExprKind::Err(_) => ExprPrecedence::Unambiguous,
19921997

1993-
ExprKind::DropTemps(ref expr, ..) => expr.precedence(),
1998+
ExprKind::DropTemps(expr, ..) => expr.precedence(),
19941999
}
19952000
}
19962001

‎compiler/rustc_hir_typeck/src/method/probe.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,15 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
13291329
mutbl: hir::Mutability,
13301330
track_unstable_candidates: bool,
13311331
) -> Result<(), MethodError<'tcx>> {
1332+
// The errors emitted by this function are part of
1333+
// the arbitrary self types work, and should not impact
1334+
// other users.
1335+
if !self.tcx.features().arbitrary_self_types()
1336+
&& !self.tcx.features().arbitrary_self_types_pointers()
1337+
{
1338+
return Ok(());
1339+
}
1340+
13321341
// We don't want to remember any of the diagnostic hints from this
13331342
// shadow search, but we do need to provide Some/None for the
13341343
// unstable_candidates in order to reflect the behavior of the

‎compiler/rustc_lint/src/types.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::ops::ControlFlow;
44
use rustc_abi::{BackendRepr, ExternAbi, TagEncoding, Variants, WrappingRange};
55
use rustc_data_structures::fx::FxHashSet;
66
use rustc_errors::DiagMessage;
7-
use rustc_hir::{Expr, ExprKind};
7+
use rustc_hir::{Expr, ExprKind, LangItem};
88
use rustc_middle::bug;
99
use rustc_middle::ty::layout::{LayoutOf, SizeSkeleton};
1010
use rustc_middle::ty::{
@@ -444,7 +444,25 @@ fn lint_fn_pointer<'tcx>(
444444
let (l_ty, l_ty_refs) = peel_refs(l_ty);
445445
let (r_ty, r_ty_refs) = peel_refs(r_ty);
446446

447-
if !l_ty.is_fn() || !r_ty.is_fn() {
447+
if l_ty.is_fn() && r_ty.is_fn() {
448+
// both operands are function pointers, fallthrough
449+
} else if let ty::Adt(l_def, l_args) = l_ty.kind()
450+
&& let ty::Adt(r_def, r_args) = r_ty.kind()
451+
&& cx.tcx.is_lang_item(l_def.did(), LangItem::Option)
452+
&& cx.tcx.is_lang_item(r_def.did(), LangItem::Option)
453+
&& let Some(l_some_arg) = l_args.get(0)
454+
&& let Some(r_some_arg) = r_args.get(0)
455+
&& l_some_arg.expect_ty().is_fn()
456+
&& r_some_arg.expect_ty().is_fn()
457+
{
458+
// both operands are `Option<{function ptr}>`
459+
return cx.emit_span_lint(
460+
UNPREDICTABLE_FUNCTION_POINTER_COMPARISONS,
461+
e.span,
462+
UnpredictableFunctionPointerComparisons::Warn,
463+
);
464+
} else {
465+
// types are not function pointers, nothing to do
448466
return;
449467
}
450468

‎compiler/rustc_passes/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,10 @@ passes_no_sanitize =
566566
`#[no_sanitize({$attr_str})]` should be applied to {$accepted_kind}
567567
.label = not {$accepted_kind}
568568
569+
passes_non_exaustive_with_default_field_values =
570+
`#[non_exhaustive]` can't be used to annotate items with default field values
571+
.label = this struct has default field values
572+
569573
passes_non_exported_macro_invalid_attrs =
570574
attribute should be applied to function or closure
571575
.label = not a function or closure

‎compiler/rustc_passes/src/check_attr.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
124124
[sym::coverage, ..] => self.check_coverage(attr, span, target),
125125
[sym::optimize, ..] => self.check_optimize(hir_id, attr, span, target),
126126
[sym::no_sanitize, ..] => self.check_no_sanitize(attr, span, target),
127-
[sym::non_exhaustive, ..] => self.check_non_exhaustive(hir_id, attr, span, target),
127+
[sym::non_exhaustive, ..] => self.check_non_exhaustive(hir_id, attr, span, target, item),
128128
[sym::marker, ..] => self.check_marker(hir_id, attr, span, target),
129129
[sym::target_feature, ..] => {
130130
self.check_target_feature(hir_id, attr, span, target, attrs)
@@ -685,9 +685,30 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
685685
}
686686

687687
/// Checks if the `#[non_exhaustive]` attribute on an `item` is valid.
688-
fn check_non_exhaustive(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
688+
fn check_non_exhaustive(
689+
&self,
690+
hir_id: HirId,
691+
attr: &Attribute,
692+
span: Span,
693+
target: Target,
694+
item: Option<ItemLike<'_>>,
695+
) {
689696
match target {
690-
Target::Struct | Target::Enum | Target::Variant => {}
697+
Target::Struct => {
698+
if let Some(ItemLike::Item(hir::Item {
699+
kind: hir::ItemKind::Struct(hir::VariantData::Struct { fields, .. }, _),
700+
..
701+
})) = item
702+
&& !fields.is_empty()
703+
&& fields.iter().any(|f| f.default.is_some())
704+
{
705+
self.dcx().emit_err(errors::NonExhaustiveWithDefaultFieldValues {
706+
attr_span: attr.span,
707+
defn_span: span,
708+
});
709+
}
710+
}
711+
Target::Enum | Target::Variant => {}
691712
// FIXME(#80564): We permit struct fields, match arms and macro defs to have an
692713
// `#[non_exhaustive]` attribute with just a lint, because we previously
693714
// erroneously allowed it and some crates used it accidentally, to be compatible

‎compiler/rustc_passes/src/errors.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ pub(crate) struct NonExhaustiveWrongLocation {
119119
pub defn_span: Span,
120120
}
121121

122+
#[derive(Diagnostic)]
123+
#[diag(passes_non_exaustive_with_default_field_values)]
124+
pub(crate) struct NonExhaustiveWithDefaultFieldValues {
125+
#[primary_span]
126+
pub attr_span: Span,
127+
#[label]
128+
pub defn_span: Span,
129+
}
130+
122131
#[derive(Diagnostic)]
123132
#[diag(passes_should_be_applied_to_trait)]
124133
pub(crate) struct AttrShouldBeAppliedToTrait {

‎library/std/src/os/windows/process.rs

Lines changed: 273 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
55
#![stable(feature = "process_extensions", since = "1.2.0")]
66

7-
use crate::ffi::OsStr;
7+
use crate::ffi::{OsStr, c_void};
8+
use crate::mem::MaybeUninit;
89
use crate::os::windows::io::{
910
AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
1011
};
1112
use crate::sealed::Sealed;
1213
use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
13-
use crate::{process, sys};
14+
use crate::{io, marker, process, ptr, sys};
1415

1516
#[stable(feature = "process_extensions", since = "1.2.0")]
1617
impl FromRawHandle for process::Stdio {
@@ -295,41 +296,25 @@ pub trait CommandExt: Sealed {
295296
#[unstable(feature = "windows_process_extensions_async_pipes", issue = "98289")]
296297
fn async_pipes(&mut self, always_async: bool) -> &mut process::Command;
297298

298-
/// Set a raw attribute on the command, providing extended configuration options for Windows
299-
/// processes.
299+
/// Executes the command as a child process with the given
300+
/// [`ProcThreadAttributeList`], returning a handle to it.
300301
///
301-
/// This method allows you to specify custom attributes for a child process on Windows systems
302-
/// using raw attribute values. Raw attributes provide extended configurability for process
303-
/// creation, but their usage can be complex and potentially unsafe.
304-
///
305-
/// The `attribute` parameter specifies the raw attribute to be set, while the `value`
306-
/// parameter holds the value associated with that attribute. Please refer to the
307-
/// [`windows-rs` documentation] or the [Win32 API documentation] for detailed information
308-
/// about available attributes and their meanings.
309-
///
310-
/// [`windows-rs` documentation]: https://microsoft.github.io/windows-docs-rs/doc/windows/
311-
/// [Win32 API documentation]: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute
302+
/// This method enables the customization of attributes for the spawned
303+
/// child process on Windows systems.
304+
/// Attributes offer extended configurability for process creation,
305+
/// but their usage can be intricate and potentially unsafe.
312306
///
313307
/// # Note
314308
///
315-
/// The maximum number of raw attributes is the value of [`u32::MAX`].
316-
/// If this limit is exceeded, the call to [`process::Command::spawn`] will return an `Error`
317-
/// indicating that the maximum number of attributes has been exceeded.
318-
///
319-
/// # Safety
320-
///
321-
/// The usage of raw attributes is potentially unsafe and should be done with caution.
322-
/// Incorrect attribute values or improper configuration can lead to unexpected behavior or
323-
/// errors.
309+
/// By default, stdin, stdout, and stderr are inherited from the parent
310+
/// process.
324311
///
325312
/// # Example
326313
///
327-
/// The following example demonstrates how to create a child process with a specific parent
328-
/// process ID using a raw attribute.
329-
///
330-
/// ```rust
314+
/// ```
331315
/// #![feature(windows_process_extensions_raw_attribute)]
332-
/// use std::os::windows::{process::CommandExt, io::AsRawHandle};
316+
/// use std::os::windows::io::AsRawHandle;
317+
/// use std::os::windows::process::{CommandExt, ProcThreadAttributeList};
333318
/// use std::process::Command;
334319
///
335320
/// # struct ProcessDropGuard(std::process::Child);
@@ -338,36 +323,27 @@ pub trait CommandExt: Sealed {
338323
/// # let _ = self.0.kill();
339324
/// # }
340325
/// # }
341-
///
326+
/// #
342327
/// let parent = Command::new("cmd").spawn()?;
343-
///
344-
/// let mut child_cmd = Command::new("cmd");
328+
/// let parent_process_handle = parent.as_raw_handle();
329+
/// # let parent = ProcessDropGuard(parent);
345330
///
346331
/// const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS: usize = 0x00020000;
332+
/// let mut attribute_list = ProcThreadAttributeList::build()
333+
/// .attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle)
334+
/// .finish()
335+
/// .unwrap();
347336
///
348-
/// unsafe {
349-
/// child_cmd.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.as_raw_handle() as isize);
350-
/// }
337+
/// let mut child = Command::new("cmd").spawn_with_attributes(&attribute_list)?;
351338
/// #
352-
/// # let parent = ProcessDropGuard(parent);
353-
///
354-
/// let mut child = child_cmd.spawn()?;
355-
///
356339
/// # child.kill()?;
357340
/// # Ok::<(), std::io::Error>(())
358341
/// ```
359-
///
360-
/// # Safety Note
361-
///
362-
/// Remember that improper use of raw attributes can lead to undefined behavior or security
363-
/// vulnerabilities. Always consult the documentation and ensure proper attribute values are
364-
/// used.
365342
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
366-
unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(
343+
fn spawn_with_attributes(
367344
&mut self,
368-
attribute: usize,
369-
value: T,
370-
) -> &mut process::Command;
345+
attribute_list: &ProcThreadAttributeList<'_>,
346+
) -> io::Result<process::Child>;
371347
}
372348

373349
#[stable(feature = "windows_process_extensions", since = "1.16.0")]
@@ -401,13 +377,13 @@ impl CommandExt for process::Command {
401377
self
402378
}
403379

404-
unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(
380+
fn spawn_with_attributes(
405381
&mut self,
406-
attribute: usize,
407-
value: T,
408-
) -> &mut process::Command {
409-
unsafe { self.as_inner_mut().raw_attribute(attribute, value) };
410-
self
382+
attribute_list: &ProcThreadAttributeList<'_>,
383+
) -> io::Result<process::Child> {
384+
self.as_inner_mut()
385+
.spawn_with_attributes(sys::process::Stdio::Inherit, true, Some(attribute_list))
386+
.map(process::Child::from_inner)
411387
}
412388
}
413389

@@ -447,3 +423,245 @@ impl ExitCodeExt for process::ExitCode {
447423
process::ExitCode::from_inner(From::from(raw))
448424
}
449425
}
426+
427+
/// A wrapper around windows [`ProcThreadAttributeList`][1].
428+
///
429+
/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist>
430+
#[derive(Debug)]
431+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
432+
pub struct ProcThreadAttributeList<'a> {
433+
attribute_list: Box<[MaybeUninit<u8>]>,
434+
_lifetime_marker: marker::PhantomData<&'a ()>,
435+
}
436+
437+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
438+
impl<'a> ProcThreadAttributeList<'a> {
439+
/// Creates a new builder for constructing a [`ProcThreadAttributeList`].
440+
pub fn build() -> ProcThreadAttributeListBuilder<'a> {
441+
ProcThreadAttributeListBuilder::new()
442+
}
443+
444+
/// Returns a pointer to the underling attribute list.
445+
#[doc(hidden)]
446+
pub fn as_ptr(&self) -> *const MaybeUninit<u8> {
447+
self.attribute_list.as_ptr()
448+
}
449+
}
450+
451+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
452+
impl<'a> Drop for ProcThreadAttributeList<'a> {
453+
/// Deletes the attribute list.
454+
///
455+
/// This method calls [`DeleteProcThreadAttributeList`][1] to delete the
456+
/// underlying attribute list.
457+
///
458+
/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-deleteprocthreadattributelist>
459+
fn drop(&mut self) {
460+
let lp_attribute_list = self.attribute_list.as_mut_ptr().cast::<c_void>();
461+
unsafe { sys::c::DeleteProcThreadAttributeList(lp_attribute_list) }
462+
}
463+
}
464+
465+
/// Builder for constructing a [`ProcThreadAttributeList`].
466+
#[derive(Clone, Debug)]
467+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
468+
pub struct ProcThreadAttributeListBuilder<'a> {
469+
attributes: alloc::collections::BTreeMap<usize, ProcThreadAttributeValue>,
470+
_lifetime_marker: marker::PhantomData<&'a ()>,
471+
}
472+
473+
#[unstable(feature = "windows_process_extensions_raw_attribute", issue = "114854")]
474+
impl<'a> ProcThreadAttributeListBuilder<'a> {
475+
fn new() -> Self {
476+
ProcThreadAttributeListBuilder {
477+
attributes: alloc::collections::BTreeMap::new(),
478+
_lifetime_marker: marker::PhantomData,
479+
}
480+
}
481+
482+
/// Sets an attribute on the attribute list.
483+
///
484+
/// The `attribute` parameter specifies the raw attribute to be set, while
485+
/// the `value` parameter holds the value associated with that attribute.
486+
/// Please refer to the [Windows documentation][1] for a list of valid attributes.
487+
///
488+
/// # Note
489+
///
490+
/// The maximum number of attributes is the value of [`u32::MAX`]. If this
491+
/// limit is exceeded, the call to [`Self::finish`] will return an `Error`
492+
/// indicating that the maximum number of attributes has been exceeded.
493+
///
494+
/// # Safety Note
495+
///
496+
/// Remember that improper use of attributes can lead to undefined behavior
497+
/// or security vulnerabilities. Always consult the documentation and ensure
498+
/// proper attribute values are used.
499+
///
500+
/// [1]: <https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute#parameters>
501+
pub fn attribute<T>(self, attribute: usize, value: &'a T) -> Self {
502+
unsafe {
503+
self.raw_attribute(
504+
attribute,
505+
ptr::addr_of!(*value).cast::<c_void>(),
506+
crate::mem::size_of::<T>(),
507+
)
508+
}
509+
}
510+
511+
/// Sets a raw attribute on the attribute list.
512+
///
513+
/// This function is useful for setting attributes with pointers or sizes
514+
/// that cannot be derived directly from their values.
515+
///
516+
/// # Safety
517+
///
518+
/// This function is marked as `unsafe` because it deals with raw pointers
519+
/// and sizes. It is the responsibility of the caller to ensure the value
520+
/// lives longer than the resulting [`ProcThreadAttributeList`] as well as
521+
/// the validity of the size parameter.
522+
///
523+
/// # Example
524+
///
525+
/// ```
526+
/// #![feature(windows_process_extensions_raw_attribute)]
527+
/// use std::ffi::c_void;
528+
/// use std::os::windows::process::{CommandExt, ProcThreadAttributeList};
529+
/// use std::os::windows::raw::HANDLE;
530+
/// use std::process::Command;
531+
///
532+
/// #[repr(C)]
533+
/// pub struct COORD {
534+
/// pub X: i16,
535+
/// pub Y: i16,
536+
/// }
537+
///
538+
/// extern "system" {
539+
/// fn CreatePipe(
540+
/// hreadpipe: *mut HANDLE,
541+
/// hwritepipe: *mut HANDLE,
542+
/// lppipeattributes: *const c_void,
543+
/// nsize: u32,
544+
/// ) -> i32;
545+
/// fn CreatePseudoConsole(
546+
/// size: COORD,
547+
/// hinput: HANDLE,
548+
/// houtput: HANDLE,
549+
/// dwflags: u32,
550+
/// phpc: *mut isize,
551+
/// ) -> i32;
552+
/// fn CloseHandle(hobject: HANDLE) -> i32;
553+
/// }
554+
///
555+
/// let [mut input_read_side, mut output_write_side, mut output_read_side, mut input_write_side] =
556+
/// [unsafe { std::mem::zeroed::<HANDLE>() }; 4];
557+
///
558+
/// unsafe {
559+
/// CreatePipe(&mut input_read_side, &mut input_write_side, std::ptr::null(), 0);
560+
/// CreatePipe(&mut output_read_side, &mut output_write_side, std::ptr::null(), 0);
561+
/// }
562+
///
563+
/// let size = COORD { X: 60, Y: 40 };
564+
/// let mut h_pc = unsafe { std::mem::zeroed() };
565+
/// unsafe { CreatePseudoConsole(size, input_read_side, output_write_side, 0, &mut h_pc) };
566+
///
567+
/// unsafe { CloseHandle(input_read_side) };
568+
/// unsafe { CloseHandle(output_write_side) };
569+
///
570+
/// const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: usize = 131094;
571+
///
572+
/// let attribute_list = unsafe {
573+
/// ProcThreadAttributeList::build()
574+
/// .raw_attribute(
575+
/// PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
576+
/// h_pc as *const c_void,
577+
/// std::mem::size_of::<isize>(),
578+
/// )
579+
/// .finish()?
580+
/// };
581+
///
582+
/// let mut child = Command::new("cmd").spawn_with_attributes(&attribute_list)?;
583+
/// #
584+
/// # child.kill()?;
585+
/// # Ok::<(), std::io::Error>(())
586+
/// ```
587+
pub unsafe fn raw_attribute<T>(
588+
mut self,
589+
attribute: usize,
590+
value_ptr: *const T,
591+
value_size: usize,
592+
) -> Self {
593+
self.attributes.insert(attribute, ProcThreadAttributeValue {
594+
ptr: value_ptr.cast::<c_void>(),
595+
size: value_size,
596+
});
597+
self
598+
}
599+
600+
/// Finalizes the construction of the `ProcThreadAttributeList`.
601+
///
602+
/// # Errors
603+
///
604+
/// Returns an error if the maximum number of attributes is exceeded
605+
/// or if there is an I/O error during initialization.
606+
pub fn finish(&self) -> io::Result<ProcThreadAttributeList<'a>> {
607+
// To initialize our ProcThreadAttributeList, we need to determine
608+
// how many bytes to allocate for it. The Windows API simplifies this
609+
// process by allowing us to call `InitializeProcThreadAttributeList`
610+
// with a null pointer to retrieve the required size.
611+
let mut required_size = 0;
612+
let Ok(attribute_count) = self.attributes.len().try_into() else {
613+
return Err(io::const_error!(
614+
io::ErrorKind::InvalidInput,
615+
"maximum number of ProcThreadAttributes exceeded",
616+
));
617+
};
618+
unsafe {
619+
sys::c::InitializeProcThreadAttributeList(
620+
ptr::null_mut(),
621+
attribute_count,
622+
0,
623+
&mut required_size,
624+
)
625+
};
626+
627+
let mut attribute_list = vec![MaybeUninit::uninit(); required_size].into_boxed_slice();
628+
629+
// Once we've allocated the necessary memory, it's safe to invoke
630+
// `InitializeProcThreadAttributeList` to properly initialize the list.
631+
sys::cvt(unsafe {
632+
sys::c::InitializeProcThreadAttributeList(
633+
attribute_list.as_mut_ptr().cast::<c_void>(),
634+
attribute_count,
635+
0,
636+
&mut required_size,
637+
)
638+
})?;
639+
640+
// # Add our attributes to the buffer.
641+
// It's theoretically possible for the attribute count to exceed a u32
642+
// value. Therefore, we ensure that we don't add more attributes than
643+
// the buffer was initialized for.
644+
for (&attribute, value) in self.attributes.iter().take(attribute_count as usize) {
645+
sys::cvt(unsafe {
646+
sys::c::UpdateProcThreadAttribute(
647+
attribute_list.as_mut_ptr().cast::<c_void>(),
648+
0,
649+
attribute,
650+
value.ptr,
651+
value.size,
652+
ptr::null_mut(),
653+
ptr::null_mut(),
654+
)
655+
})?;
656+
}
657+
658+
Ok(ProcThreadAttributeList { attribute_list, _lifetime_marker: marker::PhantomData })
659+
}
660+
}
661+
662+
/// Wrapper around the value data to be used as a Process Thread Attribute.
663+
#[derive(Clone, Debug)]
664+
struct ProcThreadAttributeValue {
665+
ptr: *const c_void,
666+
size: usize,
667+
}

‎library/std/src/process/tests.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ fn test_creation_flags() {
450450
fn test_proc_thread_attributes() {
451451
use crate::mem;
452452
use crate::os::windows::io::AsRawHandle;
453-
use crate::os::windows::process::CommandExt;
453+
use crate::os::windows::process::{CommandExt, ProcThreadAttributeList};
454454
use crate::sys::c::{BOOL, CloseHandle, HANDLE};
455455
use crate::sys::cvt;
456456

@@ -490,12 +490,14 @@ fn test_proc_thread_attributes() {
490490

491491
let mut child_cmd = Command::new("cmd");
492492

493-
unsafe {
494-
child_cmd
495-
.raw_attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parent.0.as_raw_handle() as isize);
496-
}
493+
let parent_process_handle = parent.0.as_raw_handle();
494+
495+
let mut attribute_list = ProcThreadAttributeList::build()
496+
.attribute(PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parent_process_handle)
497+
.finish()
498+
.unwrap();
497499

498-
let child = ProcessDropGuard(child_cmd.spawn().unwrap());
500+
let child = ProcessDropGuard(child_cmd.spawn_with_attributes(&mut attribute_list).unwrap());
499501

500502
let h_snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
501503

‎library/std/src/sys/pal/windows/process.rs

Lines changed: 13 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ use crate::collections::BTreeMap;
1010
use crate::env::consts::{EXE_EXTENSION, EXE_SUFFIX};
1111
use crate::ffi::{OsStr, OsString};
1212
use crate::io::{self, Error, ErrorKind};
13-
use crate::mem::MaybeUninit;
1413
use crate::num::NonZero;
1514
use crate::os::windows::ffi::{OsStrExt, OsStringExt};
1615
use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle};
16+
use crate::os::windows::process::ProcThreadAttributeList;
1717
use crate::path::{Path, PathBuf};
1818
use crate::sync::Mutex;
1919
use crate::sys::args::{self, Arg};
@@ -162,7 +162,6 @@ pub struct Command {
162162
stdout: Option<Stdio>,
163163
stderr: Option<Stdio>,
164164
force_quotes_enabled: bool,
165-
proc_thread_attributes: BTreeMap<usize, ProcThreadAttributeValue>,
166165
}
167166

168167
pub enum Stdio {
@@ -194,7 +193,6 @@ impl Command {
194193
stdout: None,
195194
stderr: None,
196195
force_quotes_enabled: false,
197-
proc_thread_attributes: Default::default(),
198196
}
199197
}
200198

@@ -248,21 +246,19 @@ impl Command {
248246
self.cwd.as_ref().map(Path::new)
249247
}
250248

251-
pub unsafe fn raw_attribute<T: Copy + Send + Sync + 'static>(
249+
pub fn spawn(
252250
&mut self,
253-
attribute: usize,
254-
value: T,
255-
) {
256-
self.proc_thread_attributes.insert(attribute, ProcThreadAttributeValue {
257-
size: mem::size_of::<T>(),
258-
data: Box::new(value),
259-
});
251+
default: Stdio,
252+
needs_stdin: bool,
253+
) -> io::Result<(Process, StdioPipes)> {
254+
self.spawn_with_attributes(default, needs_stdin, None)
260255
}
261256

262-
pub fn spawn(
257+
pub fn spawn_with_attributes(
263258
&mut self,
264259
default: Stdio,
265260
needs_stdin: bool,
261+
proc_thread_attribute_list: Option<&ProcThreadAttributeList<'_>>,
266262
) -> io::Result<(Process, StdioPipes)> {
267263
let maybe_env = self.env.capture_if_changed();
268264

@@ -355,18 +351,18 @@ impl Command {
355351

356352
let si_ptr: *mut c::STARTUPINFOW;
357353

358-
let mut proc_thread_attribute_list;
359354
let mut si_ex;
360355

361-
if !self.proc_thread_attributes.is_empty() {
356+
if let Some(proc_thread_attribute_list) = proc_thread_attribute_list {
362357
si.cb = mem::size_of::<c::STARTUPINFOEXW>() as u32;
363358
flags |= c::EXTENDED_STARTUPINFO_PRESENT;
364359

365-
proc_thread_attribute_list =
366-
make_proc_thread_attribute_list(&self.proc_thread_attributes)?;
367360
si_ex = c::STARTUPINFOEXW {
368361
StartupInfo: si,
369-
lpAttributeList: proc_thread_attribute_list.0.as_mut_ptr() as _,
362+
// SAFETY: Casting this `*const` pointer to a `*mut` pointer is "safe"
363+
// here because windows does not internally mutate the attribute list.
364+
// Ideally this should be reflected in the interface of the `windows-sys` crate.
365+
lpAttributeList: proc_thread_attribute_list.as_ptr().cast::<c_void>().cast_mut(),
370366
};
371367
si_ptr = (&raw mut si_ex) as _;
372368
} else {
@@ -896,79 +892,6 @@ fn make_dirp(d: Option<&OsString>) -> io::Result<(*const u16, Vec<u16>)> {
896892
}
897893
}
898894

899-
struct ProcThreadAttributeList(Box<[MaybeUninit<u8>]>);
900-
901-
impl Drop for ProcThreadAttributeList {
902-
fn drop(&mut self) {
903-
let lp_attribute_list = self.0.as_mut_ptr() as _;
904-
unsafe { c::DeleteProcThreadAttributeList(lp_attribute_list) }
905-
}
906-
}
907-
908-
/// Wrapper around the value data to be used as a Process Thread Attribute.
909-
struct ProcThreadAttributeValue {
910-
data: Box<dyn Send + Sync>,
911-
size: usize,
912-
}
913-
914-
fn make_proc_thread_attribute_list(
915-
attributes: &BTreeMap<usize, ProcThreadAttributeValue>,
916-
) -> io::Result<ProcThreadAttributeList> {
917-
// To initialize our ProcThreadAttributeList, we need to determine
918-
// how many bytes to allocate for it. The Windows API simplifies this process
919-
// by allowing us to call `InitializeProcThreadAttributeList` with
920-
// a null pointer to retrieve the required size.
921-
let mut required_size = 0;
922-
let Ok(attribute_count) = attributes.len().try_into() else {
923-
return Err(io::const_error!(
924-
ErrorKind::InvalidInput,
925-
"maximum number of ProcThreadAttributes exceeded",
926-
));
927-
};
928-
unsafe {
929-
c::InitializeProcThreadAttributeList(
930-
ptr::null_mut(),
931-
attribute_count,
932-
0,
933-
&mut required_size,
934-
)
935-
};
936-
937-
let mut proc_thread_attribute_list =
938-
ProcThreadAttributeList(vec![MaybeUninit::uninit(); required_size].into_boxed_slice());
939-
940-
// Once we've allocated the necessary memory, it's safe to invoke
941-
// `InitializeProcThreadAttributeList` to properly initialize the list.
942-
cvt(unsafe {
943-
c::InitializeProcThreadAttributeList(
944-
proc_thread_attribute_list.0.as_mut_ptr() as *mut _,
945-
attribute_count,
946-
0,
947-
&mut required_size,
948-
)
949-
})?;
950-
951-
// # Add our attributes to the buffer.
952-
// It's theoretically possible for the attribute count to exceed a u32 value.
953-
// Therefore, we ensure that we don't add more attributes than the buffer was initialized for.
954-
for (&attribute, value) in attributes.iter().take(attribute_count as usize) {
955-
let value_ptr = (&raw const *value.data) as _;
956-
cvt(unsafe {
957-
c::UpdateProcThreadAttribute(
958-
proc_thread_attribute_list.0.as_mut_ptr() as _,
959-
0,
960-
attribute,
961-
value_ptr,
962-
value.size,
963-
ptr::null_mut(),
964-
ptr::null_mut(),
965-
)
966-
})?;
967-
}
968-
969-
Ok(proc_thread_attribute_list)
970-
}
971-
972895
pub struct CommandArgs<'a> {
973896
iter: crate::slice::Iter<'a, Arg>,
974897
}

‎src/doc/rustdoc/src/unstable-features.md

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ Markdown file, the URL given to `--markdown-playground-url` will take precedence
315315
`--playground-url` and `#![doc(html_playground_url = "url")]` are present when rendering crate docs,
316316
the attribute will take precedence.
317317

318-
### `--sort-modules-by-appearance`: control how items on module pages are sorted
318+
## `--sort-modules-by-appearance`: control how items on module pages are sorted
319319

320320
Using this flag looks like this:
321321

@@ -328,7 +328,7 @@ some consideration for their stability, and names that end in a number). Giving
328328
`rustdoc` will disable this sorting and instead make it print the items in the order they appear in
329329
the source.
330330

331-
### `--show-type-layout`: add a section to each type's docs describing its memory layout
331+
## `--show-type-layout`: add a section to each type's docs describing its memory layout
332332

333333
* Tracking issue: [#113248](https://github.com/rust-lang/rust/issues/113248)
334334

@@ -346,7 +346,7 @@ of that type will take in memory.
346346
Note that most layout information is **completely unstable** and may even differ
347347
between compilations.
348348

349-
### `--resource-suffix`: modifying the name of CSS/JavaScript in crate docs
349+
## `--resource-suffix`: modifying the name of CSS/JavaScript in crate docs
350350

351351
* Tracking issue: [#54765](https://github.com/rust-lang/rust/issues/54765)
352352

@@ -361,7 +361,7 @@ all these files are linked from every page, changing where they are can be cumbe
361361
specially cache them. This flag will rename all these files in the output to include the suffix in
362362
the filename. For example, `light.css` would become `light-suf.css` with the above command.
363363

364-
### `--extern-html-root-url`: control how rustdoc links to non-local crates
364+
## `--extern-html-root-url`: control how rustdoc links to non-local crates
365365

366366
Using this flag looks like this:
367367

@@ -376,7 +376,7 @@ flags to control that behavior. When the `--extern-html-root-url` flag is given
376376
one of your dependencies, rustdoc use that URL for those docs. Keep in mind that if those docs exist
377377
in the output directory, those local docs will still override this flag.
378378

379-
### `-Z force-unstable-if-unmarked`
379+
## `-Z force-unstable-if-unmarked`
380380

381381
Using this flag looks like this:
382382

@@ -389,7 +389,7 @@ This is an internal flag intended for the standard library and compiler that app
389389
allows `rustdoc` to be able to generate documentation for the compiler crates and the standard
390390
library, as an equivalent command-line argument is provided to `rustc` when building those crates.
391391

392-
### `--index-page`: provide a top-level landing page for docs
392+
## `--index-page`: provide a top-level landing page for docs
393393

394394
This feature allows you to generate an index-page with a given markdown file. A good example of it
395395
is the [rust documentation index](https://doc.rust-lang.org/nightly/index.html).
@@ -398,18 +398,18 @@ With this, you'll have a page which you can customize as much as you want at the
398398

399399
Using `index-page` option enables `enable-index-page` option as well.
400400

401-
### `--enable-index-page`: generate a default index page for docs
401+
## `--enable-index-page`: generate a default index page for docs
402402

403403
This feature allows the generation of a default index-page which lists the generated crates.
404404

405-
### `--nocapture`: disable output capture for test
405+
## `--nocapture`: disable output capture for test
406406

407407
When this flag is used with `--test`, the output (stdout and stderr) of your tests won't be
408408
captured by rustdoc. Instead, the output will be directed to your terminal,
409409
as if you had run the test executable manually. This is especially useful
410410
for debugging your tests!
411411

412-
### `--check`: only checks the documentation
412+
## `--check`: only checks the documentation
413413

414414
When this flag is supplied, rustdoc will type check and lint your code, but will not generate any
415415
documentation or run your doctests.
@@ -420,7 +420,7 @@ Using this flag looks like:
420420
rustdoc -Z unstable-options --check src/lib.rs
421421
```
422422

423-
### `--static-root-path`: control how static files are loaded in HTML output
423+
## `--static-root-path`: control how static files are loaded in HTML output
424424

425425
Using this flag looks like this:
426426

@@ -435,7 +435,7 @@ JavaScript, and font files in a single location, rather than duplicating it once
435435
files like the search index will still load from the documentation root, but anything that gets
436436
renamed with `--resource-suffix` will load from the given path.
437437

438-
### `--persist-doctests`: persist doctest executables after running
438+
## `--persist-doctests`: persist doctest executables after running
439439

440440
* Tracking issue: [#56925](https://github.com/rust-lang/rust/issues/56925)
441441

@@ -449,7 +449,7 @@ This flag allows you to keep doctest executables around after they're compiled o
449449
Usually, rustdoc will immediately discard a compiled doctest after it's been tested, but
450450
with this option, you can keep those binaries around for farther testing.
451451

452-
### `--show-coverage`: calculate the percentage of items with documentation
452+
## `--show-coverage`: calculate the percentage of items with documentation
453453

454454
* Tracking issue: [#58154](https://github.com/rust-lang/rust/issues/58154)
455455

@@ -500,7 +500,7 @@ Calculating code examples follows these rules:
500500
* typedef
501501
2. If one of the previously listed items has a code example, then it'll be counted.
502502

503-
#### JSON output
503+
### JSON output
504504

505505
When using `--output-format json` with this option, it will display the coverage information in
506506
JSON format. For example, here is the JSON for a file with one documented item and one
@@ -522,7 +522,7 @@ Note that the third item is the crate root, which in this case is undocumented.
522522
If you want the JSON output to be displayed on `stdout` instead of having a file generated, you can
523523
use `-o -`.
524524

525-
### `-w`/`--output-format`: output format
525+
## `-w`/`--output-format`: output format
526526

527527
`--output-format json` emits documentation in the experimental
528528
[JSON format](https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc_json_types/). `--output-format html` has no effect,
@@ -542,7 +542,7 @@ It can also be used with `--show-coverage`. Take a look at its
542542
[documentation](#--show-coverage-calculate-the-percentage-of-items-with-documentation) for more
543543
information.
544544

545-
### `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
545+
## `--enable-per-target-ignores`: allow `ignore-foo` style filters for doctests
546546

547547
* Tracking issue: [#64245](https://github.com/rust-lang/rust/issues/64245)
548548

@@ -577,7 +577,7 @@ struct Foo;
577577
In older versions, this will be ignored on all targets, but on newer versions `ignore-gnu` will
578578
override `ignore`.
579579

580-
### `--runtool`, `--runtool-arg`: program to run tests with; args to pass to it
580+
## `--runtool`, `--runtool-arg`: program to run tests with; args to pass to it
581581

582582
* Tracking issue: [#64245](https://github.com/rust-lang/rust/issues/64245)
583583

@@ -596,7 +596,7 @@ $ rustdoc src/lib.rs -Z unstable-options --runtool valgrind
596596

597597
Another use case would be to run a test inside an emulator, or through a Virtual Machine.
598598

599-
### `--with-examples`: include examples of uses of items as documentation
599+
## `--with-examples`: include examples of uses of items as documentation
600600

601601
* Tracking issue: [#88791](https://github.com/rust-lang/rust/issues/88791)
602602

@@ -625,7 +625,7 @@ crate being documented (`foobar`) and a path to output the calls
625625
To scrape examples from test code, e.g. functions marked `#[test]`, then
626626
add the `--scrape-tests` flag.
627627

628-
### `--generate-link-to-definition`: Generate links on types in source code
628+
## `--generate-link-to-definition`: Generate links on types in source code
629629

630630
* Tracking issue: [#89095](https://github.com/rust-lang/rust/issues/89095)
631631

@@ -664,3 +664,80 @@ Similar to cargo `build.rustc-wrapper` option, this flag takes a `rustc` wrapper
664664
The first argument to the program will be the test builder program.
665665

666666
This flag can be passed multiple times to nest wrappers.
667+
668+
## Passing arguments to rustc when compiling doctests
669+
670+
You can use the `--doctest-compilation-args` flag if you want to add options when compiling the
671+
doctest. For example if you have:
672+
673+
```rust,no_run
674+
/// ```
675+
/// #![deny(warnings)]
676+
/// #![feature(async_await)]
677+
///
678+
/// let x = 12;
679+
/// ```
680+
pub struct Bar;
681+
```
682+
683+
And you run `rustdoc --test` on it, you will get:
684+
685+
```console
686+
running 1 test
687+
test foo.rs - Bar (line 1) ... FAILED
688+
689+
failures:
690+
691+
---- foo.rs - Bar (line 1) stdout ----
692+
error: the feature `async_await` has been stable since 1.39.0 and no longer requires an attribute to enable
693+
--> foo.rs:2:12
694+
|
695+
3 | #![feature(async_await)]
696+
| ^^^^^^^^^^^
697+
|
698+
note: the lint level is defined here
699+
--> foo.rs:1:9
700+
|
701+
2 | #![deny(warnings)]
702+
| ^^^^^^^^
703+
= note: `#[deny(stable_features)]` implied by `#[deny(warnings)]`
704+
705+
error: aborting due to 1 previous error
706+
707+
Couldn't compile the test.
708+
709+
failures:
710+
foo.rs - Bar (line 1)
711+
712+
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
713+
```
714+
715+
But if you can limit the lint level to warning by using `--doctest_compilation_args=--cap-lints=warn`:
716+
717+
```console
718+
$ rustdoc --test --doctest_compilation_args=--cap-lints=warn file.rs
719+
720+
running 1 test
721+
test tests/rustdoc-ui/doctest/rustflags.rs - Bar (line 5) ... ok
722+
723+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
724+
```
725+
726+
The parsing of arguments works as follows: if it encounters a `"` or a `'`, it will continue
727+
until it finds the character unescaped (without a prepending `\`). If not inside a string, a
728+
whitespace character will also split arguments. Example:
729+
730+
```text
731+
"hello 'a'\" ok" how are 'you today?'
732+
```
733+
734+
will be split as follows:
735+
736+
```text
737+
[
738+
"hello 'a'\" ok",
739+
"how",
740+
"are",
741+
"you today?",
742+
]
743+
```

‎src/librustdoc/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ pub(crate) struct Options {
172172
/// This is mainly useful for other tools that reads that debuginfo to figure out
173173
/// how to call the compiler with the same arguments.
174174
pub(crate) expanded_args: Vec<String>,
175+
176+
/// Arguments to be used when compiling doctests.
177+
pub(crate) doctest_compilation_args: Vec<String>,
175178
}
176179

177180
impl fmt::Debug for Options {
@@ -774,6 +777,7 @@ impl Options {
774777
let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx);
775778
let with_examples = matches.opt_strs("with-examples");
776779
let call_locations = crate::scrape_examples::load_call_locations(with_examples, dcx);
780+
let doctest_compilation_args = matches.opt_strs("doctest-compilation-args");
777781

778782
let unstable_features =
779783
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
@@ -819,6 +823,7 @@ impl Options {
819823
scrape_examples_options,
820824
unstable_features,
821825
expanded_args: args,
826+
doctest_compilation_args,
822827
};
823828
let render_options = RenderOptions {
824829
output,

‎src/librustdoc/doctest.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,46 @@ pub(crate) struct GlobalTestOptions {
5050
pub(crate) args_file: PathBuf,
5151
}
5252

53+
/// Function used to split command line arguments just like a shell would.
54+
fn split_args(args: &str) -> Vec<String> {
55+
let mut out = Vec::new();
56+
let mut iter = args.chars();
57+
let mut current = String::new();
58+
59+
while let Some(c) = iter.next() {
60+
if c == '\\' {
61+
if let Some(c) = iter.next() {
62+
// If it's escaped, even a quote or a whitespace will be ignored.
63+
current.push(c);
64+
}
65+
} else if c == '"' || c == '\'' {
66+
while let Some(new_c) = iter.next() {
67+
if new_c == c {
68+
break;
69+
} else if new_c == '\\' {
70+
if let Some(c) = iter.next() {
71+
// If it's escaped, even a quote will be ignored.
72+
current.push(c);
73+
}
74+
} else {
75+
current.push(new_c);
76+
}
77+
}
78+
} else if " \n\t\r".contains(c) {
79+
if !current.is_empty() {
80+
out.push(current.clone());
81+
current.clear();
82+
}
83+
} else {
84+
current.push(c);
85+
}
86+
}
87+
if !current.is_empty() {
88+
out.push(current);
89+
}
90+
out
91+
}
92+
5393
pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) -> Result<(), String> {
5494
let mut file = File::create(file_path)
5595
.map_err(|error| format!("failed to create args file: {error:?}"))?;
@@ -78,6 +118,10 @@ pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) ->
78118
content.push(format!("-Z{unstable_option_str}"));
79119
}
80120

121+
for compilation_args in &options.doctest_compilation_args {
122+
content.extend(split_args(compilation_args));
123+
}
124+
81125
let content = content.join("\n");
82126

83127
file.write_all(content.as_bytes())

‎src/librustdoc/doctest/tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,25 @@ fn main() {
379379
let (output, len) = make_test(input, None, false, &opts, None);
380380
assert_eq!((output, len), (expected, 1));
381381
}
382+
383+
#[test]
384+
fn check_split_args() {
385+
fn compare(input: &str, expected: &[&str]) {
386+
let output = super::split_args(input);
387+
let expected = expected.iter().map(|s| s.to_string()).collect::<Vec<_>>();
388+
assert_eq!(expected, output, "test failed for {input:?}");
389+
}
390+
391+
compare("'a' \"b\"c", &["a", "bc"]);
392+
compare("'a' \"b \"c d", &["a", "b c", "d"]);
393+
compare("'a' \"b\\\"c\"", &["a", "b\"c"]);
394+
compare("'a\"'", &["a\""]);
395+
compare("\"a'\"", &["a'"]);
396+
compare("\\ a", &[" a"]);
397+
compare("\\\\", &["\\"]);
398+
compare("a'", &["a"]);
399+
compare("a ", &["a"]);
400+
compare("a b", &["a", "b"]);
401+
compare("a\n\t \rb", &["a", "b"]);
402+
compare("a\n\t1 \rb", &["a", "1", "b"]);
403+
}

‎src/librustdoc/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,15 @@ fn opts() -> Vec<RustcOptGroup> {
642642
"Includes trait implementations and other crate info from provided path. Only use with --merge=finalize",
643643
"path/to/doc.parts/<crate-name>",
644644
),
645+
opt(Unstable, Flag, "", "html-no-source", "Disable HTML source code pages generation", ""),
646+
opt(
647+
Unstable,
648+
Multi,
649+
"",
650+
"doctest-compilation-args",
651+
"",
652+
"add arguments to be used when compiling doctests",
653+
),
645654
// deprecated / removed options
646655
opt(Unstable, FlagMulti, "", "disable-minification", "removed", ""),
647656
opt(
@@ -684,7 +693,6 @@ fn opts() -> Vec<RustcOptGroup> {
684693
"removed, see issue #44136 <https://github.com/rust-lang/rust/issues/44136> for more information",
685694
"[rust]",
686695
),
687-
opt(Unstable, Flag, "", "html-no-source", "Disable HTML source code pages generation", ""),
688696
]
689697
}
690698

‎tests/run-make/rustdoc-default-output/output-default.stdout

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ Options:
189189
--include-parts-dir path/to/doc.parts/<crate-name>
190190
Includes trait implementations and other crate info
191191
from provided path. Only use with --merge=finalize
192+
--html-no-source
193+
Disable HTML source code pages generation
194+
--doctest-compilation-args add arguments to be used when compiling doctests
195+
192196
--disable-minification
193197
removed
194198
--plugin-path DIR
@@ -209,8 +213,6 @@ Options:
209213
removed, see issue #44136
210214
<https://github.com/rust-lang/rust/issues/44136> for
211215
more information
212-
--html-no-source
213-
Disable HTML source code pages generation
214216

215217
@path Read newline separated options from `path`
216218

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// This test checks that the test behave when `--doctest-compilation-args` is passed
2+
// multiple times.
3+
4+
//@ check-pass
5+
//@ compile-flags: --test -Zunstable-options --doctest-compilation-args=--cfg=testcase_must_be_present
6+
//@ compile-flags: --doctest-compilation-args=--cfg=another
7+
//@ normalize-stdout-test: "tests/rustdoc-ui/doctest" -> "$$DIR"
8+
//@ normalize-stdout-test: "finished in \d+\.\d+s" -> "finished in $$TIME"
9+
10+
/// ```
11+
/// #[cfg(testcase_must_be_present)]
12+
/// #[cfg(another)]
13+
/// fn must_be_present() {}
14+
///
15+
/// fn main() { must_be_present() }
16+
/// ```
17+
pub struct Bar;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
running 1 test
3+
test $DIR/rustflags-multiple-args.rs - Bar (line 10) ... ok
4+
5+
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
6+

‎tests/rustdoc-ui/doctest/rustflags.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//@ check-pass
2+
//@ compile-flags: --test -Zunstable-options --doctest-compilation-args=--cfg=testcase_must_be_present
3+
//@ normalize-stdout-test: "tests/rustdoc-ui/doctest" -> "$$DIR"
4+
//@ normalize-stdout-test: "finished in \d+\.\d+s" -> "finished in $$TIME"
5+
6+
/// ```
7+
/// #[cfg(testcase_must_be_present)]
8+
/// fn must_be_present() {}
9+
///
10+
/// fn main() { must_be_present() }
11+
/// ```
12+
pub struct Bar;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
running 1 test
3+
test $DIR/rustflags.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+

‎tests/ui-fulldeps/pprust-parenthesis-insertion.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ static EXPRS: &[&str] = &[
6868
// These mean different things.
6969
"return - 2",
7070
"(return) - 2",
71+
// Closures and jumps have equal precedence.
72+
"|| return break 2",
73+
"return break || 2",
74+
// Closures with a return type have especially high precedence.
75+
"|| -> T { x } + 1",
76+
"(|| { x }) + 1",
7177
// These mean different things.
7278
"if let _ = true && false {}",
7379
"if let _ = (true && false) {}",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// This test checks that we lint on Option of fn ptr.
2+
//
3+
// https://github.com/rust-lang/rust/issues/134527.
4+
//
5+
//@ check-pass
6+
7+
unsafe extern "C" fn func() {}
8+
9+
type FnPtr = unsafe extern "C" fn();
10+
11+
fn main() {
12+
let _ = Some::<FnPtr>(func) == Some(func as unsafe extern "C" fn());
13+
//~^ WARN function pointer comparisons
14+
15+
// Undecided as of https://github.com/rust-lang/rust/pull/134536
16+
assert_eq!(Some::<FnPtr>(func), Some(func as unsafe extern "C" fn()));
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
warning: function pointer comparisons do not produce meaningful results since their addresses are not guaranteed to be unique
2+
--> $DIR/fn-ptr-comparisons-some.rs:12:13
3+
|
4+
LL | let _ = Some::<FnPtr>(func) == Some(func as unsafe extern "C" fn());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: the address of the same function can vary between different codegen units
8+
= note: furthermore, different functions could have the same address after being merged together
9+
= note: for more information visit <https://doc.rust-lang.org/nightly/core/ptr/fn.fn_addr_eq.html>
10+
= note: `#[warn(unpredictable_function_pointer_comparisons)]` on by default
11+
12+
warning: 1 warning emitted
13+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//@ run-pass
2+
3+
#![allow(dead_code)]
4+
#![allow(incomplete_features)]
5+
6+
#![feature(arbitrary_self_types)]
7+
#![feature(arbitrary_self_types_pointers)]
8+
#![feature(pin_ergonomics)]
9+
10+
use std::pin::Pin;
11+
use std::pin::pin;
12+
use std::marker::PhantomData;
13+
14+
struct A;
15+
16+
impl A {
17+
fn m(self: *const SmartPtr<Self>) -> usize { 2 }
18+
fn n(self: *const SmartPtr<Self>) -> usize { 2 }
19+
20+
fn o(self: Pin<&SmartPtr2<Self>>) -> usize { 2 }
21+
fn p(self: Pin<&SmartPtr2<Self>>) -> usize { 2 }
22+
}
23+
24+
struct SmartPtr<T>(T);
25+
26+
impl<T> core::ops::Receiver for SmartPtr<T> {
27+
type Target = *mut T;
28+
}
29+
30+
impl<T> SmartPtr<T> {
31+
// In general we try to detect cases where a method in a smart pointer
32+
// "shadows" a method in the referent (in this test, A).
33+
// This method "shadows" the 'n' method in the inner type A
34+
// We do not attempt to produce an error in these shadowing cases
35+
// since the type signature of this method and the corresponding
36+
// method in A are pretty unlikely to occur in practice,
37+
// and because it shows up conflicts between *const::cast and *mut::cast.
38+
fn n(self: *mut Self) -> usize { 1 }
39+
}
40+
41+
struct SmartPtr2<'a, T>(T, PhantomData<&'a T>);
42+
43+
impl<'a, T> core::ops::Receiver for SmartPtr2<'a, T> {
44+
type Target = Pin<&'a mut T>;
45+
}
46+
47+
impl<T> SmartPtr2<'_, T> {
48+
// Similarly, this method shadows the method in A
49+
// Can only happen with the unstable feature pin_ergonomics
50+
fn p(self: Pin<&mut Self>) -> usize { 1 }
51+
}
52+
53+
fn main() {
54+
let mut sm = SmartPtr(A);
55+
let smp: *mut SmartPtr<A> = &mut sm as *mut SmartPtr<A>;
56+
assert_eq!(smp.m(), 2);
57+
assert_eq!(smp.n(), 1);
58+
59+
let smp: Pin<&mut SmartPtr2<A>> = pin!(SmartPtr2(A, PhantomData));
60+
assert_eq!(smp.o(), 2);
61+
let smp: Pin<&mut SmartPtr2<A>> = pin!(SmartPtr2(A, PhantomData));
62+
assert_eq!(smp.p(), 1);
63+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error[E0034]: multiple applicable items in scope
2+
--> $DIR/arbitrary_self_types_pin_getref.rs:23:22
3+
|
4+
LL | let _ = pinned_a.get_ref();
5+
| ^^^^^^^ multiple `get_ref` found
6+
|
7+
note: candidate #1 is defined in an impl for the type `A`
8+
--> $DIR/arbitrary_self_types_pin_getref.rs:17:5
9+
|
10+
LL | fn get_ref(self: &Pin<&A>) {} // note &Pin
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
= note: candidate #2 is defined in an impl for the type `Pin<&'a T>`
13+
14+
error: aborting due to 1 previous error
15+
16+
For more information about this error, try `rustc --explain E0034`.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Confirms that Pin::get_ref can no longer shadow methods in pointees
2+
// once arbitrary_self_types is enabled.
3+
//
4+
//@ revisions: default feature
5+
#![cfg_attr(feature, feature(arbitrary_self_types))]
6+
7+
//@[default] check-pass
8+
9+
#![allow(dead_code)]
10+
11+
use std::pin::Pin;
12+
use std::pin::pin;
13+
14+
struct A;
15+
16+
impl A {
17+
fn get_ref(self: &Pin<&A>) {} // note &Pin
18+
}
19+
20+
fn main() {
21+
let pinned_a: Pin<&mut A> = pin!(A);
22+
let pinned_a: Pin<&A> = pinned_a.as_ref();
23+
let _ = pinned_a.get_ref();
24+
//[feature]~^ ERROR: multiple applicable items
25+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![feature(default_field_values)]
2+
3+
#[derive(Default)]
4+
#[non_exhaustive] //~ ERROR `#[non_exhaustive]` can't be used to annotate items with default field values
5+
struct Foo {
6+
x: i32 = 42 + 3,
7+
}
8+
9+
#[derive(Default)]
10+
enum Bar {
11+
#[non_exhaustive]
12+
#[default]
13+
Baz { //~ ERROR default variant must be exhaustive
14+
x: i32 = 42 + 3,
15+
}
16+
}
17+
18+
fn main () {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: default variant must be exhaustive
2+
--> $DIR/default-field-values-non_exhaustive.rs:13:5
3+
|
4+
LL | #[non_exhaustive]
5+
| ----------------- declared `#[non_exhaustive]` here
6+
LL | #[default]
7+
LL | Baz {
8+
| ^^^
9+
|
10+
= help: consider a manual implementation of `Default`
11+
12+
error: `#[non_exhaustive]` can't be used to annotate items with default field values
13+
--> $DIR/default-field-values-non_exhaustive.rs:4:1
14+
|
15+
LL | #[non_exhaustive]
16+
| ^^^^^^^^^^^^^^^^^
17+
LL | / struct Foo {
18+
LL | | x: i32 = 42 + 3,
19+
LL | | }
20+
| |_- this struct has default field values
21+
22+
error: aborting due to 2 previous errors
23+

0 commit comments

Comments
 (0)
Please sign in to comment.