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:
commit
84a1747022
28 changed files with 601 additions and 483 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
127
compiler/rustc_mir_transform/src/coverage/expansion.rs
Normal file
127
compiler/rustc_mir_transform/src/coverage/expansion.rs
Normal 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 }
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
136
src/tools/compiletest/src/panic_hook.rs
Normal file
136
src/tools/compiletest/src/panic_hook.rs
Normal 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
|
||||
}
|
||||
|
|
@ -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]}";
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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| | ;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
12
tests/coverage/rustfmt-skip.cov-map
Normal file
12
tests/coverage/rustfmt-skip.cov-map
Normal 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
|
||||
|
||||
18
tests/coverage/rustfmt-skip.coverage
Normal file
18
tests/coverage/rustfmt-skip.coverage
Normal 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|}
|
||||
|
||||
17
tests/coverage/rustfmt-skip.rs
Normal file
17
tests/coverage/rustfmt-skip.rs
Normal 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"
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
//~^ ERROR: invalid preceding whitespace for frontmatter opening
|
||||
//~^^ ERROR: unclosed frontmatter
|
||||
---
|
||||
//~^ ERROR: invalid preceding whitespace for frontmatter close
|
||||
|
||||
#![feature(frontmatter)]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue