Auto merge of #146077 - Zalathar:rollup-l7ip5yi, r=Zalathar

Rollup of 5 pull requests

Successful merges:

 - rust-lang/rust#145468 (dedup recip, powi, to_degrees, and to_radians float tests)
 - rust-lang/rust#145643 (coverage: Build an "expansion tree" and use it to unexpand raw spans)
 - rust-lang/rust#145754 (fix(lexer): Don't require frontmatters to be escaped with indented fences)
 - rust-lang/rust#146060 (fixup nix dev shell again)
 - rust-lang/rust#146068 (compiletest: Capture panic messages via a custom panic hook)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-09-01 07:44:42 +00:00
commit 84a1747022
28 changed files with 601 additions and 483 deletions

View file

@ -550,28 +550,20 @@ impl Cursor<'_> {
self.eat_while(|ch| ch != '\n' && is_whitespace(ch));
let invalid_infostring = self.first() != '\n';
let mut s = self.as_str();
let mut found = false;
let mut size = 0;
while let Some(closing) = s.find(&"-".repeat(length_opening as usize)) {
let preceding_chars_start = s[..closing].rfind("\n").map_or(0, |i| i + 1);
if s[preceding_chars_start..closing].chars().all(is_whitespace) {
// candidate found
self.bump_bytes(size + closing);
// in case like
// ---cargo
// --- blahblah
// or
// ---cargo
// ----
// combine those stuff into this frontmatter token such that it gets detected later.
self.eat_until(b'\n');
found = true;
break;
} else {
s = &s[closing + length_opening as usize..];
size += closing + length_opening as usize;
}
let nl_fence_pattern = format!("\n{:-<1$}", "", length_opening as usize);
if let Some(closing) = self.as_str().find(&nl_fence_pattern) {
// candidate found
self.bump_bytes(closing + nl_fence_pattern.len());
// in case like
// ---cargo
// --- blahblah
// or
// ---cargo
// ----
// combine those stuff into this frontmatter token such that it gets detected later.
self.eat_until(b'\n');
found = true;
}
if !found {

View file

@ -0,0 +1,127 @@
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet, IndexEntry};
use rustc_middle::mir::coverage::BasicCoverageBlock;
use rustc_span::{ExpnId, ExpnKind, Span};
#[derive(Clone, Copy, Debug)]
pub(crate) struct SpanWithBcb {
pub(crate) span: Span,
pub(crate) bcb: BasicCoverageBlock,
}
#[derive(Debug)]
pub(crate) struct ExpnTree {
nodes: FxIndexMap<ExpnId, ExpnNode>,
}
impl ExpnTree {
pub(crate) fn get(&self, expn_id: ExpnId) -> Option<&ExpnNode> {
self.nodes.get(&expn_id)
}
/// Yields the tree node for the given expansion ID (if present), followed
/// by the nodes of all of its descendants in depth-first order.
pub(crate) fn iter_node_and_descendants(
&self,
root_expn_id: ExpnId,
) -> impl Iterator<Item = &ExpnNode> {
gen move {
let Some(root_node) = self.get(root_expn_id) else { return };
yield root_node;
// Stack of child-node-ID iterators that drives the depth-first traversal.
let mut iter_stack = vec![root_node.child_expn_ids.iter()];
while let Some(curr_iter) = iter_stack.last_mut() {
// Pull the next ID from the top of the stack.
let Some(&curr_id) = curr_iter.next() else {
iter_stack.pop();
continue;
};
// Yield this node.
let Some(node) = self.get(curr_id) else { continue };
yield node;
// Push the node's children, to be traversed next.
if !node.child_expn_ids.is_empty() {
iter_stack.push(node.child_expn_ids.iter());
}
}
}
}
}
#[derive(Debug)]
pub(crate) struct ExpnNode {
/// Storing the expansion ID in its own node is not strictly necessary,
/// but is helpful for debugging and might be useful later.
#[expect(dead_code)]
pub(crate) expn_id: ExpnId,
// Useful info extracted from `ExpnData`.
pub(crate) expn_kind: ExpnKind,
/// Non-dummy `ExpnData::call_site` span.
pub(crate) call_site: Option<Span>,
/// Expansion ID of `call_site`, if present.
/// This links an expansion node to its parent in the tree.
pub(crate) call_site_expn_id: Option<ExpnId>,
/// Spans (and their associated BCBs) belonging to this expansion.
pub(crate) spans: Vec<SpanWithBcb>,
/// Expansions whose call-site is in this expansion.
pub(crate) child_expn_ids: FxIndexSet<ExpnId>,
}
impl ExpnNode {
fn new(expn_id: ExpnId) -> Self {
let expn_data = expn_id.expn_data();
let call_site = Some(expn_data.call_site).filter(|sp| !sp.is_dummy());
let call_site_expn_id = try { call_site?.ctxt().outer_expn() };
Self {
expn_id,
expn_kind: expn_data.kind.clone(),
call_site,
call_site_expn_id,
spans: vec![],
child_expn_ids: FxIndexSet::default(),
}
}
}
/// Given a collection of span/BCB pairs from potentially-different syntax contexts,
/// arranges them into an "expansion tree" based on their expansion call-sites.
pub(crate) fn build_expn_tree(spans: impl IntoIterator<Item = SpanWithBcb>) -> ExpnTree {
let mut nodes = FxIndexMap::default();
let new_node = |&expn_id: &ExpnId| ExpnNode::new(expn_id);
for span_with_bcb in spans {
// Create a node for this span's enclosing expansion, and add the span to it.
let expn_id = span_with_bcb.span.ctxt().outer_expn();
let node = nodes.entry(expn_id).or_insert_with_key(new_node);
node.spans.push(span_with_bcb);
// Now walk up the expansion call-site chain, creating nodes and registering children.
let mut prev = expn_id;
let mut curr_expn_id = node.call_site_expn_id;
while let Some(expn_id) = curr_expn_id {
let entry = nodes.entry(expn_id);
let node_existed = matches!(entry, IndexEntry::Occupied(_));
let node = entry.or_insert_with_key(new_node);
node.child_expn_ids.insert(prev);
if node_existed {
break;
}
prev = expn_id;
curr_expn_id = node.call_site_expn_id;
}
}
ExpnTree { nodes }
}

View file

@ -8,6 +8,7 @@ use crate::coverage::graph::CoverageGraph;
use crate::coverage::mappings::ExtractedMappings;
mod counters;
mod expansion;
mod graph;
mod hir_info;
mod mappings;

View file

@ -1,15 +1,14 @@
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::mir;
use rustc_middle::mir::coverage::{Mapping, MappingKind, START_BCB};
use rustc_middle::ty::TyCtxt;
use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, DesugaringKind, ExpnKind, MacroKind, Span};
use rustc_span::{BytePos, DesugaringKind, ExpnId, ExpnKind, MacroKind, Span};
use tracing::instrument;
use crate::coverage::expansion::{self, ExpnTree, SpanWithBcb};
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
use crate::coverage::hir_info::ExtractedHirInfo;
use crate::coverage::spans::from_mir::{Hole, RawSpanFromMir, SpanFromMir};
use crate::coverage::unexpand;
use crate::coverage::spans::from_mir::{Hole, RawSpanFromMir};
mod from_mir;
@ -34,19 +33,51 @@ pub(super) fn extract_refined_covspans<'tcx>(
let &ExtractedHirInfo { body_span, .. } = hir_info;
let raw_spans = from_mir::extract_raw_spans_from_mir(mir_body, graph);
let mut covspans = raw_spans
.into_iter()
.filter_map(|RawSpanFromMir { raw_span, bcb }| try {
let (span, expn_kind) =
unexpand::unexpand_into_body_span_with_expn_kind(raw_span, body_span)?;
// Discard any spans that fill the entire body, because they tend
// to represent compiler-inserted code, e.g. implicitly returning `()`.
if span.source_equal(body_span) {
return None;
};
SpanFromMir { span, expn_kind, bcb }
})
.collect::<Vec<_>>();
// Use the raw spans to build a tree of expansions for this function.
let expn_tree = expansion::build_expn_tree(
raw_spans
.into_iter()
.map(|RawSpanFromMir { raw_span, bcb }| SpanWithBcb { span: raw_span, bcb }),
);
let mut covspans = vec![];
let mut push_covspan = |covspan: Covspan| {
let covspan_span = covspan.span;
// Discard any spans not contained within the function body span.
// Also discard any spans that fill the entire body, because they tend
// to represent compiler-inserted code, e.g. implicitly returning `()`.
if !body_span.contains(covspan_span) || body_span.source_equal(covspan_span) {
return;
}
// Each pushed covspan should have the same context as the body span.
// If it somehow doesn't, discard the covspan, or panic in debug builds.
if !body_span.eq_ctxt(covspan_span) {
debug_assert!(
false,
"span context mismatch: body_span={body_span:?}, covspan.span={covspan_span:?}"
);
return;
}
covspans.push(covspan);
};
if let Some(node) = expn_tree.get(body_span.ctxt().outer_expn()) {
for &SpanWithBcb { span, bcb } in &node.spans {
push_covspan(Covspan { span, bcb });
}
// For each expansion with its call-site in the body span, try to
// distill a corresponding covspan.
for &child_expn_id in &node.child_expn_ids {
if let Some(covspan) =
single_covspan_for_child_expn(tcx, graph, &expn_tree, child_expn_id)
{
push_covspan(covspan);
}
}
}
// Only proceed if we found at least one usable span.
if covspans.is_empty() {
@ -57,17 +88,10 @@ pub(super) fn extract_refined_covspans<'tcx>(
// Otherwise, add a fake span at the start of the body, to avoid an ugly
// gap between the start of the body and the first real span.
// FIXME: Find a more principled way to solve this problem.
covspans.push(SpanFromMir::for_fn_sig(
hir_info.fn_sig_span.unwrap_or_else(|| body_span.shrink_to_lo()),
));
// First, perform the passes that need macro information.
covspans.sort_by(|a, b| graph.cmp_in_dominator_order(a.bcb, b.bcb));
remove_unwanted_expansion_spans(&mut covspans);
shrink_visible_macro_spans(tcx, &mut covspans);
// We no longer need the extra information in `SpanFromMir`, so convert to `Covspan`.
let mut covspans = covspans.into_iter().map(SpanFromMir::into_covspan).collect::<Vec<_>>();
covspans.push(Covspan {
span: hir_info.fn_sig_span.unwrap_or_else(|| body_span.shrink_to_lo()),
bcb: START_BCB,
});
let compare_covspans = |a: &Covspan, b: &Covspan| {
compare_spans(a.span, b.span)
@ -117,43 +141,37 @@ pub(super) fn extract_refined_covspans<'tcx>(
}));
}
/// Macros that expand into branches (e.g. `assert!`, `trace!`) tend to generate
/// multiple condition/consequent blocks that have the span of the whole macro
/// invocation, which is unhelpful. Keeping only the first such span seems to
/// give better mappings, so remove the others.
///
/// Similarly, `await` expands to a branch on the discriminant of `Poll`, which
/// leads to incorrect coverage if the `Future` is immediately ready (#98712).
///
/// (The input spans should be sorted in BCB dominator order, so that the
/// retained "first" span is likely to dominate the others.)
fn remove_unwanted_expansion_spans(covspans: &mut Vec<SpanFromMir>) {
let mut deduplicated_spans = FxHashSet::default();
/// For a single child expansion, try to distill it into a single span+BCB mapping.
fn single_covspan_for_child_expn(
tcx: TyCtxt<'_>,
graph: &CoverageGraph,
expn_tree: &ExpnTree,
expn_id: ExpnId,
) -> Option<Covspan> {
let node = expn_tree.get(expn_id)?;
covspans.retain(|covspan| {
match covspan.expn_kind {
// Retain only the first await-related or macro-expanded covspan with this span.
Some(ExpnKind::Desugaring(DesugaringKind::Await)) => {
deduplicated_spans.insert(covspan.span)
}
Some(ExpnKind::Macro(MacroKind::Bang, _)) => deduplicated_spans.insert(covspan.span),
// Ignore (retain) other spans.
_ => true,
let bcbs =
expn_tree.iter_node_and_descendants(expn_id).flat_map(|n| n.spans.iter().map(|s| s.bcb));
let bcb = match node.expn_kind {
// For bang-macros (e.g. `assert!`, `trace!`) and for `await`, taking
// the "first" BCB in dominator order seems to give good results.
ExpnKind::Macro(MacroKind::Bang, _) | ExpnKind::Desugaring(DesugaringKind::Await) => {
bcbs.min_by(|&a, &b| graph.cmp_in_dominator_order(a, b))?
}
});
}
// For other kinds of expansion, taking the "last" (most-dominated) BCB
// seems to give good results.
_ => bcbs.max_by(|&a, &b| graph.cmp_in_dominator_order(a, b))?,
};
/// When a span corresponds to a macro invocation that is visible from the
/// function body, truncate it to just the macro name plus `!`.
/// This seems to give better results for code that uses macros.
fn shrink_visible_macro_spans(tcx: TyCtxt<'_>, covspans: &mut Vec<SpanFromMir>) {
let source_map = tcx.sess.source_map();
for covspan in covspans {
if matches!(covspan.expn_kind, Some(ExpnKind::Macro(MacroKind::Bang, _))) {
covspan.span = source_map.span_through_char(covspan.span, '!');
}
// For bang-macro expansions, limit the call-site span to just the macro
// name plus `!`, excluding the macro arguments.
let mut span = node.call_site?;
if matches!(node.expn_kind, ExpnKind::Macro(MacroKind::Bang, _)) {
span = tcx.sess.source_map().span_through_char(span, '!');
}
Some(Covspan { span, bcb })
}
/// Discard all covspans that overlap a hole.

View file

@ -5,10 +5,9 @@ use rustc_middle::mir::coverage::CoverageKind;
use rustc_middle::mir::{
self, FakeReadCause, Statement, StatementKind, Terminator, TerminatorKind,
};
use rustc_span::{ExpnKind, Span};
use rustc_span::Span;
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, START_BCB};
use crate::coverage::spans::Covspan;
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
#[derive(Debug)]
pub(crate) struct RawSpanFromMir {
@ -160,32 +159,3 @@ impl Hole {
true
}
}
#[derive(Debug)]
pub(crate) struct SpanFromMir {
/// A span that has been extracted from MIR and then "un-expanded" back to
/// within the current function's `body_span`. After various intermediate
/// processing steps, this span is emitted as part of the final coverage
/// mappings.
///
/// With the exception of `fn_sig_span`, this should always be contained
/// within `body_span`.
pub(crate) span: Span,
pub(crate) expn_kind: Option<ExpnKind>,
pub(crate) bcb: BasicCoverageBlock,
}
impl SpanFromMir {
pub(crate) fn for_fn_sig(fn_sig_span: Span) -> Self {
Self::new(fn_sig_span, None, START_BCB)
}
pub(crate) fn new(span: Span, expn_kind: Option<ExpnKind>, bcb: BasicCoverageBlock) -> Self {
Self { span, expn_kind, bcb }
}
pub(crate) fn into_covspan(self) -> Covspan {
let Self { span, expn_kind: _, bcb } = self;
Covspan { span, bcb }
}
}

View file

@ -1,4 +1,4 @@
use rustc_span::{ExpnKind, Span};
use rustc_span::Span;
/// Walks through the expansion ancestors of `original_span` to find a span that
/// is contained in `body_span` and has the same [syntax context] as `body_span`.
@ -7,49 +7,3 @@ pub(crate) fn unexpand_into_body_span(original_span: Span, body_span: Span) -> O
// we can just delegate directly to `find_ancestor_inside_same_ctxt`.
original_span.find_ancestor_inside_same_ctxt(body_span)
}
/// Walks through the expansion ancestors of `original_span` to find a span that
/// is contained in `body_span` and has the same [syntax context] as `body_span`.
///
/// If the returned span represents a bang-macro invocation (e.g. `foo!(..)`),
/// the returned symbol will be the name of that macro (e.g. `foo`).
pub(crate) fn unexpand_into_body_span_with_expn_kind(
original_span: Span,
body_span: Span,
) -> Option<(Span, Option<ExpnKind>)> {
let (span, prev) = unexpand_into_body_span_with_prev(original_span, body_span)?;
let expn_kind = prev.map(|prev| prev.ctxt().outer_expn_data().kind);
Some((span, expn_kind))
}
/// Walks through the expansion ancestors of `original_span` to find a span that
/// is contained in `body_span` and has the same [syntax context] as `body_span`.
/// The ancestor that was traversed just before the matching span (if any) is
/// also returned.
///
/// For example, a return value of `Some((ancestor, Some(prev)))` means that:
/// - `ancestor == original_span.find_ancestor_inside_same_ctxt(body_span)`
/// - `prev.parent_callsite() == ancestor`
///
/// [syntax context]: rustc_span::SyntaxContext
fn unexpand_into_body_span_with_prev(
original_span: Span,
body_span: Span,
) -> Option<(Span, Option<Span>)> {
let mut prev = None;
let mut curr = original_span;
while !body_span.contains(curr) || !curr.eq_ctxt(body_span) {
prev = Some(curr);
curr = curr.parent_callsite()?;
}
debug_assert_eq!(Some(curr), original_span.find_ancestor_inside_same_ctxt(body_span));
if let Some(prev) = prev {
debug_assert_eq!(Some(curr), prev.parent_callsite());
}
Some((curr, prev))
}

View file

@ -5,6 +5,7 @@
#![feature(const_type_name)]
#![feature(cow_is_borrowed)]
#![feature(file_buffered)]
#![feature(gen_blocks)]
#![feature(if_let_guard)]
#![feature(impl_trait_in_assoc_type)]
#![feature(try_blocks)]

View file

@ -1,18 +1,18 @@
// FIXME(f16_f128): only tested on platforms that have symbols and aren't buggy
#![cfg(target_has_reliable_f128)]
use std::f128::consts;
use super::{assert_approx_eq, assert_biteq};
// Note these tolerances make sense around zero, but not for more extreme exponents.
/// Default tolerances. Works for values that should be near precise but not exact. Roughly
/// the precision carried by `100 * 100`.
#[allow(unused)]
const TOL: f128 = 1e-12;
/// For operations that are near exact, usually not involving math of different
/// signs.
#[allow(unused)]
const TOL_PRECISE: f128 = 1e-28;
/// First pattern over the mantissa
@ -44,70 +44,12 @@ fn test_mul_add() {
#[test]
#[cfg(any(miri, target_has_reliable_f128_math))]
fn test_recip() {
let nan: f128 = f128::NAN;
let inf: f128 = f128::INFINITY;
let neg_inf: f128 = f128::NEG_INFINITY;
assert_biteq!(1.0f128.recip(), 1.0);
assert_biteq!(2.0f128.recip(), 0.5);
assert_biteq!((-0.4f128).recip(), -2.5);
assert_biteq!(0.0f128.recip(), inf);
fn test_max_recip() {
assert_approx_eq!(
f128::MAX.recip(),
8.40525785778023376565669454330438228902076605e-4933,
1e-4900
);
assert!(nan.recip().is_nan());
assert_biteq!(inf.recip(), 0.0);
assert_biteq!(neg_inf.recip(), -0.0);
}
#[test]
#[cfg(not(miri))]
#[cfg(target_has_reliable_f128_math)]
fn test_powi() {
let nan: f128 = f128::NAN;
let inf: f128 = f128::INFINITY;
let neg_inf: f128 = f128::NEG_INFINITY;
assert_biteq!(1.0f128.powi(1), 1.0);
assert_approx_eq!((-3.1f128).powi(2), 9.6100000000000005506706202140776519387, TOL);
assert_approx_eq!(5.9f128.powi(-2), 0.028727377190462507313100483690639638451, TOL);
assert_biteq!(8.3f128.powi(0), 1.0);
assert!(nan.powi(2).is_nan());
assert_biteq!(inf.powi(3), inf);
assert_biteq!(neg_inf.powi(2), inf);
}
#[test]
fn test_to_degrees() {
let pi: f128 = consts::PI;
let nan: f128 = f128::NAN;
let inf: f128 = f128::INFINITY;
let neg_inf: f128 = f128::NEG_INFINITY;
assert_biteq!(0.0f128.to_degrees(), 0.0);
assert_approx_eq!((-5.8f128).to_degrees(), -332.31552117587745090765431723855668471, TOL);
assert_approx_eq!(pi.to_degrees(), 180.0, TOL);
assert!(nan.to_degrees().is_nan());
assert_biteq!(inf.to_degrees(), inf);
assert_biteq!(neg_inf.to_degrees(), neg_inf);
assert_biteq!(1_f128.to_degrees(), 57.2957795130823208767981548141051703);
}
#[test]
fn test_to_radians() {
let pi: f128 = consts::PI;
let nan: f128 = f128::NAN;
let inf: f128 = f128::INFINITY;
let neg_inf: f128 = f128::NEG_INFINITY;
assert_biteq!(0.0f128.to_radians(), 0.0);
assert_approx_eq!(154.6f128.to_radians(), 2.6982790235832334267135442069489767804, TOL);
assert_approx_eq!((-332.31f128).to_radians(), -5.7999036373023566567593094812182763013, TOL);
// check approx rather than exact because round trip for pi doesn't fall on an exactly
// representable value (unlike `f32` and `f64`).
assert_approx_eq!(180.0f128.to_radians(), pi, TOL_PRECISE);
assert!(nan.to_radians().is_nan());
assert_biteq!(inf.to_radians(), inf);
assert_biteq!(neg_inf.to_radians(), neg_inf);
}
#[test]

View file

@ -1,8 +1,6 @@
// FIXME(f16_f128): only tested on platforms that have symbols and aren't buggy
#![cfg(target_has_reliable_f16)]
use std::f16::consts;
use super::{assert_approx_eq, assert_biteq};
/// Tolerance for results on the order of 10.0e-2
@ -50,64 +48,8 @@ fn test_mul_add() {
#[test]
#[cfg(any(miri, target_has_reliable_f16_math))]
fn test_recip() {
let nan: f16 = f16::NAN;
let inf: f16 = f16::INFINITY;
let neg_inf: f16 = f16::NEG_INFINITY;
assert_biteq!(1.0f16.recip(), 1.0);
assert_biteq!(2.0f16.recip(), 0.5);
assert_biteq!((-0.4f16).recip(), -2.5);
assert_biteq!(0.0f16.recip(), inf);
fn test_max_recip() {
assert_approx_eq!(f16::MAX.recip(), 1.526624e-5f16, 1e-4);
assert!(nan.recip().is_nan());
assert_biteq!(inf.recip(), 0.0);
assert_biteq!(neg_inf.recip(), -0.0);
}
#[test]
#[cfg(not(miri))]
#[cfg(target_has_reliable_f16_math)]
fn test_powi() {
let nan: f16 = f16::NAN;
let inf: f16 = f16::INFINITY;
let neg_inf: f16 = f16::NEG_INFINITY;
assert_biteq!(1.0f16.powi(1), 1.0);
assert_approx_eq!((-3.1f16).powi(2), 9.61, TOL_0);
assert_approx_eq!(5.9f16.powi(-2), 0.028727, TOL_N2);
assert_biteq!(8.3f16.powi(0), 1.0);
assert!(nan.powi(2).is_nan());
assert_biteq!(inf.powi(3), inf);
assert_biteq!(neg_inf.powi(2), inf);
}
#[test]
fn test_to_degrees() {
let pi: f16 = consts::PI;
let nan: f16 = f16::NAN;
let inf: f16 = f16::INFINITY;
let neg_inf: f16 = f16::NEG_INFINITY;
assert_biteq!(0.0f16.to_degrees(), 0.0);
assert_approx_eq!((-5.8f16).to_degrees(), -332.315521, TOL_P2);
assert_approx_eq!(pi.to_degrees(), 180.0, TOL_P2);
assert!(nan.to_degrees().is_nan());
assert_biteq!(inf.to_degrees(), inf);
assert_biteq!(neg_inf.to_degrees(), neg_inf);
assert_biteq!(1_f16.to_degrees(), 57.2957795130823208767981548141051703);
}
#[test]
fn test_to_radians() {
let pi: f16 = consts::PI;
let nan: f16 = f16::NAN;
let inf: f16 = f16::INFINITY;
let neg_inf: f16 = f16::NEG_INFINITY;
assert_biteq!(0.0f16.to_radians(), 0.0);
assert_approx_eq!(154.6f16.to_radians(), 2.698279, TOL_0);
assert_approx_eq!((-332.31f16).to_radians(), -5.799903, TOL_0);
assert_approx_eq!(180.0f16.to_radians(), pi, TOL_0);
assert!(nan.to_radians().is_nan());
assert_biteq!(inf.to_radians(), inf);
assert_biteq!(neg_inf.to_radians(), neg_inf);
}
#[test]

View file

@ -1,5 +1,4 @@
use core::f32;
use core::f32::consts;
use super::{assert_approx_eq, assert_biteq};
@ -9,11 +8,6 @@ const NAN_MASK1: u32 = 0x002a_aaaa;
/// Second pattern over the mantissa
const NAN_MASK2: u32 = 0x0055_5555;
/// Miri adds some extra errors to float functions; make sure the tests still pass.
/// These values are purely used as a canary to test against and are thus not a stable guarantee Rust provides.
/// They serve as a way to get an idea of the real precision of floating point operations on different platforms.
const APPROX_DELTA: f32 = if cfg!(miri) { 1e-4 } else { 1e-6 };
// FIXME(#140515): mingw has an incorrect fma https://sourceforge.net/p/mingw-w64/bugs/848/
#[cfg_attr(all(target_os = "windows", target_env = "gnu", not(target_abi = "llvm")), ignore)]
#[test]
@ -32,64 +26,6 @@ fn test_mul_add() {
assert_biteq!(f32::math::mul_add(-3.2f32, 2.4, neg_inf), neg_inf);
}
#[test]
fn test_recip() {
let nan: f32 = f32::NAN;
let inf: f32 = f32::INFINITY;
let neg_inf: f32 = f32::NEG_INFINITY;
assert_biteq!(1.0f32.recip(), 1.0);
assert_biteq!(2.0f32.recip(), 0.5);
assert_biteq!((-0.4f32).recip(), -2.5);
assert_biteq!(0.0f32.recip(), inf);
assert!(nan.recip().is_nan());
assert_biteq!(inf.recip(), 0.0);
assert_biteq!(neg_inf.recip(), -0.0);
}
#[test]
fn test_powi() {
let nan: f32 = f32::NAN;
let inf: f32 = f32::INFINITY;
let neg_inf: f32 = f32::NEG_INFINITY;
assert_approx_eq!(1.0f32.powi(1), 1.0);
assert_approx_eq!((-3.1f32).powi(2), 9.61, APPROX_DELTA);
assert_approx_eq!(5.9f32.powi(-2), 0.028727);
assert_biteq!(8.3f32.powi(0), 1.0);
assert!(nan.powi(2).is_nan());
assert_biteq!(inf.powi(3), inf);
assert_biteq!(neg_inf.powi(2), inf);
}
#[test]
fn test_to_degrees() {
let pi: f32 = consts::PI;
let nan: f32 = f32::NAN;
let inf: f32 = f32::INFINITY;
let neg_inf: f32 = f32::NEG_INFINITY;
assert_biteq!(0.0f32.to_degrees(), 0.0);
assert_approx_eq!((-5.8f32).to_degrees(), -332.315521);
assert_biteq!(pi.to_degrees(), 180.0);
assert!(nan.to_degrees().is_nan());
assert_biteq!(inf.to_degrees(), inf);
assert_biteq!(neg_inf.to_degrees(), neg_inf);
assert_biteq!(1_f32.to_degrees(), 57.2957795130823208767981548141051703);
}
#[test]
fn test_to_radians() {
let pi: f32 = consts::PI;
let nan: f32 = f32::NAN;
let inf: f32 = f32::INFINITY;
let neg_inf: f32 = f32::NEG_INFINITY;
assert_biteq!(0.0f32.to_radians(), 0.0);
assert_approx_eq!(154.6f32.to_radians(), 2.698279);
assert_approx_eq!((-332.31f32).to_radians(), -5.799903);
assert_biteq!(180.0f32.to_radians(), pi);
assert!(nan.to_radians().is_nan());
assert_biteq!(inf.to_radians(), inf);
assert_biteq!(neg_inf.to_radians(), neg_inf);
}
#[test]
fn test_float_bits_conv() {
assert_eq!((1f32).to_bits(), 0x3f800000);

View file

@ -1,5 +1,4 @@
use core::f64;
use core::f64::consts;
use super::{assert_approx_eq, assert_biteq};
@ -27,63 +26,6 @@ fn test_mul_add() {
assert_biteq!((-3.2f64).mul_add(2.4, neg_inf), neg_inf);
}
#[test]
fn test_recip() {
let nan: f64 = f64::NAN;
let inf: f64 = f64::INFINITY;
let neg_inf: f64 = f64::NEG_INFINITY;
assert_biteq!(1.0f64.recip(), 1.0);
assert_biteq!(2.0f64.recip(), 0.5);
assert_biteq!((-0.4f64).recip(), -2.5);
assert_biteq!(0.0f64.recip(), inf);
assert!(nan.recip().is_nan());
assert_biteq!(inf.recip(), 0.0);
assert_biteq!(neg_inf.recip(), -0.0);
}
#[test]
fn test_powi() {
let nan: f64 = f64::NAN;
let inf: f64 = f64::INFINITY;
let neg_inf: f64 = f64::NEG_INFINITY;
assert_approx_eq!(1.0f64.powi(1), 1.0);
assert_approx_eq!((-3.1f64).powi(2), 9.61);
assert_approx_eq!(5.9f64.powi(-2), 0.028727);
assert_biteq!(8.3f64.powi(0), 1.0);
assert!(nan.powi(2).is_nan());
assert_biteq!(inf.powi(3), inf);
assert_biteq!(neg_inf.powi(2), inf);
}
#[test]
fn test_to_degrees() {
let pi: f64 = consts::PI;
let nan: f64 = f64::NAN;
let inf: f64 = f64::INFINITY;
let neg_inf: f64 = f64::NEG_INFINITY;
assert_biteq!(0.0f64.to_degrees(), 0.0);
assert_approx_eq!((-5.8f64).to_degrees(), -332.315521);
assert_biteq!(pi.to_degrees(), 180.0);
assert!(nan.to_degrees().is_nan());
assert_biteq!(inf.to_degrees(), inf);
assert_biteq!(neg_inf.to_degrees(), neg_inf);
}
#[test]
fn test_to_radians() {
let pi: f64 = consts::PI;
let nan: f64 = f64::NAN;
let inf: f64 = f64::INFINITY;
let neg_inf: f64 = f64::NEG_INFINITY;
assert_biteq!(0.0f64.to_radians(), 0.0);
assert_approx_eq!(154.6f64.to_radians(), 2.698279);
assert_approx_eq!((-332.31f64).to_radians(), -5.799903);
assert_biteq!(180.0f64.to_radians(), pi);
assert!(nan.to_radians().is_nan());
assert_biteq!(inf.to_radians(), inf);
assert_biteq!(neg_inf.to_radians(), neg_inf);
}
#[test]
fn test_float_bits_conv() {
assert_eq!((1f64).to_bits(), 0x3ff0000000000000);

View file

@ -1,13 +1,20 @@
use std::num::FpCategory as Fp;
use std::ops::{Add, Div, Mul, Rem, Sub};
trait TestableFloat {
trait TestableFloat: Sized {
/// Unsigned int with the same size, for converting to/from bits.
type Int;
/// Set the default tolerance for float comparison based on the type.
const APPROX: Self;
/// Allow looser tolerance for f32 on miri
const POWI_APPROX: Self = Self::APPROX;
/// Allow looser tolerance for f16
const _180_TO_RADIANS_APPROX: Self = Self::APPROX;
/// Allow for looser tolerance for f16
const PI_TO_DEGREES_APPROX: Self = Self::APPROX;
const ZERO: Self;
const ONE: Self;
const PI: Self;
const MIN_POSITIVE_NORMAL: Self;
const MAX_SUBNORMAL: Self;
/// Smallest number
@ -25,8 +32,11 @@ trait TestableFloat {
impl TestableFloat for f16 {
type Int = u16;
const APPROX: Self = 1e-3;
const _180_TO_RADIANS_APPROX: Self = 1e-2;
const PI_TO_DEGREES_APPROX: Self = 0.125;
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const PI: Self = std::f16::consts::PI;
const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE;
const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down();
const TINY: Self = Self::from_bits(0x1);
@ -39,8 +49,13 @@ impl TestableFloat for f16 {
impl TestableFloat for f32 {
type Int = u32;
const APPROX: Self = 1e-6;
/// Miri adds some extra errors to float functions; make sure the tests still pass.
/// These values are purely used as a canary to test against and are thus not a stable guarantee Rust provides.
/// They serve as a way to get an idea of the real precision of floating point operations on different platforms.
const POWI_APPROX: Self = if cfg!(miri) { 1e-4 } else { Self::APPROX };
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const PI: Self = std::f32::consts::PI;
const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE;
const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down();
const TINY: Self = Self::from_bits(0x1);
@ -55,6 +70,7 @@ impl TestableFloat for f64 {
const APPROX: Self = 1e-6;
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const PI: Self = std::f64::consts::PI;
const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE;
const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down();
const TINY: Self = Self::from_bits(0x1);
@ -69,6 +85,7 @@ impl TestableFloat for f128 {
const APPROX: Self = 1e-9;
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const PI: Self = std::f128::consts::PI;
const MIN_POSITIVE_NORMAL: Self = Self::MIN_POSITIVE;
const MAX_SUBNORMAL: Self = Self::MIN_POSITIVE.next_down();
const TINY: Self = Self::from_bits(0x1);
@ -1340,3 +1357,86 @@ float_test! {
assert_eq!(Ordering::Less, Float::total_cmp(&-s_nan(), &s_nan()));
}
}
float_test! {
name: recip,
attrs: {
f16: #[cfg(any(miri, target_has_reliable_f16_math))],
f128: #[cfg(any(miri, target_has_reliable_f128_math))],
},
test<Float> {
let nan: Float = Float::NAN;
let inf: Float = Float::INFINITY;
let neg_inf: Float = Float::NEG_INFINITY;
assert_biteq!((1.0 as Float).recip(), 1.0);
assert_biteq!((2.0 as Float).recip(), 0.5);
assert_biteq!((-0.4 as Float).recip(), -2.5);
assert_biteq!((0.0 as Float).recip(), inf);
assert!(nan.recip().is_nan());
assert_biteq!(inf.recip(), 0.0);
assert_biteq!(neg_inf.recip(), -0.0);
}
}
float_test! {
name: powi,
attrs: {
const: #[cfg(false)],
f16: #[cfg(all(not(miri), target_has_reliable_f16_math))],
f128: #[cfg(all(not(miri), target_has_reliable_f128_math))],
},
test<Float> {
let nan: Float = Float::NAN;
let inf: Float = Float::INFINITY;
let neg_inf: Float = Float::NEG_INFINITY;
assert_approx_eq!(Float::ONE.powi(1), Float::ONE);
assert_approx_eq!((-3.1 as Float).powi(2), 9.6100000000000005506706202140776519387, Float::POWI_APPROX);
assert_approx_eq!((5.9 as Float).powi(-2), 0.028727377190462507313100483690639638451);
assert_biteq!((8.3 as Float).powi(0), Float::ONE);
assert!(nan.powi(2).is_nan());
assert_biteq!(inf.powi(3), inf);
assert_biteq!(neg_inf.powi(2), inf);
}
}
float_test! {
name: to_degrees,
attrs: {
f16: #[cfg(target_has_reliable_f16)],
f128: #[cfg(target_has_reliable_f128)],
},
test<Float> {
let pi: Float = Float::PI;
let nan: Float = Float::NAN;
let inf: Float = Float::INFINITY;
let neg_inf: Float = Float::NEG_INFINITY;
assert_biteq!((0.0 as Float).to_degrees(), 0.0);
assert_approx_eq!((-5.8 as Float).to_degrees(), -332.31552117587745090765431723855668471);
assert_approx_eq!(pi.to_degrees(), 180.0, Float::PI_TO_DEGREES_APPROX);
assert!(nan.to_degrees().is_nan());
assert_biteq!(inf.to_degrees(), inf);
assert_biteq!(neg_inf.to_degrees(), neg_inf);
assert_biteq!((1.0 as Float).to_degrees(), 57.2957795130823208767981548141051703);
}
}
float_test! {
name: to_radians,
attrs: {
f16: #[cfg(target_has_reliable_f16)],
f128: #[cfg(target_has_reliable_f128)],
},
test<Float> {
let pi: Float = Float::PI;
let nan: Float = Float::NAN;
let inf: Float = Float::INFINITY;
let neg_inf: Float = Float::NEG_INFINITY;
assert_biteq!((0.0 as Float).to_radians(), 0.0);
assert_approx_eq!((154.6 as Float).to_radians(), 2.6982790235832334267135442069489767804);
assert_approx_eq!((-332.31 as Float).to_radians(), -5.7999036373023566567593094812182763013);
assert_approx_eq!((180.0 as Float).to_radians(), pi, Float::_180_TO_RADIANS_APPROX);
assert!(nan.to_radians().is_nan());
assert_biteq!(inf.to_radians(), inf);
assert_biteq!(neg_inf.to_radians(), neg_inf);
}
}

View file

@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex, mpsc};
use std::{env, hint, io, mem, panic, thread};
use crate::common::{Config, TestPaths};
use crate::panic_hook;
mod deadline;
mod json;
@ -120,6 +121,11 @@ fn run_test_inner(
completion_sender: mpsc::Sender<TestCompletion>,
) {
let is_capture = !runnable_test.config.nocapture;
// Install a panic-capture buffer for use by the custom panic hook.
if is_capture {
panic_hook::set_capture_buf(Default::default());
}
let capture_buf = is_capture.then(|| Arc::new(Mutex::new(vec![])));
if let Some(capture_buf) = &capture_buf {
@ -128,6 +134,13 @@ fn run_test_inner(
let panic_payload = panic::catch_unwind(move || runnable_test.run()).err();
if let Some(panic_buf) = panic_hook::take_capture_buf() {
let panic_buf = panic_buf.lock().unwrap_or_else(|e| e.into_inner());
// For now, forward any captured panic message to (captured) stderr.
// FIXME(Zalathar): Once we have our own output-capture buffer for
// non-panic output, append the panic message to that buffer instead.
eprint!("{panic_buf}");
}
if is_capture {
io::set_output_capture(None);
}

View file

@ -15,6 +15,7 @@ pub mod directives;
pub mod errors;
mod executor;
mod json;
mod panic_hook;
mod raise_fd_limit;
mod read2;
pub mod runtest;
@ -493,6 +494,8 @@ pub fn opt_str2(maybestr: Option<String>) -> String {
pub fn run_tests(config: Arc<Config>) {
debug!(?config, "run_tests");
panic_hook::install_panic_hook();
// If we want to collect rustfix coverage information,
// we first make sure that the coverage file does not exist.
// It will be created later on.

View file

@ -0,0 +1,136 @@
use std::backtrace::{Backtrace, BacktraceStatus};
use std::cell::Cell;
use std::fmt::{Display, Write};
use std::panic::PanicHookInfo;
use std::sync::{Arc, LazyLock, Mutex};
use std::{env, mem, panic, thread};
type PanicHook = Box<dyn Fn(&PanicHookInfo<'_>) + Sync + Send + 'static>;
type CaptureBuf = Arc<Mutex<String>>;
thread_local!(
static CAPTURE_BUF: Cell<Option<CaptureBuf>> = const { Cell::new(None) };
);
/// Installs a custom panic hook that will divert panic output to a thread-local
/// capture buffer, but only for threads that have a capture buffer set.
///
/// Otherwise, the custom hook delegates to a copy of the default panic hook.
pub(crate) fn install_panic_hook() {
let default_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| custom_panic_hook(&default_hook, info)));
}
pub(crate) fn set_capture_buf(buf: CaptureBuf) {
CAPTURE_BUF.set(Some(buf));
}
pub(crate) fn take_capture_buf() -> Option<CaptureBuf> {
CAPTURE_BUF.take()
}
fn custom_panic_hook(default_hook: &PanicHook, info: &panic::PanicHookInfo<'_>) {
// Temporarily taking the capture buffer means that if a panic occurs in
// the subsequent code, that panic will fall back to the default hook.
let Some(buf) = take_capture_buf() else {
// There was no capture buffer, so delegate to the default hook.
default_hook(info);
return;
};
let mut out = buf.lock().unwrap_or_else(|e| e.into_inner());
let thread = thread::current().name().unwrap_or("(test runner)").to_owned();
let location = get_location(info);
let payload = payload_as_str(info).unwrap_or("Box<dyn Any>");
let backtrace = Backtrace::capture();
writeln!(out, "\nthread '{thread}' panicked at {location}:\n{payload}").unwrap();
match backtrace.status() {
BacktraceStatus::Captured => {
let bt = trim_backtrace(backtrace.to_string());
write!(out, "stack backtrace:\n{bt}",).unwrap();
}
BacktraceStatus::Disabled => {
writeln!(
out,
"note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace",
)
.unwrap();
}
_ => {}
}
drop(out);
set_capture_buf(buf);
}
fn get_location<'a>(info: &'a PanicHookInfo<'_>) -> &'a dyn Display {
match info.location() {
Some(location) => location,
None => &"(unknown)",
}
}
/// FIXME(Zalathar): Replace with `PanicHookInfo::payload_as_str` when that's
/// stable in beta.
fn payload_as_str<'a>(info: &'a PanicHookInfo<'_>) -> Option<&'a str> {
let payload = info.payload();
if let Some(s) = payload.downcast_ref::<&str>() {
Some(s)
} else if let Some(s) = payload.downcast_ref::<String>() {
Some(s)
} else {
None
}
}
fn rust_backtrace_full() -> bool {
static RUST_BACKTRACE_FULL: LazyLock<bool> =
LazyLock::new(|| matches!(env::var("RUST_BACKTRACE").as_deref(), Ok("full")));
*RUST_BACKTRACE_FULL
}
/// On stable, short backtraces are only available to the default panic hook,
/// so if we want something similar we have to resort to string processing.
fn trim_backtrace(full_backtrace: String) -> String {
if rust_backtrace_full() {
return full_backtrace;
}
let mut buf = String::with_capacity(full_backtrace.len());
// Don't print any frames until after the first `__rust_end_short_backtrace`.
let mut on = false;
// After the short-backtrace state is toggled, skip its associated "at" if present.
let mut skip_next_at = false;
let mut lines = full_backtrace.lines();
while let Some(line) = lines.next() {
if mem::replace(&mut skip_next_at, false) && line.trim_start().starts_with("at ") {
continue;
}
if line.contains("__rust_end_short_backtrace") {
on = true;
skip_next_at = true;
continue;
}
if line.contains("__rust_begin_short_backtrace") {
on = false;
skip_next_at = true;
continue;
}
if on {
writeln!(buf, "{line}").unwrap();
}
}
writeln!(
buf,
"note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace."
)
.unwrap();
buf
}

View file

@ -14,6 +14,7 @@ pkgs.mkShell {
packages = [
pkgs.git
pkgs.nix
pkgs.glibc.out
pkgs.glibc.static
x
# Get the runtime deps of the x wrapper
@ -23,5 +24,7 @@ pkgs.mkShell {
# Avoid creating text files for ICEs.
RUSTC_ICE = 0;
SSL_CERT_FILE = cacert;
# cargo seems to dlopen libcurl, so we need it in the ld library path
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath [pkgs.stdenv.cc.cc.lib pkgs.curl]}";
};
}

View file

@ -118,21 +118,23 @@ Number of file 0 mappings: 4
Highest counter ID seen: (none)
Function name: closure::main::{closure#12} (unused)
Raw bytes (10): 0x[01, 01, 00, 01, 00, a7, 01, 0a, 00, 16]
Raw bytes (15): 0x[01, 01, 00, 02, 00, a7, 01, 01, 00, 09, 00, 00, 0a, 00, 16]
Number of files: 1
- file 0 => $DIR/closure.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 167, 10) to (start + 0, 22)
Number of file 0 mappings: 2
- Code(Zero) at (prev + 167, 1) to (start + 0, 9)
- Code(Zero) at (prev + 0, 10) to (start + 0, 22)
Highest counter ID seen: (none)
Function name: closure::main::{closure#13} (unused)
Raw bytes (10): 0x[01, 01, 00, 01, 00, ad, 01, 11, 00, 1d]
Raw bytes (15): 0x[01, 01, 00, 02, 00, ac, 01, 0d, 00, 15, 00, 01, 11, 00, 1d]
Number of files: 1
- file 0 => $DIR/closure.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 173, 17) to (start + 0, 29)
Number of file 0 mappings: 2
- Code(Zero) at (prev + 172, 13) to (start + 0, 21)
- Code(Zero) at (prev + 1, 17) to (start + 0, 29)
Highest counter ID seen: (none)
Function name: closure::main::{closure#14}
@ -289,30 +291,33 @@ Number of file 0 mappings: 7
Highest counter ID seen: (none)
Function name: closure::main::{closure#5}
Raw bytes (10): 0x[01, 01, 00, 01, 01, 8c, 01, 46, 00, 4e]
Raw bytes (15): 0x[01, 01, 00, 02, 01, 8c, 01, 3d, 00, 45, 01, 00, 46, 00, 4e]
Number of files: 1
- file 0 => $DIR/closure.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Counter(0)) at (prev + 140, 70) to (start + 0, 78)
Number of file 0 mappings: 2
- Code(Counter(0)) at (prev + 140, 61) to (start + 0, 69)
- Code(Counter(0)) at (prev + 0, 70) to (start + 0, 78)
Highest counter ID seen: c0
Function name: closure::main::{closure#6}
Raw bytes (10): 0x[01, 01, 00, 01, 01, 8d, 01, 4a, 00, 56]
Raw bytes (15): 0x[01, 01, 00, 02, 01, 8d, 01, 41, 00, 49, 01, 00, 4a, 00, 56]
Number of files: 1
- file 0 => $DIR/closure.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Counter(0)) at (prev + 141, 74) to (start + 0, 86)
Number of file 0 mappings: 2
- Code(Counter(0)) at (prev + 141, 65) to (start + 0, 73)
- Code(Counter(0)) at (prev + 0, 74) to (start + 0, 86)
Highest counter ID seen: c0
Function name: closure::main::{closure#7} (unused)
Raw bytes (10): 0x[01, 01, 00, 01, 00, 8e, 01, 44, 00, 50]
Raw bytes (15): 0x[01, 01, 00, 02, 00, 8e, 01, 3b, 00, 43, 00, 00, 44, 00, 50]
Number of files: 1
- file 0 => $DIR/closure.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Zero) at (prev + 142, 68) to (start + 0, 80)
Number of file 0 mappings: 2
- Code(Zero) at (prev + 142, 59) to (start + 0, 67)
- Code(Zero) at (prev + 0, 68) to (start + 0, 80)
Highest counter ID seen: (none)
Function name: closure::main::{closure#8} (unused)

View file

@ -139,9 +139,9 @@
LL| |
LL| 1| let short_used_covered_closure_macro = | used_arg: u8 | println!("called");
LL| 1| let short_used_not_covered_closure_macro = | used_arg: u8 | println!("not called");
^0
^0 ^0
LL| 1| let _short_unused_closure_macro = | _unused_arg: u8 | println!("not called");
^0
^0 ^0
LL| |
LL| |
LL| |
@ -173,7 +173,7 @@
LL| |
LL| 1| let _short_unused_closure_line_break_no_block2 =
LL| | | _unused_arg: u8 |
LL| | println!(
LL| 0| println!(
LL| 0| "not called"
LL| | )
LL| | ;

View file

@ -1,10 +1,11 @@
Function name: macro_in_closure::NO_BLOCK::{closure#0}
Raw bytes (9): 0x[01, 01, 00, 01, 01, 07, 25, 00, 2c]
Raw bytes (14): 0x[01, 01, 00, 02, 01, 07, 1c, 00, 24, 01, 00, 25, 00, 2c]
Number of files: 1
- file 0 => $DIR/macro_in_closure.rs
Number of expressions: 0
Number of file 0 mappings: 1
- Code(Counter(0)) at (prev + 7, 37) to (start + 0, 44)
Number of file 0 mappings: 2
- Code(Counter(0)) at (prev + 7, 28) to (start + 0, 36)
- Code(Counter(0)) at (prev + 0, 37) to (start + 0, 44)
Highest counter ID seen: c0
Function name: macro_in_closure::WITH_BLOCK::{closure#0}

View file

@ -0,0 +1,12 @@
Function name: rustfmt_skip::main
Raw bytes (24): 0x[01, 01, 00, 04, 01, 0a, 01, 00, 0a, 01, 02, 05, 00, 0d, 01, 03, 09, 00, 10, 01, 02, 01, 00, 02]
Number of files: 1
- file 0 => $DIR/rustfmt-skip.rs
Number of expressions: 0
Number of file 0 mappings: 4
- Code(Counter(0)) at (prev + 10, 1) to (start + 0, 10)
- Code(Counter(0)) at (prev + 2, 5) to (start + 0, 13)
- Code(Counter(0)) at (prev + 3, 9) to (start + 0, 16)
- Code(Counter(0)) at (prev + 2, 1) to (start + 0, 2)
Highest counter ID seen: c0

View file

@ -0,0 +1,18 @@
LL| |//@ edition: 2024
LL| |
LL| |// The presence of `#[rustfmt::skip]` on a function should not cause macros
LL| |// within that function to mysteriously not be instrumented.
LL| |//
LL| |// This test detects problems that can occur when building an expansion tree
LL| |// based on `ExpnData::parent` instead of `ExpnData::call_site`, for example.
LL| |
LL| |#[rustfmt::skip]
LL| 1|fn main() {
LL| | // Ensure a gap between the body start and the first statement.
LL| 1| println!(
LL| | // Keep this on a separate line, to distinguish instrumentation of
LL| | // `println!` from instrumentation of its arguments.
LL| 1| "hello"
LL| | );
LL| 1|}

View file

@ -0,0 +1,17 @@
//@ edition: 2024
// The presence of `#[rustfmt::skip]` on a function should not cause macros
// within that function to mysteriously not be instrumented.
//
// This test detects problems that can occur when building an expansion tree
// based on `ExpnData::parent` instead of `ExpnData::call_site`, for example.
#[rustfmt::skip]
fn main() {
// Ensure a gap between the body start and the first statement.
println!(
// Keep this on a separate line, to distinguish instrumentation of
// `println!` from instrumentation of its arguments.
"hello"
);
}

View file

@ -1,7 +1,7 @@
---
//~^ ERROR: invalid preceding whitespace for frontmatter opening
//~^^ ERROR: unclosed frontmatter
---
//~^ ERROR: invalid preceding whitespace for frontmatter close
#![feature(frontmatter)]

View file

@ -10,17 +10,21 @@ note: frontmatter opening should not be preceded by whitespace
LL | ---
| ^^
error: invalid preceding whitespace for frontmatter close
--> $DIR/frontmatter-whitespace-1.rs:3:1
error: unclosed frontmatter
--> $DIR/frontmatter-whitespace-1.rs:1:3
|
LL | / ---
LL | |
LL | |
LL | | ---
LL | |
| |_^
|
note: frontmatter opening here was not closed
--> $DIR/frontmatter-whitespace-1.rs:1:3
|
LL | ---
| ^^^^^
|
note: frontmatter close should not be preceded by whitespace
--> $DIR/frontmatter-whitespace-1.rs:3:1
|
LL | ---
| ^^
| ^^^
error: aborting due to 2 previous errors

View file

@ -1,4 +1,5 @@
---cargo
//~^ ERROR: unclosed frontmatter
//@ compile-flags: --crate-type lib
@ -6,10 +7,8 @@
fn foo(x: i32) -> i32 {
---x
//~^ ERROR: invalid preceding whitespace for frontmatter close
//~| ERROR: extra characters after frontmatter close are not allowed
//~^ WARNING: use of a double negation [double_negations]
}
//~^ ERROR: unexpected closing delimiter: `}`
// this test is for the weird case that valid Rust code can have three dashes
// within them and get treated as a frontmatter close.

View file

@ -1,26 +1,30 @@
error: invalid preceding whitespace for frontmatter close
--> $DIR/frontmatter-whitespace-2.rs:8:1
error: unclosed frontmatter
--> $DIR/frontmatter-whitespace-2.rs:1:1
|
LL | / ---cargo
... |
LL | |
| |_^
|
note: frontmatter opening here was not closed
--> $DIR/frontmatter-whitespace-2.rs:1:1
|
LL | ---cargo
| ^^^
warning: use of a double negation
--> $DIR/frontmatter-whitespace-2.rs:9:6
|
LL | ---x
| ^^^^^^^^
| ^^^
|
note: frontmatter close should not be preceded by whitespace
--> $DIR/frontmatter-whitespace-2.rs:8:1
= note: the prefix `--` could be misinterpreted as a decrement operator which exists in other languages
= note: use `-= 1` if you meant to decrement the value
= note: `#[warn(double_negations)]` on by default
help: add parentheses for clarity
|
LL | ---x
| ^^^^
LL | --(-x)
| + +
error: extra characters after frontmatter close are not allowed
--> $DIR/frontmatter-whitespace-2.rs:8:1
|
LL | ---x
| ^^^^^^^^
error: unexpected closing delimiter: `}`
--> $DIR/frontmatter-whitespace-2.rs:11:1
|
LL | }
| ^ unexpected closing delimiter
error: aborting due to 3 previous errors
error: aborting due to 1 previous error; 1 warning emitted

View file

@ -1,12 +1,12 @@
---
---
//~^ ERROR: invalid preceding whitespace for frontmatter close
---
//~^ ERROR: expected item, found `-`
// FIXME(frontmatter): make this diagnostic better
---
// hyphens only need to be escaped when at the start of a line
//@ check-pass
#![feature(frontmatter)]
fn main() {}

View file

@ -1,22 +0,0 @@
error: invalid preceding whitespace for frontmatter close
--> $DIR/multifrontmatter-2.rs:2:1
|
LL | ---
| ^^^^
|
note: frontmatter close should not be preceded by whitespace
--> $DIR/multifrontmatter-2.rs:2:1
|
LL | ---
| ^
error: expected item, found `-`
--> $DIR/multifrontmatter-2.rs:5:2
|
LL | ---
| ^ expected item
|
= note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>
error: aborting due to 2 previous errors