Auto merge of #129994 - matthiaskrgr:rollup-zkj4ekl, r=matthiaskrgr

Rollup of 8 pull requests

Successful merges:

 - #128820 (fix: get llvm type of global val)
 - #129028 (`impl_trait_overcaptures`: Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime)
 - #129471 ([rustdoc] Sort impl associated items by kinds and then by appearance)
 - #129706 (Rename dump of coroutine by-move-body to be more consistent, fix ICE in dump_mir)
 - #129720 (Simplify DestProp memory management)
 - #129796 (Unify scraped examples with other code examples)
 - #129938 (Elaborate on deriving vs implementing `Copy`)
 - #129973 (run_make_support: rename `Command::stdin` to `stdin_buf` and add `std{in,out,err}` config helpers)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2024-09-05 20:34:38 +00:00
commit 9c01301c52
60 changed files with 1084 additions and 460 deletions

View File

@ -390,7 +390,7 @@ impl<'ll> CodegenCx<'ll, '_> {
let val_llty = self.val_ty(v);
let g = self.get_static_inner(def_id, val_llty);
let llty = self.val_ty(g);
let llty = llvm::LLVMGlobalGetValueType(g);
let g = if val_llty == llty {
g

View File

@ -974,6 +974,7 @@ unsafe extern "C" {
pub fn LLVMGetAlignment(Global: &Value) -> c_uint;
pub fn LLVMSetAlignment(Global: &Value, Bytes: c_uint);
pub fn LLVMSetDLLStorageClass(V: &Value, C: DLLStorageClass);
pub fn LLVMGlobalGetValueType(Global: &Value) -> &Type;
// Operations on global variables
pub fn LLVMIsAGlobalVariable(GlobalVar: &Value) -> Option<&Value>;

View File

@ -1,19 +1,29 @@
use rustc_data_structures::fx::FxIndexSet;
use std::assert_matches::debug_assert_matches;
use std::cell::LazyCell;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
use rustc_data_structures::unord::UnordSet;
use rustc_errors::{Applicability, LintDiagnostic};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_macros::LintDiagnostic;
use rustc_middle::bug;
use rustc_middle::middle::resolve_bound_vars::ResolvedArg;
use rustc_middle::ty::relate::{
structurally_relate_consts, structurally_relate_tys, Relate, RelateResult, TypeRelation,
};
use rustc_middle::ty::{
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
};
use rustc_middle::{bug, span_bug};
use rustc_session::lint::FutureIncompatibilityReason;
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::edition::Edition;
use rustc_span::Span;
use rustc_span::{Span, Symbol};
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt;
use rustc_trait_selection::traits::ObligationCtxt;
use crate::{fluent_generated as fluent, LateContext, LateLintPass};
@ -119,20 +129,41 @@ impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures {
}
}
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
enum ParamKind {
// Early-bound var.
Early(Symbol, u32),
// Late-bound var on function, not within a binder. We can capture these.
Free(DefId, Symbol),
// Late-bound var in a binder. We can't capture these yet.
Late,
}
fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) {
let sig = tcx.fn_sig(parent_def_id).instantiate_identity();
let mut in_scope_parameters = FxIndexSet::default();
let mut in_scope_parameters = FxIndexMap::default();
// Populate the in_scope_parameters list first with all of the generics in scope
let mut current_def_id = Some(parent_def_id.to_def_id());
while let Some(def_id) = current_def_id {
let generics = tcx.generics_of(def_id);
for param in &generics.own_params {
in_scope_parameters.insert(param.def_id);
in_scope_parameters.insert(param.def_id, ParamKind::Early(param.name, param.index));
}
current_def_id = generics.parent;
}
for bound_var in sig.bound_vars() {
let ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, name)) = bound_var
else {
span_bug!(tcx.def_span(parent_def_id), "unexpected non-lifetime binder on fn sig");
};
in_scope_parameters.insert(def_id, ParamKind::Free(def_id, name));
}
let sig = tcx.liberate_late_bound_regions(parent_def_id.to_def_id(), sig);
// Then visit the signature to walk through all the binders (incl. the late-bound
// vars on the function itself, which we need to count too).
sig.visit_with(&mut VisitOpaqueTypes {
@ -140,21 +171,45 @@ fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) {
parent_def_id,
in_scope_parameters,
seen: Default::default(),
// Lazily compute these two, since they're likely a bit expensive.
variances: LazyCell::new(|| {
let mut functional_variances = FunctionalVariances {
tcx: tcx,
variances: FxHashMap::default(),
ambient_variance: ty::Covariant,
generics: tcx.generics_of(parent_def_id),
};
functional_variances.relate(sig, sig).unwrap();
functional_variances.variances
}),
outlives_env: LazyCell::new(|| {
let param_env = tcx.param_env(parent_def_id);
let infcx = tcx.infer_ctxt().build();
let ocx = ObligationCtxt::new(&infcx);
let assumed_wf_tys = ocx.assumed_wf_types(param_env, parent_def_id).unwrap_or_default();
let implied_bounds =
infcx.implied_bounds_tys_compat(param_env, parent_def_id, &assumed_wf_tys, false);
OutlivesEnvironment::with_bounds(param_env, implied_bounds)
}),
});
}
struct VisitOpaqueTypes<'tcx> {
struct VisitOpaqueTypes<'tcx, VarFn, OutlivesFn> {
tcx: TyCtxt<'tcx>,
parent_def_id: LocalDefId,
in_scope_parameters: FxIndexSet<DefId>,
in_scope_parameters: FxIndexMap<DefId, ParamKind>,
variances: LazyCell<FxHashMap<DefId, ty::Variance>, VarFn>,
outlives_env: LazyCell<OutlivesEnvironment<'tcx>, OutlivesFn>,
seen: FxIndexSet<LocalDefId>,
}
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
&mut self,
t: &ty::Binder<'tcx, T>,
) -> Self::Result {
impl<'tcx, VarFn, OutlivesFn> TypeVisitor<TyCtxt<'tcx>>
for VisitOpaqueTypes<'tcx, VarFn, OutlivesFn>
where
VarFn: FnOnce() -> FxHashMap<DefId, ty::Variance>,
OutlivesFn: FnOnce() -> OutlivesEnvironment<'tcx>,
{
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) {
// When we get into a binder, we need to add its own bound vars to the scope.
let mut added = vec![];
for arg in t.bound_vars() {
@ -163,8 +218,8 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, ..))
| ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id, _)) => {
added.push(def_id);
let unique = self.in_scope_parameters.insert(def_id);
assert!(unique);
let unique = self.in_scope_parameters.insert(def_id, ParamKind::Late);
assert_eq!(unique, None);
}
_ => {
self.tcx.dcx().span_delayed_bug(
@ -184,7 +239,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
}
}
fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result {
fn visit_ty(&mut self, t: Ty<'tcx>) {
if !t.has_aliases() {
return;
}
@ -207,89 +262,126 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
&& let hir::OpaqueTyOrigin::FnReturn(parent_def_id) = opaque.origin
&& parent_def_id == self.parent_def_id
{
// Compute the set of args that are captured by the opaque...
let mut captured = FxIndexSet::default();
let variances = self.tcx.variances_of(opaque_def_id);
let mut current_def_id = Some(opaque_def_id.to_def_id());
while let Some(def_id) = current_def_id {
let generics = self.tcx.generics_of(def_id);
for param in &generics.own_params {
// A param is captured if it's invariant.
if variances[param.index as usize] != ty::Invariant {
continue;
}
// We need to turn all `ty::Param`/`ConstKind::Param` and
// `ReEarlyParam`/`ReBound` into def ids.
captured.insert(extract_def_id_from_arg(
self.tcx,
generics,
opaque_ty.args[param.index as usize],
));
}
current_def_id = generics.parent;
}
// Compute the set of in scope params that are not captured. Get their spans,
// since that's all we really care about them for emitting the diagnostic.
let uncaptured_spans: Vec<_> = self
.in_scope_parameters
.iter()
.filter(|def_id| !captured.contains(*def_id))
.map(|def_id| self.tcx.def_span(def_id))
.collect();
let opaque_span = self.tcx.def_span(opaque_def_id);
let new_capture_rules =
opaque_span.at_least_rust_2024() || self.tcx.features().lifetime_capture_rules_2024;
// If we have uncaptured args, and if the opaque doesn't already have
// `use<>` syntax on it, and we're < edition 2024, then warn the user.
if !new_capture_rules
&& !opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..)))
&& !uncaptured_spans.is_empty()
{
let suggestion = if let Ok(snippet) =
self.tcx.sess.source_map().span_to_snippet(opaque_span)
&& snippet.starts_with("impl ")
{
let (lifetimes, others): (Vec<_>, Vec<_>) = captured
.into_iter()
.partition(|def_id| self.tcx.def_kind(*def_id) == DefKind::LifetimeParam);
// Take all lifetime params first, then all others (ty/ct).
let generics: Vec<_> = lifetimes
.into_iter()
.chain(others)
.map(|def_id| self.tcx.item_name(def_id).to_string())
.collect();
// Make sure that we're not trying to name any APITs
if generics.iter().all(|name| !name.starts_with("impl ")) {
Some((
format!(" + use<{}>", generics.join(", ")),
opaque_span.shrink_to_hi(),
))
// Compute the set of args that are captured by the opaque...
let mut captured = FxIndexSet::default();
let mut captured_regions = FxIndexSet::default();
let variances = self.tcx.variances_of(opaque_def_id);
let mut current_def_id = Some(opaque_def_id.to_def_id());
while let Some(def_id) = current_def_id {
let generics = self.tcx.generics_of(def_id);
for param in &generics.own_params {
// A param is captured if it's invariant.
if variances[param.index as usize] != ty::Invariant {
continue;
}
let arg = opaque_ty.args[param.index as usize];
// We need to turn all `ty::Param`/`ConstKind::Param` and
// `ReEarlyParam`/`ReBound` into def ids.
captured.insert(extract_def_id_from_arg(self.tcx, generics, arg));
captured_regions.extend(arg.as_region());
}
current_def_id = generics.parent;
}
// Compute the set of in scope params that are not captured.
let mut uncaptured_args: FxIndexSet<_> = self
.in_scope_parameters
.iter()
.filter(|&(def_id, _)| !captured.contains(def_id))
.collect();
// Remove the set of lifetimes that are in-scope that outlive some other captured
// lifetime and are contravariant (i.e. covariant in argument position).
uncaptured_args.retain(|&(def_id, kind)| {
let Some(ty::Bivariant | ty::Contravariant) = self.variances.get(def_id) else {
// Keep all covariant/invariant args. Also if variance is `None`,
// then that means it's either not a lifetime, or it didn't show up
// anywhere in the signature.
return true;
};
// We only computed variance of lifetimes...
debug_assert_matches!(self.tcx.def_kind(def_id), DefKind::LifetimeParam);
let uncaptured = match *kind {
ParamKind::Early(name, index) => ty::Region::new_early_param(
self.tcx,
ty::EarlyParamRegion { name, index },
),
ParamKind::Free(def_id, name) => ty::Region::new_late_param(
self.tcx,
self.parent_def_id.to_def_id(),
ty::BoundRegionKind::BrNamed(def_id, name),
),
// Totally ignore late bound args from binders.
ParamKind::Late => return true,
};
// Does this region outlive any captured region?
!captured_regions.iter().any(|r| {
self.outlives_env
.free_region_map()
.sub_free_regions(self.tcx, *r, uncaptured)
})
});
// If we have uncaptured args, and if the opaque doesn't already have
// `use<>` syntax on it, and we're < edition 2024, then warn the user.
if !uncaptured_args.is_empty() {
let suggestion = if let Ok(snippet) =
self.tcx.sess.source_map().span_to_snippet(opaque_span)
&& snippet.starts_with("impl ")
{
let (lifetimes, others): (Vec<_>, Vec<_>) =
captured.into_iter().partition(|def_id| {
self.tcx.def_kind(*def_id) == DefKind::LifetimeParam
});
// Take all lifetime params first, then all others (ty/ct).
let generics: Vec<_> = lifetimes
.into_iter()
.chain(others)
.map(|def_id| self.tcx.item_name(def_id).to_string())
.collect();
// Make sure that we're not trying to name any APITs
if generics.iter().all(|name| !name.starts_with("impl ")) {
Some((
format!(" + use<{}>", generics.join(", ")),
opaque_span.shrink_to_hi(),
))
} else {
None
}
} else {
None
}
} else {
None
};
};
self.tcx.emit_node_span_lint(
IMPL_TRAIT_OVERCAPTURES,
self.tcx.local_def_id_to_hir_id(opaque_def_id),
opaque_span,
ImplTraitOvercapturesLint {
self_ty: t,
num_captured: uncaptured_spans.len(),
uncaptured_spans,
suggestion,
},
);
let uncaptured_spans: Vec<_> = uncaptured_args
.into_iter()
.map(|(def_id, _)| self.tcx.def_span(def_id))
.collect();
self.tcx.emit_node_span_lint(
IMPL_TRAIT_OVERCAPTURES,
self.tcx.local_def_id_to_hir_id(opaque_def_id),
opaque_span,
ImplTraitOvercapturesLint {
self_ty: t,
num_captured: uncaptured_spans.len(),
uncaptured_spans,
suggestion,
},
);
}
}
// Otherwise, if we are edition 2024, have `use<>` syntax, and
// have no uncaptured args, then we should warn to the user that
// it's redundant to capture all args explicitly.
else if new_capture_rules
if new_capture_rules
&& let Some((captured_args, capturing_span)) =
opaque.bounds.iter().find_map(|bound| match *bound {
hir::GenericBound::Use(a, s) => Some((a, s)),
@ -327,7 +419,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> {
if self
.in_scope_parameters
.iter()
.all(|def_id| explicitly_captured.contains(def_id))
.all(|(def_id, _)| explicitly_captured.contains(def_id))
{
self.tcx.emit_node_span_lint(
IMPL_TRAIT_REDUNDANT_CAPTURES,
@ -396,7 +488,11 @@ fn extract_def_id_from_arg<'tcx>(
ty::ReBound(
_,
ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. },
) => def_id,
)
| ty::ReLateParam(ty::LateParamRegion {
scope: _,
bound_region: ty::BoundRegionKind::BrNamed(def_id, ..),
}) => def_id,
_ => unreachable!(),
},
ty::GenericArgKind::Type(ty) => {
@ -413,3 +509,106 @@ fn extract_def_id_from_arg<'tcx>(
}
}
}
/// Computes the variances of regions that appear in the type, but considering
/// late-bound regions too, which don't have their variance computed usually.
///
/// Like generalization, this is a unary operation implemented on top of the binary
/// relation infrastructure, mostly because it's much easier to have the relation
/// track the variance for you, rather than having to do it yourself.
struct FunctionalVariances<'tcx> {
tcx: TyCtxt<'tcx>,
variances: FxHashMap<DefId, ty::Variance>,
ambient_variance: ty::Variance,
generics: &'tcx ty::Generics,
}
impl<'tcx> TypeRelation<TyCtxt<'tcx>> for FunctionalVariances<'tcx> {
fn cx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn relate_with_variance<T: ty::relate::Relate<TyCtxt<'tcx>>>(
&mut self,
variance: rustc_type_ir::Variance,
_: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
a: T,
b: T,
) -> RelateResult<'tcx, T> {
let old_variance = self.ambient_variance;
self.ambient_variance = self.ambient_variance.xform(variance);
self.relate(a, b).unwrap();
self.ambient_variance = old_variance;
Ok(a)
}
fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
structurally_relate_tys(self, a, b).unwrap();
Ok(a)
}
fn regions(
&mut self,
a: ty::Region<'tcx>,
_: ty::Region<'tcx>,
) -> RelateResult<'tcx, ty::Region<'tcx>> {
let def_id = match *a {
ty::ReEarlyParam(ebr) => self.generics.region_param(ebr, self.tcx).def_id,
ty::ReBound(
_,
ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. },
)
| ty::ReLateParam(ty::LateParamRegion {
scope: _,
bound_region: ty::BoundRegionKind::BrNamed(def_id, ..),
}) => def_id,
_ => {
return Ok(a);
}
};
if let Some(variance) = self.variances.get_mut(&def_id) {
*variance = unify(*variance, self.ambient_variance);
} else {
self.variances.insert(def_id, self.ambient_variance);
}
Ok(a)
}
fn consts(
&mut self,
a: ty::Const<'tcx>,
b: ty::Const<'tcx>,
) -> RelateResult<'tcx, ty::Const<'tcx>> {
structurally_relate_consts(self, a, b).unwrap();
Ok(a)
}
fn binders<T>(
&mut self,
a: ty::Binder<'tcx, T>,
b: ty::Binder<'tcx, T>,
) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
where
T: Relate<TyCtxt<'tcx>>,
{
self.relate(a.skip_binder(), b.skip_binder()).unwrap();
Ok(a)
}
}
/// What is the variance that satisfies the two variances?
fn unify(a: ty::Variance, b: ty::Variance) -> ty::Variance {
match (a, b) {
// Bivariance is lattice bottom.
(ty::Bivariant, other) | (other, ty::Bivariant) => other,
// Invariant is lattice top.
(ty::Invariant, _) | (_, ty::Invariant) => ty::Invariant,
// If type is required to be covariant and contravariant, then it's invariant.
(ty::Contravariant, ty::Covariant) | (ty::Covariant, ty::Contravariant) => ty::Invariant,
// Otherwise, co + co = co, contra + contra = contra.
(ty::Contravariant, ty::Contravariant) => ty::Contravariant,
(ty::Covariant, ty::Covariant) => ty::Covariant,
}
}

View File

@ -30,6 +30,7 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![doc(rust_logo)]
#![feature(array_windows)]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(control_flow_enum)]
#![feature(extract_if)]

View File

@ -612,7 +612,9 @@ fn write_mir_sig(tcx: TyCtxt<'_>, body: &Body<'_>, w: &mut dyn io::Write) -> io:
let def_id = body.source.def_id();
let kind = tcx.def_kind(def_id);
let is_function = match kind {
DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) => true,
DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(..) | DefKind::SyntheticCoroutineBody => {
true
}
_ => tcx.is_closure_like(def_id),
};
match (kind, body.source.promoted) {

View File

@ -207,11 +207,12 @@ pub fn coroutine_by_move_body_def_id<'tcx>(
let mut by_move_body = body.clone();
MakeByMoveBody { tcx, field_remapping, by_move_coroutine_ty }.visit_body(&mut by_move_body);
dump_mir(tcx, false, "coroutine_by_move", &0, &by_move_body, |_, _| Ok(()));
let body_def = tcx.create_def(coroutine_def_id, kw::Empty, DefKind::SyntheticCoroutineBody);
// This will always be `{closure#1}`, since the original coroutine is `{closure#0}`.
let body_def = tcx.create_def(parent_def_id, kw::Empty, DefKind::SyntheticCoroutineBody);
by_move_body.source =
mir::MirSource::from_instance(InstanceKind::Item(body_def.def_id().to_def_id()));
dump_mir(tcx, false, "built", &"after", &by_move_body, |_, _| Ok(()));
// Inherited from the by-ref coroutine.
body_def.codegen_fn_attrs(tcx.codegen_fn_attrs(coroutine_def_id).clone());

View File

@ -163,7 +163,8 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id();
let mut allocations = Allocations::default();
let mut candidates = Candidates::default();
let mut write_info = WriteInfo::default();
trace!(func = ?tcx.def_path_str(def_id));
let borrowed = rustc_mir_dataflow::impls::borrowed_locals(body);
@ -191,12 +192,7 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
loop {
// PERF: Can we do something smarter than recalculating the candidates and liveness
// results?
let mut candidates = find_candidates(
body,
&borrowed,
&mut allocations.candidates,
&mut allocations.candidates_reverse,
);
candidates.reset_and_find(body, &borrowed);
trace!(?candidates);
dest_prop_mir_dump(tcx, body, &points, &live, round_count);
@ -204,7 +200,7 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
&mut candidates,
&points,
&live,
&mut allocations.write_info,
&mut write_info,
body,
);
@ -253,20 +249,8 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
}
}
/// Container for the various allocations that we need.
///
/// We store these here and hand out `&mut` access to them, instead of dropping and recreating them
/// frequently. Everything with a `&'alloc` lifetime points into here.
#[derive(Default)]
struct Allocations {
candidates: FxIndexMap<Local, Vec<Local>>,
candidates_reverse: FxIndexMap<Local, Vec<Local>>,
write_info: WriteInfo,
// PERF: Do this for `MaybeLiveLocals` allocations too.
}
#[derive(Debug)]
struct Candidates<'alloc> {
#[derive(Debug, Default)]
struct Candidates {
/// The set of candidates we are considering in this optimization.
///
/// We will always merge the key into at most one of its values.
@ -281,11 +265,12 @@ struct Candidates<'alloc> {
///
/// We will still report that we would like to merge `_1` and `_2` in an attempt to allow us to
/// remove that assignment.
c: &'alloc mut FxIndexMap<Local, Vec<Local>>,
c: FxIndexMap<Local, Vec<Local>>,
/// A reverse index of the `c` set; if the `c` set contains `a => Place { local: b, proj }`,
/// then this contains `b => a`.
// PERF: Possibly these should be `SmallVec`s?
reverse: &'alloc mut FxIndexMap<Local, Vec<Local>>,
reverse: FxIndexMap<Local, Vec<Local>>,
}
//////////////////////////////////////////////////////////
@ -358,19 +343,40 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Merger<'a, 'tcx> {
//
// This section enforces bullet point 2
struct FilterInformation<'a, 'body, 'alloc, 'tcx> {
body: &'body Body<'tcx>,
struct FilterInformation<'a, 'tcx> {
body: &'a Body<'tcx>,
points: &'a DenseLocationMap,
live: &'a SparseIntervalMatrix<Local, PointIndex>,
candidates: &'a mut Candidates<'alloc>,
write_info: &'alloc mut WriteInfo,
candidates: &'a mut Candidates,
write_info: &'a mut WriteInfo,
at: Location,
}
// We first implement some utility functions which we will expose removing candidates according to
// different needs. Throughout the liveness filtering, the `candidates` are only ever accessed
// through these methods, and not directly.
impl<'alloc> Candidates<'alloc> {
impl Candidates {
/// Collects the candidates for merging.
///
/// This is responsible for enforcing the first and third bullet point.
fn reset_and_find<'tcx>(&mut self, body: &Body<'tcx>, borrowed: &BitSet<Local>) {
self.c.clear();
self.reverse.clear();
let mut visitor = FindAssignments { body, candidates: &mut self.c, borrowed };
visitor.visit_body(body);
// Deduplicate candidates.
for (_, cands) in self.c.iter_mut() {
cands.sort();
cands.dedup();
}
// Generate the reverse map.
for (src, cands) in self.c.iter() {
for dest in cands.iter().copied() {
self.reverse.entry(dest).or_default().push(*src);
}
}
}
/// Just `Vec::retain`, but the condition is inverted and we add debugging output
fn vec_filter_candidates(
src: Local,
@ -445,7 +451,7 @@ enum CandidateFilter {
Remove,
}
impl<'a, 'body, 'alloc, 'tcx> FilterInformation<'a, 'body, 'alloc, 'tcx> {
impl<'a, 'tcx> FilterInformation<'a, 'tcx> {
/// Filters the set of candidates to remove those that conflict.
///
/// The steps we take are exactly those that are outlined at the top of the file. For each
@ -463,12 +469,12 @@ impl<'a, 'body, 'alloc, 'tcx> FilterInformation<'a, 'body, 'alloc, 'tcx> {
/// before the statement/terminator will correctly report locals that are read in the
/// statement/terminator to be live. We are additionally conservative by treating all written to
/// locals as also being read from.
fn filter_liveness<'b>(
candidates: &mut Candidates<'alloc>,
fn filter_liveness(
candidates: &mut Candidates,
points: &DenseLocationMap,
live: &SparseIntervalMatrix<Local, PointIndex>,
write_info_alloc: &'alloc mut WriteInfo,
body: &'b Body<'tcx>,
write_info: &mut WriteInfo,
body: &Body<'tcx>,
) {
let mut this = FilterInformation {
body,
@ -477,7 +483,7 @@ impl<'a, 'body, 'alloc, 'tcx> FilterInformation<'a, 'body, 'alloc, 'tcx> {
candidates,
// We don't actually store anything at this scope, we just keep things here to be able
// to reuse the allocation.
write_info: write_info_alloc,
write_info,
// Doesn't matter what we put here, will be overwritten before being used
at: Location::START,
};
@ -734,40 +740,13 @@ fn places_to_candidate_pair<'tcx>(
Some((a, b))
}
/// Collects the candidates for merging
///
/// This is responsible for enforcing the first and third bullet point.
fn find_candidates<'alloc, 'tcx>(
body: &Body<'tcx>,
borrowed: &BitSet<Local>,
candidates: &'alloc mut FxIndexMap<Local, Vec<Local>>,
candidates_reverse: &'alloc mut FxIndexMap<Local, Vec<Local>>,
) -> Candidates<'alloc> {
candidates.clear();
candidates_reverse.clear();
let mut visitor = FindAssignments { body, candidates, borrowed };
visitor.visit_body(body);
// Deduplicate candidates
for (_, cands) in candidates.iter_mut() {
cands.sort();
cands.dedup();
}
// Generate the reverse map
for (src, cands) in candidates.iter() {
for dest in cands.iter().copied() {
candidates_reverse.entry(dest).or_default().push(*src);
}
}
Candidates { c: candidates, reverse: candidates_reverse }
}
struct FindAssignments<'a, 'alloc, 'tcx> {
struct FindAssignments<'a, 'tcx> {
body: &'a Body<'tcx>,
candidates: &'alloc mut FxIndexMap<Local, Vec<Local>>,
candidates: &'a mut FxIndexMap<Local, Vec<Local>>,
borrowed: &'a BitSet<Local>,
}
impl<'tcx> Visitor<'tcx> for FindAssignments<'_, '_, 'tcx> {
impl<'tcx> Visitor<'tcx> for FindAssignments<'_, 'tcx> {
fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) {
if let StatementKind::Assign(box (
lhs,
@ -819,9 +798,9 @@ fn is_local_required(local: Local, body: &Body<'_>) -> bool {
/////////////////////////////////////////////////////////
// MIR Dump
fn dest_prop_mir_dump<'body, 'tcx>(
fn dest_prop_mir_dump<'tcx>(
tcx: TyCtxt<'tcx>,
body: &'body Body<'tcx>,
body: &Body<'tcx>,
points: &DenseLocationMap,
live: &SparseIntervalMatrix<Local, PointIndex>,
round: usize,

View File

@ -288,8 +288,19 @@ marker_impls! {
/// }
/// ```
///
/// There is a small difference between the two: the `derive` strategy will also place a `Copy`
/// bound on type parameters, which isn't always desired.
/// There is a small difference between the two. The `derive` strategy will also place a `Copy`
/// bound on type parameters:
///
/// ```
/// #[derive(Clone)]
/// struct MyStruct<T>(T);
///
/// impl<T: Copy> Copy for MyStruct<T> { }
/// ```
///
/// This isn't always desired. For example, shared references (`&T`) can be copied regardless of
/// whether `T` is `Copy`. Likewise, a generic struct containing markers such as [`PhantomData`]
/// could potentially be duplicated with a bit-wise copy.
///
/// ## What's the difference between `Copy` and `Clone`?
///

View File

@ -1794,13 +1794,64 @@ fn render_impl(
let mut default_impl_items = Buffer::empty_from(w);
let impl_ = i.inner_impl();
// Impl items are grouped by kinds:
//
// 1. Constants
// 2. Types
// 3. Functions
//
// This order is because you can have associated constants used in associated types (like array
// length), and both in associcated functions. So with this order, when reading from top to
// bottom, you should see items definitions before they're actually used most of the time.
let mut assoc_types = Vec::new();
let mut methods = Vec::new();
if !impl_.is_negative_trait_impl() {
for trait_item in &impl_.items {
match *trait_item.kind {
clean::MethodItem(..) | clean::TyMethodItem(_) => methods.push(trait_item),
clean::TyAssocTypeItem(..) | clean::AssocTypeItem(..) => {
assoc_types.push(trait_item)
}
clean::TyAssocConstItem(..) | clean::AssocConstItem(_) => {
// We render it directly since they're supposed to come first.
doc_impl_item(
&mut default_impl_items,
&mut impl_items,
cx,
trait_item,
if trait_.is_some() { &i.impl_item } else { parent },
link,
render_mode,
false,
trait_,
rendering_params,
);
}
_ => {}
}
}
for assoc_type in assoc_types {
doc_impl_item(
&mut default_impl_items,
&mut impl_items,
cx,
trait_item,
assoc_type,
if trait_.is_some() { &i.impl_item } else { parent },
link,
render_mode,
false,
trait_,
rendering_params,
);
}
for method in methods {
doc_impl_item(
&mut default_impl_items,
&mut impl_items,
cx,
method,
if trait_.is_some() { &i.impl_item } else { parent },
link,
render_mode,
@ -2455,28 +2506,6 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &c
let needs_expansion = line_max - line_min > NUM_VISIBLE_LINES;
let locations_encoded = serde_json::to_string(&line_ranges).unwrap();
write!(
&mut w,
"<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\
<div class=\"scraped-example-title\">\
{name} (<a href=\"{url}\">{title}</a>)\
</div>\
<div class=\"code-wrapper\">",
expanded_cls = if needs_expansion { "" } else { "expanded" },
name = call_data.display_name,
url = init_url,
title = init_title,
// The locations are encoded as a data attribute, so they can be read
// later by the JS for interactions.
locations = Escape(&locations_encoded)
)
.unwrap();
if line_ranges.len() > 1 {
w.write_str(r#"<button class="prev">&pr;</button> <button class="next">&sc;</button>"#)
.unwrap();
}
// Look for the example file in the source map if it exists, otherwise return a dummy span
let file_span = (|| {
let source_map = tcx.sess.source_map();
@ -2507,9 +2536,16 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &c
cx,
&cx.root_path(),
highlight::DecorationInfo(decoration_info),
sources::SourceContext::Embedded { offset: line_min, needs_expansion },
sources::SourceContext::Embedded(sources::ScrapedInfo {
needs_prev_next_buttons: line_ranges.len() > 1,
needs_expansion,
offset: line_min,
name: &call_data.display_name,
url: init_url,
title: init_title,
locations: locations_encoded,
}),
);
w.write_str("</div></div>").unwrap();
true
};

View File

@ -843,33 +843,6 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
}
}
if !required_types.is_empty() {
write_section_heading(
w,
"Required Associated Types",
"required-associated-types",
None,
"<div class=\"methods\">",
);
for t in required_types {
trait_item(w, cx, t, it);
}
w.write_str("</div>");
}
if !provided_types.is_empty() {
write_section_heading(
w,
"Provided Associated Types",
"provided-associated-types",
None,
"<div class=\"methods\">",
);
for t in provided_types {
trait_item(w, cx, t, it);
}
w.write_str("</div>");
}
if !required_consts.is_empty() {
write_section_heading(
w,
@ -897,6 +870,33 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
w.write_str("</div>");
}
if !required_types.is_empty() {
write_section_heading(
w,
"Required Associated Types",
"required-associated-types",
None,
"<div class=\"methods\">",
);
for t in required_types {
trait_item(w, cx, t, it);
}
w.write_str("</div>");
}
if !provided_types.is_empty() {
write_section_heading(
w,
"Provided Associated Types",
"provided-associated-types",
None,
"<div class=\"methods\">",
);
for t in provided_types {
trait_item(w, cx, t, it);
}
w.write_str("</div>");
}
// Output the documentation for each function individually
if !required_methods.is_empty() || must_implement_one_of_functions.is_some() {
write_section_heading(

View File

@ -302,10 +302,10 @@ fn sidebar_trait<'a>(
blocks.extend(
[
("required-associated-types", "Required Associated Types", req_assoc),
("provided-associated-types", "Provided Associated Types", prov_assoc),
("required-associated-consts", "Required Associated Constants", req_assoc_const),
("provided-associated-consts", "Provided Associated Constants", prov_assoc_const),
("required-associated-types", "Required Associated Types", req_assoc),
("provided-associated-types", "Provided Associated Types", prov_assoc),
("required-methods", "Required Methods", req_method),
("provided-methods", "Provided Methods", prov_method),
("foreign-impls", "Implementations on Foreign Types", foreign_impls),
@ -394,6 +394,7 @@ fn sidebar_assoc_items<'a>(
let cache = cx.cache();
let mut assoc_consts = Vec::new();
let mut assoc_types = Vec::new();
let mut methods = Vec::new();
if let Some(v) = cache.impls.get(&did) {
let mut used_links = FxHashSet::default();
@ -401,22 +402,14 @@ fn sidebar_assoc_items<'a>(
{
let used_links_bor = &mut used_links;
assoc_consts.extend(
v.iter()
.filter(|i| i.inner_impl().trait_.is_none())
.flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)),
);
for impl_ in v.iter().map(|i| i.inner_impl()).filter(|i| i.trait_.is_none()) {
assoc_consts.extend(get_associated_constants(impl_, used_links_bor));
assoc_types.extend(get_associated_types(impl_, used_links_bor));
methods.extend(get_methods(impl_, false, used_links_bor, false, cx.tcx()));
}
// We want links' order to be reproducible so we don't use unstable sort.
assoc_consts.sort();
#[rustfmt::skip] // rustfmt makes the pipeline less readable
methods.extend(
v.iter()
.filter(|i| i.inner_impl().trait_.is_none())
.flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())),
);
// We want links' order to be reproducible so we don't use unstable sort.
assoc_types.sort();
methods.sort();
}
@ -426,6 +419,11 @@ fn sidebar_assoc_items<'a>(
"associatedconstant",
assoc_consts,
),
LinkBlock::new(
Link::new("implementations", "Associated Types"),
"associatedtype",
assoc_types,
),
LinkBlock::new(Link::new("implementations", "Methods"), "method", methods),
];
@ -452,6 +450,7 @@ fn sidebar_assoc_items<'a>(
&mut blocks,
);
}
links.append(&mut blocks);
}
}
@ -715,3 +714,19 @@ fn get_associated_constants<'a>(
})
.collect::<Vec<_>>()
}
fn get_associated_types<'a>(
i: &'a clean::Impl,
used_links: &mut FxHashSet<String>,
) -> Vec<Link<'a>> {
i.items
.iter()
.filter_map(|item| match item.name {
Some(ref name) if !name.is_empty() && item.is_associated_type() => Some(Link::new(
get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::AssocType)),
name.as_str(),
)),
_ => None,
})
.collect::<Vec<_>>()
}

View File

@ -290,9 +290,34 @@ where
}
}
pub(crate) enum SourceContext {
pub(crate) struct ScrapedInfo<'a> {
pub(crate) offset: usize,
pub(crate) needs_prev_next_buttons: bool,
pub(crate) name: &'a str,
pub(crate) url: &'a str,
pub(crate) title: &'a str,
pub(crate) locations: String,
pub(crate) needs_expansion: bool,
}
#[derive(Template)]
#[template(path = "scraped_source.html")]
struct ScrapedSource<'a, Code: std::fmt::Display> {
info: ScrapedInfo<'a>,
lines: RangeInclusive<usize>,
code_html: Code,
}
#[derive(Template)]
#[template(path = "source.html")]
struct Source<Code: std::fmt::Display> {
lines: RangeInclusive<usize>,
code_html: Code,
}
pub(crate) enum SourceContext<'a> {
Standalone,
Embedded { offset: usize, needs_expansion: bool },
Embedded(ScrapedInfo<'a>),
}
/// Wrapper struct to render the source code of a file. This will do things like
@ -304,23 +329,8 @@ pub(crate) fn print_src(
context: &Context<'_>,
root_path: &str,
decoration_info: highlight::DecorationInfo,
source_context: SourceContext,
source_context: SourceContext<'_>,
) {
#[derive(Template)]
#[template(path = "source.html")]
struct Source<Code: std::fmt::Display> {
embedded: bool,
needs_expansion: bool,
lines: RangeInclusive<usize>,
code_html: Code,
}
let lines = s.lines().count();
let (embedded, needs_expansion, lines) = match source_context {
SourceContext::Standalone => (false, false, 1..=lines),
SourceContext::Embedded { offset, needs_expansion } => {
(true, needs_expansion, (1 + offset)..=(lines + offset))
}
};
let current_href = context
.href_from_span(clean::Span::new(file_span), false)
.expect("only local crates should have sources emitted");
@ -333,5 +343,14 @@ pub(crate) fn print_src(
);
Ok(())
});
Source { embedded, needs_expansion, lines, code_html: code }.render_into(&mut writer).unwrap();
let lines = s.lines().count();
match source_context {
SourceContext::Standalone => {
Source { lines: (1..=lines), code_html: code }.render_into(&mut writer).unwrap()
}
SourceContext::Embedded(info) => {
let lines = (1 + info.offset)..=(lines + info.offset);
ScrapedSource { info, lines, code_html: code }.render_into(&mut writer).unwrap();
}
};
}

View File

@ -59,6 +59,8 @@ nav.sub {
--copy-path-button-color: #999;
--copy-path-img-filter: invert(50%);
--copy-path-img-hover-filter: invert(35%);
--code-example-button-color: #7f7f7f;
--code-example-button-hover-color: #595959;
--codeblock-error-hover-color: rgb(255, 0, 0);
--codeblock-error-color: rgba(255, 0, 0, .5);
--codeblock-ignore-hover-color: rgb(255, 142, 0);
@ -162,6 +164,8 @@ nav.sub {
--copy-path-button-color: #999;
--copy-path-img-filter: invert(50%);
--copy-path-img-hover-filter: invert(65%);
--code-example-button-color: #7f7f7f;
--code-example-button-hover-color: #a5a5a5;
--codeblock-error-hover-color: rgb(255, 0, 0);
--codeblock-error-color: rgba(255, 0, 0, .5);
--codeblock-ignore-hover-color: rgb(255, 142, 0);

View File

@ -378,7 +378,7 @@ pre.item-decl {
.src .content pre {
padding: 20px;
}
.rustdoc.src .example-wrap pre.src-line-numbers {
.rustdoc.src .example-wrap .src-line-numbers {
padding: 20px 0 20px 4px;
}
@ -757,10 +757,32 @@ ul.block, .block li, .block ul {
margin-bottom: 10px;
}
.rustdoc .example-wrap > pre {
.rustdoc .example-wrap > pre,
.rustdoc .scraped-example .src-line-numbers,
.rustdoc .scraped-example .src-line-numbers > pre {
border-radius: 6px;
}
/*
If the code example line numbers are displayed, there will be a weird radius in the middle from
both the code example and the line numbers, so we need to remove the radius in this case.
*/
.rustdoc .example-wrap > .example-line-numbers,
.rustdoc .scraped-example .src-line-numbers,
.rustdoc .scraped-example .src-line-numbers > pre {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.rustdoc .example-wrap > .example-line-numbers + pre,
.rustdoc .scraped-example .rust {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.rustdoc .scraped-example {
position: relative;
}
/* For the last child of a div, the margin will be taken care of
by the margin-top of the next item. */
.rustdoc .example-wrap:last-child {
@ -772,15 +794,36 @@ ul.block, .block li, .block ul {
flex-grow: 1;
}
.rustdoc:not(.src) .example-wrap pre {
.scraped-example:not(.expanded) .example-wrap {
/* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number
* of lines shown in the un-expanded example code viewer. This pre needs to have
* a max-height equal to line-height * N. The line-height is currently 1.5em,
* and we include additional 10px for padding. */
max-height: calc(1.5em * 5 + 10px);
}
.rustdoc:not(.src) .scraped-example:not(.expanded) .src-line-numbers,
.rustdoc:not(.src) .scraped-example:not(.expanded) .src-line-numbers > pre,
.rustdoc:not(.src) .scraped-example:not(.expanded) pre.rust {
padding-bottom: 0;
/* See above comment, should be the same max-height. */
overflow: auto hidden;
}
.rustdoc:not(.src) .scraped-example .src-line-numbers {
padding-top: 0;
}
.rustdoc:not(.src) .scraped-example.expanded .src-line-numbers {
padding-bottom: 0;
}
.rustdoc:not(.src) .example-wrap pre {
overflow: auto;
}
.rustdoc .example-wrap pre.example-line-numbers,
.rustdoc .example-wrap pre.src-line-numbers {
flex-grow: 0;
.rustdoc .example-wrap .src-line-numbers {
min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */
overflow: initial;
flex-grow: 0;
text-align: right;
-webkit-user-select: none;
user-select: none;
@ -788,7 +831,7 @@ ul.block, .block li, .block ul {
color: var(--src-line-numbers-span-color);
}
.rustdoc .example-wrap pre.src-line-numbers {
.rustdoc .scraped-example .src-line-numbers {
padding: 14px 0;
}
.src-line-numbers a, .src-line-numbers span {
@ -1500,17 +1543,23 @@ instead, we check that it's not a "finger" cursor.
.example-wrap .button-holder.keep-visible {
visibility: visible;
}
.example-wrap .button-holder .copy-button, .example-wrap .test-arrow {
.example-wrap .button-holder > * {
background: var(--main-background-color);
cursor: pointer;
border-radius: var(--button-border-radius);
height: var(--copy-path-height);
width: var(--copy-path-width);
border: 0;
color: var(--code-example-button-color);
}
.example-wrap .button-holder > *:hover {
color: var(--code-example-button-hover-color);
}
.example-wrap .button-holder > *:not(:first-child) {
margin-left: var(--button-left-margin);
}
.example-wrap .button-holder .copy-button {
margin-left: var(--button-left-margin);
padding: 2px 0 0 4px;
border: 0;
}
.example-wrap .button-holder .copy-button::before,
.example-wrap .test-arrow::before {
@ -2254,6 +2303,7 @@ in src-script.js and main.js
}
}
/* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */
@media (min-width: 701px) {
/* Places file-link for a scraped example on top of the example to save space.
@ -2356,99 +2406,41 @@ in src-script.js and main.js
color: var(--scrape-example-help-hover-color);
}
.scraped-example {
/* So .scraped-example-title can be positioned absolutely */
position: relative;
}
.scraped-example .code-wrapper {
position: relative;
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.scraped-example:not(.expanded) .code-wrapper {
/* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number
* of lines shown in the un-expanded example code viewer. This pre needs to have
* a max-height equal to line-height * N. The line-height is currently 1.5em,
* and we include additional 10px for padding. */
max-height: calc(1.5em * 5 + 10px);
}
.scraped-example:not(.expanded) .code-wrapper pre {
overflow-y: hidden;
padding-bottom: 0;
/* See above comment, should be the same max-height. */
max-height: calc(1.5em * 5 + 10px);
}
.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper,
.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre {
/* See above comment, except this height is based on HIDDEN_MAX_LINES. */
max-height: calc(1.5em * 10 + 10px);
}
.scraped-example .code-wrapper .next,
.scraped-example .code-wrapper .prev,
.scraped-example .code-wrapper .expand {
color: var(--main-color);
position: absolute;
top: 0.25em;
z-index: 1;
padding: 0;
background: none;
border: none;
/* iOS button gradient: https://stackoverflow.com/q/5438567 */
-webkit-appearance: none;
opacity: 1;
}
.scraped-example .code-wrapper .prev {
right: 2.25em;
}
.scraped-example .code-wrapper .next {
right: 1.25em;
}
.scraped-example .code-wrapper .expand {
right: 0.25em;
}
.scraped-example:not(.expanded) .code-wrapper::before,
.scraped-example:not(.expanded) .code-wrapper::after {
.scraped-example:not(.expanded) .example-wrap::before,
.scraped-example:not(.expanded) .example-wrap::after {
content: " ";
width: 100%;
height: 5px;
position: absolute;
z-index: 1;
}
.scraped-example:not(.expanded) .code-wrapper::before {
.scraped-example:not(.expanded) .example-wrap::before {
top: 0;
background: linear-gradient(to bottom,
var(--scrape-example-code-wrapper-background-start),
var(--scrape-example-code-wrapper-background-end));
}
.scraped-example:not(.expanded) .code-wrapper::after {
.scraped-example:not(.expanded) .example-wrap::after {
bottom: 0;
background: linear-gradient(to top,
var(--scrape-example-code-wrapper-background-start),
var(--scrape-example-code-wrapper-background-end));
}
.scraped-example .code-wrapper .example-wrap {
.scraped-example:not(.expanded) {
width: 100%;
overflow-y: hidden;
margin-bottom: 0;
}
.scraped-example:not(.expanded) .code-wrapper .example-wrap {
.scraped-example:not(.expanded) {
overflow-x: hidden;
}
.scraped-example .example-wrap .rust span.highlight {
.scraped-example .rust span.highlight {
background: var(--scrape-example-code-line-highlight);
}
.scraped-example .example-wrap .rust span.highlight.focus {
.scraped-example .rust span.highlight.focus {
background: var(--scrape-example-code-line-highlight-focus);
}
@ -2542,6 +2534,8 @@ by default.
--copy-path-button-color: #999;
--copy-path-img-filter: invert(50%);
--copy-path-img-hover-filter: invert(35%);
--code-example-button-color: #7f7f7f;
--code-example-button-hover-color: #595959;
--codeblock-error-hover-color: rgb(255, 0, 0);
--codeblock-error-color: rgba(255, 0, 0, .5);
--codeblock-ignore-hover-color: rgb(255, 142, 0);
@ -2644,6 +2638,8 @@ by default.
--copy-path-button-color: #999;
--copy-path-img-filter: invert(50%);
--copy-path-img-hover-filter: invert(65%);
--code-example-button-color: #7f7f7f;
--code-example-button-hover-color: #a5a5a5;
--codeblock-error-hover-color: rgb(255, 0, 0);
--codeblock-error-color: rgba(255, 0, 0, .5);
--codeblock-ignore-hover-color: rgb(255, 142, 0);
@ -2753,6 +2749,8 @@ Original by Dempfi (https://github.com/dempfi/ayu)
--copy-path-button-color: #fff;
--copy-path-img-filter: invert(70%);
--copy-path-img-hover-filter: invert(100%);
--code-example-button-color: #b2b2b2;
--code-example-button-hover-color: #fff;
--codeblock-error-hover-color: rgb(255, 0, 0);
--codeblock-error-color: rgba(255, 0, 0, .5);
--codeblock-ignore-hover-color: rgb(255, 142, 0);

View File

@ -1855,8 +1855,13 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
// Since the button will be added, no need to keep this listener around.
elem.removeEventListener("mouseover", addCopyButton);
const parent = document.createElement("div");
parent.className = "button-holder";
// If this is a scrapped example, there will already be a "button-holder" element.
let parent = elem.querySelector(".button-holder");
if (!parent) {
parent = document.createElement("div");
parent.className = "button-holder";
}
const runButton = elem.querySelector(".test-arrow");
if (runButton !== null) {
// If there is a run button, we move it into the same div.

View File

@ -13,7 +13,7 @@
// Scroll code block to the given code location
function scrollToLoc(elt, loc, isHidden) {
const lines = elt.querySelector(".src-line-numbers");
const lines = elt.querySelector(".src-line-numbers > pre");
let scrollOffset;
// If the block is greater than the size of the viewer,
@ -24,8 +24,7 @@
const line = Math.max(0, loc[0] - 1);
scrollOffset = lines.children[line].offsetTop;
} else {
const wrapper = elt.querySelector(".code-wrapper");
const halfHeight = wrapper.offsetHeight / 2;
const halfHeight = elt.offsetHeight / 2;
const offsetTop = lines.children[loc[0]].offsetTop;
const lastLine = lines.children[loc[1]];
const offsetBot = lastLine.offsetTop + lastLine.offsetHeight;
@ -33,7 +32,7 @@
scrollOffset = offsetMid - halfHeight;
}
lines.scrollTo(0, scrollOffset);
lines.parentElement.scrollTo(0, scrollOffset);
elt.querySelector(".rust").scrollTo(0, scrollOffset);
}

View File

@ -0,0 +1,33 @@
<div class="scraped-example{% if !info.needs_expansion +%} expanded{% endif %}" data-locs="{{info.locations}}"> {# #}
<div class="scraped-example-title">
{{info.name +}} (<a href="{{info.url}}">{{info.title}}</a>) {# #}
</div>
<div class="example-wrap"> {# #}
{# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
Do not show "1 2 3 4 5 ..." in web search results. #}
<div class="src-line-numbers" data-nosnippet> {# #}
<pre>
{% for line in lines.clone() %}
{# ~#}
<span>{{line|safe}}</span>
{% endfor %}
</pre> {# #}
</div> {# #}
<pre class="rust"> {# #}
<code>
{{code_html|safe}}
</code> {# #}
</pre> {# #}
{% if info.needs_prev_next_buttons || info.needs_expansion %}
<div class="button-holder">
{% if info.needs_prev_next_buttons %}
<button class="prev">&pr;</button> {# #}
<button class="next">&sc;</button>
{% endif %}
{% if info.needs_expansion %}
<button class="expand">&varr;</button>
{% endif %}
</div>
{% endif %}
</div> {# #}
</div> {# #}

View File

@ -1,21 +1,15 @@
<div class="example-wrap"> {# #}
<div class="example-wrap">
{# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
Do not show "1 2 3 4 5 ..." in web search results. #}
<div data-nosnippet><pre class="src-line-numbers">
{% for line in lines.clone() %}
{% if embedded %}
<span>{{line|safe}}</span>
{%~ else %}
<a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a>
{%~ endif %}
{# ~#}
<a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a>
{% endfor %}
</pre></div> {# #}
<pre class="rust"> {# #}
<code>
{% if needs_expansion %}
<button class="expand">&varr;</button>
{% endif %}
{{code_html|safe}}
</code> {# #}
</pre> {# #}
</div>
</div> {# #}

View File

@ -15,7 +15,7 @@ use crate::{
/// This is a custom command wrapper that simplifies working with commands and makes it easier to
/// ensure that we check the exit status of executed processes.
///
/// # A [`Command`] must be executed
/// # A [`Command`] must be executed exactly once
///
/// A [`Command`] is armed by a [`DropBomb`] on construction to enforce that it will be executed. If
/// a [`Command`] is constructed but never executed, the drop bomb will explode and cause the test
@ -23,26 +23,73 @@ use crate::{
/// containing constructed but never executed commands is dangerous because it can give a false
/// sense of confidence.
///
/// Each [`Command`] invocation can also only be executed once, because we want to enforce
/// `std{in,out,err}` config via [`std::process::Stdio`] but [`std::process::Stdio`] is not
/// cloneable.
///
/// In this sense, [`Command`] exhibits linear type semantics but enforced at run-time.
///
/// [`run`]: Self::run
/// [`run_fail`]: Self::run_fail
/// [`run_unchecked`]: Self::run_unchecked
#[derive(Debug)]
pub struct Command {
cmd: StdCommand,
stdin: Option<Box<[u8]>>,
// Convience for providing a quick stdin buffer.
stdin_buf: Option<Box<[u8]>>,
// Configurations for child process's std{in,out,err} handles.
stdin: Option<Stdio>,
stdout: Option<Stdio>,
stderr: Option<Stdio>,
// Emulate linear type semantics.
drop_bomb: DropBomb,
already_executed: bool,
}
impl Command {
#[track_caller]
pub fn new<P: AsRef<OsStr>>(program: P) -> Self {
let program = program.as_ref();
Self { cmd: StdCommand::new(program), stdin: None, drop_bomb: DropBomb::arm(program) }
Self {
cmd: StdCommand::new(program),
stdin_buf: None,
drop_bomb: DropBomb::arm(program),
stdin: None,
stdout: None,
stderr: None,
already_executed: false,
}
}
/// Specify a stdin input
pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.stdin = Some(input.as_ref().to_vec().into_boxed_slice());
/// Specify a stdin input buffer. This is a convenience helper,
pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.stdin_buf = Some(input.as_ref().to_vec().into_boxed_slice());
self
}
/// Configuration for the child processs standard input (stdin) handle.
///
/// See [`std::process::Command::stdin`].
pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
self.stdin = Some(cfg.into());
self
}
/// Configuration for the child processs standard output (stdout) handle.
///
/// See [`std::process::Command::stdout`].
pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
self.stdout = Some(cfg.into());
self
}
/// Configuration for the child processs standard error (stderr) handle.
///
/// See [`std::process::Command::stderr`].
pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
self.stderr = Some(cfg.into());
self
}
@ -105,6 +152,8 @@ impl Command {
}
/// Run the constructed command and assert that it is successfully run.
///
/// By default, std{in,out,err} are [`Stdio::piped()`].
#[track_caller]
pub fn run(&mut self) -> CompletedProcess {
let output = self.command_output();
@ -115,6 +164,8 @@ impl Command {
}
/// Run the constructed command and assert that it does not successfully run.
///
/// By default, std{in,out,err} are [`Stdio::piped()`].
#[track_caller]
pub fn run_fail(&mut self) -> CompletedProcess {
let output = self.command_output();
@ -124,10 +175,10 @@ impl Command {
output
}
/// Run the command but do not check its exit status.
/// Only use if you explicitly don't care about the exit status.
/// Prefer to use [`Self::run`] and [`Self::run_fail`]
/// whenever possible.
/// Run the command but do not check its exit status. Only use if you explicitly don't care
/// about the exit status.
///
/// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
#[track_caller]
pub fn run_unchecked(&mut self) -> CompletedProcess {
self.command_output()
@ -135,13 +186,19 @@ impl Command {
#[track_caller]
fn command_output(&mut self) -> CompletedProcess {
if self.already_executed {
panic!("command was already executed");
} else {
self.already_executed = true;
}
self.drop_bomb.defuse();
// let's make sure we piped all the input and outputs
self.cmd.stdin(Stdio::piped());
self.cmd.stdout(Stdio::piped());
self.cmd.stderr(Stdio::piped());
self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));
self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped()));
self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped()));
let output = if let Some(input) = &self.stdin {
let output = if let Some(input) = &self.stdin_buf {
let mut child = self.cmd.spawn().unwrap();
{

View File

@ -227,9 +227,10 @@ impl LlvmFilecheck {
Self { cmd }
}
/// Pipe a read file into standard input containing patterns that will be matched against the .patterns(path) call.
pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.cmd.stdin(input);
/// Provide a buffer representing standard input containing patterns that will be matched
/// against the `.patterns(path)` call.
pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.cmd.stdin_buf(input);
self
}

View File

@ -291,9 +291,9 @@ impl Rustc {
self
}
/// Specify a stdin input
pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.cmd.stdin(input);
/// Specify a stdin input buffer.
pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.cmd.stdin_buf(input);
self
}

View File

@ -85,9 +85,9 @@ impl Rustdoc {
self
}
/// Specify a stdin input
pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.cmd.stdin(input);
/// Specify a stdin input buffer.
pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
self.cmd.stdin_buf(input);
self
}

View File

@ -34,6 +34,7 @@ pub mod rfs {
}
// Re-exports of third-party library crates.
// tidy-alphabetical-start
pub use bstr;
pub use gimli;
pub use libc;
@ -41,6 +42,7 @@ pub use object;
pub use regex;
pub use serde_json;
pub use wasmparser;
// tidy-alphabetical-end
// Re-exports of external dependencies.
pub use external_deps::{c_build, cc, clang, htmldocck, llvm, python, rustc, rustdoc};

View File

@ -1,11 +1,11 @@
// MIR for `main::{closure#0}::{closure#0}::{closure#0}` 0 coroutine_by_move
// MIR for `main::{closure#0}::{closure#0}::{closure#0}` after built
fn main::{closure#0}::{closure#0}::{closure#0}(_1: {async closure body@$DIR/async_closure_shims.rs:53:53: 56:10}, _2: ResumeTy) -> ()
fn main::{closure#0}::{closure#0}::{closure#0}(_1: {async closure body@$DIR/async_closure_shims.rs:54:53: 57:10}, _2: ResumeTy) -> ()
yields ()
{
debug _task_context => _2;
debug a => (_1.0: i32);
debug b => (_1.1: i32);
debug b => (*(_1.1: &i32));
let mut _0: ();
let _3: i32;
scope 1 {
@ -28,7 +28,7 @@ yields ()
_4 = &_3;
FakeRead(ForLet(None), _4);
StorageLive(_5);
_5 = &(_1.1: i32);
_5 = &(*(_1.1: &i32));
FakeRead(ForLet(None), _5);
_0 = const ();
StorageDead(_5);

View File

@ -1,6 +1,6 @@
// MIR for `main::{closure#0}::{closure#0}::{closure#0}` 0 coroutine_by_move
// MIR for `main::{closure#0}::{closure#0}::{closure#1}` after built
fn main::{closure#0}::{closure#0}::{closure#0}(_1: {async closure body@$DIR/async_closure_shims.rs:53:53: 56:10}, _2: ResumeTy) -> ()
fn main::{closure#0}::{closure#0}::{closure#1}(_1: {async closure body@$DIR/async_closure_shims.rs:54:53: 57:10}, _2: ResumeTy) -> ()
yields ()
{
debug _task_context => _2;

View File

@ -1,10 +1,10 @@
// MIR for `main::{closure#0}::{closure#0}` 0 coroutine_closure_by_move
fn main::{closure#0}::{closure#0}(_1: {async closure@$DIR/async_closure_shims.rs:53:33: 53:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:53:53: 56:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:53:53: 56:10};
fn main::{closure#0}::{closure#0}(_1: {async closure@$DIR/async_closure_shims.rs:54:33: 54:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:54:53: 57:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:54:53: 57:10};
bb0: {
_0 = {coroutine@$DIR/async_closure_shims.rs:53:53: 56:10 (#0)} { a: move _2, b: move (_1.0: i32) };
_0 = {coroutine@$DIR/async_closure_shims.rs:54:53: 57:10 (#0)} { a: move _2, b: move (_1.0: i32) };
return;
}
}

View File

@ -1,10 +0,0 @@
// MIR for `main::{closure#0}::{closure#0}` 0 coroutine_closure_by_move
fn main::{closure#0}::{closure#0}(_1: {async closure@$DIR/async_closure_shims.rs:53:33: 53:52}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:53:53: 56:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:53:53: 56:10};
bb0: {
_0 = {coroutine@$DIR/async_closure_shims.rs:53:53: 56:10 (#0)} { a: move _2, b: move (_1.0: i32) };
return;
}
}

View File

@ -1,6 +1,6 @@
// MIR for `main::{closure#0}::{closure#1}::{closure#0}` 0 coroutine_by_move
// MIR for `main::{closure#0}::{closure#1}::{closure#0}` after built
fn main::{closure#0}::{closure#1}::{closure#0}(_1: {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10}, _2: ResumeTy) -> ()
fn main::{closure#0}::{closure#1}::{closure#0}(_1: {async closure body@$DIR/async_closure_shims.rs:63:48: 66:10}, _2: ResumeTy) -> ()
yields ()
{
debug _task_context => _2;

View File

@ -1,6 +1,6 @@
// MIR for `main::{closure#0}::{closure#1}::{closure#0}` 0 coroutine_by_move
// MIR for `main::{closure#0}::{closure#1}::{closure#1}` after built
fn main::{closure#0}::{closure#1}::{closure#0}(_1: {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10}, _2: ResumeTy) -> ()
fn main::{closure#0}::{closure#1}::{closure#1}(_1: {async closure body@$DIR/async_closure_shims.rs:63:48: 66:10}, _2: ResumeTy) -> ()
yields ()
{
debug _task_context => _2;

View File

@ -1,10 +1,10 @@
// MIR for `main::{closure#0}::{closure#1}` 0 coroutine_closure_by_move
fn main::{closure#0}::{closure#1}(_1: {async closure@$DIR/async_closure_shims.rs:62:33: 62:47}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10};
fn main::{closure#0}::{closure#1}(_1: {async closure@$DIR/async_closure_shims.rs:63:33: 63:47}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:63:48: 66:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:63:48: 66:10};
bb0: {
_0 = {coroutine@$DIR/async_closure_shims.rs:62:48: 65:10 (#0)} { a: move _2, b: move (_1.0: &i32) };
_0 = {coroutine@$DIR/async_closure_shims.rs:63:48: 66:10 (#0)} { a: move _2, b: move (_1.0: &i32) };
return;
}
}

View File

@ -1,10 +0,0 @@
// MIR for `main::{closure#0}::{closure#1}` 0 coroutine_closure_by_move
fn main::{closure#0}::{closure#1}(_1: {async closure@$DIR/async_closure_shims.rs:62:33: 62:47}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10};
bb0: {
_0 = {coroutine@$DIR/async_closure_shims.rs:62:48: 65:10 (#0)} { a: move _2, b: move (_1.0: &i32) };
return;
}
}

View File

@ -1,10 +1,10 @@
// MIR for `main::{closure#0}::{closure#1}` 0 coroutine_closure_by_ref
fn main::{closure#0}::{closure#1}(_1: &{async closure@$DIR/async_closure_shims.rs:62:33: 62:47}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10};
fn main::{closure#0}::{closure#1}(_1: &{async closure@$DIR/async_closure_shims.rs:63:33: 63:47}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:63:48: 66:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:63:48: 66:10};
bb0: {
_0 = {coroutine@$DIR/async_closure_shims.rs:62:48: 65:10 (#0)} { a: move _2, b: copy ((*_1).0: &i32) };
_0 = {coroutine@$DIR/async_closure_shims.rs:63:48: 66:10 (#0)} { a: move _2, b: copy ((*_1).0: &i32) };
return;
}
}

View File

@ -1,10 +0,0 @@
// MIR for `main::{closure#0}::{closure#1}` 0 coroutine_closure_by_ref
fn main::{closure#0}::{closure#1}(_1: &{async closure@$DIR/async_closure_shims.rs:62:33: 62:47}, _2: i32) -> {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10} {
let mut _0: {async closure body@$DIR/async_closure_shims.rs:62:48: 65:10};
bb0: {
_0 = {coroutine@$DIR/async_closure_shims.rs:62:48: 65:10 (#0)} { a: move _2, b: copy ((*_1).0: &i32) };
return;
}
}

View File

@ -1,6 +1,5 @@
//@ edition:2021
// skip-filecheck
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
#![feature(async_closure, noop_waker, async_fn_traits)]
#![allow(unused)]
@ -22,7 +21,7 @@ pub fn block_on<T>(fut: impl Future<Output = T>) -> T {
}
}
async fn call(f: &mut impl AsyncFn(i32)) {
async fn call(f: &impl AsyncFn(i32)) {
f(0).await;
}
@ -43,10 +42,12 @@ async fn call_normal_mut<F: Future<Output = ()>>(f: &mut impl FnMut(i32) -> F) {
}
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#0}.coroutine_closure_by_move.0.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#0}-{closure#0}.coroutine_by_move.0.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#0}-{closure#0}.built.after.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#0}-{closure#1}.built.after.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#1}.coroutine_closure_by_ref.0.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#1}.coroutine_closure_by_move.0.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#1}-{closure#0}.coroutine_by_move.0.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#1}-{closure#0}.built.after.mir
// EMIT_MIR async_closure_shims.main-{closure#0}-{closure#1}-{closure#1}.built.after.mir
pub fn main() {
block_on(async {
let b = 2i32;
@ -54,7 +55,7 @@ pub fn main() {
let a = &a;
let b = &b;
};
call(&mut async_closure).await;
call(&async_closure).await;
call_mut(&mut async_closure).await;
call_once(async_closure).await;

View File

@ -8,6 +8,6 @@
use run_make_support::rustc;
fn main() {
let output = rustc().output("").stdin(b"fn main() {}").run_fail();
let output = rustc().output("").stdin_buf(b"fn main() {}").run_fail();
output.assert_stderr_not_contains("panic");
}

View File

@ -14,7 +14,7 @@ fn main() {
rustc()
.arg("-")
.stdin("fn main() {}")
.stdin_buf("fn main() {}")
.emit("link,obj")
.arg("-Csave-temps")
.target(target)

View File

@ -8,6 +8,6 @@
use run_make_support::{run, rustc};
fn main() {
rustc().arg("-").stdin("fn main() {}").run();
rustc().arg("-").stdin_buf("fn main() {}").run();
run("rust_out");
}

View File

@ -21,7 +21,7 @@ fn run_tests(extra_args: &[&str], expected_file: &str) {
.run_fail();
let test_stdout = &cmd_out.stdout_utf8();
python_command().arg("validate_junit.py").stdin(test_stdout).run();
python_command().arg("validate_junit.py").stdin_buf(test_stdout).run();
diff()
.expected_file(expected_file)

View File

@ -17,7 +17,7 @@ fn main() {
.codegen_units(16)
.opt_level("2")
.target(&env_var("TARGET"))
.stdin("fn main(){}")
.stdin_buf("fn main(){}")
.run();
// `llvm-dis` is used here since `--emit=llvm-ir` does not emit LLVM IR

View File

@ -11,8 +11,8 @@ fn main() {
let p = cwd();
path_bc = p.join("nonexistant_dir_bc");
path_ir = p.join("nonexistant_dir_ir");
rustc().input("-").stdin("fn main() {}").out_dir(&path_bc).emit("llvm-bc").run();
rustc().input("-").stdin("fn main() {}").out_dir(&path_ir).emit("llvm-ir").run();
rustc().input("-").stdin_buf("fn main() {}").out_dir(&path_bc).emit("llvm-bc").run();
rustc().input("-").stdin_buf("fn main() {}").out_dir(&path_ir).emit("llvm-ir").run();
assert!(path_bc.exists());
assert!(path_ir.exists());
});

View File

@ -9,5 +9,5 @@ use run_make_support::{llvm_filecheck, rfs, rustc};
fn main() {
rustc().input("no_builtins.rs").emit("link").run();
rustc().input("main.rs").emit("llvm-ir").run();
llvm_filecheck().patterns("filecheck.main.txt").stdin(rfs::read("main.ll")).run();
llvm_filecheck().patterns("filecheck.main.txt").stdin_buf(rfs::read("main.ll")).run();
}

View File

@ -35,5 +35,8 @@ fn main() {
.codegen_units(1)
.emit("llvm-ir")
.run();
llvm_filecheck().patterns("filecheck-patterns.txt").stdin(rfs::read("interesting.ll")).run();
llvm_filecheck()
.patterns("filecheck-patterns.txt")
.stdin_buf(rfs::read("interesting.ll"))
.run();
}

View File

@ -29,5 +29,8 @@ fn main() {
.codegen_units(1)
.emit("llvm-ir")
.run();
llvm_filecheck().patterns("filecheck-patterns.txt").stdin(rfs::read("interesting.ll")).run();
llvm_filecheck()
.patterns("filecheck-patterns.txt")
.stdin_buf(rfs::read("interesting.ll"))
.run();
}

View File

@ -51,5 +51,5 @@ fn main() {
let lines: Vec<_> = ir.lines().rev().collect();
let mut reversed_ir = lines.join("\n");
reversed_ir.push('\n');
llvm_filecheck().patterns("filecheck-patterns.txt").stdin(reversed_ir.as_bytes()).run();
llvm_filecheck().patterns("filecheck-patterns.txt").stdin_buf(reversed_ir.as_bytes()).run();
}

View File

@ -8,7 +8,7 @@
use run_make_support::{run, rustc};
fn main() {
rustc().stdin(b"fn main(){}").arg("-Zno-link").arg("-").run();
rustc().stdin_buf(b"fn main(){}").arg("-Zno-link").arg("-").run();
rustc().arg("-Zlink-only").input("rust_out.rlink").run();
run("rust_out");
}

View File

@ -28,7 +28,7 @@ fn ok_compiler_version(compiler: &str) -> bool {
}
let compiler_output =
cmd(compiler).stdin(trigger).arg("-").arg("-E").arg("-x").arg("c").run().stdout_utf8();
cmd(compiler).stdin_buf(trigger).arg("-").arg("-E").arg("-x").arg("c").run().stdout_utf8();
let re = Regex::new(r"(?m)^(\d+)").unwrap();
let version: u32 =
re.captures(&compiler_output).unwrap().get(1).unwrap().as_str().parse().unwrap();

View File

@ -14,7 +14,7 @@ const NOT_UTF8: &[u8] = &[0xff, 0xff, 0xff];
fn main() {
// echo $HELLO_WORLD | rustc -
rustc().arg("-").stdin(HELLO_WORLD).run();
rustc().arg("-").stdin_buf(HELLO_WORLD).run();
assert!(
PathBuf::from(if !is_windows() { "rust_out" } else { "rust_out.exe" })
.try_exists()
@ -22,7 +22,7 @@ fn main() {
);
// echo $NOT_UTF8 | rustc -
rustc().arg("-").stdin(NOT_UTF8).run_fail().assert_stderr_contains(
rustc().arg("-").stdin_buf(NOT_UTF8).run_fail().assert_stderr_contains(
"error: couldn't read from stdin, as it did not contain valid UTF-8",
);
}

View File

@ -15,11 +15,11 @@ fn main() {
let out_dir = PathBuf::from("doc");
// rustdoc -
rustdoc().arg("-").out_dir(&out_dir).stdin(INPUT).run();
rustdoc().arg("-").out_dir(&out_dir).stdin_buf(INPUT).run();
assert!(out_dir.join("rust_out/struct.F.html").try_exists().unwrap());
// rustdoc --test -
rustdoc().arg("--test").arg("-").stdin(INPUT).run();
rustdoc().arg("--test").arg("-").stdin_buf(INPUT).run();
// rustdoc file.rs -
rustdoc().arg("file.rs").arg("-").run_fail();

View File

@ -34,7 +34,7 @@ fn check_crate_is_unstable(cr: &Crate) {
.target(target())
.extern_(name, path)
.input("-")
.stdin(format!("extern crate {name};"))
.stdin_buf(format!("extern crate {name};"))
.run_fail();
// Make sure it failed for the intended reason, not some other reason.

View File

@ -12,7 +12,7 @@
use run_make_support::{diff, rustc};
fn main() {
let out = rustc().crate_type("rlib").stdin(b"mod unknown;").arg("-").run_fail();
let out = rustc().crate_type("rlib").stdin_buf(b"mod unknown;").arg("-").run_fail();
diff()
.actual_text("actual-stdout", out.stdout_utf8())
.expected_file("unknown-mod.stdout")

View File

@ -78,19 +78,23 @@ fn check(func_re: &str, mut checks: &str) {
// This is because frame pointers are optional, and them being enabled requires
// an additional `popq` in the pattern checking file.
if func_re == "std::io::stdio::_print::[[:alnum:]]+" {
let output = llvm_filecheck().stdin(&dump).patterns(checks).run_unchecked();
let output = llvm_filecheck().stdin_buf(&dump).patterns(checks).run_unchecked();
if !output.status().success() {
checks = "print.without_frame_pointers.checks";
llvm_filecheck().stdin(&dump).patterns(checks).run();
llvm_filecheck().stdin_buf(&dump).patterns(checks).run();
}
} else {
llvm_filecheck().stdin(&dump).patterns(checks).run();
llvm_filecheck().stdin_buf(&dump).patterns(checks).run();
}
if !["rust_plus_one_global_asm", "cmake_plus_one_c_global_asm", "cmake_plus_one_cxx_global_asm"]
.contains(&func_re)
{
// The assembler cannot avoid explicit `ret` instructions. Sequences
// of `shlq $0x0, (%rsp); lfence; retq` are used instead.
llvm_filecheck().args(&["--implicit-check-not", "ret"]).stdin(dump).patterns(checks).run();
llvm_filecheck()
.args(&["--implicit-check-not", "ret"])
.stdin_buf(dump)
.patterns(checks)
.run();
}
}

View File

@ -94,3 +94,24 @@ call-function: ("check-buttons",{
"filter": "invert(0.5)",
"filter_hover": "invert(0.35)",
})
define-function: (
"check-buttons-position",
[pre_selector],
block {
move-cursor-to: |pre_selector| + " .rust:not(.item-decl)"
store-position: (|pre_selector| + " .rust:not(.item-decl)", {"x": x, "y": y})
assert-position: (|pre_selector| + " .rust:not(.item-decl) + .button-holder", {
"y": |y| + 4,
})
}
)
call-function: ("check-buttons-position", {"pre_selector": ".example-wrap"})
go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
// We should work as well for scraped examples.
call-function: ("check-buttons-position", {"pre_selector": ".scraped-example .example-wrap"})
// And also when the scraped example "title" goes above.
set-window-size: (600, 600)
call-function: ("check-buttons-position", {"pre_selector": ".scraped-example .example-wrap"})

View File

@ -5,6 +5,18 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/fn.foo.html"
// We check that without this setting, there is no line number displayed.
assert-false: "pre.example-line-numbers"
// All corners should be rounded.
assert-css: (
".example-wrap .rust",
{
"border-top-left-radius": "6px",
"border-bottom-left-radius": "6px",
"border-top-right-radius": "6px",
"border-bottom-right-radius": "6px",
},
ALL,
)
// We set the setting to show the line numbers on code examples.
set-local-storage: {"rustdoc-line-numbers": "true"}
reload:
@ -29,9 +41,21 @@ define-function: (
"margin": "0px",
"padding": "14px 8px",
"text-align": "right",
// There should not be a radius on the right of the line numbers.
"border-top-left-radius": "6px",
"border-bottom-left-radius": "6px",
"border-top-right-radius": "0px",
"border-bottom-right-radius": "0px",
},
ALL,
)
// There should not be a radius on the left of the line numbers.
assert-css: ("pre.example-line-numbers + .rust", {
"border-top-left-radius": "0px",
"border-bottom-left-radius": "0px",
"border-top-right-radius": "6px",
"border-bottom-right-radius": "6px",
})
},
)
call-function: ("check-colors", {
@ -64,7 +88,56 @@ wait-for: 100 // wait-for-false does not exist
assert-false: "pre.example-line-numbers"
assert-local-storage: {"rustdoc-line-numbers": "false" }
// Check that the rounded corners are back.
assert-css: (
".example-wrap .rust",
{
"border-top-left-radius": "6px",
"border-bottom-left-radius": "6px",
"border-top-right-radius": "6px",
"border-bottom-right-radius": "6px",
},
ALL,
)
// Finally, turn it on again.
click: "input#line-numbers"
wait-for: "pre.example-line-numbers"
assert-local-storage: {"rustdoc-line-numbers": "true" }
// Same check with scraped examples line numbers.
go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
assert-css: (
".scraped-example .src-line-numbers > pre",
{
// There should not be a radius on the right of the line numbers.
"border-top-left-radius": "6px",
"border-bottom-left-radius": "6px",
"border-top-right-radius": "0px",
"border-bottom-right-radius": "0px",
},
ALL,
)
assert-css: (
".scraped-example .src-line-numbers",
{
// There should not be a radius on the right of the line numbers.
"border-top-left-radius": "6px",
"border-bottom-left-radius": "6px",
"border-top-right-radius": "0px",
"border-bottom-right-radius": "0px",
},
ALL,
)
assert-css: (
".scraped-example .rust",
{
// There should not be a radius on the left of the code.
"border-top-left-radius": "0px",
"border-bottom-left-radius": "0px",
"border-top-right-radius": "6px",
"border-bottom-right-radius": "6px",
},
ALL,
)

View File

@ -3,29 +3,53 @@
go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test.html"
// The next/prev buttons vertically scroll the code viewport between examples
store-property: (".scraped-example-list > .scraped-example pre", {"scrollTop": initialScrollTop})
move-cursor-to: ".scraped-example-list > .scraped-example"
store-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollTop": initialScrollTop,
})
assert-property: (".scraped-example-list > .scraped-example .rust", {
"scrollTop": |initialScrollTop|,
})
focus: ".scraped-example-list > .scraped-example .next"
press-key: "Enter"
assert-property-false: (".scraped-example-list > .scraped-example pre", {
assert-property-false: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollTop": |initialScrollTop|
}, NEAR)
assert-property-false: (".scraped-example-list > .scraped-example .rust", {
"scrollTop": |initialScrollTop|
}, NEAR)
focus: ".scraped-example-list > .scraped-example .prev"
press-key: "Enter"
assert-property: (".scraped-example-list > .scraped-example pre", {
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollTop": |initialScrollTop|
}, NEAR)
assert-property: (".scraped-example-list > .scraped-example .rust", {
"scrollTop": |initialScrollTop|
}, NEAR)
// The expand button increases the scrollHeight of the minimized code viewport
store-property: (".scraped-example-list > .scraped-example pre", {"offsetHeight": smallOffsetHeight})
assert-property-false: (".scraped-example-list > .scraped-example pre", {
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollHeight": |smallOffsetHeight|
}, NEAR)
assert-property: (".scraped-example-list > .scraped-example .rust", {
"scrollHeight": |smallOffsetHeight|
}, NEAR)
focus: ".scraped-example-list > .scraped-example .expand"
press-key: "Enter"
assert-property-false: (".scraped-example-list > .scraped-example pre", {
assert-property-false: (".scraped-example-list > .scraped-example .src-line-numbers", {
"offsetHeight": |smallOffsetHeight|
}, NEAR)
store-property: (".scraped-example-list > .scraped-example pre", {"offsetHeight": fullOffsetHeight})
assert-property: (".scraped-example-list > .scraped-example pre", {
assert-property-false: (".scraped-example-list > .scraped-example .rust", {
"offsetHeight": |smallOffsetHeight|
}, NEAR)
store-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"offsetHeight": fullOffsetHeight,
})
assert-property: (".scraped-example-list > .scraped-example .rust", {
"offsetHeight": |fullOffsetHeight|,
"scrollHeight": |fullOffsetHeight|,
})
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollHeight": |fullOffsetHeight|
}, NEAR)

View File

@ -10,10 +10,10 @@ define-function: (
block {
call-function: ("switch-theme", {"theme": |theme|})
wait-for: ".more-examples-toggle"
assert-css: (".scraped-example .example-wrap .rust span.highlight:not(.focus)", {
assert-css: (".scraped-example .rust span.highlight:not(.focus)", {
"background-color": |highlight|,
}, ALL)
assert-css: (".scraped-example .example-wrap .rust span.highlight.focus", {
assert-css: (".scraped-example .rust span.highlight.focus", {
"background-color": |highlight_focus|,
}, ALL)
@ -67,11 +67,11 @@ define-function: (
[theme, background_color_start, background_color_end],
block {
call-function: ("switch-theme", {"theme": |theme|})
assert-css: (".scraped-example:not(.expanded) .code-wrapper::before", {
assert-css: (".scraped-example:not(.expanded) .example-wrap::before", {
"background-image": "linear-gradient(" + |background_color_start| + ", " +
|background_color_end| + ")",
})
assert-css: (".scraped-example:not(.expanded) .code-wrapper::after", {
assert-css: (".scraped-example:not(.expanded) .example-wrap::after", {
"background-image": "linear-gradient(to top, " + |background_color_start| + ", " +
|background_color_end| + ")",
})

View File

@ -1,48 +1,115 @@
// Check that the line number column has the correct layout.
go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
set-window-size: (1000, 1000)
// Check that it's not zero.
assert-property-false: (
".more-scraped-examples .scraped-example .code-wrapper .src-line-numbers",
".more-scraped-examples .scraped-example .src-line-numbers",
{"clientWidth": "0"}
)
// Check that examples with very long lines have the same width as ones that don't.
store-property: (
".more-scraped-examples .scraped-example:nth-child(2) .code-wrapper .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(2) .src-line-numbers",
{"clientWidth": clientWidth},
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(3) .code-wrapper .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(3) .src-line-numbers",
{"clientWidth": |clientWidth|}
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(4) .code-wrapper .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(4) .src-line-numbers",
{"clientWidth": |clientWidth|}
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(5) .code-wrapper .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(5) .src-line-numbers",
{"clientWidth": |clientWidth|}
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(6) .code-wrapper .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(6) .src-line-numbers",
{"clientWidth": |clientWidth|}
)
// The "title" should be located at the right bottom corner of the code example.
store-position: (".scraped-example .example-wrap", {"x": x, "y": y})
store-size: (".scraped-example .example-wrap", {"width": width, "height": height})
store-size: (".scraped-example .scraped-example-title", {
"width": title_width,
"height": title_height,
})
assert-position: (".scraped-example .scraped-example-title", {
"x": |x| + |width| - |title_width| - 5,
"y": |y| + |height| - |title_height| - 8,
})
// Check that the expand button works and also that line number aligns with code.
move-cursor-to: ".scraped-example .rust"
click: ".scraped-example .button-holder .expand"
wait-for: ".scraped-example.expanded"
// They should have the same y position.
compare-elements-position: (
".scraped-example.expanded .src-line-numbers pre span",
".scraped-example.expanded .rust code",
["y"],
)
// And they should have the same height.
compare-elements-size: (
".scraped-example.expanded .src-line-numbers",
".scraped-example.expanded .rust",
["height"],
)
// Collapse code again.
click: ".scraped-example .button-holder .expand"
// Check that for both mobile and desktop sizes, the buttons in scraped examples are displayed
// correctly.
store-value: (offset_y, 4)
// First with desktop
assert-position: (".scraped-example .code-wrapper", {"y": 226})
assert-position: (".scraped-example .code-wrapper .prev", {"y": 226 + |offset_y|})
assert-position: (".scraped-example", {"y": 226})
assert-position: (".scraped-example .prev", {"y": 226 + |offset_y|})
// Gradient background should be at the top of the code block.
assert-css: (".scraped-example .example-wrap::before", {"top": "0px"})
assert-css: (".scraped-example .example-wrap::after", {"bottom": "0px"})
// Then with mobile
set-window-size: (600, 600)
assert-position: (".scraped-example .code-wrapper", {"y": 308})
assert-position: (".scraped-example .code-wrapper .prev", {"y": 308 + |offset_y|})
store-size: (".scraped-example .scraped-example-title", {"height": title_height})
assert-position: (".scraped-example", {"y": 284})
assert-position: (".scraped-example .prev", {"y": 284 + |offset_y| + |title_height|})
define-function: (
"check_title_and_code_position",
[],
block {
// Title should be above the code.
store-position: (".scraped-example .example-wrap .src-line-numbers", {"x": x, "y": y})
store-size: (".scraped-example .scraped-example-title", { "height": title_height })
assert-position: (".scraped-example .scraped-example-title", {
"x": |x|, // same X position.
"y": |y| - |title_height|,
})
// Line numbers should be right beside the code.
compare-elements-position: (
".scraped-example .example-wrap .src-line-numbers",
".scraped-example .example-wrap .rust",
["y"],
)
}
)
// Check that the title is now above the code.
call-function: ("check_title_and_code_position", {})
// Then with small mobile
set-window-size: (300, 300)
call-function: ("check_title_and_code_position", {})

View File

@ -23,6 +23,8 @@
--copy-path-button-color: #999;
--copy-path-img-filter: invert(50%);
--copy-path-img-hover-filter: invert(35%);
--code-example-button-color: #7f7f7f;
--code-example-button-hover-color: #a5a5a5;
--codeblock-error-hover-color: rgb(255, 0, 0);
--codeblock-error-color: rgba(255, 0, 0, .5);
--codeblock-ignore-hover-color: rgb(255, 142, 0);

View File

@ -0,0 +1,42 @@
// This test ensures that impl associated items always follow this order:
//
// 1. Consts
// 2. Types
// 3. Functions
#![feature(inherent_associated_types)]
#![allow(incomplete_features)]
#![crate_name = "foo"]
//@ has 'foo/struct.Bar.html'
pub struct Bar;
impl Bar {
//@ has - '//*[@id="implementations-list"]//*[@class="impl-items"]/section[3]/h4' \
// 'pub fn foo()'
pub fn foo() {}
//@ has - '//*[@id="implementations-list"]//*[@class="impl-items"]/section[1]/h4' \
// 'pub const X: u8 = 12u8'
pub const X: u8 = 12;
//@ has - '//*[@id="implementations-list"]//*[@class="impl-items"]/section[2]/h4' \
// 'pub type Y = u8'
pub type Y = u8;
}
pub trait Foo {
const W: u32;
fn yeay();
type Z;
}
impl Foo for Bar {
//@ has - '//*[@id="trait-implementations-list"]//*[@class="impl-items"]/section[2]/h4' \
// 'type Z = u8'
type Z = u8;
//@ has - '//*[@id="trait-implementations-list"]//*[@class="impl-items"]/section[1]/h4' \
// 'const W: u32 = 12u32'
const W: u32 = 12;
//@ has - '//*[@id="trait-implementations-list"]//*[@class="impl-items"]/section[3]/h4' \
// 'fn yeay()'
fn yeay() {}
}

View File

@ -0,0 +1,42 @@
// This test ensures that impl/trait associated items are listed in the sidebar.
// ignore-tidy-linelength
#![feature(inherent_associated_types)]
#![feature(associated_type_defaults)]
#![allow(incomplete_features)]
#![crate_name = "foo"]
//@ has 'foo/struct.Bar.html'
pub struct Bar;
impl Bar {
//@ has - '//*[@class="sidebar-elems"]//h3[1]' 'Associated Constants'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block associatedconstant"]/li/a[@href="#associatedconstant.X"]' 'X'
pub const X: u8 = 12;
//@ has - '//*[@class="sidebar-elems"]//h3[2]' 'Associated Types'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block associatedtype"]/li/a[@href="#associatedtype.Y"]' 'Y'
pub type Y = u8;
}
//@ has 'foo/trait.Foo.html'
pub trait Foo {
//@ has - '//*[@class="sidebar-elems"]//h3[5]' 'Required Methods'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block"][5]/li/a[@href="#tymethod.yeay"]' 'yeay'
fn yeay();
//@ has - '//*[@class="sidebar-elems"]//h3[6]' 'Provided Methods'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block"][6]/li/a[@href="#method.boo"]' 'boo'
fn boo() {}
//@ has - '//*[@class="sidebar-elems"]//h3[1]' 'Required Associated Constants'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block"][1]/li/a[@href="#associatedconstant.W"]' 'W'
const W: u32;
//@ has - '//*[@class="sidebar-elems"]//h3[2]' 'Provided Associated Constants'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block"][2]/li/a[@href="#associatedconstant.U"]' 'U'
const U: u32 = 0;
//@ has - '//*[@class="sidebar-elems"]//h3[3]' 'Required Associated Types'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block"][3]/li/a[@href="#associatedtype.Z"]' 'Z'
type Z;
//@ has - '//*[@class="sidebar-elems"]//h3[4]' 'Provided Associated Types'
//@ has - '//*[@class="sidebar-elems"]//ul[@class="block"][4]/li/a[@href="#associatedtype.T"]' 'T'
type T = u32;
}

View File

@ -0,0 +1,15 @@
//@ check-pass
#![deny(impl_trait_overcaptures)]
struct Ctxt<'tcx>(&'tcx ());
// In `compute`, we don't care that we're "overcapturing" `'tcx`
// in edition 2024, because it can be shortened at the call site
// and we know it outlives `'_`.
impl<'tcx> Ctxt<'tcx> {
fn compute(&self) -> impl Sized + '_ {}
}
fn main() {}