Merge from rustc

This commit is contained in:
Ralf Jung 2024-10-14 17:04:43 +02:00
commit 9d579f5358
1487 changed files with 21017 additions and 14840 deletions

View file

@ -1,6 +1,8 @@
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use crate::ci::CiEnv;
pub struct GitConfig<'a> {
pub git_repository: &'a str,
pub nightly_branch: &'a str,
@ -114,8 +116,8 @@ fn git_upstream_merge_base(
/// Searches for the nearest merge commit in the repository that also exists upstream.
///
/// If it fails to find the upstream remote, it then looks for the most recent commit made
/// by the merge bot by matching the author's email address with the merge bot's email.
/// It looks for the most recent commit made by the merge bot by matching the author's email
/// address with the merge bot's email.
pub fn get_closest_merge_commit(
git_dir: Option<&Path>,
config: &GitConfig<'_>,
@ -127,7 +129,15 @@ pub fn get_closest_merge_commit(
git.current_dir(git_dir);
}
let merge_base = git_upstream_merge_base(config, git_dir).unwrap_or_else(|_| "HEAD".into());
let merge_base = {
if CiEnv::is_ci() {
git_upstream_merge_base(config, git_dir).unwrap()
} else {
// For non-CI environments, ignore rust-lang/rust upstream as it usually gets
// outdated very quickly.
"HEAD".to_string()
}
};
git.args([
"rev-list",
@ -196,67 +206,3 @@ pub fn get_git_untracked_files(
.collect();
Ok(Some(files))
}
/// Print a warning if the branch returned from `updated_master_branch` is old
///
/// For certain configurations of git repository, this remote will not be
/// updated when running `git pull`.
///
/// This can result in formatting thousands of files instead of a dozen,
/// so we should warn the user something is wrong.
pub fn warn_old_master_branch(config: &GitConfig<'_>, git_dir: &Path) {
if crate::ci::CiEnv::is_ci() {
// this warning is useless in CI,
// and CI probably won't have the right branches anyway.
return;
}
// this will be overwritten by the actual name, if possible
let mut updated_master = "the upstream master branch".to_string();
match warn_old_master_branch_(config, git_dir, &mut updated_master) {
Ok(branch_is_old) => {
if !branch_is_old {
return;
}
// otherwise fall through and print the rest of the warning
}
Err(err) => {
eprintln!("warning: unable to check if {updated_master} is old due to error: {err}")
}
}
eprintln!(
"warning: {updated_master} is used to determine if files have been modified\n\
warning: if it is not updated, this may cause files to be needlessly reformatted"
);
}
pub fn warn_old_master_branch_(
config: &GitConfig<'_>,
git_dir: &Path,
updated_master: &mut String,
) -> Result<bool, Box<dyn std::error::Error>> {
use std::time::Duration;
*updated_master = updated_master_branch(config, Some(git_dir))?;
let branch_path = git_dir.join(".git/refs/remotes").join(&updated_master);
const WARN_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 10);
let meta = match std::fs::metadata(&branch_path) {
Ok(meta) => meta,
Err(err) => {
let gcd = git_common_dir(&git_dir)?;
if branch_path.starts_with(&gcd) {
return Err(Box::new(err));
}
std::fs::metadata(Path::new(&gcd).join("refs/remotes").join(&updated_master))?
}
};
if meta.modified()?.elapsed()? > WARN_AFTER {
eprintln!("warning: {updated_master} has not been updated in 10 days");
Ok(true)
} else {
Ok(false)
}
}
fn git_common_dir(dir: &Path) -> Result<String, String> {
output_result(Command::new("git").arg("-C").arg(dir).arg("rev-parse").arg("--git-common-dir"))
.map(|x| x.trim().to_string())
}

@ -1 +1 @@
Subproject commit 80d82ca22abbee5fb7b51fa1abeb1ae34e99e88a
Subproject commit 15fbd2f607d4defc87053b8b76bf5038f2483cf4

View file

@ -1,15 +1,15 @@
use super::{ALLOW_ATTRIBUTES_WITHOUT_REASON, Attribute};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use rustc_ast::{MetaItemKind, NestedMetaItem};
use rustc_ast::{MetaItemInner, MetaItemKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_span::sym;
use rustc_span::symbol::Symbol;
pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMetaItem], attr: &'cx Attribute) {
pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[MetaItemInner], attr: &'cx Attribute) {
// Check if the reason is present
if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
if let Some(item) = items.last().and_then(MetaItemInner::meta_item)
&& let MetaItemKind::NameValue(_) = &item.kind
&& item.path == sym::reason
{

View file

@ -1,12 +1,12 @@
use super::BLANKET_CLIPPY_RESTRICTION_LINTS;
use super::utils::extract_clippy_lint;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use rustc_ast::NestedMetaItem;
use rustc_ast::MetaItemInner;
use rustc_lint::{LateContext, Level, LintContext};
use rustc_span::symbol::Symbol;
use rustc_span::{DUMMY_SP, sym};
pub(super) fn check(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
pub(super) fn check(cx: &LateContext<'_>, name: Symbol, items: &[MetaItemInner]) {
for lint in items {
if let Some(lint_name) = extract_clippy_lint(lint) {
if lint_name.as_str() == "restriction" && name != sym::allow {

View file

@ -14,7 +14,7 @@ mod utils;
use clippy_config::Conf;
use clippy_config::msrvs::{self, Msrv};
use rustc_ast::{Attribute, MetaItemKind, NestedMetaItem};
use rustc_ast::{Attribute, MetaItemInner, MetaItemKind};
use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
@ -456,7 +456,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
return;
}
for item in items {
if let NestedMetaItem::MetaItem(mi) = &item
if let MetaItemInner::MetaItem(mi) = &item
&& let MetaItemKind::NameValue(lit) = &mi.kind
&& mi.has_name(sym::since)
{

View file

@ -1,7 +1,7 @@
use super::{Attribute, NON_MINIMAL_CFG};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::SpanRangeExt;
use rustc_ast::{MetaItemKind, NestedMetaItem};
use rustc_ast::{MetaItemInner, MetaItemKind};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::sym;
@ -14,9 +14,9 @@ pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
}
}
fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[MetaItemInner]) {
for item in items {
if let NestedMetaItem::MetaItem(meta) = item {
if let MetaItemInner::MetaItem(meta) = item {
if !meta.has_name(sym::any) && !meta.has_name(sym::all) {
continue;
}

View file

@ -2,7 +2,7 @@ use super::utils::{extract_clippy_lint, is_lint_level, is_word};
use super::{Attribute, USELESS_ATTRIBUTE};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{SpanRangeExt, first_line_of_span};
use rustc_ast::NestedMetaItem;
use rustc_ast::MetaItemInner;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LintContext};
@ -21,7 +21,7 @@ pub(super) fn check(cx: &LateContext<'_>, item: &Item<'_>, attrs: &[Attribute])
for lint in lint_list {
match item.kind {
ItemKind::Use(..) => {
if let NestedMetaItem::MetaItem(meta_item) = lint
if let MetaItemInner::MetaItem(meta_item) = lint
&& meta_item.is_word()
&& let Some(ident) = meta_item.ident()
&& matches!(

View file

@ -1,5 +1,5 @@
use clippy_utils::macros::{is_panic, macro_backtrace};
use rustc_ast::{AttrId, NestedMetaItem};
use rustc_ast::{AttrId, MetaItemInner};
use rustc_hir::{
Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
};
@ -8,8 +8,8 @@ use rustc_middle::ty;
use rustc_span::sym;
use rustc_span::symbol::Symbol;
pub(super) fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
if let NestedMetaItem::MetaItem(mi) = &nmi {
pub(super) fn is_word(nmi: &MetaItemInner, expected: Symbol) -> bool {
if let MetaItemInner::MetaItem(mi) = &nmi {
mi.is_word() && mi.has_name(expected)
} else {
false
@ -74,7 +74,7 @@ fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>
}
/// Returns the lint name if it is clippy lint.
pub(super) fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
pub(super) fn extract_clippy_lint(lint: &MetaItemInner) -> Option<Symbol> {
if let Some(meta_item) = lint.meta_item()
&& meta_item.path.segments.len() > 1
&& let tool_name = meta_item.path.segments[0].ident

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::NestedMetaItem;
use rustc_ast::MetaItemInner;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
@ -47,7 +47,7 @@ impl EarlyLintPass for CfgNotTest {
}
}
fn contains_not_test(list: Option<&[NestedMetaItem]>, not: bool) -> bool {
fn contains_not_test(list: Option<&[MetaItemInner]>, not: bool) -> bool {
list.is_some_and(|list| {
list.iter().any(|item| {
item.ident().is_some_and(|ident| match ident.name {

View file

@ -6,7 +6,7 @@ use rustc_errors::Applicability;
use rustc_hir::intravisit::{Visitor, walk_impl_item, walk_item, walk_param_bound, walk_ty};
use rustc_hir::{
BodyId, ExprKind, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
PredicateOrigin, Ty, TyKind, WherePredicate,
PredicateOrigin, Ty, WherePredicate,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
@ -199,12 +199,6 @@ impl<'tcx> Visitor<'tcx> for TypeWalker<'_, 'tcx> {
fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) {
if let Some((def_id, _)) = t.peel_refs().as_generic_param() {
self.ty_params.remove(&def_id);
} else if let TyKind::OpaqueDef(id, _) = t.kind {
// Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls
// `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're
// using `OnlyBodies`, so the check ends up failing and the type isn't fully walked.
let item = self.nested_visit_map().item(id);
walk_item(self, item);
} else {
walk_ty(self, t);
}

View file

@ -3,7 +3,7 @@ use clippy_utils::source::snippet;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::def_id::DefId;
use rustc_hir::{
AssocItemConstraint, GenericArg, GenericBound, GenericBounds, ItemKind, PredicateOrigin, TraitBoundModifier,
AssocItemConstraint, GenericArg, GenericBound, GenericBounds, PredicateOrigin, TraitBoundModifier,
TyKind, WherePredicate,
};
use rustc_hir_analysis::lower_ty;
@ -342,11 +342,8 @@ impl<'tcx> LateLintPass<'tcx> for ImpliedBoundsInImpls {
}
}
fn check_ty(&mut self, cx: &LateContext<'_>, ty: &rustc_hir::Ty<'_>) {
if let TyKind::OpaqueDef(item_id, ..) = ty.kind
&& let item = cx.tcx.hir().item(item_id)
&& let ItemKind::OpaqueTy(opaque_ty) = item.kind
{
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &rustc_hir::Ty<'tcx>) {
if let TyKind::OpaqueDef(opaque_ty, ..) = ty.kind {
check(cx, opaque_ty.bounds);
}
}

View file

@ -5,7 +5,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLet;
use clippy_utils::ty::is_copy;
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::HirId;
@ -133,7 +133,7 @@ fn lint_slice(cx: &LateContext<'_>, slice: &SliceLintInformation) {
.index_use
.iter()
.map(|(index, _)| *index)
.collect::<FxHashSet<_>>();
.collect::<FxIndexSet<_>>();
let value_name = |index| format!("{}_{index}", slice.ident.name);

View file

@ -308,11 +308,7 @@ enum LenOutput {
fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
if let ty::Alias(_, alias_ty) = ty.kind()
&& let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(alias_ty.def_id)
&& let Item {
kind: ItemKind::OpaqueTy(opaque),
..
} = item
&& let Some(Node::OpaqueTy(opaque)) = cx.tcx.hir().get_if_local(alias_ty.def_id)
&& let OpaqueTyOrigin::AsyncFn { .. } = opaque.origin
&& let [GenericBound::Trait(trait_ref, _)] = &opaque.bounds
&& let Some(segment) = trait_ref.trait_ref.path.segments.last()

View file

@ -1,7 +1,6 @@
#![feature(array_windows)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(box_patterns)]
#![feature(control_flow_enum)]
#![feature(f128)]
#![feature(f16)]
#![feature(if_let_guard)]
@ -27,8 +26,6 @@
unused_qualifications,
rustc::internal
)]
// Disable this rustc lint for now, as it was also done in rustc
#![allow(rustc::potential_query_instability)]
// FIXME: switch to something more ergonomic here, once available.
// (Currently there is no way to opt into sysroot crates without `extern crate`.)

View file

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::trait_ref_of_method;
use itertools::Itertools;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_errors::Applicability;
use rustc_hir::FnRetTy::Return;
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
use rustc_hir::intravisit::{
Visitor, walk_fn_decl, walk_generic_args, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
Visitor, walk_fn_decl, walk_generic_args, walk_generics, walk_impl_item_ref, walk_param_bound,
walk_poly_trait_ref, walk_trait_ref, walk_ty, walk_where_predicate,
};
use rustc_hir::{
@ -311,7 +311,7 @@ fn could_use_elision<'tcx>(
Some((elidable_lts, usages))
}
fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet<LocalDefId> {
fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet<LocalDefId> {
named_generics
.iter()
.filter_map(|par| {
@ -420,11 +420,9 @@ impl<'tcx> Visitor<'tcx> for RefVisitor<'_, 'tcx> {
fn visit_ty(&mut self, ty: &'tcx Ty<'_>) {
match ty.kind {
TyKind::OpaqueDef(item, bounds) => {
let map = self.cx.tcx.hir();
let item = map.item(item);
TyKind::OpaqueDef(opaque, bounds) => {
let len = self.lts.len();
walk_item(self, item);
self.visit_opaque_ty(opaque);
self.lts.truncate(len);
self.lts.extend(bounds.iter().filter_map(|bound| match bound {
GenericArg::Lifetime(&l) => Some(l),
@ -499,7 +497,7 @@ struct Usage {
struct LifetimeChecker<'cx, 'tcx, F> {
cx: &'cx LateContext<'tcx>,
map: FxHashMap<LocalDefId, Vec<Usage>>,
map: FxIndexMap<LocalDefId, Vec<Usage>>,
where_predicate_depth: usize,
generic_args_depth: usize,
phantom: std::marker::PhantomData<F>,
@ -621,7 +619,7 @@ fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'
fn report_elidable_impl_lifetimes<'tcx>(
cx: &LateContext<'tcx>,
impl_: &'tcx Impl<'_>,
map: &FxHashMap<LocalDefId, Vec<Usage>>,
map: &FxIndexMap<LocalDefId, Vec<Usage>>,
) {
let single_usages = map
.iter()

View file

@ -5,7 +5,7 @@ use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, sugg};
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{Visitor, walk_expr};
@ -39,7 +39,7 @@ pub(super) fn check<'tcx>(
var: canonical_id,
indexed_mut: FxHashSet::default(),
indexed_indirectly: FxHashMap::default(),
indexed_directly: FxHashMap::default(),
indexed_directly: FxIndexMap::default(),
referenced: FxHashSet::default(),
nonindex: false,
prefer_mutable: false,
@ -229,7 +229,7 @@ struct VarVisitor<'a, 'tcx> {
indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>,
/// subset of `indexed` of vars that are indexed directly: `v[i]`
/// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]`
indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
indexed_directly: FxIndexMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>,
/// Any names that are used outside an index operation.
/// Used to detect things like `&mut vec` used together with `vec[i]`
referenced: FxHashSet<Symbol>,

View file

@ -4,7 +4,7 @@ use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
Block, Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnDecl,
FnRetTy, GenericArg, GenericBound, ImplItem, Item, ItemKind, LifetimeName, Node, TraitRef, Ty, TyKind,
FnRetTy, GenericArg, GenericBound, ImplItem, Item, LifetimeName, Node, TraitRef, Ty, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -105,9 +105,7 @@ fn future_trait_ref<'tcx>(
cx: &LateContext<'tcx>,
ty: &'tcx Ty<'tcx>,
) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> {
if let TyKind::OpaqueDef(item_id, bounds) = ty.kind
&& let item = cx.tcx.hir().item(item_id)
&& let ItemKind::OpaqueTy(opaque) = &item.kind
if let TyKind::OpaqueDef(opaque, bounds) = ty.kind
&& let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| {
if let GenericBound::Trait(poly, _) = bound {
Some(&poly.trait_ref)

View file

@ -7,6 +7,7 @@ use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
use clippy_utils::{get_attr, is_lint_allowed};
use itertools::Itertools;
use rustc_ast::Mutability;
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::{Applicability, Diag};
use rustc_hir::intravisit::{Visitor, walk_expr};
use rustc_hir::{Arm, Expr, ExprKind, MatchSource};
@ -475,19 +476,19 @@ impl<'tcx> Visitor<'tcx> for SigDropHelper<'_, 'tcx> {
struct ArmSigDropHelper<'a, 'tcx> {
sig_drop_checker: SigDropChecker<'a, 'tcx>,
found_sig_drop_spans: FxHashSet<Span>,
found_sig_drop_spans: FxIndexSet<Span>,
}
impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
ArmSigDropHelper {
sig_drop_checker: SigDropChecker::new(cx),
found_sig_drop_spans: FxHashSet::<Span>::default(),
found_sig_drop_spans: FxIndexSet::<Span>::default(),
}
}
}
fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &[&'tcx Expr<'_>]) -> FxHashSet<Span> {
fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &[&'tcx Expr<'_>]) -> FxIndexSet<Span> {
let mut helper = ArmSigDropHelper::new(cx);
for arm in arms {
helper.visit_expr(arm);

View file

@ -8,7 +8,7 @@ use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{eq_expr_value, hash_expr, higher};
use rustc_ast::{LitKind, RangeLimits};
use rustc_data_structures::packed::Pu128;
use rustc_data_structures::unhash::UnhashMap;
use rustc_data_structures::unhash::UnindexMap;
use rustc_errors::{Applicability, Diag};
use rustc_hir::{BinOp, Block, Body, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
@ -226,7 +226,7 @@ fn upper_index_expr(expr: &Expr<'_>) -> Option<usize> {
}
/// Checks if the expression is an index into a slice and adds it to `indexes`
fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) {
fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap<u64, Vec<IndexEntry<'hir>>>) {
if let ExprKind::Index(slice, index_lit, _) = expr.kind
&& cx.typeck_results().expr_ty_adjusted(slice).peel_refs().is_slice()
&& let Some(index) = upper_index_expr(index_lit)
@ -274,7 +274,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Unh
}
/// Checks if the expression is an `assert!` expression and adds it to `asserts`
fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnhashMap<u64, Vec<IndexEntry<'hir>>>) {
fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap<u64, Vec<IndexEntry<'hir>>>) {
if let Some((comparison, asserted_len, slice)) = assert_len_expr(cx, expr) {
let hash = hash_expr(cx, slice);
let indexes = map.entry(hash).or_default();
@ -311,7 +311,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
/// Inspects indexes and reports lints.
///
/// Called at the end of this lint after all indexing and `assert!` expressions have been collected.
fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap<u64, Vec<IndexEntry<'_>>>) {
fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_>>>) {
for bucket in map.values() {
for entry in bucket {
let Some(full_span) = entry
@ -403,7 +403,7 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnhashMap<u64, Vec<IndexEntry<'_>>
impl LateLintPass<'_> for MissingAssertsForIndexing {
fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
let mut map = UnhashMap::default();
let mut map = UnindexMap::default();
for_each_expr_without_closures(body.value, |expr| {
check_index(cx, expr, &mut map);

View file

@ -193,8 +193,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
| hir::ItemKind::Trait(..)
| hir::ItemKind::TraitAlias(..)
| hir::ItemKind::TyAlias(..)
| hir::ItemKind::Union(..)
| hir::ItemKind::OpaqueTy(..) => {},
| hir::ItemKind::Union(..) => {}
hir::ItemKind::ExternCrate(..)
| hir::ItemKind::ForeignMod { .. }
| hir::ItemKind::GlobalAsm(..)

View file

@ -130,7 +130,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline {
| hir::ItemKind::GlobalAsm(..)
| hir::ItemKind::TyAlias(..)
| hir::ItemKind::Union(..)
| hir::ItemKind::OpaqueTy(..)
| hir::ItemKind::ExternCrate(..)
| hir::ItemKind::ForeignMod { .. }
| hir::ItemKind::Impl { .. }

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LOCAL_CRATE;
@ -87,7 +87,7 @@ impl EarlyLintPass for ModStyle {
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
// `[path, to]` but not foo
let mut folder_segments = FxHashSet::default();
let mut folder_segments = FxIndexSet::default();
// `mod_folders` is all the unique folder names that contain a mod.rs file
let mut mod_folders = FxHashSet::default();
// `file_map` maps file names to the full path including the file name
@ -144,7 +144,7 @@ impl EarlyLintPass for ModStyle {
/// is `mod.rs` we add it's parent folder to `mod_folders`.
fn process_paths_for_mod_files<'a>(
path: &'a Path,
folder_segments: &mut FxHashSet<&'a OsStr>,
folder_segments: &mut FxIndexSet<&'a OsStr>,
mod_folders: &mut FxHashSet<&'a OsStr>,
) {
let mut comp = path.components().rev().peekable();

View file

@ -5,7 +5,7 @@ use clippy_utils::source::snippet;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{inherits_cfg, is_from_proc_macro, is_self};
use core::ops::ControlFlow;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
@ -101,7 +101,7 @@ fn check_closures<'tcx>(
ctx: &mut MutablyUsedVariablesCtxt<'tcx>,
cx: &LateContext<'tcx>,
checked_closures: &mut FxHashSet<LocalDefId>,
closures: FxHashSet<LocalDefId>,
closures: FxIndexSet<LocalDefId>,
) {
let hir = cx.tcx.hir();
for closure in closures {
@ -196,7 +196,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
prev_bind: None,
prev_move_to_closure: HirIdSet::default(),
aliases: HirIdMap::default(),
async_closures: FxHashSet::default(),
async_closures: FxIndexSet::default(),
tcx: cx.tcx,
};
euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx)
@ -207,7 +207,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
// We retrieve all the closures declared in the function because they will not be found
// by `euv::Delegate`.
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
let mut closures: FxIndexSet<LocalDefId> = FxIndexSet::default();
for_each_expr(cx, body, |expr| {
if let ExprKind::Closure(closure) = expr.kind {
closures.insert(closure.def_id);
@ -307,7 +307,7 @@ struct MutablyUsedVariablesCtxt<'tcx> {
/// use of a variable.
prev_move_to_closure: HirIdSet,
aliases: HirIdMap<HirId>,
async_closures: FxHashSet<LocalDefId>,
async_closures: FxIndexSet<LocalDefId>,
tcx: TyCtxt<'tcx>,
}

View file

@ -7,7 +7,7 @@ use clippy_utils::{
path_to_local_id, span_contains_cfg, span_find_starting_semi,
};
use core::ops::ControlFlow;
use rustc_ast::NestedMetaItem;
use rustc_ast::MetaItemInner;
use rustc_errors::Applicability;
use rustc_hir::LangItem::ResultErr;
use rustc_hir::intravisit::FnKind;
@ -407,7 +407,7 @@ fn check_final_expr<'tcx>(
}
}
if ret_span.from_expansion() {
if ret_span.from_expansion() || is_from_proc_macro(cx, expr) {
return;
}
@ -421,7 +421,7 @@ fn check_final_expr<'tcx>(
if matches!(Level::from_attr(attr), Some(Level::Expect(_)))
&& let metas = attr.meta_item_list()
&& let Some(lst) = metas
&& let [NestedMetaItem::MetaItem(meta_item), ..] = lst.as_slice()
&& let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice()
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
&& tool.ident.name == sym::clippy
&& matches!(

View file

@ -6,9 +6,9 @@ use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{can_mut_borrow_both, eq_expr_value, is_in_const_context, std_or_core};
use itertools::Itertools;
use rustc_data_structures::fx::FxIndexSet;
use rustc_hir::intravisit::{Visitor, walk_expr};
use crate::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, LetStmt, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -334,7 +334,7 @@ struct IndexBinding<'a, 'tcx> {
impl<'tcx> IndexBinding<'_, 'tcx> {
fn snippet_index_bindings(&mut self, exprs: &[&'tcx Expr<'tcx>]) -> String {
let mut bindings = FxHashSet::default();
let mut bindings = FxIndexSet::default();
for expr in exprs {
bindings.insert(self.snippet_index_binding(expr));
}

View file

@ -5,7 +5,7 @@ use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability};
use clippy_utils::{SpanlessEq, SpanlessHash, is_from_proc_macro};
use core::hash::{Hash, Hasher};
use itertools::Itertools;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, IndexEntry};
use rustc_data_structures::unhash::UnhashMap;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
@ -16,7 +16,6 @@ use rustc_hir::{
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{BytePos, Span};
use std::collections::hash_map::Entry;
declare_clippy_lint! {
/// ### What it does
@ -427,7 +426,7 @@ fn rollup_traits(
bounds: &[GenericBound<'_>],
msg: &'static str,
) -> Vec<(ComparableTraitRef, Span)> {
let mut map = FxHashMap::default();
let mut map = FxIndexMap::default();
let mut repeated_res = false;
let only_comparable_trait_refs = |bound: &GenericBound<'_>| {
@ -442,8 +441,8 @@ fn rollup_traits(
for bound in bounds.iter().filter_map(only_comparable_trait_refs) {
let (comparable_bound, span_direct) = bound;
match map.entry(comparable_bound) {
Entry::Occupied(_) => repeated_res = true,
Entry::Vacant(e) => {
IndexEntry::Occupied(_) => repeated_res = true,
IndexEntry::Vacant(e) => {
e.insert((span_direct, i));
i += 1;
},

View file

@ -85,10 +85,6 @@ const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
if matches!(item.kind, ItemKind::OpaqueTy(_)) {
// skip over `ItemKind::OpaqueTy` in order to lint `foo() -> impl <..>`
return;
}
// We push the self types of `impl`s on a stack here. Only the top type on the stack is
// relevant for linting, since this is the self type of the `impl` we're currently in. To
// avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that
@ -130,10 +126,8 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
self.stack.push(stack_item);
}
fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
if !matches!(item.kind, ItemKind::OpaqueTy(_)) {
self.stack.pop();
}
fn check_item_post(&mut self, _: &LateContext<'_>, _: &Item<'_>) {
self.stack.pop();
}
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {

View file

@ -141,62 +141,89 @@ fn path_search_pat(path: &Path<'_>) -> (Pat, Pat) {
/// Get the search patterns to use for the given expression
fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
match e.kind {
ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
// Parenthesis are trimmed from the text before the search patterns are matched.
// See: `span_matches_pat`
ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat(tcx, e).1),
ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat(tcx, e).1),
ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat(tcx, e).1),
ExprKind::Lit(lit) => lit_search_pat(&lit.node),
ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => (expr_search_pat(tcx, e).0, Pat::Str("(")),
ExprKind::Call(first, [.., last])
| ExprKind::MethodCall(_, first, [.., last], _)
| ExprKind::Binary(_, first, last)
| ExprKind::Tup([first, .., last])
| ExprKind::Assign(first, last, _)
| ExprKind::AssignOp(_, first, last) => (expr_search_pat(tcx, first).0, expr_search_pat(tcx, last).1),
ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat(tcx, e),
ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("")),
ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat(tcx, let_expr.init).1),
ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
(Pat::Str("for"), Pat::Str("}"))
},
ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
(expr_search_pat(tcx, e).0, Pat::Str("await"))
},
ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, tcx.hir().body(body).value).1),
ExprKind::Block(
Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
..
fn expr_search_pat_inner(tcx: TyCtxt<'_>, e: &Expr<'_>, outer_span: Span) -> (Pat, Pat) {
// The expression can have subexpressions in different contexts, in which case
// building up a search pattern from the macro expansion would lead to false positives;
// e.g. `return format!(..)` would be considered to be from a proc macro
// if we build up a pattern for the macro expansion and compare it to the invocation `format!()`.
// So instead we return an empty pattern such that `span_matches_pat` always returns true.
if !e.span.eq_ctxt(outer_span) {
return (Pat::Str(""), Pat::Str(""));
}
match e.kind {
ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
// Parenthesis are trimmed from the text before the search patterns are matched.
// See: `span_matches_pat`
ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat_inner(tcx, e, outer_span).1),
ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat_inner(tcx, e, outer_span).1),
ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat_inner(tcx, e, outer_span).1),
ExprKind::Lit(lit) => lit_search_pat(&lit.node),
ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => {
(expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("("))
},
None,
) => (Pat::Str("unsafe"), Pat::Str("}")),
ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)),
ExprKind::Index(e, _, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")),
ExprKind::Path(ref path) => qpath_search_pat(path),
ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1),
ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
ExprKind::Break(Destination { label: Some(name), .. }, None) => (Pat::Str("break"), Pat::Sym(name.ident.name)),
ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat(tcx, e).1),
ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
ExprKind::Continue(Destination { label: Some(name), .. }) => (Pat::Str("continue"), Pat::Sym(name.ident.name)),
ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat(tcx, e).1),
ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat(tcx, e).1),
_ => (Pat::Str(""), Pat::Str("")),
ExprKind::Call(first, [.., last])
| ExprKind::MethodCall(_, first, [.., last], _)
| ExprKind::Binary(_, first, last)
| ExprKind::Tup([first, .., last])
| ExprKind::Assign(first, last, _)
| ExprKind::AssignOp(_, first, last) => (
expr_search_pat_inner(tcx, first, outer_span).0,
expr_search_pat_inner(tcx, last, outer_span).1,
),
ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat_inner(tcx, e, outer_span),
ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("")),
ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat_inner(tcx, let_expr.init, outer_span).1),
ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
(Pat::Str("for"), Pat::Str("}"))
},
ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => {
(expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("?"))
},
ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
(expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("await"))
},
ExprKind::Closure(&Closure { body, .. }) => (
Pat::Str(""),
expr_search_pat_inner(tcx, tcx.hir().body(body).value, outer_span).1,
),
ExprKind::Block(
Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
..
},
None,
) => (Pat::Str("unsafe"), Pat::Str("}")),
ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
ExprKind::Field(e, name) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Sym(name.name)),
ExprKind::Index(e, _, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("]")),
ExprKind::Path(ref path) => qpath_search_pat(path),
ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat_inner(tcx, e, outer_span).1),
ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
ExprKind::Break(Destination { label: Some(name), .. }, None) => {
(Pat::Str("break"), Pat::Sym(name.ident.name))
},
ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat_inner(tcx, e, outer_span).1),
ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
ExprKind::Continue(Destination { label: Some(name), .. }) => {
(Pat::Str("continue"), Pat::Sym(name.ident.name))
},
ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat_inner(tcx, e, outer_span).1),
ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat_inner(tcx, e, outer_span).1),
_ => (Pat::Str(""), Pat::Str("")),
}
}
expr_search_pat_inner(tcx, e, e.span)
}
fn fn_header_search_pat(header: FnHeader) -> Pat {
@ -220,7 +247,7 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
ItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
ItemKind::TyAlias(..) | ItemKind::OpaqueTy(_) => (Pat::Str("type"), Pat::Str(";")),
ItemKind::TyAlias(..) => (Pat::Str("type"), Pat::Str(";")),
ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
ItemKind::Struct(VariantData::Struct { .. }, _) => (Pat::Str("struct"), Pat::Str("}")),
ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),

View file

@ -1,6 +1,5 @@
#![feature(array_chunks)]
#![feature(box_patterns)]
#![feature(control_flow_enum)]
#![feature(f128)]
#![feature(f16)]
#![feature(if_let_guard)]

View file

@ -287,6 +287,7 @@ impl SourceFileRange {
self.sf
.src
.as_ref()
.map(|src| src.as_str())
.or_else(|| self.sf.external_src.get().and_then(|src| src.get_source()))
.and_then(|x| x.get(self.range.clone()))
}

View file

@ -86,6 +86,7 @@ mod issue9612 {
util();
}
#[allow(unconditional_panic)]
fn util() {
let _a: u8 = 4.try_into().unwrap();
let _a: u8 = 5.try_into().expect("");

View file

@ -86,6 +86,7 @@ mod issue9612 {
util();
}
#[allow(unconditional_panic)]
fn util() {
let _a: u8 = 4.try_into().unwrap();
let _a: u8 = 5.try_into().expect("");

View file

@ -274,7 +274,7 @@ LL | let _ = &boxed_slice[1];
| ~~~~~~~~~~~~~~~
error: called `.get().unwrap()` on a slice
--> tests/ui-toml/unwrap_used/unwrap_used.rs:93:17
--> tests/ui-toml/unwrap_used/unwrap_used.rs:94:17
|
LL | let _ = Box::new([0]).get(1).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,10 +0,0 @@
#![allow(incomplete_features)]
#![feature(unnamed_fields)]
#[repr(C)]
struct Foo {
_: struct {
},
}
fn main() {}

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
#![allow(clippy::unnecessary_operation, clippy::no_effect)]
#![allow(clippy::unnecessary_operation, clippy::no_effect, clippy::unit_arg)]
fn foo(n: u32) -> u32 {
if let Some(n) = n.checked_sub(4) { n } else { n }

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
#![allow(clippy::unnecessary_operation, clippy::no_effect)]
#![allow(clippy::unnecessary_operation, clippy::no_effect, clippy::unit_arg)]
fn foo(n: u32) -> u32 {
if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }

View file

@ -3,7 +3,7 @@
#![allow(dead_code, incomplete_features)]
#![warn(clippy::doc_markdown)]
#![feature(custom_inner_attributes, generic_const_exprs, const_option)]
#![feature(custom_inner_attributes, generic_const_exprs)]
#![rustfmt::skip]
/// The `foo_bar` function does _nothing_. See also `foo::bar`. (note the dot there)

View file

@ -3,7 +3,7 @@
#![allow(dead_code, incomplete_features)]
#![warn(clippy::doc_markdown)]
#![feature(custom_inner_attributes, generic_const_exprs, const_option)]
#![feature(custom_inner_attributes, generic_const_exprs)]
#![rustfmt::skip]
/// The foo_bar function does _nothing_. See also foo::bar. (note the dot there)

View file

@ -70,6 +70,7 @@ fn main() {
mod issue9909 {
#![allow(clippy::identity_op, clippy::unwrap_used, dead_code)]
#[allow(unconditional_panic)]
fn reduced() {
let f = &[1, 2, 3];

View file

@ -70,6 +70,7 @@ fn main() {
mod issue9909 {
#![allow(clippy::identity_op, clippy::unwrap_used, dead_code)]
#[allow(unconditional_panic)]
fn reduced() {
let f = &[1, 2, 3];

View file

@ -266,7 +266,7 @@ LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec();
= help: consider using `expect()` to provide a better panic message
error: called `.get().unwrap()` on a slice
--> tests/ui/get_unwrap.rs:77:24
--> tests/ui/get_unwrap.rs:78:24
|
LL | let _x: &i32 = f.get(1 + 2).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^
@ -277,7 +277,7 @@ LL | let _x: &i32 = &f[1 + 2];
| ~~~~~~~~~
error: called `.get().unwrap()` on a slice
--> tests/ui/get_unwrap.rs:80:18
--> tests/ui/get_unwrap.rs:81:18
|
LL | let _x = f.get(1 + 2).unwrap().to_string();
| ^^^^^^^^^^^^^^^^^^^^^
@ -288,7 +288,7 @@ LL | let _x = f[1 + 2].to_string();
| ~~~~~~~~
error: called `.get().unwrap()` on a slice
--> tests/ui/get_unwrap.rs:83:18
--> tests/ui/get_unwrap.rs:84:18
|
LL | let _x = f.get(1 + 2).unwrap().abs();
| ^^^^^^^^^^^^^^^^^^^^^
@ -299,7 +299,7 @@ LL | let _x = f[1 + 2].abs();
| ~~~~~~~~
error: called `.get_mut().unwrap()` on a slice
--> tests/ui/get_unwrap.rs:100:33
--> tests/ui/get_unwrap.rs:101:33
|
LL | let b = rest.get_mut(linidx(j, k) - linidx(i, k) - 1).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,3 +1,4 @@
//@aux-build:proc_macros.rs
#![feature(yeet_expr)]
#![allow(unused)]
#![allow(
@ -9,6 +10,9 @@
)]
#![warn(clippy::needless_return)]
extern crate proc_macros;
use proc_macros::with_span;
use std::cell::RefCell;
macro_rules! the_answer {
@ -359,6 +363,10 @@ fn issue12907() -> String {
"".split("").next().unwrap().to_string()
}
fn issue13458() {
with_span!(span return);
}
fn main() {}
fn a(x: Option<u8>) -> Option<u8> {

View file

@ -1,3 +1,4 @@
//@aux-build:proc_macros.rs
#![feature(yeet_expr)]
#![allow(unused)]
#![allow(
@ -9,6 +10,9 @@
)]
#![warn(clippy::needless_return)]
extern crate proc_macros;
use proc_macros::with_span;
use std::cell::RefCell;
macro_rules! the_answer {
@ -369,6 +373,10 @@ fn issue12907() -> String {
return "".split("").next().unwrap().to_string();
}
fn issue13458() {
with_span!(span return);
}
fn main() {}
fn a(x: Option<u8>) -> Option<u8> {

View file

@ -1,5 +1,5 @@
error: unneeded `return` statement
--> tests/ui/needless_return.rs:25:5
--> tests/ui/needless_return.rs:29:5
|
LL | return true;
| ^^^^^^^^^^^
@ -13,7 +13,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:29:5
--> tests/ui/needless_return.rs:33:5
|
LL | return true;
| ^^^^^^^^^^^
@ -25,7 +25,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:34:5
--> tests/ui/needless_return.rs:38:5
|
LL | return true;;;
| ^^^^^^^^^^^
@ -37,7 +37,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:39:5
--> tests/ui/needless_return.rs:43:5
|
LL | return true;; ; ;
| ^^^^^^^^^^^
@ -49,7 +49,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:44:9
--> tests/ui/needless_return.rs:48:9
|
LL | return true;
| ^^^^^^^^^^^
@ -61,7 +61,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:46:9
--> tests/ui/needless_return.rs:50:9
|
LL | return false;
| ^^^^^^^^^^^^
@ -73,7 +73,7 @@ LL + false
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:52:17
--> tests/ui/needless_return.rs:56:17
|
LL | true => return false,
| ^^^^^^^^^^^^
@ -84,7 +84,7 @@ LL | true => false,
| ~~~~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:54:13
--> tests/ui/needless_return.rs:58:13
|
LL | return true;
| ^^^^^^^^^^^
@ -96,7 +96,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:61:9
--> tests/ui/needless_return.rs:65:9
|
LL | return true;
| ^^^^^^^^^^^
@ -108,7 +108,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:63:16
--> tests/ui/needless_return.rs:67:16
|
LL | let _ = || return true;
| ^^^^^^^^^^^
@ -119,7 +119,7 @@ LL | let _ = || true;
| ~~~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:67:5
--> tests/ui/needless_return.rs:71:5
|
LL | return the_answer!();
| ^^^^^^^^^^^^^^^^^^^^
@ -131,7 +131,7 @@ LL + the_answer!()
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:70:21
--> tests/ui/needless_return.rs:74:21
|
LL | fn test_void_fun() {
| _____________________^
@ -146,7 +146,7 @@ LL + fn test_void_fun() {
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:75:11
--> tests/ui/needless_return.rs:79:11
|
LL | if b {
| ___________^
@ -161,7 +161,7 @@ LL + if b {
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:77:13
--> tests/ui/needless_return.rs:81:13
|
LL | } else {
| _____________^
@ -176,7 +176,7 @@ LL + } else {
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:85:14
--> tests/ui/needless_return.rs:89:14
|
LL | _ => return,
| ^^^^^^
@ -187,7 +187,7 @@ LL | _ => (),
| ~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:93:24
--> tests/ui/needless_return.rs:97:24
|
LL | let _ = 42;
| ________________________^
@ -202,7 +202,7 @@ LL + let _ = 42;
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:96:14
--> tests/ui/needless_return.rs:100:14
|
LL | _ => return,
| ^^^^^^
@ -213,7 +213,7 @@ LL | _ => (),
| ~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:109:9
--> tests/ui/needless_return.rs:113:9
|
LL | return String::from("test");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -225,7 +225,7 @@ LL + String::from("test")
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:111:9
--> tests/ui/needless_return.rs:115:9
|
LL | return String::new();
| ^^^^^^^^^^^^^^^^^^^^
@ -237,7 +237,7 @@ LL + String::new()
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:133:32
--> tests/ui/needless_return.rs:137:32
|
LL | bar.unwrap_or_else(|_| return)
| ^^^^^^
@ -248,7 +248,7 @@ LL | bar.unwrap_or_else(|_| {})
| ~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:137:21
--> tests/ui/needless_return.rs:141:21
|
LL | let _ = || {
| _____________________^
@ -263,7 +263,7 @@ LL + let _ = || {
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:140:20
--> tests/ui/needless_return.rs:144:20
|
LL | let _ = || return;
| ^^^^^^
@ -274,7 +274,7 @@ LL | let _ = || {};
| ~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:146:32
--> tests/ui/needless_return.rs:150:32
|
LL | res.unwrap_or_else(|_| return Foo)
| ^^^^^^^^^^
@ -284,18 +284,6 @@ help: remove `return`
LL | res.unwrap_or_else(|_| Foo)
| ~~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:155:5
|
LL | return true;
| ^^^^^^^^^^^
|
help: remove `return`
|
LL - return true;
LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:159:5
|
@ -309,7 +297,19 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:164:9
--> tests/ui/needless_return.rs:163:5
|
LL | return true;
| ^^^^^^^^^^^
|
help: remove `return`
|
LL - return true;
LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:168:9
|
LL | return true;
| ^^^^^^^^^^^
@ -321,7 +321,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:166:9
--> tests/ui/needless_return.rs:170:9
|
LL | return false;
| ^^^^^^^^^^^^
@ -333,7 +333,7 @@ LL + false
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:172:17
--> tests/ui/needless_return.rs:176:17
|
LL | true => return false,
| ^^^^^^^^^^^^
@ -344,7 +344,7 @@ LL | true => false,
| ~~~~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:174:13
--> tests/ui/needless_return.rs:178:13
|
LL | return true;
| ^^^^^^^^^^^
@ -356,7 +356,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:181:9
--> tests/ui/needless_return.rs:185:9
|
LL | return true;
| ^^^^^^^^^^^
@ -368,7 +368,7 @@ LL + true
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:183:16
--> tests/ui/needless_return.rs:187:16
|
LL | let _ = || return true;
| ^^^^^^^^^^^
@ -379,7 +379,7 @@ LL | let _ = || true;
| ~~~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:187:5
--> tests/ui/needless_return.rs:191:5
|
LL | return the_answer!();
| ^^^^^^^^^^^^^^^^^^^^
@ -391,7 +391,7 @@ LL + the_answer!()
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:190:33
--> tests/ui/needless_return.rs:194:33
|
LL | async fn async_test_void_fun() {
| _________________________________^
@ -406,7 +406,7 @@ LL + async fn async_test_void_fun() {
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:195:11
--> tests/ui/needless_return.rs:199:11
|
LL | if b {
| ___________^
@ -421,7 +421,7 @@ LL + if b {
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:197:13
--> tests/ui/needless_return.rs:201:13
|
LL | } else {
| _____________^
@ -436,7 +436,7 @@ LL + } else {
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:205:14
--> tests/ui/needless_return.rs:209:14
|
LL | _ => return,
| ^^^^^^
@ -447,7 +447,7 @@ LL | _ => (),
| ~~
error: unneeded `return` statement
--> tests/ui/needless_return.rs:218:9
--> tests/ui/needless_return.rs:222:9
|
LL | return String::from("test");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -459,7 +459,7 @@ LL + String::from("test")
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:220:9
--> tests/ui/needless_return.rs:224:9
|
LL | return String::new();
| ^^^^^^^^^^^^^^^^^^^^
@ -471,7 +471,7 @@ LL + String::new()
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:236:5
--> tests/ui/needless_return.rs:240:5
|
LL | return format!("Hello {}", "world!");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -483,7 +483,7 @@ LL + format!("Hello {}", "world!")
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:277:9
--> tests/ui/needless_return.rs:281:9
|
LL | return true;
| ^^^^^^^^^^^
@ -497,7 +497,7 @@ LL ~ }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:279:9
--> tests/ui/needless_return.rs:283:9
|
LL | return false;
| ^^^^^^^^^^^^
@ -509,7 +509,7 @@ LL ~ }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:286:13
--> tests/ui/needless_return.rs:290:13
|
LL | return 10;
| ^^^^^^^^^
@ -524,7 +524,7 @@ LL ~ }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:289:13
--> tests/ui/needless_return.rs:293:13
|
LL | return 100;
| ^^^^^^^^^^
@ -537,7 +537,7 @@ LL ~ }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:297:9
--> tests/ui/needless_return.rs:301:9
|
LL | return 0;
| ^^^^^^^^
@ -549,7 +549,7 @@ LL ~ }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:304:13
--> tests/ui/needless_return.rs:308:13
|
LL | return *(x as *const isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -564,7 +564,7 @@ LL ~ }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:306:13
--> tests/ui/needless_return.rs:310:13
|
LL | return !*(x as *const isize);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -577,7 +577,7 @@ LL ~ }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:313:20
--> tests/ui/needless_return.rs:317:20
|
LL | let _ = 42;
| ____________________^
@ -594,7 +594,7 @@ LL + let _ = 42;
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:320:20
--> tests/ui/needless_return.rs:324:20
|
LL | let _ = 42; return;
| ^^^^^^^
@ -606,7 +606,7 @@ LL + let _ = 42;
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:332:9
--> tests/ui/needless_return.rs:336:9
|
LL | return Ok(format!("ok!"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^
@ -618,7 +618,7 @@ LL + Ok(format!("ok!"))
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:334:9
--> tests/ui/needless_return.rs:338:9
|
LL | return Err(format!("err!"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -630,7 +630,7 @@ LL + Err(format!("err!"))
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:340:9
--> tests/ui/needless_return.rs:344:9
|
LL | return if true { 1 } else { 2 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -642,7 +642,7 @@ LL + if true { 1 } else { 2 }
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:344:9
--> tests/ui/needless_return.rs:348:9
|
LL | return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -654,7 +654,7 @@ LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:365:5
--> tests/ui/needless_return.rs:369:5
|
LL | return { "a".to_string() } + "b" + { "c" };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -666,7 +666,7 @@ LL + ({ "a".to_string() } + "b" + { "c" })
|
error: unneeded `return` statement
--> tests/ui/needless_return.rs:369:5
--> tests/ui/needless_return.rs:373:5
|
LL | return "".split("").next().unwrap().to_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,7 +1,7 @@
#![warn(clippy::transmute_float_to_int)]
#![allow(clippy::missing_transmute_annotations)]
#![feature(f128, f128_const)]
#![feature(f16, f16_const)]
#![feature(f128)]
#![feature(f16)]
fn float_to_int() {
let _: u32 = unsafe { 1f32.to_bits() };

View file

@ -1,7 +1,7 @@
#![warn(clippy::transmute_float_to_int)]
#![allow(clippy::missing_transmute_annotations)]
#![feature(f128, f128_const)]
#![feature(f16, f16_const)]
#![feature(f128)]
#![feature(f16)]
fn float_to_int() {
let _: u32 = unsafe { std::mem::transmute(1f32) };

View file

@ -84,8 +84,11 @@ fn issue_10449() {
}
// Pointers cannot be cast to integers in const contexts
#[allow(ptr_to_integer_transmute_in_consts, reason = "This is tested in the compiler test suite")]
const fn issue_12402<P>(ptr: *const P) {
unsafe { transmute::<*const i32, usize>(&42i32) };
unsafe { transmute::<fn(*const P), usize>(issue_12402) };
let _ = unsafe { transmute::<_, usize>(ptr) };
// This test exists even though the compiler lints against it
// to test that clippy's transmute lints do not trigger on this.
unsafe { std::mem::transmute::<*const i32, usize>(&42i32) };
unsafe { std::mem::transmute::<fn(*const P), usize>(issue_12402) };
let _ = unsafe { std::mem::transmute::<_, usize>(ptr) };
}

View file

@ -84,8 +84,11 @@ fn issue_10449() {
}
// Pointers cannot be cast to integers in const contexts
#[allow(ptr_to_integer_transmute_in_consts, reason = "This is tested in the compiler test suite")]
const fn issue_12402<P>(ptr: *const P) {
unsafe { transmute::<*const i32, usize>(&42i32) };
unsafe { transmute::<fn(*const P), usize>(issue_12402) };
let _ = unsafe { transmute::<_, usize>(ptr) };
// This test exists even though the compiler lints against it
// to test that clippy's transmute lints do not trigger on this.
unsafe { std::mem::transmute::<*const i32, usize>(&42i32) };
unsafe { std::mem::transmute::<fn(*const P), usize>(issue_12402) };
let _ = unsafe { std::mem::transmute::<_, usize>(ptr) };
}

View file

@ -42,11 +42,14 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"ignore-cdb",
"ignore-compare-mode-next-solver",
"ignore-compare-mode-polonius",
"ignore-coverage-map",
"ignore-coverage-run",
"ignore-cross-compile",
"ignore-debug",
"ignore-eabi",
"ignore-emscripten",
"ignore-endian-big",
"ignore-enzyme",
"ignore-freebsd",
"ignore-fuchsia",
"ignore-gdb",
@ -64,23 +67,6 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"ignore-loongarch64",
"ignore-macabi",
"ignore-macos",
"ignore-mode-assembly",
"ignore-mode-codegen",
"ignore-mode-codegen-units",
"ignore-mode-coverage-map",
"ignore-mode-coverage-run",
"ignore-mode-crashes",
"ignore-mode-debuginfo",
"ignore-mode-incremental",
"ignore-mode-js-doc-test",
"ignore-mode-mir-opt",
"ignore-mode-pretty",
"ignore-mode-run-make",
"ignore-mode-run-pass-valgrind",
"ignore-mode-rustdoc",
"ignore-mode-rustdoc-json",
"ignore-mode-ui",
"ignore-mode-ui-fulldeps",
"ignore-msp430",
"ignore-msvc",
"ignore-musl",
@ -144,7 +130,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"needs-git-hash",
"needs-llvm-components",
"needs-llvm-zstd",
"needs-profiler-support",
"needs-profiler-runtime",
"needs-relocation-model-pic",
"needs-run-enabled",
"needs-rust-lld",
@ -223,6 +209,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
"pretty-compare-only",
"pretty-expanded",
"pretty-mode",
"reference",
"regex-error-pattern",
"remap-src-base",
"revisions",

View file

@ -53,7 +53,6 @@ macro_rules! string_enum {
string_enum! {
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Mode {
RunPassValgrind => "run-pass-valgrind",
Pretty => "pretty",
DebugInfo => "debuginfo",
Codegen => "codegen",
@ -207,13 +206,6 @@ pub struct Config {
/// Path to LLVM's bin directory.
pub llvm_bin_dir: Option<PathBuf>,
/// The valgrind path.
pub valgrind_path: Option<String>,
/// Whether to fail if we can't run run-pass-valgrind tests under valgrind
/// (or, alternatively, to silently run them like regular run-pass tests).
pub force_valgrind: bool,
/// The path to the Clang executable to run Clang-based tests with. If
/// `None` then these tests will be ignored.
pub run_clang_based_tests_with: Option<String>,
@ -393,8 +385,8 @@ pub struct Config {
pub git_merge_commit_email: String,
/// True if the profiler runtime is enabled for this target.
/// Used by the "needs-profiler-support" header in test files.
pub profiler_support: bool,
/// Used by the "needs-profiler-runtime" directive in test files.
pub profiler_runtime: bool,
}
impl Config {

View file

@ -0,0 +1,272 @@
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use crate::common::{Config, Debugger};
pub(crate) fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
config.cdb.as_ref()?;
Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() }))
}
pub(crate) fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
config.gdb_version?;
if config.matches_env("msvc") {
return None;
}
if config.remote_test_client.is_some() && !config.target.contains("android") {
println!(
"WARNING: debuginfo tests are not available when \
testing with remote"
);
return None;
}
if config.target.contains("android") {
println!(
"{} debug-info test uses tcp 5039 port.\
please reserve it",
config.target
);
// android debug-info test uses remote debugger so, we test 1 thread
// at once as they're all sharing the same TCP port to communicate
// over.
//
// we should figure out how to lift this restriction! (run them all
// on different ports allocated dynamically).
env::set_var("RUST_TEST_THREADS", "1");
}
Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
}
pub(crate) fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
config.lldb_python_dir.as_ref()?;
if let Some(350) = config.lldb_version {
println!(
"WARNING: The used version of LLDB (350) has a \
known issue that breaks debuginfo tests. See \
issue #32520 for more information. Skipping all \
LLDB-based tests!",
);
return None;
}
Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
}
/// Returns `true` if the given target is an Android target for the
/// purposes of GDB testing.
pub(crate) fn is_android_gdb_target(target: &str) -> bool {
matches!(
&target[..],
"arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
)
}
/// Returns `true` if the given target is a MSVC target for the purposes of CDB testing.
fn is_pc_windows_msvc_target(target: &str) -> bool {
target.ends_with("-pc-windows-msvc")
}
fn find_cdb(target: &str) -> Option<OsString> {
if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
return None;
}
let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
let cdb_arch = if cfg!(target_arch = "x86") {
"x86"
} else if cfg!(target_arch = "x86_64") {
"x64"
} else if cfg!(target_arch = "aarch64") {
"arm64"
} else if cfg!(target_arch = "arm") {
"arm"
} else {
return None; // No compatible CDB.exe in the Windows 10 SDK
};
let mut path = PathBuf::new();
path.push(pf86);
path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
path.push(cdb_arch);
path.push(r"cdb.exe");
if !path.exists() {
return None;
}
Some(path.into_os_string())
}
/// Returns Path to CDB
pub(crate) fn analyze_cdb(
cdb: Option<String>,
target: &str,
) -> (Option<OsString>, Option<[u16; 4]>) {
let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
let mut version = None;
if let Some(cdb) = cdb.as_ref() {
if let Ok(output) = Command::new(cdb).arg("/version").output() {
if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
version = extract_cdb_version(&first_line);
}
}
}
(cdb, version)
}
pub(crate) fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
// Example full_version_line: "cdb version 10.0.18362.1"
let version = full_version_line.rsplit(' ').next()?;
let mut components = version.split('.');
let major: u16 = components.next().unwrap().parse().unwrap();
let minor: u16 = components.next().unwrap().parse().unwrap();
let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
let build: u16 = components.next().unwrap_or("0").parse().unwrap();
Some([major, minor, patch, build])
}
/// Returns (Path to GDB, GDB Version)
pub(crate) fn analyze_gdb(
gdb: Option<String>,
target: &str,
android_cross_path: &PathBuf,
) -> (Option<String>, Option<u32>) {
#[cfg(not(windows))]
const GDB_FALLBACK: &str = "gdb";
#[cfg(windows)]
const GDB_FALLBACK: &str = "gdb.exe";
let fallback_gdb = || {
if is_android_gdb_target(target) {
let mut gdb_path = match android_cross_path.to_str() {
Some(x) => x.to_owned(),
None => panic!("cannot find android cross path"),
};
gdb_path.push_str("/bin/gdb");
gdb_path
} else {
GDB_FALLBACK.to_owned()
}
};
let gdb = match gdb {
None => fallback_gdb(),
Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
Some(ref s) => s.to_owned(),
};
let mut version_line = None;
if let Ok(output) = Command::new(&gdb).arg("--version").output() {
if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
version_line = Some(first_line.to_string());
}
}
let version = match version_line {
Some(line) => extract_gdb_version(&line),
None => return (None, None),
};
(Some(gdb), version)
}
pub(crate) fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
let full_version_line = full_version_line.trim();
// GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
// of the ? sections being optional
// We will parse up to 3 digits for each component, ignoring the date
// We skip text in parentheses. This avoids accidentally parsing
// the openSUSE version, which looks like:
// GNU gdb (GDB; openSUSE Leap 15.0) 8.1
// This particular form is documented in the GNU coding standards:
// https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
let unbracketed_part = full_version_line.split('[').next().unwrap();
let mut splits = unbracketed_part.trim_end().rsplit(' ');
let version_string = splits.next().unwrap();
let mut splits = version_string.split('.');
let major = splits.next().unwrap();
let minor = splits.next().unwrap();
let patch = splits.next();
let major: u32 = major.parse().unwrap();
let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
None => {
let minor = minor.parse().unwrap();
let patch: u32 = match patch {
Some(patch) => match patch.find(not_a_digit) {
None => patch.parse().unwrap(),
Some(idx) if idx > 3 => 0,
Some(idx) => patch[..idx].parse().unwrap(),
},
None => 0,
};
(minor, patch)
}
// There is no patch version after minor-date (e.g. "4-2012").
Some(idx) => {
let minor = minor[..idx].parse().unwrap();
(minor, 0)
}
};
Some(((major * 1000) + minor) * 1000 + patch)
}
/// Returns LLDB version
pub(crate) fn extract_lldb_version(full_version_line: &str) -> Option<u32> {
// Extract the major LLDB version from the given version string.
// LLDB version strings are different for Apple and non-Apple platforms.
// The Apple variant looks like this:
//
// LLDB-179.5 (older versions)
// lldb-300.2.51 (new versions)
//
// We are only interested in the major version number, so this function
// will return `Some(179)` and `Some(300)` respectively.
//
// Upstream versions look like:
// lldb version 6.0.1
//
// There doesn't seem to be a way to correlate the Apple version
// with the upstream version, and since the tests were originally
// written against Apple versions, we make a fake Apple version by
// multiplying the first number by 100. This is a hack.
let full_version_line = full_version_line.trim();
if let Some(apple_ver) =
full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
{
if let Some(idx) = apple_ver.find(not_a_digit) {
let version: u32 = apple_ver[..idx].parse().unwrap();
return Some(version);
}
} else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
if let Some(idx) = lldb_ver.find(not_a_digit) {
let version: u32 = lldb_ver[..idx].parse().ok()?;
return Some(version * 100);
}
}
None
}
fn not_a_digit(c: char) -> bool {
!c.is_ascii_digit()
}

View file

@ -5,17 +5,17 @@ use std::io::BufReader;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::OnceLock;
use regex::Regex;
use tracing::*;
use crate::common::{Config, Debugger, FailMode, Mode, PassMode};
use crate::debuggers::{extract_cdb_version, extract_gdb_version};
use crate::header::auxiliary::{AuxProps, parse_and_update_aux};
use crate::header::cfg::{MatchOutcome, parse_cfg_name_directive};
use crate::header::needs::CachedNeedsConditions;
use crate::util::static_regex;
use crate::{extract_cdb_version, extract_gdb_version};
pub(crate) mod auxiliary;
mod cfg;
mod needs;
#[cfg(test)]
@ -35,9 +35,10 @@ impl HeadersCache {
/// the test.
#[derive(Default)]
pub struct EarlyProps {
pub aux: Vec<String>,
pub aux_bin: Vec<String>,
pub aux_crate: Vec<(String, String)>,
/// Auxiliary crates that should be built and made available to this test.
/// Included in [`EarlyProps`] so that the indicated files can participate
/// in up-to-date checking. Building happens via [`TestProps::aux`] instead.
pub(crate) aux: AuxProps,
pub revisions: Vec<String>,
}
@ -56,23 +57,9 @@ impl EarlyProps {
&mut poisoned,
testfile,
rdr,
&mut |HeaderLine { directive: ln, .. }| {
config.push_name_value_directive(ln, directives::AUX_BUILD, &mut props.aux, |r| {
r.trim().to_string()
});
config.push_name_value_directive(
ln,
directives::AUX_BIN,
&mut props.aux_bin,
|r| r.trim().to_string(),
);
config.push_name_value_directive(
ln,
directives::AUX_CRATE,
&mut props.aux_crate,
Config::parse_aux_crate,
);
config.parse_and_update_revisions(ln, &mut props.revisions);
&mut |DirectiveLine { directive: ln, .. }| {
parse_and_update_aux(config, ln, &mut props.aux);
config.parse_and_update_revisions(testfile, ln, &mut props.revisions);
},
);
@ -100,18 +87,8 @@ pub struct TestProps {
// If present, the name of a file that this test should match when
// pretty-printed
pub pp_exact: Option<PathBuf>,
// Other crates that should be compiled (typically from the same
// directory as the test, but for backwards compatibility reasons
// we also check the auxiliary directory)
pub aux_builds: Vec<String>,
// Auxiliary crates that should be compiled as `#![crate_type = "bin"]`.
pub aux_bins: Vec<String>,
// Similar to `aux_builds`, but a list of NAME=somelib.rs of dependencies
// to build and pass with the `--extern` flag.
pub aux_crates: Vec<(String, String)>,
/// Similar to `aux_builds`, but also passes the resulting dylib path to
/// `-Zcodegen-backend`.
pub aux_codegen_backend: Option<String>,
/// Auxiliary crates that should be built and made available to this test.
pub(crate) aux: AuxProps,
// Environment settings to use for compiling
pub rustc_env: Vec<(String, String)>,
// Environment variables to unset prior to compiling.
@ -278,10 +255,7 @@ impl TestProps {
run_flags: vec![],
doc_flags: vec![],
pp_exact: None,
aux_builds: vec![],
aux_bins: vec![],
aux_crates: vec![],
aux_codegen_backend: None,
aux: Default::default(),
revisions: vec![],
rustc_env: vec![
("RUSTC_ICE".to_string(), "0".to_string()),
@ -370,7 +344,7 @@ impl TestProps {
&mut poisoned,
testfile,
file,
&mut |HeaderLine { header_revision, directive: ln, .. }| {
&mut |DirectiveLine { header_revision, directive: ln, .. }| {
if header_revision.is_some() && header_revision != test_revision {
return;
}
@ -417,7 +391,7 @@ impl TestProps {
has_edition = true;
}
config.parse_and_update_revisions(ln, &mut self.revisions);
config.parse_and_update_revisions(testfile, ln, &mut self.revisions);
if let Some(flags) = config.parse_name_value_directive(ln, RUN_FLAGS) {
self.run_flags.extend(split_flags(&flags));
@ -456,21 +430,10 @@ impl TestProps {
PRETTY_COMPARE_ONLY,
&mut self.pretty_compare_only,
);
config.push_name_value_directive(ln, AUX_BUILD, &mut self.aux_builds, |r| {
r.trim().to_string()
});
config.push_name_value_directive(ln, AUX_BIN, &mut self.aux_bins, |r| {
r.trim().to_string()
});
config.push_name_value_directive(
ln,
AUX_CRATE,
&mut self.aux_crates,
Config::parse_aux_crate,
);
if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) {
self.aux_codegen_backend = Some(r.trim().to_owned());
}
// Call a helper method to deal with aux-related directives.
parse_and_update_aux(config, ln, &mut self.aux);
config.push_name_value_directive(
ln,
EXEC_ENV,
@ -717,7 +680,7 @@ impl TestProps {
/// Extract an `(Option<line_revision>, directive)` directive from a line if comment is present.
///
/// See [`HeaderLine`] for a diagram.
/// See [`DirectiveLine`] for a diagram.
pub fn line_directive<'line>(
comment: &str,
original_line: &'line str,
@ -775,17 +738,13 @@ const KNOWN_JSONDOCCK_DIRECTIVE_NAMES: &[&str] =
/// ```text
/// //@ compile-flags: -O
/// ^^^^^^^^^^^^^^^^^ directive
/// ^^^^^^^^^^^^^^^^^^^^^ original_line
///
/// //@ [foo] compile-flags: -O
/// ^^^ header_revision
/// ^^^^^^^^^^^^^^^^^ directive
/// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ original_line
/// ```
struct HeaderLine<'ln> {
struct DirectiveLine<'ln> {
line_number: usize,
/// Raw line from the test file, including comment prefix and any revision.
original_line: &'ln str,
/// Some header directives start with a revision name in square brackets
/// (e.g. `[foo]`), and only apply to that revision of the test.
/// If present, this field contains the revision name (e.g. `foo`).
@ -797,7 +756,6 @@ struct HeaderLine<'ln> {
pub(crate) struct CheckDirectiveResult<'ln> {
is_known_directive: bool,
directive_name: &'ln str,
trailing_directive: Option<&'ln str>,
}
@ -832,11 +790,7 @@ pub(crate) fn check_directive<'a>(
}
.then_some(trailing);
CheckDirectiveResult {
is_known_directive: is_known(&directive_name),
directive_name: directive_ln,
trailing_directive,
}
CheckDirectiveResult { is_known_directive: is_known(&directive_name), trailing_directive }
}
fn iter_header(
@ -845,150 +799,119 @@ fn iter_header(
poisoned: &mut bool,
testfile: &Path,
rdr: impl Read,
it: &mut dyn FnMut(HeaderLine<'_>),
it: &mut dyn FnMut(DirectiveLine<'_>),
) {
if testfile.is_dir() {
return;
}
// Coverage tests in coverage-run mode always have these extra directives,
// without needing to specify them manually in every test file.
// (Some of the comments below have been copied over from the old
// `tests/run-make/coverage-reports/Makefile`, which no longer exists.)
// Coverage tests in coverage-run mode always have these extra directives, without needing to
// specify them manually in every test file. (Some of the comments below have been copied over
// from the old `tests/run-make/coverage-reports/Makefile`, which no longer exists.)
//
// FIXME(jieyouxu): I feel like there's a better way to do this, leaving for later.
if mode == Mode::CoverageRun {
let extra_directives: &[&str] = &[
"needs-profiler-support",
// FIXME(pietroalbini): this test currently does not work on cross-compiled
// targets because remote-test is not capable of sending back the *.profraw
// files generated by the LLVM instrumentation.
"needs-profiler-runtime",
// FIXME(pietroalbini): this test currently does not work on cross-compiled targets
// because remote-test is not capable of sending back the *.profraw files generated by
// the LLVM instrumentation.
"ignore-cross-compile",
];
// Process the extra implied directives, with a dummy line number of 0.
for directive in extra_directives {
it(HeaderLine { line_number: 0, original_line: "", header_revision: None, directive });
it(DirectiveLine { line_number: 0, header_revision: None, directive });
}
}
// NOTE(jieyouxu): once we get rid of `Makefile`s we can unconditionally check for `//@`.
let comment = if testfile.extension().is_some_and(|e| e == "rs") { "//@" } else { "#" };
let mut rdr = BufReader::with_capacity(1024, rdr);
let mut ln = String::new();
let mut line_number = 0;
// Match on error annotations like `//~ERROR`.
static REVISION_MAGIC_COMMENT_RE: OnceLock<Regex> = OnceLock::new();
let revision_magic_comment_re =
REVISION_MAGIC_COMMENT_RE.get_or_init(|| Regex::new("//(\\[.*\\])?~.*").unwrap());
loop {
line_number += 1;
ln.clear();
if rdr.read_line(&mut ln).unwrap() == 0 {
break;
}
// Assume that any directives will be found before the first
// module or function. This doesn't seem to be an optimization
// with a warm page cache. Maybe with a cold one.
let original_line = &ln;
let ln = ln.trim();
// Assume that any directives will be found before the first module or function. This
// doesn't seem to be an optimization with a warm page cache. Maybe with a cold one.
// FIXME(jieyouxu): this will cause `//@` directives in the rest of the test file to
// not be checked.
if ln.starts_with("fn") || ln.starts_with("mod") {
return;
}
// First try to accept `ui_test` style comments (`//@`)
} else if let Some((header_revision, non_revisioned_directive_line)) =
line_directive(comment, ln)
{
// Perform unknown directive check on Rust files.
if testfile.extension().map(|e| e == "rs").unwrap_or(false) {
let directive_ln = non_revisioned_directive_line.trim();
let Some((header_revision, non_revisioned_directive_line)) = line_directive(comment, ln)
else {
continue;
};
let CheckDirectiveResult { is_known_directive, trailing_directive, .. } =
check_directive(directive_ln, mode, ln);
// Perform unknown directive check on Rust files.
if testfile.extension().map(|e| e == "rs").unwrap_or(false) {
let directive_ln = non_revisioned_directive_line.trim();
if !is_known_directive {
*poisoned = true;
let CheckDirectiveResult { is_known_directive, trailing_directive } =
check_directive(directive_ln, mode, ln);
eprintln!(
"error: detected unknown compiletest test directive `{}` in {}:{}",
directive_ln,
testfile.display(),
line_number,
);
return;
}
if let Some(trailing_directive) = &trailing_directive {
*poisoned = true;
eprintln!(
"error: detected trailing compiletest test directive `{}` in {}:{}\n \
help: put the trailing directive in it's own line: `//@ {}`",
trailing_directive,
testfile.display(),
line_number,
trailing_directive,
);
return;
}
}
it(HeaderLine {
line_number,
original_line,
header_revision,
directive: non_revisioned_directive_line,
});
// Then we try to check for legacy-style candidates, which are not the magic ~ERROR family
// error annotations.
} else if !revision_magic_comment_re.is_match(ln) {
let Some((_, rest)) = line_directive("//", ln) else {
continue;
};
if rest.trim_start().starts_with(':') {
// This is likely a markdown link:
// `[link_name]: https://example.org`
continue;
}
let rest = rest.trim_start();
let CheckDirectiveResult { is_known_directive, directive_name, .. } =
check_directive(rest, mode, ln);
if is_known_directive {
if !is_known_directive {
*poisoned = true;
eprintln!(
"error: detected legacy-style directive {} in compiletest test: {}:{}, please use `ui_test`-style directives `//@` instead: {:#?}",
directive_name,
"error: detected unknown compiletest test directive `{}` in {}:{}",
directive_ln,
testfile.display(),
line_number,
line_directive("//", ln),
);
return;
}
if let Some(trailing_directive) = &trailing_directive {
*poisoned = true;
eprintln!(
"error: detected trailing compiletest test directive `{}` in {}:{}\n \
help: put the trailing directive in it's own line: `//@ {}`",
trailing_directive,
testfile.display(),
line_number,
trailing_directive,
);
return;
}
}
it(DirectiveLine {
line_number,
header_revision,
directive: non_revisioned_directive_line,
});
}
}
impl Config {
fn parse_aux_crate(r: String) -> (String, String) {
let mut parts = r.trim().splitn(2, '=');
(
parts.next().expect("missing aux-crate name (e.g. log=log.rs)").to_string(),
parts.next().expect("missing aux-crate value (e.g. log=log.rs)").to_string(),
)
}
fn parse_and_update_revisions(&self, line: &str, existing: &mut Vec<String>) {
fn parse_and_update_revisions(&self, testfile: &Path, line: &str, existing: &mut Vec<String>) {
if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
if self.mode == Mode::RunMake {
panic!("`run-make` tests do not support revisions: {}", testfile.display());
}
let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
for revision in raw.split_whitespace().map(|r| r.to_string()) {
if !duplicates.insert(revision.clone()) {
panic!("Duplicate revision: `{}` in line `{}`", revision, raw);
panic!(
"duplicate revision: `{}` in line `{}`: {}",
revision,
raw,
testfile.display()
);
}
existing.push(revision);
}
@ -1362,13 +1285,14 @@ pub fn make_test_description<R: Read>(
let mut local_poisoned = false;
// Scan through the test file to handle `ignore-*`, `only-*`, and `needs-*` directives.
iter_header(
config.mode,
&config.suite,
&mut local_poisoned,
path,
src,
&mut |HeaderLine { header_revision, original_line, directive: ln, line_number }| {
&mut |DirectiveLine { header_revision, directive: ln, line_number }| {
if header_revision.is_some() && header_revision != test_revision {
return;
}
@ -1393,17 +1317,7 @@ pub fn make_test_description<R: Read>(
};
}
if let Some((_, post)) = original_line.trim_start().split_once("//") {
let post = post.trim_start();
if post.starts_with("ignore-tidy") {
// Not handled by compiletest.
} else {
decision!(cfg::handle_ignore(config, ln));
}
} else {
decision!(cfg::handle_ignore(config, ln));
}
decision!(cfg::handle_ignore(config, ln));
decision!(cfg::handle_only(config, ln));
decision!(needs::handle_needs(&cache.needs, config, ln));
decision!(ignore_llvm(config, ln));

View file

@ -0,0 +1,60 @@
//! Code for dealing with test directives that request an "auxiliary" crate to
//! be built and made available to the test in some way.
use std::iter;
use crate::common::Config;
use crate::header::directives::{AUX_BIN, AUX_BUILD, AUX_CODEGEN_BACKEND, AUX_CRATE};
/// Properties parsed from `aux-*` test directives.
#[derive(Clone, Debug, Default)]
pub(crate) struct AuxProps {
/// Other crates that should be built and made available to this test.
/// These are filenames relative to `./auxiliary/` in the test's directory.
pub(crate) builds: Vec<String>,
/// Auxiliary crates that should be compiled as `#![crate_type = "bin"]`.
pub(crate) bins: Vec<String>,
/// Similar to `builds`, but a list of NAME=somelib.rs of dependencies
/// to build and pass with the `--extern` flag.
pub(crate) crates: Vec<(String, String)>,
/// Similar to `builds`, but also uses the resulting dylib as a
/// `-Zcodegen-backend` when compiling the test file.
pub(crate) codegen_backend: Option<String>,
}
impl AuxProps {
/// Yields all of the paths (relative to `./auxiliary/`) that have been
/// specified in `aux-*` directives for this test.
pub(crate) fn all_aux_path_strings(&self) -> impl Iterator<Item = &str> {
let Self { builds, bins, crates, codegen_backend } = self;
iter::empty()
.chain(builds.iter().map(String::as_str))
.chain(bins.iter().map(String::as_str))
.chain(crates.iter().map(|(_, path)| path.as_str()))
.chain(codegen_backend.iter().map(String::as_str))
}
}
/// If the given test directive line contains an `aux-*` directive, parse it
/// and update [`AuxProps`] accordingly.
pub(super) fn parse_and_update_aux(config: &Config, ln: &str, aux: &mut AuxProps) {
if !ln.starts_with("aux-") {
return;
}
config.push_name_value_directive(ln, AUX_BUILD, &mut aux.builds, |r| r.trim().to_string());
config.push_name_value_directive(ln, AUX_BIN, &mut aux.bins, |r| r.trim().to_string());
config.push_name_value_directive(ln, AUX_CRATE, &mut aux.crates, parse_aux_crate);
if let Some(r) = config.parse_name_value_directive(ln, AUX_CODEGEN_BACKEND) {
aux.codegen_backend = Some(r.trim().to_owned());
}
}
fn parse_aux_crate(r: String) -> (String, String) {
let mut parts = r.trim().splitn(2, '=');
(
parts.next().expect("missing aux-crate name (e.g. log=log.rs)").to_string(),
parts.next().expect("missing aux-crate value (e.g. log=log.rs)").to_string(),
)
}

View file

@ -1,6 +1,6 @@
use std::collections::HashSet;
use crate::common::{CompareMode, Config, Debugger, Mode};
use crate::common::{CompareMode, Config, Debugger};
use crate::header::IgnoreDecision;
const EXTRA_ARCHS: &[&str] = &["spirv"];
@ -166,6 +166,12 @@ pub(super) fn parse_cfg_name_directive<'a>(
message: "when the target vendor is Apple"
}
condition! {
name: "enzyme",
condition: config.has_enzyme,
message: "when rustc is built with LLVM Enzyme"
}
// Technically the locally built compiler uses the "dev" channel rather than the "nightly"
// channel, even though most people don't know or won't care about it. To avoid confusion, we
// treat the "dev" channel as the "nightly" channel when processing the directive.
@ -217,13 +223,10 @@ pub(super) fn parse_cfg_name_directive<'a>(
}
// Coverage tests run the same test file in multiple modes.
// If a particular test should not be run in one of the modes, ignore it
// with "ignore-mode-coverage-map" or "ignore-mode-coverage-run".
// with "ignore-coverage-map" or "ignore-coverage-run".
condition! {
name: format!("mode-{}", config.mode.to_str()),
allowed_names: ContainsPrefixed {
prefix: "mode-",
inner: Mode::STR_VARIANTS,
},
name: config.mode.to_str(),
allowed_names: ["coverage-map", "coverage-run"],
message: "when the test mode is {name}",
}

View file

@ -100,9 +100,9 @@ pub(super) fn handle_needs(
ignore_reason: "ignored on targets without unwinding support",
},
Need {
name: "needs-profiler-support",
condition: cache.profiler_support,
ignore_reason: "ignored when profiler support is disabled",
name: "needs-profiler-runtime",
condition: config.profiler_runtime,
ignore_reason: "ignored when the profiler runtime is not available",
},
Need {
name: "needs-force-clang-based-tests",
@ -220,7 +220,6 @@ pub(super) struct CachedNeedsConditions {
sanitizer_memtag: bool,
sanitizer_shadow_call_stack: bool,
sanitizer_safestack: bool,
profiler_support: bool,
xray: bool,
rust_lld: bool,
dlltool: bool,
@ -247,7 +246,6 @@ impl CachedNeedsConditions {
sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag),
sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack),
sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack),
profiler_support: config.profiler_support,
xray: config.target_cfg().xray,
// For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`),

View file

@ -1,6 +1,5 @@
use std::io::Read;
use std::path::Path;
use std::str::FromStr;
use super::iter_header;
use crate::common::{Config, Debugger, Mode};
@ -70,7 +69,7 @@ struct ConfigBuilder {
llvm_version: Option<String>,
git_hash: bool,
system_llvm: bool,
profiler_support: bool,
profiler_runtime: bool,
}
impl ConfigBuilder {
@ -114,8 +113,8 @@ impl ConfigBuilder {
self
}
fn profiler_support(&mut self, s: bool) -> &mut Self {
self.profiler_support = s;
fn profiler_runtime(&mut self, is_available: bool) -> &mut Self {
self.profiler_runtime = is_available;
self
}
@ -163,8 +162,8 @@ impl ConfigBuilder {
if self.system_llvm {
args.push("--system-llvm".to_owned());
}
if self.profiler_support {
args.push("--profiler-support".to_owned());
if self.profiler_runtime {
args.push("--profiler-runtime".to_owned());
}
args.push("--rustc-path".to_string());
@ -243,7 +242,8 @@ fn aux_build() {
//@ aux-build: b.rs
"
)
.aux,
.aux
.builds,
vec!["a.rs", "b.rs"],
);
}
@ -369,12 +369,12 @@ fn sanitizers() {
}
#[test]
fn profiler_support() {
let config: Config = cfg().profiler_support(false).build();
assert!(check_ignore(&config, "//@ needs-profiler-support"));
fn profiler_runtime() {
let config: Config = cfg().profiler_runtime(false).build();
assert!(check_ignore(&config, "//@ needs-profiler-runtime"));
let config: Config = cfg().profiler_support(true).build();
assert!(!check_ignore(&config, "//@ needs-profiler-support"));
let config: Config = cfg().profiler_runtime(true).build();
assert!(!check_ignore(&config, "//@ needs-profiler-runtime"));
}
#[test]
@ -423,7 +423,7 @@ fn test_extract_version_range() {
}
#[test]
#[should_panic(expected = "Duplicate revision: `rpass1` in line ` rpass1 rpass1`")]
#[should_panic(expected = "duplicate revision: `rpass1` in line ` rpass1 rpass1`")]
fn test_duplicate_revisions() {
let config: Config = cfg().build();
parse_rs(&config, "//@ revisions: rpass1 rpass1");
@ -573,19 +573,15 @@ fn families() {
}
#[test]
fn ignore_mode() {
for &mode in Mode::STR_VARIANTS {
// Indicate profiler support so that "coverage-run" tests aren't skipped.
let config: Config = cfg().mode(mode).profiler_support(true).build();
let other = if mode == "coverage-run" { "coverage-map" } else { "coverage-run" };
fn ignore_coverage() {
// Indicate profiler runtime availability so that "coverage-run" tests aren't skipped.
let config = cfg().mode("coverage-map").profiler_runtime(true).build();
assert!(check_ignore(&config, "//@ ignore-coverage-map"));
assert!(!check_ignore(&config, "//@ ignore-coverage-run"));
assert_ne!(mode, other);
assert_eq!(config.mode, Mode::from_str(mode).unwrap());
assert_ne!(config.mode, Mode::from_str(other).unwrap());
assert!(check_ignore(&config, &format!("//@ ignore-mode-{mode}")));
assert!(!check_ignore(&config, &format!("//@ ignore-mode-{other}")));
}
let config = cfg().mode("coverage-run").profiler_runtime(true).build();
assert!(!check_ignore(&config, "//@ ignore-coverage-map"));
assert!(check_ignore(&config, "//@ ignore-coverage-run"));
}
#[test]
@ -621,17 +617,6 @@ fn test_unknown_directive_check() {
assert!(poisoned);
}
#[test]
fn test_known_legacy_directive_check() {
let mut poisoned = false;
run_path(
&mut poisoned,
Path::new("a.rs"),
include_bytes!("./test-auxillary/known_legacy_directive.rs"),
);
assert!(poisoned);
}
#[test]
fn test_known_directive_check_no_error() {
let mut poisoned = false;

View file

@ -10,6 +10,7 @@ mod tests;
pub mod common;
pub mod compute_diff;
mod debuggers;
pub mod errors;
pub mod header;
mod json;
@ -36,8 +37,8 @@ use walkdir::WalkDir;
use self::header::{EarlyProps, make_test_description};
use crate::common::{
Config, Debugger, Mode, PassMode, TestPaths, UI_EXTENSIONS, expected_output_path,
output_base_dir, output_relative_path,
Config, Mode, PassMode, TestPaths, UI_EXTENSIONS, expected_output_path, output_base_dir,
output_relative_path,
};
use crate::header::HeadersCache;
use crate::util::logv;
@ -53,8 +54,6 @@ pub fn parse_config(args: Vec<String>) -> Config {
.reqopt("", "python", "path to python to use for doc tests", "PATH")
.optopt("", "jsondocck-path", "path to jsondocck to use for doc tests", "PATH")
.optopt("", "jsondoclint-path", "path to jsondoclint to use for doc tests", "PATH")
.optopt("", "valgrind-path", "path to Valgrind executable for Valgrind tests", "PROGRAM")
.optflag("", "force-valgrind", "fail if Valgrind tests cannot be run under Valgrind")
.optopt("", "run-clang-based-tests-with", "path to Clang executable", "PATH")
.optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
.reqopt("", "src-base", "directory to scan for test files", "PATH")
@ -65,7 +64,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
"",
"mode",
"which sort of compile tests to run",
"run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
"pretty | debug-info | codegen | rustdoc \
| rustdoc-json | codegen-units | incremental | run-make | ui \
| js-doc-test | mir-opt | assembly | crashes",
)
@ -155,7 +154,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
.optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
.optflag("", "only-modified", "only run tests that result been modified")
.optflag("", "nocapture", "")
.optflag("", "profiler-support", "is the profiler runtime enabled for this target")
.optflag("", "profiler-runtime", "is the profiler runtime enabled for this target")
.optflag("h", "help", "show this message")
.reqopt("", "channel", "current Rust channel", "CHANNEL")
.optflag(
@ -206,9 +205,11 @@ pub fn parse_config(args: Vec<String>) -> Config {
let target = opt_str2(matches.opt_str("target"));
let android_cross_path = opt_path(matches, "android-cross-path");
let (cdb, cdb_version) = analyze_cdb(matches.opt_str("cdb"), &target);
let (gdb, gdb_version) = analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
let lldb_version = matches.opt_str("lldb-version").as_deref().and_then(extract_lldb_version);
let (cdb, cdb_version) = debuggers::analyze_cdb(matches.opt_str("cdb"), &target);
let (gdb, gdb_version) =
debuggers::analyze_gdb(matches.opt_str("gdb"), &target, &android_cross_path);
let lldb_version =
matches.opt_str("lldb-version").as_deref().and_then(debuggers::extract_lldb_version);
let color = match matches.opt_str("color").as_deref() {
Some("auto") | None => ColorConfig::AutoColor,
Some("always") => ColorConfig::AlwaysColor,
@ -269,8 +270,6 @@ pub fn parse_config(args: Vec<String>) -> Config {
python: matches.opt_str("python").unwrap(),
jsondocck_path: matches.opt_str("jsondocck-path"),
jsondoclint_path: matches.opt_str("jsondoclint-path"),
valgrind_path: matches.opt_str("valgrind-path"),
force_valgrind: matches.opt_present("force-valgrind"),
run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
@ -359,7 +358,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
nightly_branch: matches.opt_str("nightly-branch").unwrap(),
git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
profiler_support: matches.opt_present("profiler-support"),
profiler_runtime: matches.opt_present("profiler-runtime"),
}
}
@ -447,9 +446,9 @@ pub fn run_tests(config: Arc<Config>) {
if let Mode::DebugInfo = config.mode {
// Debugging emscripten code doesn't make sense today
if !config.target.contains("emscripten") {
configs.extend(configure_cdb(&config));
configs.extend(configure_gdb(&config));
configs.extend(configure_lldb(&config));
configs.extend(debuggers::configure_cdb(&config));
configs.extend(debuggers::configure_gdb(&config));
configs.extend(debuggers::configure_lldb(&config));
}
} else {
configs.push(config.clone());
@ -502,62 +501,6 @@ pub fn run_tests(config: Arc<Config>) {
}
}
fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
config.cdb.as_ref()?;
Some(Arc::new(Config { debugger: Some(Debugger::Cdb), ..config.clone() }))
}
fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
config.gdb_version?;
if config.matches_env("msvc") {
return None;
}
if config.remote_test_client.is_some() && !config.target.contains("android") {
println!(
"WARNING: debuginfo tests are not available when \
testing with remote"
);
return None;
}
if config.target.contains("android") {
println!(
"{} debug-info test uses tcp 5039 port.\
please reserve it",
config.target
);
// android debug-info test uses remote debugger so, we test 1 thread
// at once as they're all sharing the same TCP port to communicate
// over.
//
// we should figure out how to lift this restriction! (run them all
// on different ports allocated dynamically).
env::set_var("RUST_TEST_THREADS", "1");
}
Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
}
fn configure_lldb(config: &Config) -> Option<Arc<Config>> {
config.lldb_python_dir.as_ref()?;
if let Some(350) = config.lldb_version {
println!(
"WARNING: The used version of LLDB (350) has a \
known issue that breaks debuginfo tests. See \
issue #32520 for more information. Skipping all \
LLDB-based tests!",
);
return None;
}
Some(Arc::new(Config { debugger: Some(Debugger::Lldb), ..config.clone() }))
}
pub fn test_opts(config: &Config) -> test::TestOpts {
if env::var("RUST_TEST_NOCAPTURE").is_ok() {
eprintln!(
@ -866,7 +809,8 @@ fn files_related_to_test(
related.push(testpaths.file.clone());
}
for aux in &props.aux {
for aux in props.aux.all_aux_path_strings() {
// FIXME(Zalathar): Perform all `auxiliary` path resolution in one place.
let path = testpaths.file.parent().unwrap().join("auxiliary").join(aux);
related.push(path);
}
@ -984,212 +928,6 @@ fn make_test_closure(
}))
}
/// Returns `true` if the given target is an Android target for the
/// purposes of GDB testing.
fn is_android_gdb_target(target: &str) -> bool {
matches!(
&target[..],
"arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android"
)
}
/// Returns `true` if the given target is a MSVC target for the purposes of CDB testing.
fn is_pc_windows_msvc_target(target: &str) -> bool {
target.ends_with("-pc-windows-msvc")
}
fn find_cdb(target: &str) -> Option<OsString> {
if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
return None;
}
let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
let cdb_arch = if cfg!(target_arch = "x86") {
"x86"
} else if cfg!(target_arch = "x86_64") {
"x64"
} else if cfg!(target_arch = "aarch64") {
"arm64"
} else if cfg!(target_arch = "arm") {
"arm"
} else {
return None; // No compatible CDB.exe in the Windows 10 SDK
};
let mut path = PathBuf::new();
path.push(pf86);
path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
path.push(cdb_arch);
path.push(r"cdb.exe");
if !path.exists() {
return None;
}
Some(path.into_os_string())
}
/// Returns Path to CDB
fn analyze_cdb(cdb: Option<String>, target: &str) -> (Option<OsString>, Option<[u16; 4]>) {
let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
let mut version = None;
if let Some(cdb) = cdb.as_ref() {
if let Ok(output) = Command::new(cdb).arg("/version").output() {
if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
version = extract_cdb_version(&first_line);
}
}
}
(cdb, version)
}
fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
// Example full_version_line: "cdb version 10.0.18362.1"
let version = full_version_line.rsplit(' ').next()?;
let mut components = version.split('.');
let major: u16 = components.next().unwrap().parse().unwrap();
let minor: u16 = components.next().unwrap().parse().unwrap();
let patch: u16 = components.next().unwrap_or("0").parse().unwrap();
let build: u16 = components.next().unwrap_or("0").parse().unwrap();
Some([major, minor, patch, build])
}
/// Returns (Path to GDB, GDB Version)
fn analyze_gdb(
gdb: Option<String>,
target: &str,
android_cross_path: &PathBuf,
) -> (Option<String>, Option<u32>) {
#[cfg(not(windows))]
const GDB_FALLBACK: &str = "gdb";
#[cfg(windows)]
const GDB_FALLBACK: &str = "gdb.exe";
let fallback_gdb = || {
if is_android_gdb_target(target) {
let mut gdb_path = match android_cross_path.to_str() {
Some(x) => x.to_owned(),
None => panic!("cannot find android cross path"),
};
gdb_path.push_str("/bin/gdb");
gdb_path
} else {
GDB_FALLBACK.to_owned()
}
};
let gdb = match gdb {
None => fallback_gdb(),
Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb
Some(ref s) => s.to_owned(),
};
let mut version_line = None;
if let Ok(output) = Command::new(&gdb).arg("--version").output() {
if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() {
version_line = Some(first_line.to_string());
}
}
let version = match version_line {
Some(line) => extract_gdb_version(&line),
None => return (None, None),
};
(Some(gdb), version)
}
fn extract_gdb_version(full_version_line: &str) -> Option<u32> {
let full_version_line = full_version_line.trim();
// GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both
// of the ? sections being optional
// We will parse up to 3 digits for each component, ignoring the date
// We skip text in parentheses. This avoids accidentally parsing
// the openSUSE version, which looks like:
// GNU gdb (GDB; openSUSE Leap 15.0) 8.1
// This particular form is documented in the GNU coding standards:
// https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion
let unbracketed_part = full_version_line.split('[').next().unwrap();
let mut splits = unbracketed_part.trim_end().rsplit(' ');
let version_string = splits.next().unwrap();
let mut splits = version_string.split('.');
let major = splits.next().unwrap();
let minor = splits.next().unwrap();
let patch = splits.next();
let major: u32 = major.parse().unwrap();
let (minor, patch): (u32, u32) = match minor.find(not_a_digit) {
None => {
let minor = minor.parse().unwrap();
let patch: u32 = match patch {
Some(patch) => match patch.find(not_a_digit) {
None => patch.parse().unwrap(),
Some(idx) if idx > 3 => 0,
Some(idx) => patch[..idx].parse().unwrap(),
},
None => 0,
};
(minor, patch)
}
// There is no patch version after minor-date (e.g. "4-2012").
Some(idx) => {
let minor = minor[..idx].parse().unwrap();
(minor, 0)
}
};
Some(((major * 1000) + minor) * 1000 + patch)
}
/// Returns LLDB version
fn extract_lldb_version(full_version_line: &str) -> Option<u32> {
// Extract the major LLDB version from the given version string.
// LLDB version strings are different for Apple and non-Apple platforms.
// The Apple variant looks like this:
//
// LLDB-179.5 (older versions)
// lldb-300.2.51 (new versions)
//
// We are only interested in the major version number, so this function
// will return `Some(179)` and `Some(300)` respectively.
//
// Upstream versions look like:
// lldb version 6.0.1
//
// There doesn't seem to be a way to correlate the Apple version
// with the upstream version, and since the tests were originally
// written against Apple versions, we make a fake Apple version by
// multiplying the first number by 100. This is a hack.
let full_version_line = full_version_line.trim();
if let Some(apple_ver) =
full_version_line.strip_prefix("LLDB-").or_else(|| full_version_line.strip_prefix("lldb-"))
{
if let Some(idx) = apple_ver.find(not_a_digit) {
let version: u32 = apple_ver[..idx].parse().unwrap();
return Some(version);
}
} else if let Some(lldb_ver) = full_version_line.strip_prefix("lldb version ") {
if let Some(idx) = lldb_ver.find(not_a_digit) {
let version: u32 = lldb_ver[..idx].parse().ok()?;
return Some(version * 100);
}
}
None
}
fn not_a_digit(c: char) -> bool {
!c.is_ascii_digit()
}
fn check_overlapping_tests(found_paths: &HashSet<PathBuf>) {
let mut collisions = Vec::new();
for path in found_paths {

View file

@ -18,15 +18,11 @@ fn main() {
let config = Arc::new(parse_config(env::args().collect()));
if config.valgrind_path.is_none() && config.force_valgrind {
panic!("Can't find Valgrind to run Valgrind tests");
}
if !config.has_tidy && config.mode == Mode::Rustdoc {
eprintln!("warning: `tidy` is not installed; diffs will not be generated");
}
if !config.profiler_support && config.mode == Mode::CoverageRun {
if !config.profiler_runtime && config.mode == Mode::CoverageRun {
let actioned = if config.bless { "blessed" } else { "checked" };
eprintln!(
r#"

View file

@ -20,9 +20,9 @@ use tracing::*;
use crate::common::{
Assembly, Codegen, CodegenUnits, CompareMode, Config, CoverageMap, CoverageRun, Crashes,
DebugInfo, Debugger, FailMode, Incremental, JsDocTest, MirOpt, PassMode, Pretty, RunMake,
RunPassValgrind, Rustdoc, RustdocJson, TestPaths, UI_EXTENSIONS, UI_FIXED, UI_RUN_STDERR,
UI_RUN_STDOUT, UI_STDERR, UI_STDOUT, UI_SVG, UI_WINDOWS_SVG, Ui, expected_output_path,
incremental_dir, output_base_dir, output_base_name, output_testname_unique,
Rustdoc, RustdocJson, TestPaths, UI_EXTENSIONS, UI_FIXED, UI_RUN_STDERR, UI_RUN_STDOUT,
UI_STDERR, UI_STDOUT, UI_SVG, UI_WINDOWS_SVG, Ui, expected_output_path, incremental_dir,
output_base_dir, output_base_name, output_testname_unique,
};
use crate::compute_diff::{write_diff, write_filtered_diff};
use crate::errors::{self, Error, ErrorKind};
@ -49,7 +49,6 @@ mod run_make;
mod rustdoc;
mod rustdoc_json;
mod ui;
mod valgrind;
// tidy-alphabet-end
#[cfg(test)]
@ -253,7 +252,6 @@ impl<'test> TestCx<'test> {
self.fatal("cannot use should-ice in a test that is not cfail");
}
match self.config.mode {
RunPassValgrind => self.run_valgrind_test(),
Pretty => self.run_pretty_test(),
DebugInfo => self.run_debuginfo_test(),
Codegen => self.run_codegen_test(),
@ -320,10 +318,29 @@ impl<'test> TestCx<'test> {
}
}
fn check_if_test_should_compile(&self, proc_res: &ProcRes, pm: Option<PassMode>) {
if self.should_compile_successfully(pm) {
fn check_if_test_should_compile(
&self,
fail_mode: Option<FailMode>,
pass_mode: Option<PassMode>,
proc_res: &ProcRes,
) {
if self.should_compile_successfully(pass_mode) {
if !proc_res.status.success() {
self.fatal_proc_rec("test compilation failed although it shouldn't!", proc_res);
match (fail_mode, pass_mode) {
(Some(FailMode::Build), Some(PassMode::Check)) => {
// A `build-fail` test needs to `check-pass`.
self.fatal_proc_rec(
"`build-fail` test is required to pass check build, but check build failed",
proc_res,
);
}
_ => {
self.fatal_proc_rec(
"test compilation failed although it shouldn't!",
proc_res,
);
}
}
}
} else {
if proc_res.status.success() {
@ -843,13 +860,13 @@ impl<'test> TestCx<'test> {
/// Auxiliaries, no matter how deep, have the same root_out_dir and root_testpaths.
fn document(&self, root_out_dir: &Path, root_testpaths: &TestPaths) -> ProcRes {
if self.props.build_aux_docs {
for rel_ab in &self.props.aux_builds {
for rel_ab in &self.props.aux.builds {
let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab);
let aux_props =
let props_for_aux =
self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
let aux_cx = TestCx {
config: self.config,
props: &aux_props,
props: &props_for_aux,
testpaths: &aux_testpaths,
revision: self.revision,
};
@ -1061,11 +1078,11 @@ impl<'test> TestCx<'test> {
fn aux_output_dir(&self) -> PathBuf {
let aux_dir = self.aux_output_dir_name();
if !self.props.aux_builds.is_empty() {
if !self.props.aux.builds.is_empty() {
remove_and_create_dir_all(&aux_dir);
}
if !self.props.aux_bins.is_empty() {
if !self.props.aux.bins.is_empty() {
let aux_bin_dir = self.aux_bin_output_dir_name();
remove_and_create_dir_all(&aux_dir);
remove_and_create_dir_all(&aux_bin_dir);
@ -1075,15 +1092,15 @@ impl<'test> TestCx<'test> {
}
fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Path, rustc: &mut Command) {
for rel_ab in &self.props.aux_builds {
for rel_ab in &self.props.aux.builds {
self.build_auxiliary(of, rel_ab, &aux_dir, false /* is_bin */);
}
for rel_ab in &self.props.aux_bins {
for rel_ab in &self.props.aux.bins {
self.build_auxiliary(of, rel_ab, &aux_dir, true /* is_bin */);
}
for (aux_name, aux_path) in &self.props.aux_crates {
for (aux_name, aux_path) in &self.props.aux.crates {
let aux_type = self.build_auxiliary(of, &aux_path, &aux_dir, false /* is_bin */);
let lib_name =
get_lib_name(&aux_path.trim_end_matches(".rs").replace('-', "_"), aux_type);
@ -1099,7 +1116,7 @@ impl<'test> TestCx<'test> {
// Build any `//@ aux-codegen-backend`, and pass the resulting library
// to `-Zcodegen-backend` when compiling the test file.
if let Some(aux_file) = &self.props.aux_codegen_backend {
if let Some(aux_file) = &self.props.aux.codegen_backend {
let aux_type = self.build_auxiliary(of, aux_file, aux_dir, false);
if let Some(lib_name) = get_lib_name(aux_file.trim_end_matches(".rs"), aux_type) {
let lib_path = aux_dir.join(&lib_name);
@ -1500,8 +1517,7 @@ impl<'test> TestCx<'test> {
Crashes => {
set_mir_dump_dir(&mut rustc);
}
RunPassValgrind | Pretty | DebugInfo | Rustdoc | RustdocJson | RunMake
| CodegenUnits | JsDocTest => {
Pretty | DebugInfo | Rustdoc | RustdocJson | RunMake | CodegenUnits | JsDocTest => {
// do not use JSON output
}
}
@ -1783,58 +1799,14 @@ impl<'test> TestCx<'test> {
proc_res.fatal(None, || on_failure(*self));
}
fn get_output_file(&self, extension: &str) -> TargetLocation {
let thin_lto = self.props.compile_flags.iter().any(|s| s.ends_with("lto=thin"));
if thin_lto {
TargetLocation::ThisDirectory(self.output_base_dir())
} else {
// This works with both `--emit asm` (as default output name for the assembly)
// and `ptx-linker` because the latter can write output at requested location.
let output_path = self.output_base_name().with_extension(extension);
TargetLocation::ThisFile(output_path.clone())
}
}
fn get_filecheck_file(&self, extension: &str) -> PathBuf {
let thin_lto = self.props.compile_flags.iter().any(|s| s.ends_with("lto=thin"));
if thin_lto {
let name = self.testpaths.file.file_stem().unwrap().to_str().unwrap();
let canonical_name = name.replace('-', "_");
let mut output_file = None;
for entry in self.output_base_dir().read_dir().unwrap() {
if let Ok(entry) = entry {
let entry_path = entry.path();
let entry_file = entry_path.file_name().unwrap().to_str().unwrap();
if entry_file.starts_with(&format!("{}.{}", name, canonical_name))
&& entry_file.ends_with(extension)
{
assert!(
output_file.is_none(),
"thinlto doesn't support multiple cgu tests"
);
output_file = Some(entry_file.to_string());
}
}
}
if let Some(output_file) = output_file {
self.output_base_dir().join(output_file)
} else {
self.output_base_name().with_extension(extension)
}
} else {
self.output_base_name().with_extension(extension)
}
}
// codegen tests (using FileCheck)
fn compile_test_and_save_ir(&self) -> (ProcRes, PathBuf) {
let output_file = self.get_output_file("ll");
let output_path = self.output_base_name().with_extension("ll");
let input_file = &self.testpaths.file;
let rustc = self.make_compile_args(
input_file,
output_file,
TargetLocation::ThisFile(output_path.clone()),
Emit::LlvmIr,
AllowUnused::No,
LinkToAux::Yes,
@ -1842,35 +1814,27 @@ impl<'test> TestCx<'test> {
);
let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
let output_path = self.get_filecheck_file("ll");
(proc_res, output_path)
}
fn compile_test_and_save_assembly(&self) -> (ProcRes, PathBuf) {
let output_file = self.get_output_file("s");
// This works with both `--emit asm` (as default output name for the assembly)
// and `ptx-linker` because the latter can write output at requested location.
let output_path = self.output_base_name().with_extension("s");
let input_file = &self.testpaths.file;
let mut emit = Emit::None;
match self.props.assembly_output.as_ref().map(AsRef::as_ref) {
Some("emit-asm") => {
emit = Emit::Asm;
}
Some("bpf-linker") => {
emit = Emit::LinkArgsAsm;
}
Some("ptx-linker") => {
// No extra flags needed.
}
Some(header) => self.fatal(&format!("unknown 'assembly-output' header: {header}")),
None => self.fatal("missing 'assembly-output' header"),
}
// Use the `//@ assembly-output:` directive to determine how to emit assembly.
let emit = match self.props.assembly_output.as_deref() {
Some("emit-asm") => Emit::Asm,
Some("bpf-linker") => Emit::LinkArgsAsm,
Some("ptx-linker") => Emit::None, // No extra flags needed.
Some(other) => self.fatal(&format!("unknown 'assembly-output' directive: {other}")),
None => self.fatal("missing 'assembly-output' directive"),
};
let rustc = self.make_compile_args(
input_file,
output_file,
TargetLocation::ThisFile(output_path.clone()),
emit,
AllowUnused::No,
LinkToAux::Yes,
@ -1878,7 +1842,6 @@ impl<'test> TestCx<'test> {
);
let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
let output_path = self.get_filecheck_file("s");
(proc_res, output_path)
}
@ -2109,6 +2072,10 @@ impl<'test> TestCx<'test> {
.collect()
}
/// This method is used for `//@ check-test-line-numbers-match`.
///
/// It checks that doctests line in the displayed doctest "name" matches where they are
/// defined in source code.
fn check_rustdoc_test_option(&self, res: ProcRes) {
let mut other_files = Vec::new();
let mut files: HashMap<String, Vec<usize>> = HashMap::new();
@ -2651,33 +2618,6 @@ impl<'test> TestCx<'test> {
}
}
// FIXME(jieyouxu): `run_rpass_test` is hoisted out here and not in incremental because
// apparently valgrind test falls back to `run_rpass_test` if valgrind isn't available, which
// seems highly questionable to me.
fn run_rpass_test(&self) {
let emit_metadata = self.should_emit_metadata(self.pass_mode());
let should_run = self.run_if_enabled();
let proc_res = self.compile_test(should_run, emit_metadata);
if !proc_res.status.success() {
self.fatal_proc_rec("compilation failed!", &proc_res);
}
// FIXME(#41968): Move this check to tidy?
if !errors::load_errors(&self.testpaths.file, self.revision).is_empty() {
self.fatal("run-pass tests with expected warnings should be moved to ui/");
}
if let WillExecute::Disabled = should_run {
return;
}
let proc_res = self.exec_compiled_test();
if !proc_res.status.success() {
self.fatal_proc_rec("test run failed!", &proc_res);
}
}
fn aggressive_rm_rf(&self, path: &Path) -> io::Result<()> {
for e in path.read_dir()? {
let entry = e?;

View file

@ -9,8 +9,8 @@ use tracing::debug;
use super::debugger::DebuggerCommands;
use super::{Debugger, Emit, ProcRes, TestCx, Truncated, WillExecute};
use crate::common::Config;
use crate::debuggers::{extract_gdb_version, is_android_gdb_target};
use crate::util::logv;
use crate::{extract_gdb_version, is_android_gdb_target};
impl TestCx<'_> {
pub(super) fn run_debuginfo_test(&self) {

View file

@ -1,10 +1,6 @@
use super::{TestCx, WillExecute};
use super::{FailMode, TestCx, WillExecute};
use crate::errors;
// FIXME(jieyouxu): `run_rpass_test` got hoisted out of this because apparently valgrind falls back
// to `run_rpass_test` if valgrind isn't available, which is questionable, but keeping it for
// refactoring changes to preserve current behavior.
impl TestCx<'_> {
pub(super) fn run_incremental_test(&self) {
// Basic plan for a test incremental/foo/bar.rs:
@ -73,10 +69,34 @@ impl TestCx<'_> {
}
}
fn run_rpass_test(&self) {
let emit_metadata = self.should_emit_metadata(self.pass_mode());
let should_run = self.run_if_enabled();
let proc_res = self.compile_test(should_run, emit_metadata);
if !proc_res.status.success() {
self.fatal_proc_rec("compilation failed!", &proc_res);
}
// FIXME(#41968): Move this check to tidy?
if !errors::load_errors(&self.testpaths.file, self.revision).is_empty() {
self.fatal("run-pass tests with expected warnings should be moved to ui/");
}
if let WillExecute::Disabled = should_run {
return;
}
let proc_res = self.exec_compiled_test();
if !proc_res.status.success() {
self.fatal_proc_rec("test run failed!", &proc_res);
}
}
fn run_cfail_test(&self) {
let pm = self.pass_mode();
let proc_res = self.compile_test(WillExecute::No, self.should_emit_metadata(pm));
self.check_if_test_should_compile(&proc_res, pm);
self.check_if_test_should_compile(Some(FailMode::Build), pm, &proc_res);
self.check_no_compiler_crash(&proc_res, self.props.should_ice);
let output_to_check = self.get_output(&proc_res);
@ -115,12 +135,6 @@ impl TestCx<'_> {
let proc_res = self.exec_compiled_test();
// The value our Makefile configures valgrind to return on failure
const VALGRIND_ERR: i32 = 100;
if proc_res.status.code() == Some(VALGRIND_ERR) {
self.fatal_proc_rec("run-fail test isn't valgrind-clean!", &proc_res);
}
let output_to_check = self.get_output(&proc_res);
self.check_correct_failure_status(&proc_res);
self.check_all_error_patterns(&output_to_check, &proc_res, pm);

View file

@ -18,14 +18,14 @@ impl TestCx<'_> {
let pm = Some(PassMode::Check);
let proc_res =
self.compile_test_general(WillExecute::No, Emit::Metadata, pm, Vec::new());
self.check_if_test_should_compile(&proc_res, pm);
self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res);
}
let pm = self.pass_mode();
let should_run = self.should_run(pm);
let emit_metadata = self.should_emit_metadata(pm);
let proc_res = self.compile_test(should_run, emit_metadata);
self.check_if_test_should_compile(&proc_res, pm);
self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res);
if matches!(proc_res.truncated, Truncated::Yes)
&& !self.props.dont_check_compiler_stdout
&& !self.props.dont_check_compiler_stderr

View file

@ -1,34 +0,0 @@
use super::{Emit, TestCx, WillExecute};
impl TestCx<'_> {
pub(super) fn run_valgrind_test(&self) {
assert!(self.revision.is_none(), "revisions not relevant here");
// FIXME(jieyouxu): does this really make any sense? If a valgrind test isn't testing
// valgrind, what is it even testing?
if self.config.valgrind_path.is_none() {
assert!(!self.config.force_valgrind);
return self.run_rpass_test();
}
let should_run = self.run_if_enabled();
let mut proc_res = self.compile_test(should_run, Emit::None);
if !proc_res.status.success() {
self.fatal_proc_rec("compilation failed!", &proc_res);
}
if let WillExecute::Disabled = should_run {
return;
}
let mut new_config = self.config.clone();
new_config.runner = new_config.valgrind_path.clone();
let new_cx = TestCx { config: &new_config, ..*self };
proc_res = new_cx.exec_compiled_test();
if !proc_res.status.success() {
self.fatal_proc_rec("test run failed!", &proc_res);
}
}
}

View file

@ -1,5 +1,8 @@
use super::header::extract_llvm_version;
use super::*;
use std::ffi::OsString;
use crate::debuggers::{extract_gdb_version, extract_lldb_version};
use crate::header::extract_llvm_version;
use crate::is_test;
#[test]
fn test_extract_gdb_version() {

View file

@ -56,6 +56,7 @@ pub(crate) fn dump_covfun_mappings(
expression_resolver.push_operands(lhs, rhs);
}
let mut max_counter = None;
for i in 0..num_files {
let num_mappings = parser.read_uleb128_u32()?;
println!("Number of file {i} mappings: {num_mappings}");
@ -63,6 +64,11 @@ pub(crate) fn dump_covfun_mappings(
for _ in 0..num_mappings {
let (kind, region) = parser.read_mapping_kind_and_region()?;
println!("- {kind:?} at {region:?}");
kind.for_each_term(|term| {
if let CovTerm::Counter(n) = term {
max_counter = max_counter.max(Some(n));
}
});
match kind {
// Also print expression mappings in resolved form.
@ -83,6 +89,16 @@ pub(crate) fn dump_covfun_mappings(
}
parser.ensure_empty()?;
// Printing the highest counter ID seen in the functions mappings makes
// it easier to determine whether a change to coverage instrumentation
// has increased or decreased the number of physical counters needed.
// (It's possible for the generated code to have more counters that
// aren't used by any mappings, but that should hopefully be rare.)
println!("Highest counter ID seen: {}", match max_counter {
Some(id) => format!("c{id}"),
None => "(none)".to_owned(),
});
println!();
}
Ok(())
@ -271,6 +287,32 @@ enum MappingKind {
},
}
impl MappingKind {
fn for_each_term(&self, mut callback: impl FnMut(CovTerm)) {
match *self {
Self::Code(term) => callback(term),
Self::Gap(term) => callback(term),
Self::Expansion(_id) => {}
Self::Skip => {}
Self::Branch { r#true, r#false } => {
callback(r#true);
callback(r#false);
}
Self::MCDCBranch {
r#true,
r#false,
condition_id: _,
true_next_id: _,
false_next_id: _,
} => {
callback(r#true);
callback(r#false);
}
Self::MCDCDecision { bitmap_idx: _, conditions_num: _ } => {}
}
}
}
struct MappingRegion {
/// Offset of this region's start line, relative to the *start line* of
/// the *previous mapping* (or 0). Line numbers are 1-based.

View file

@ -418,7 +418,7 @@ impl<'a> Validator<'a> {
} else if !self.missing_ids.contains(id) {
self.missing_ids.insert(id);
let sels = json_find::find_selector(&self.krate_json, &Value::String(id.0.clone()));
let sels = json_find::find_selector(&self.krate_json, &Value::Number(id.0.into()));
assert_ne!(sels.len(), 0);
self.fail(id, ErrorKind::NotFound(sels))

View file

@ -15,24 +15,20 @@ fn check(krate: &Crate, errs: &[Error]) {
assert_eq!(errs, &validator.errs[..]);
}
fn id(s: &str) -> Id {
Id(s.to_owned())
}
#[test]
fn errors_on_missing_links() {
let k = Crate {
root: id("0"),
root: Id(0),
crate_version: None,
includes_private: false,
index: FxHashMap::from_iter([(id("0"), Item {
index: FxHashMap::from_iter([(Id(0), Item {
name: Some("root".to_owned()),
id: id(""),
id: Id(0),
crate_id: 0,
span: None,
visibility: Visibility::Public,
docs: None,
links: FxHashMap::from_iter([("Not Found".to_owned(), id("1"))]),
links: FxHashMap::from_iter([("Not Found".to_owned(), Id(1))]),
attrs: vec![],
deprecation: None,
inner: ItemEnum::Module(Module { is_crate: true, items: vec![], is_stripped: false }),
@ -49,7 +45,7 @@ fn errors_on_missing_links() {
SelectorPart::Field("links".to_owned()),
SelectorPart::Field("Not Found".to_owned()),
]]),
id: id("1"),
id: Id(1),
}]);
}
@ -58,28 +54,28 @@ fn errors_on_missing_links() {
#[test]
fn errors_on_local_in_paths_and_not_index() {
let krate = Crate {
root: id("0:0:1572"),
root: Id(0),
crate_version: None,
includes_private: false,
index: FxHashMap::from_iter([
(id("0:0:1572"), Item {
id: id("0:0:1572"),
(Id(0), Item {
id: Id(0),
crate_id: 0,
name: Some("microcore".to_owned()),
span: None,
visibility: Visibility::Public,
docs: None,
links: FxHashMap::from_iter([(("prim@i32".to_owned(), id("0:1:1571")))]),
links: FxHashMap::from_iter([(("prim@i32".to_owned(), Id(2)))]),
attrs: Vec::new(),
deprecation: None,
inner: ItemEnum::Module(Module {
is_crate: true,
items: vec![id("0:1:717")],
items: vec![Id(1)],
is_stripped: false,
}),
}),
(id("0:1:717"), Item {
id: id("0:1:717"),
(Id(1), Item {
id: Id(1),
crate_id: 0,
name: Some("i32".to_owned()),
span: None,
@ -91,7 +87,7 @@ fn errors_on_local_in_paths_and_not_index() {
inner: ItemEnum::Primitive(Primitive { name: "i32".to_owned(), impls: vec![] }),
}),
]),
paths: FxHashMap::from_iter([(id("0:1:1571"), ItemSummary {
paths: FxHashMap::from_iter([(Id(2), ItemSummary {
crate_id: 0,
path: vec!["microcore".to_owned(), "i32".to_owned()],
kind: ItemKind::Primitive,
@ -101,7 +97,7 @@ fn errors_on_local_in_paths_and_not_index() {
};
check(&krate, &[Error {
id: id("0:1:1571"),
id: Id(2),
kind: ErrorKind::Custom("Id for local item in `paths` but not in `index`".to_owned()),
}]);
}
@ -110,11 +106,11 @@ fn errors_on_local_in_paths_and_not_index() {
#[should_panic = "LOCAL_CRATE_ID is wrong"]
fn checks_local_crate_id_is_correct() {
let krate = Crate {
root: id("root"),
root: Id(0),
crate_version: None,
includes_private: false,
index: FxHashMap::from_iter([(id("root"), Item {
id: id("root"),
index: FxHashMap::from_iter([(Id(0), Item {
id: Id(0),
crate_id: LOCAL_CRATE_ID.wrapping_add(1),
name: Some("irrelavent".to_owned()),
span: None,

View file

@ -473,14 +473,14 @@ pub fn report_leaks<'tcx>(
leaks: Vec<(AllocId, MemoryKind, Allocation<Provenance, AllocExtra<'tcx>, MiriAllocBytes>)>,
) {
let mut any_pruned = false;
for (id, kind, mut alloc) in leaks {
for (id, kind, alloc) in leaks {
let mut title = format!(
"memory leaked: {id:?} ({}, size: {:?}, align: {:?})",
kind,
alloc.size().bytes(),
alloc.align.bytes()
);
let Some(backtrace) = alloc.extra.backtrace.take() else {
let Some(backtrace) = alloc.extra.backtrace else {
ecx.tcx.dcx().err(title);
continue;
};

View file

@ -473,7 +473,7 @@ pub fn eval_entry<'tcx>(
}
// Check for memory leaks.
info!("Additional static roots: {:?}", ecx.machine.static_roots);
let leaks = ecx.find_leaked_allocations(&ecx.machine.static_roots);
let leaks = ecx.take_leaked_allocations(|ecx| &ecx.machine.static_roots);
if !leaks.is_empty() {
report_leaks(&ecx, leaks);
tcx.dcx().note("set `MIRIFLAGS=-Zmiri-ignore-leaks` to disable this check");

View file

@ -295,6 +295,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
this.write_scalar(res, dest)?;
}
"fmuladdf32" => {
let [a, b, c] = check_arg_count(args)?;
let a = this.read_scalar(a)?.to_f32()?;
let b = this.read_scalar(b)?.to_f32()?;
let c = this.read_scalar(c)?.to_f32()?;
let fuse: bool = this.machine.rng.get_mut().gen();
let res = if fuse {
// FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
} else {
((a * b).value + c).value
};
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"fmuladdf64" => {
let [a, b, c] = check_arg_count(args)?;
let a = this.read_scalar(a)?.to_f64()?;
let b = this.read_scalar(b)?.to_f64()?;
let c = this.read_scalar(c)?.to_f64()?;
let fuse: bool = this.machine.rng.get_mut().gen();
let res = if fuse {
// FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
} else {
((a * b).value + c).value
};
let res = this.adjust_nan(res, &[a, b, c]);
this.write_scalar(res, dest)?;
}
"powf32" => {
let [f1, f2] = check_arg_count(args)?;
let f1 = this.read_scalar(f1)?.to_f32()?;

View file

@ -1,6 +1,5 @@
#![feature(rustc_private)]
#![feature(cell_update)]
#![feature(const_option)]
#![feature(float_gamma)]
#![feature(map_try_insert)]
#![feature(never_type)]

View file

@ -321,7 +321,7 @@ impl ProvenanceExtra {
}
/// Extra per-allocation data
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct AllocExtra<'tcx> {
/// Global state of the borrow tracker, if enabled.
pub borrow_tracker: Option<borrow_tracker::AllocState>,
@ -338,6 +338,14 @@ pub struct AllocExtra<'tcx> {
pub backtrace: Option<Vec<FrameInfo<'tcx>>>,
}
// We need a `Clone` impl because the machine passes `Allocation` through `Cow`...
// but that should never end up actually cloning our `AllocExtra`.
impl<'tcx> Clone for AllocExtra<'tcx> {
fn clone(&self) -> Self {
panic!("our allocations should never be cloned");
}
}
impl VisitProvenance for AllocExtra<'_> {
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
let AllocExtra { borrow_tracker, data_race, weak_memory, backtrace: _ } = self;

View file

@ -30,6 +30,7 @@ fn main() {
libm();
test_fast();
test_algebraic();
test_fmuladd();
}
trait Float: Copy + PartialEq + Debug {
@ -1041,3 +1042,20 @@ fn test_algebraic() {
test_operations_f32(11., 2.);
test_operations_f32(10., 15.);
}
fn test_fmuladd() {
use std::intrinsics::{fmuladdf32, fmuladdf64};
#[inline(never)]
pub fn test_operations_f32(a: f32, b: f32, c: f32) {
assert_approx_eq!(unsafe { fmuladdf32(a, b, c) }, a * b + c);
}
#[inline(never)]
pub fn test_operations_f64(a: f64, b: f64, c: f64) {
assert_approx_eq!(unsafe { fmuladdf64(a, b, c) }, a * b + c);
}
test_operations_f32(0.1, 0.2, 0.3);
test_operations_f64(1.1, 1.2, 1.3);
}

View file

@ -0,0 +1,44 @@
#![feature(core_intrinsics)]
use std::intrinsics::{fmuladdf32, fmuladdf64};
fn main() {
let mut saw_zero = false;
let mut saw_nonzero = false;
for _ in 0..50 {
let a = std::hint::black_box(0.1_f64);
let b = std::hint::black_box(0.2);
let c = std::hint::black_box(-a * b);
// It is unspecified whether the following operation is fused or not. The
// following evaluates to 0.0 if unfused, and nonzero (-1.66e-18) if fused.
let x = unsafe { fmuladdf64(a, b, c) };
if x == 0.0 {
saw_zero = true;
} else {
saw_nonzero = true;
}
}
assert!(
saw_zero && saw_nonzero,
"`fmuladdf64` failed to be evaluated as both fused and unfused"
);
let mut saw_zero = false;
let mut saw_nonzero = false;
for _ in 0..50 {
let a = std::hint::black_box(0.1_f32);
let b = std::hint::black_box(0.2);
let c = std::hint::black_box(-a * b);
// It is unspecified whether the following operation is fused or not. The
// following evaluates to 0.0 if unfused, and nonzero (-8.1956386e-10) if fused.
let x = unsafe { fmuladdf32(a, b, c) };
if x == 0.0 {
saw_zero = true;
} else {
saw_nonzero = true;
}
}
assert!(
saw_zero && saw_nonzero,
"`fmuladdf32` failed to be evaluated as both fused and unfused"
);
}

View file

@ -1,5 +1,7 @@
// Various tests ensuring that underscore patterns really just construct the place, but don't check its contents.
#![feature(strict_provenance)]
#![feature(never_type)]
use std::ptr;
fn main() {
@ -9,6 +11,7 @@ fn main() {
invalid_let();
dangling_let_type_annotation();
invalid_let_type_annotation();
never();
}
fn dangling_match() {
@ -34,6 +37,13 @@ fn invalid_match() {
_ => {}
}
}
unsafe {
let x: Uninit<!> = Uninit { uninit: () };
match x.value {
_ => {}
}
}
}
fn dangling_let() {
@ -41,6 +51,11 @@ fn dangling_let() {
let ptr = ptr::without_provenance::<bool>(0x40);
let _ = *ptr;
}
unsafe {
let ptr = ptr::without_provenance::<!>(0x40);
let _ = *ptr;
}
}
fn invalid_let() {
@ -49,6 +64,12 @@ fn invalid_let() {
let ptr = ptr::addr_of!(val).cast::<bool>();
let _ = *ptr;
}
unsafe {
let val = 3u8;
let ptr = ptr::addr_of!(val).cast::<!>();
let _ = *ptr;
}
}
// Adding a type annotation used to change how MIR is generated, make sure we cover both cases.
@ -57,6 +78,11 @@ fn dangling_let_type_annotation() {
let ptr = ptr::without_provenance::<bool>(0x40);
let _: bool = *ptr;
}
unsafe {
let ptr = ptr::without_provenance::<!>(0x40);
let _: ! = *ptr;
}
}
fn invalid_let_type_annotation() {
@ -65,7 +91,28 @@ fn invalid_let_type_annotation() {
let ptr = ptr::addr_of!(val).cast::<bool>();
let _: bool = *ptr;
}
unsafe {
let val = 3u8;
let ptr = ptr::addr_of!(val).cast::<!>();
let _: ! = *ptr;
}
}
// FIXME: we should also test `!`, not just `bool` -- but that s currently buggy:
// https://github.com/rust-lang/rust/issues/117288
// Regression test from <https://github.com/rust-lang/rust/issues/117288>.
fn never() {
unsafe {
let x = 3u8;
let x: *const ! = &x as *const u8 as *const _;
let _: ! = *x;
}
// Without a type annotation, make sure we don't implicitly coerce `!` to `()`
// when we do the noop `*x` (as that would require a `!` *value*, creating
// which is UB).
unsafe {
let x = 3u8;
let x: *const ! = &x as *const u8 as *const _;
let _ = *x;
}
}

View file

@ -96,7 +96,6 @@ llvm-config = "{llvm_config}"
"tests/incremental",
"tests/mir-opt",
"tests/pretty",
"tests/run-pass-valgrind",
"tests/ui",
"tests/crashes",
];

View file

@ -1,28 +1,23 @@
#[cfg(test)]
mod tests {
use crate::*;
use crate::diff;
#[test]
fn test_diff() {
let expected = "foo\nbar\nbaz\n";
let actual = "foo\nbar\nbaz\n";
#[test]
fn test_diff() {
let expected = "foo\nbar\nbaz\n";
let actual = "foo\nbar\nbaz\n";
diff().expected_text("EXPECTED_TEXT", expected).actual_text("ACTUAL_TEXT", actual).run();
}
#[test]
fn test_should_panic() {
let expected = "foo\nbar\nbaz\n";
let actual = "foo\nbaz\nbar\n";
let output = std::panic::catch_unwind(|| {
diff().expected_text("EXPECTED_TEXT", expected).actual_text("ACTUAL_TEXT", actual).run();
}
})
.unwrap_err();
#[test]
fn test_should_panic() {
let expected = "foo\nbar\nbaz\n";
let actual = "foo\nbaz\nbar\n";
let output = std::panic::catch_unwind(|| {
diff()
.expected_text("EXPECTED_TEXT", expected)
.actual_text("ACTUAL_TEXT", actual)
.run();
})
.unwrap_err();
let expected_output = "\
let expected_output = "\
test failed: `EXPECTED_TEXT` is different from `ACTUAL_TEXT`
--- EXPECTED_TEXT
@ -34,28 +29,27 @@ test failed: `EXPECTED_TEXT` is different from `ACTUAL_TEXT`
-baz
";
assert_eq!(output.downcast_ref::<String>().unwrap(), expected_output);
}
assert_eq!(output.downcast_ref::<String>().unwrap(), expected_output);
}
#[test]
fn test_normalize() {
let expected = "
#[test]
fn test_normalize() {
let expected = "
running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
";
let actual = "
let actual = "
running 2 tests
..
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
";
diff()
.expected_text("EXPECTED_TEXT", expected)
.actual_text("ACTUAL_TEXT", actual)
.normalize(r#"finished in \d+\.\d+s"#, "finished in $$TIME")
.run();
}
diff()
.expected_text("EXPECTED_TEXT", expected)
.actual_text("ACTUAL_TEXT", actual)
.normalize(r#"finished in \d+\.\d+s"#, "finished in $$TIME")
.run();
}

View file

@ -70,6 +70,30 @@ macro_rules! impl_common_helpers {
self
}
/// Configuration for the child processs standard input (stdin) handle.
///
/// See [`std::process::Command::stdin`].
pub fn stdin<T: Into<::std::process::Stdio>>(&mut self, cfg: T) -> &mut Self {
self.cmd.stdin(cfg);
self
}
/// Configuration for the child processs standard output (stdout) handle.
///
/// See [`std::process::Command::stdout`].
pub fn stdout<T: Into<::std::process::Stdio>>(&mut self, cfg: T) -> &mut Self {
self.cmd.stdout(cfg);
self
}
/// Configuration for the child processs standard error (stderr) handle.
///
/// See [`std::process::Command::stderr`].
pub fn stderr<T: Into<::std::process::Stdio>>(&mut self, cfg: T) -> &mut Self {
self.cmd.stderr(cfg);
self
}
/// Inspect what the underlying [`Command`] is up to the
/// current construction.
pub fn inspect<I>(&mut self, inspector: I) -> &mut Self

View file

@ -16,7 +16,7 @@ env:
RUSTFLAGS: "-D warnings -W unreachable-pub"
RUSTUP_MAX_RETRIES: 10
FETCH_DEPTH: 0 # pull in the tags for the version string
MACOSX_DEPLOYMENT_TARGET: 10.15
MACOSX_DEPLOYMENT_TARGET: 13.0
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
@ -43,10 +43,10 @@ jobs:
- os: ubuntu-20.04
target: arm-unknown-linux-gnueabihf
code-target: linux-armhf
- os: macos-12
- os: macos-13
target: x86_64-apple-darwin
code-target: darwin-x64
- os: macos-12
- os: macos-13
target: aarch64-apple-darwin
code-target: darwin-arm64

View file

@ -145,9 +145,12 @@ dependencies = [
[[package]]
name = "cc"
version = "1.1.10"
version = "1.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292"
checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0"
dependencies = [
"shlex",
]
[[package]]
name = "cfg"
@ -1852,6 +1855,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.13.2"

View file

@ -4,7 +4,7 @@ exclude = ["crates/proc-macro-srv/proc-macro-test/imp"]
resolver = "2"
[workspace.package]
rust-version = "1.80"
rust-version = "1.81"
edition = "2021"
license = "MIT OR Apache-2.0"
authors = ["rust-analyzer team"]

View file

@ -49,6 +49,10 @@ impl CfgOptions {
cfg.fold(&|atom| self.enabled.contains(atom))
}
pub fn check_atom(&self, cfg: &CfgAtom) -> bool {
self.enabled.contains(cfg)
}
pub fn insert_atom(&mut self, key: Symbol) {
self.enabled.insert(CfgAtom::Flag(key));
}

View file

@ -36,7 +36,7 @@ macro_rules! f {
}
struct#0:1@58..64#1# MyTraitMap2#0:2@31..42#0# {#0:1@72..73#1#
map#0:1@86..89#1#:#0:1@89..90#1# #0:1@89..90#1#::#0:1@91..92#1#std#0:1@93..96#1#::#0:1@96..97#1#collections#0:1@98..109#1#::#0:1@109..110#1#HashSet#0:1@111..118#1#<#0:1@118..119#1#(#0:1@119..120#1#)#0:1@120..121#1#>#0:1@121..122#1#,#0:1@122..123#1#
map#0:1@86..89#1#:#0:1@89..90#1# #0:1@89..90#1#::#0:1@91..93#1#std#0:1@93..96#1#::#0:1@96..98#1#collections#0:1@98..109#1#::#0:1@109..111#1#HashSet#0:1@111..118#1#<#0:1@118..119#1#(#0:1@119..120#1#)#0:1@120..121#1#>#0:1@121..122#1#,#0:1@122..123#1#
}#0:1@132..133#1#
"#]],
);

View file

@ -6,7 +6,7 @@
use std::{cmp::Ordering, iter, mem, ops::Not};
use base_db::{CrateId, CrateOrigin, Dependency, LangCrateOrigin};
use cfg::{CfgExpr, CfgOptions};
use cfg::{CfgAtom, CfgExpr, CfgOptions};
use either::Either;
use hir_expand::{
attrs::{Attr, AttrId},
@ -1324,13 +1324,21 @@ impl DefCollector<'_> {
};
// Skip #[test]/#[bench] expansion, which would merely result in more memory usage
// due to duplicating functions into macro expansions
// due to duplicating functions into macro expansions, but only if `cfg(test)` is active,
// otherwise they are expanded to nothing and this can impact e.g. diagnostics (due to things
// being cfg'ed out).
// Ideally we will just expand them to nothing here. But we are only collecting macro calls,
// not expanding them, so we have no way to do that.
if matches!(
def.kind,
MacroDefKind::BuiltInAttr(_, expander)
if expander.is_test() || expander.is_bench()
) {
return recollect_without(self);
let test_is_active =
self.cfg_options.check_atom(&CfgAtom::Flag(sym::test.clone()));
if test_is_active {
return recollect_without(self);
}
}
let call_id = || {

View file

@ -4,6 +4,8 @@ use span::{MacroCallId, Span};
use crate::{db::ExpandDatabase, name, tt, ExpandResult, MacroCallKind};
use super::quote;
macro_rules! register_builtin {
($(($name:ident, $variant:ident) => $expand:ident),* ) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -52,15 +54,15 @@ impl BuiltinAttrExpander {
}
register_builtin! {
(bench, Bench) => dummy_attr_expand,
(bench, Bench) => dummy_gate_test_expand,
(cfg_accessible, CfgAccessible) => dummy_attr_expand,
(cfg_eval, CfgEval) => dummy_attr_expand,
(derive, Derive) => derive_expand,
// derive const is equivalent to derive for our proposes.
(derive_const, DeriveConst) => derive_expand,
(global_allocator, GlobalAllocator) => dummy_attr_expand,
(test, Test) => dummy_attr_expand,
(test_case, TestCase) => dummy_attr_expand
(test, Test) => dummy_gate_test_expand,
(test_case, TestCase) => dummy_gate_test_expand
}
pub fn find_builtin_attr(ident: &name::Name) -> Option<BuiltinAttrExpander> {
@ -76,6 +78,19 @@ fn dummy_attr_expand(
ExpandResult::ok(tt.clone())
}
fn dummy_gate_test_expand(
_db: &dyn ExpandDatabase,
_id: MacroCallId,
tt: &tt::Subtree,
span: Span,
) -> ExpandResult<tt::Subtree> {
let result = quote::quote! { span=>
#[cfg(test)]
#tt
};
ExpandResult::ok(result)
}
/// We generate a very specific expansion here, as we do not actually expand the `#[derive]` attribute
/// itself in name res, but we do want to expand it to something for the IDE layer, so that the input
/// derive attributes can be downmapped, and resolved as proper paths.

View file

@ -16,7 +16,10 @@ use crate::{
cfg_process,
declarative::DeclarativeMacroExpander,
fixup::{self, SyntaxFixupUndoInfo},
hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt},
hygiene::{
span_with_call_site_ctxt, span_with_def_site_ctxt, span_with_mixed_site_ctxt,
SyntaxContextExt as _,
},
proc_macro::ProcMacros,
span_map::{RealSpanMap, SpanMap, SpanMapRef},
tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander,
@ -300,14 +303,16 @@ pub fn expand_speculative(
token_tree_to_syntax_node(&speculative_expansion.value, expand_to, loc.def.edition);
let syntax_node = node.syntax_node();
let token = rev_tmap
let (token, _) = rev_tmap
.ranges_with_span(span_map.span_for_range(token_to_map.text_range()))
.filter_map(|range| syntax_node.covering_element(range).into_token())
.min_by_key(|t| {
// prefer tokens of the same kind and text
.filter_map(|(range, ctx)| syntax_node.covering_element(range).into_token().zip(Some(ctx)))
.min_by_key(|(t, ctx)| {
// prefer tokens of the same kind and text, as well as non opaque marked ones
// Note the inversion of the score here, as we want to prefer the first token in case
// of all tokens having the same score
(t.kind() != token_to_map.kind()) as u8 + 2 * ((t.text() != token_to_map.text()) as u8)
ctx.is_opaque(db) as u8
+ 2 * (t.kind() != token_to_map.kind()) as u8
+ 4 * ((t.text() != token_to_map.text()) as u8)
})?;
Some((node.syntax_node(), token))
}

View file

@ -151,6 +151,7 @@ pub trait SyntaxContextExt {
fn remove_mark(&mut self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency);
fn outer_mark(self, db: &dyn ExpandDatabase) -> (Option<MacroCallId>, Transparency);
fn marks(self, db: &dyn ExpandDatabase) -> Vec<(MacroCallId, Transparency)>;
fn is_opaque(self, db: &dyn ExpandDatabase) -> bool;
}
impl SyntaxContextExt for SyntaxContextId {
@ -177,6 +178,9 @@ impl SyntaxContextExt for SyntaxContextId {
marks.reverse();
marks
}
fn is_opaque(self, db: &dyn ExpandDatabase) -> bool {
!self.is_root() && db.lookup_intern_syntax_context(self).outer_transparency.is_opaque()
}
}
// FIXME: Make this a SyntaxContextExt method once we have RPIT

View file

@ -25,6 +25,7 @@ mod prettify_macro_expansion_;
use attrs::collect_attrs;
use rustc_hash::FxHashMap;
use stdx::TupleExt;
use triomphe::Arc;
use std::hash::Hash;
@ -772,14 +773,15 @@ impl ExpansionInfo {
/// Maps the passed in file range down into a macro expansion if it is the input to a macro call.
///
/// Note this does a linear search through the entire backing vector of the spanmap.
// FIXME: Consider adding a reverse map to ExpansionInfo to get rid of the linear search which
// potentially results in quadratic look ups (notably this might improve semantic highlighting perf)
pub fn map_range_down_exact(
&self,
span: Span,
) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + '_>> {
let tokens = self
.exp_map
.ranges_with_span_exact(span)
.flat_map(move |range| self.expanded.value.covering_element(range).into_token());
) -> Option<InMacroFile<impl Iterator<Item = (SyntaxToken, SyntaxContextId)> + '_>> {
let tokens = self.exp_map.ranges_with_span_exact(span).flat_map(move |(range, ctx)| {
self.expanded.value.covering_element(range).into_token().zip(Some(ctx))
});
Some(InMacroFile::new(self.expanded.file_id, tokens))
}
@ -791,11 +793,10 @@ impl ExpansionInfo {
pub fn map_range_down(
&self,
span: Span,
) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + '_>> {
let tokens = self
.exp_map
.ranges_with_span(span)
.flat_map(move |range| self.expanded.value.covering_element(range).into_token());
) -> Option<InMacroFile<impl Iterator<Item = (SyntaxToken, SyntaxContextId)> + '_>> {
let tokens = self.exp_map.ranges_with_span(span).flat_map(move |(range, ctx)| {
self.expanded.value.covering_element(range).into_token().zip(Some(ctx))
});
Some(InMacroFile::new(self.expanded.file_id, tokens))
}
@ -845,7 +846,8 @@ impl ExpansionInfo {
self.arg.file_id,
arg_map
.ranges_with_span_exact(span)
.filter(|range| range.intersect(arg_range).is_some())
.filter(|(range, _)| range.intersect(arg_range).is_some())
.map(TupleExt::head)
.collect(),
)
}

View file

@ -382,8 +382,9 @@ impl chalk_solve::RustIrDatabase<Interner> for ChalkContext<'_> {
}
fn is_object_safe(&self, trait_id: chalk_ir::TraitId<Interner>) -> bool {
// FIXME: When cargo is updated, change to dyn_compatibility
let trait_ = from_chalk_trait_id(trait_id);
crate::object_safety::object_safety(self.db, trait_).is_none()
crate::dyn_compatibility::dyn_compatibility(self.db, trait_).is_none()
}
fn closure_kind(

View file

@ -20,11 +20,11 @@ use triomphe::Arc;
use crate::{
chalk_db,
consteval::ConstEvalError,
dyn_compatibility::DynCompatibilityViolation,
layout::{Layout, LayoutError},
lower::{GenericDefaults, GenericPredicates},
method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
mir::{BorrowckResult, MirBody, MirLowerError},
object_safety::ObjectSafetyViolation,
Binders, ClosureId, Const, FnDefId, ImplTraitId, ImplTraits, InferenceResult, Interner,
PolyFnSig, Substitution, TraitEnvironment, TraitRef, Ty, TyDefId, ValueTyDefId,
};
@ -108,8 +108,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[salsa::invoke(crate::layout::target_data_layout_query)]
fn target_data_layout(&self, krate: CrateId) -> Result<Arc<TargetDataLayout>, Arc<str>>;
#[salsa::invoke(crate::object_safety::object_safety_of_trait_query)]
fn object_safety_of_trait(&self, trait_: TraitId) -> Option<ObjectSafetyViolation>;
#[salsa::invoke(crate::dyn_compatibility::dyn_compatibility_of_trait_query)]
fn dyn_compatibility_of_trait(&self, trait_: TraitId) -> Option<DynCompatibilityViolation>;
#[salsa::invoke(crate::lower::ty_query)]
#[salsa::cycle(crate::lower::ty_recover)]
@ -280,8 +280,8 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
}
#[test]
fn hir_database_is_object_safe() {
fn _assert_object_safe(_: &dyn HirDatabase) {}
fn hir_database_is_dyn_compatible() {
fn _assert_dyn_compatible(_: &dyn HirDatabase) {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View file

@ -58,7 +58,7 @@ impl fmt::Display for CaseType {
let repr = match self {
CaseType::LowerSnakeCase => "snake_case",
CaseType::UpperSnakeCase => "UPPER_SNAKE_CASE",
CaseType::UpperCamelCase => "CamelCase",
CaseType::UpperCamelCase => "UpperCamelCase",
};
repr.fmt(f)

View file

@ -111,7 +111,7 @@ mod tests {
check(to_lower_snake_case, "lower_snake_case", expect![[""]]);
check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]);
check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]);
check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]);
check(to_lower_snake_case, "UpperCamelCase", expect![["upper_camel_case"]]);
check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]);
check(to_lower_snake_case, "a", expect![[""]]);
check(to_lower_snake_case, "abc", expect![[""]]);
@ -121,8 +121,8 @@ mod tests {
#[test]
fn test_to_camel_case() {
check(to_camel_case, "CamelCase", expect![[""]]);
check(to_camel_case, "CamelCase_", expect![[""]]);
check(to_camel_case, "UpperCamelCase", expect![[""]]);
check(to_camel_case, "UpperCamelCase_", expect![[""]]);
check(to_camel_case, "_CamelCase", expect![[""]]);
check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]);
check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]);
@ -143,7 +143,7 @@ mod tests {
check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]);
check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]);
check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]);
check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]);
check(to_upper_snake_case, "UpperCamelCase", expect![["UPPER_CAMEL_CASE"]]);
check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]);
check(to_upper_snake_case, "A", expect![[""]]);
check(to_upper_snake_case, "ABC", expect![[""]]);

View file

@ -1,4 +1,4 @@
//! Compute the object-safety of a trait
//! Compute the dyn-compatibility of a trait
use std::ops::ControlFlow;
@ -28,14 +28,14 @@ use crate::{
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ObjectSafetyViolation {
pub enum DynCompatibilityViolation {
SizedSelf,
SelfReferential,
Method(FunctionId, MethodViolationCode),
AssocConst(ConstId),
GAT(TypeAliasId),
// This doesn't exist in rustc, but added for better visualization
HasNonSafeSuperTrait(TraitId),
HasNonCompatibleSuperTrait(TraitId),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -50,70 +50,73 @@ pub enum MethodViolationCode {
UndispatchableReceiver,
}
pub fn object_safety(db: &dyn HirDatabase, trait_: TraitId) -> Option<ObjectSafetyViolation> {
pub fn dyn_compatibility(
db: &dyn HirDatabase,
trait_: TraitId,
) -> Option<DynCompatibilityViolation> {
for super_trait in all_super_traits(db.upcast(), trait_).into_iter().skip(1).rev() {
if db.object_safety_of_trait(super_trait).is_some() {
return Some(ObjectSafetyViolation::HasNonSafeSuperTrait(super_trait));
if db.dyn_compatibility_of_trait(super_trait).is_some() {
return Some(DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait));
}
}
db.object_safety_of_trait(trait_)
db.dyn_compatibility_of_trait(trait_)
}
pub fn object_safety_with_callback<F>(
pub fn dyn_compatibility_with_callback<F>(
db: &dyn HirDatabase,
trait_: TraitId,
cb: &mut F,
) -> ControlFlow<()>
where
F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>,
F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
{
for super_trait in all_super_traits(db.upcast(), trait_).into_iter().skip(1).rev() {
if db.object_safety_of_trait(super_trait).is_some() {
cb(ObjectSafetyViolation::HasNonSafeSuperTrait(trait_))?;
if db.dyn_compatibility_of_trait(super_trait).is_some() {
cb(DynCompatibilityViolation::HasNonCompatibleSuperTrait(trait_))?;
}
}
object_safety_of_trait_with_callback(db, trait_, cb)
dyn_compatibility_of_trait_with_callback(db, trait_, cb)
}
pub fn object_safety_of_trait_with_callback<F>(
pub fn dyn_compatibility_of_trait_with_callback<F>(
db: &dyn HirDatabase,
trait_: TraitId,
cb: &mut F,
) -> ControlFlow<()>
where
F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>,
F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
{
// Check whether this has a `Sized` bound
if generics_require_sized_self(db, trait_.into()) {
cb(ObjectSafetyViolation::SizedSelf)?;
cb(DynCompatibilityViolation::SizedSelf)?;
}
// Check if there exist bounds that referencing self
if predicates_reference_self(db, trait_) {
cb(ObjectSafetyViolation::SelfReferential)?;
cb(DynCompatibilityViolation::SelfReferential)?;
}
if bounds_reference_self(db, trait_) {
cb(ObjectSafetyViolation::SelfReferential)?;
cb(DynCompatibilityViolation::SelfReferential)?;
}
// rustc checks for non-lifetime binders here, but we don't support HRTB yet
let trait_data = db.trait_data(trait_);
for (_, assoc_item) in &trait_data.items {
object_safety_violation_for_assoc_item(db, trait_, *assoc_item, cb)?;
dyn_compatibility_violation_for_assoc_item(db, trait_, *assoc_item, cb)?;
}
ControlFlow::Continue(())
}
pub fn object_safety_of_trait_query(
pub fn dyn_compatibility_of_trait_query(
db: &dyn HirDatabase,
trait_: TraitId,
) -> Option<ObjectSafetyViolation> {
) -> Option<DynCompatibilityViolation> {
let mut res = None;
object_safety_of_trait_with_callback(db, trait_, &mut |osv| {
dyn_compatibility_of_trait_with_callback(db, trait_, &mut |osv| {
res = Some(osv);
ControlFlow::Break(())
});
@ -321,14 +324,14 @@ fn contains_illegal_self_type_reference<T: TypeVisitable<Interner>>(
t.visit_with(visitor.as_dyn(), outer_binder).is_break()
}
fn object_safety_violation_for_assoc_item<F>(
fn dyn_compatibility_violation_for_assoc_item<F>(
db: &dyn HirDatabase,
trait_: TraitId,
item: AssocItemId,
cb: &mut F,
) -> ControlFlow<()>
where
F: FnMut(ObjectSafetyViolation) -> ControlFlow<()>,
F: FnMut(DynCompatibilityViolation) -> ControlFlow<()>,
{
// Any item that has a `Self : Sized` requisite is otherwise
// exempt from the regulations.
@ -337,10 +340,10 @@ where
}
match item {
AssocItemId::ConstId(it) => cb(ObjectSafetyViolation::AssocConst(it)),
AssocItemId::ConstId(it) => cb(DynCompatibilityViolation::AssocConst(it)),
AssocItemId::FunctionId(it) => {
virtual_call_violations_for_method(db, trait_, it, &mut |mvc| {
cb(ObjectSafetyViolation::Method(it, mvc))
cb(DynCompatibilityViolation::Method(it, mvc))
})
}
AssocItemId::TypeAliasId(it) => {
@ -350,7 +353,7 @@ where
} else {
let generic_params = db.generic_params(item.into());
if !generic_params.is_empty() {
cb(ObjectSafetyViolation::GAT(it))
cb(DynCompatibilityViolation::GAT(it))
} else {
ControlFlow::Continue(())
}
@ -469,7 +472,7 @@ fn receiver_is_dispatchable(
return false;
};
// `self: Self` can't be dispatched on, but this is already considered object safe.
// `self: Self` can't be dispatched on, but this is already considered dyn compatible
// See rustc's comment on https://github.com/rust-lang/rust/blob/3f121b9461cce02a703a0e7e450568849dfaa074/compiler/rustc_trait_selection/src/traits/object_safety.rs#L433-L437
if sig
.skip_binders()

View file

@ -5,29 +5,29 @@ use rustc_hash::{FxHashMap, FxHashSet};
use syntax::ToSmolStr;
use test_fixture::WithFixture;
use crate::{object_safety::object_safety_with_callback, test_db::TestDB};
use crate::{dyn_compatibility::dyn_compatibility_with_callback, test_db::TestDB};
use super::{
DynCompatibilityViolation,
MethodViolationCode::{self, *},
ObjectSafetyViolation,
};
use ObjectSafetyViolationKind::*;
use DynCompatibilityViolationKind::*;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum ObjectSafetyViolationKind {
enum DynCompatibilityViolationKind {
SizedSelf,
SelfReferential,
Method(MethodViolationCode),
AssocConst,
GAT,
HasNonSafeSuperTrait,
HasNonCompatibleSuperTrait,
}
fn check_object_safety<'a>(
fn check_dyn_compatibility<'a>(
ra_fixture: &str,
expected: impl IntoIterator<Item = (&'a str, Vec<ObjectSafetyViolationKind>)>,
expected: impl IntoIterator<Item = (&'a str, Vec<DynCompatibilityViolationKind>)>,
) {
let mut expected: FxHashMap<_, _> =
expected.into_iter().map(|(id, osvs)| (id, FxHashSet::from_iter(osvs))).collect();
@ -53,18 +53,20 @@ fn check_object_safety<'a>(
continue;
};
let mut osvs = FxHashSet::default();
object_safety_with_callback(&db, trait_id, &mut |osv| {
dyn_compatibility_with_callback(&db, trait_id, &mut |osv| {
osvs.insert(match osv {
ObjectSafetyViolation::SizedSelf => SizedSelf,
ObjectSafetyViolation::SelfReferential => SelfReferential,
ObjectSafetyViolation::Method(_, mvc) => Method(mvc),
ObjectSafetyViolation::AssocConst(_) => AssocConst,
ObjectSafetyViolation::GAT(_) => GAT,
ObjectSafetyViolation::HasNonSafeSuperTrait(_) => HasNonSafeSuperTrait,
DynCompatibilityViolation::SizedSelf => SizedSelf,
DynCompatibilityViolation::SelfReferential => SelfReferential,
DynCompatibilityViolation::Method(_, mvc) => Method(mvc),
DynCompatibilityViolation::AssocConst(_) => AssocConst,
DynCompatibilityViolation::GAT(_) => GAT,
DynCompatibilityViolation::HasNonCompatibleSuperTrait(_) => {
HasNonCompatibleSuperTrait
}
});
ControlFlow::Continue(())
});
assert_eq!(osvs, expected, "Object safety violations for `{name}` do not match;");
assert_eq!(osvs, expected, "Dyn Compatibility violations for `{name}` do not match;");
}
let remains: Vec<_> = expected.keys().collect();
@ -73,7 +75,7 @@ fn check_object_safety<'a>(
#[test]
fn item_bounds_can_reference_self() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: eq
pub trait Foo {
@ -88,7 +90,7 @@ pub trait Foo {
#[test]
fn associated_consts() {
check_object_safety(
check_dyn_compatibility(
r#"
trait Bar {
const X: usize;
@ -100,7 +102,7 @@ trait Bar {
#[test]
fn bounds_reference_self() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: eq
trait X {
@ -113,7 +115,7 @@ trait X {
#[test]
fn by_value_self() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Bar {
@ -135,7 +137,7 @@ trait Quux {
#[test]
fn generic_methods() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Bar {
@ -157,7 +159,7 @@ trait Qax {
#[test]
fn mentions_self() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Bar {
@ -182,7 +184,7 @@ trait Quux {
#[test]
fn no_static() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Foo {
@ -195,7 +197,7 @@ trait Foo {
#[test]
fn sized_self() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Bar: Sized {
@ -205,7 +207,7 @@ trait Bar: Sized {
[("Bar", vec![SizedSelf])],
);
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Bar
@ -220,7 +222,7 @@ trait Bar
#[test]
fn supertrait_gat() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait GatTrait {
@ -229,13 +231,13 @@ trait GatTrait {
trait SuperTrait<T>: GatTrait {}
"#,
[("GatTrait", vec![GAT]), ("SuperTrait", vec![HasNonSafeSuperTrait])],
[("GatTrait", vec![GAT]), ("SuperTrait", vec![HasNonCompatibleSuperTrait])],
);
}
#[test]
fn supertrait_mentions_self() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Bar<T> {
@ -251,7 +253,7 @@ trait Baz : Bar<Self> {
#[test]
fn rustc_issue_19538() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Foo {
@ -260,13 +262,13 @@ trait Foo {
trait Bar: Foo {}
"#,
[("Foo", vec![Method(Generic)]), ("Bar", vec![HasNonSafeSuperTrait])],
[("Foo", vec![Method(Generic)]), ("Bar", vec![HasNonCompatibleSuperTrait])],
);
}
#[test]
fn rustc_issue_22040() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: fmt, eq, dispatch_from_dyn
use core::fmt::Debug;
@ -281,7 +283,7 @@ trait Expr: Debug + PartialEq {
#[test]
fn rustc_issue_102762() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: future, send, sync, dispatch_from_dyn, deref
use core::pin::Pin;
@ -313,7 +315,7 @@ pub trait Fetcher: Send + Sync {
#[test]
fn rustc_issue_102933() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: future, dispatch_from_dyn, deref
use core::future::Future;
@ -351,7 +353,7 @@ pub trait B2: Service<Response = i32> + B1 {
#[test]
fn rustc_issue_106247() {
check_object_safety(
check_dyn_compatibility(
r#"
//- minicore: sync, dispatch_from_dyn
pub trait Trait {
@ -363,8 +365,8 @@ pub trait Trait {
}
#[test]
fn std_error_is_object_safe() {
check_object_safety(
fn std_error_is_dyn_compatible() {
check_dyn_compatibility(
r#"
//- minicore: fmt, dispatch_from_dyn
trait Erased<'a>: 'a {}
@ -380,14 +382,14 @@ pub trait Error: core::fmt::Debug + core::fmt::Display {
}
#[test]
fn lifetime_gat_is_object_unsafe() {
check_object_safety(
fn lifetime_gat_is_dyn_incompatible() {
check_dyn_compatibility(
r#"
//- minicore: dispatch_from_dyn
trait Foo {
type Bar<'a>;
}
"#,
[("Foo", vec![ObjectSafetyViolationKind::GAT])],
[("Foo", vec![DynCompatibilityViolationKind::GAT])],
);
}

View file

@ -38,11 +38,11 @@ pub mod consteval;
pub mod db;
pub mod diagnostics;
pub mod display;
pub mod dyn_compatibility;
pub mod lang_items;
pub mod layout;
pub mod method_resolution;
pub mod mir;
pub mod object_safety;
pub mod primitive;
pub mod traits;

View file

@ -386,82 +386,91 @@ fn ever_initialized_map(
fn dfs(
db: &dyn HirDatabase,
body: &MirBody,
b: BasicBlockId,
l: LocalId,
stack: &mut Vec<BasicBlockId>,
result: &mut ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>>,
) {
let mut is_ever_initialized = result[b][l]; // It must be filled, as we use it as mark for dfs
let block = &body.basic_blocks[b];
for statement in &block.statements {
match &statement.kind {
StatementKind::Assign(p, _) => {
if p.projection.lookup(&body.projection_store).is_empty() && p.local == l {
while let Some(b) = stack.pop() {
let mut is_ever_initialized = result[b][l]; // It must be filled, as we use it as mark for dfs
let block = &body.basic_blocks[b];
for statement in &block.statements {
match &statement.kind {
StatementKind::Assign(p, _) => {
if p.projection.lookup(&body.projection_store).is_empty() && p.local == l {
is_ever_initialized = true;
}
}
StatementKind::StorageDead(p) => {
if *p == l {
is_ever_initialized = false;
}
}
StatementKind::Deinit(_)
| StatementKind::FakeRead(_)
| StatementKind::Nop
| StatementKind::StorageLive(_) => (),
}
}
let Some(terminator) = &block.terminator else {
never!(
"Terminator should be none only in construction.\nThe body:\n{}",
body.pretty_print(db)
);
return;
};
let mut process = |target, is_ever_initialized| {
if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized {
result[target].insert(l, is_ever_initialized);
stack.push(target);
}
};
match &terminator.kind {
TerminatorKind::Goto { target } => process(*target, is_ever_initialized),
TerminatorKind::SwitchInt { targets, .. } => {
targets.all_targets().iter().for_each(|&it| process(it, is_ever_initialized));
}
TerminatorKind::UnwindResume
| TerminatorKind::Abort
| TerminatorKind::Return
| TerminatorKind::Unreachable => (),
TerminatorKind::Call { target, cleanup, destination, .. } => {
if destination.projection.lookup(&body.projection_store).is_empty()
&& destination.local == l
{
is_ever_initialized = true;
}
target.iter().chain(cleanup).for_each(|&it| process(it, is_ever_initialized));
}
StatementKind::StorageDead(p) => {
if *p == l {
is_ever_initialized = false;
}
TerminatorKind::Drop { target, unwind, place: _ } => {
iter::once(target)
.chain(unwind)
.for_each(|&it| process(it, is_ever_initialized));
}
StatementKind::Deinit(_)
| StatementKind::FakeRead(_)
| StatementKind::Nop
| StatementKind::StorageLive(_) => (),
}
}
let Some(terminator) = &block.terminator else {
never!(
"Terminator should be none only in construction.\nThe body:\n{}",
body.pretty_print(db)
);
return;
};
let mut process = |target, is_ever_initialized| {
if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized {
result[target].insert(l, is_ever_initialized);
dfs(db, body, target, l, result);
}
};
match &terminator.kind {
TerminatorKind::Goto { target } => process(*target, is_ever_initialized),
TerminatorKind::SwitchInt { targets, .. } => {
targets.all_targets().iter().for_each(|&it| process(it, is_ever_initialized));
}
TerminatorKind::UnwindResume
| TerminatorKind::Abort
| TerminatorKind::Return
| TerminatorKind::Unreachable => (),
TerminatorKind::Call { target, cleanup, destination, .. } => {
if destination.projection.lookup(&body.projection_store).is_empty()
&& destination.local == l
{
is_ever_initialized = true;
TerminatorKind::DropAndReplace { .. }
| TerminatorKind::Assert { .. }
| TerminatorKind::Yield { .. }
| TerminatorKind::CoroutineDrop
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. } => {
never!("We don't emit these MIR terminators yet");
}
target.iter().chain(cleanup).for_each(|&it| process(it, is_ever_initialized));
}
TerminatorKind::Drop { target, unwind, place: _ } => {
iter::once(target).chain(unwind).for_each(|&it| process(it, is_ever_initialized));
}
TerminatorKind::DropAndReplace { .. }
| TerminatorKind::Assert { .. }
| TerminatorKind::Yield { .. }
| TerminatorKind::CoroutineDrop
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. } => {
never!("We don't emit these MIR terminators yet");
}
}
}
let mut stack = Vec::new();
for &l in &body.param_locals {
result[body.start_block].insert(l, true);
dfs(db, body, body.start_block, l, &mut result);
stack.clear();
stack.push(body.start_block);
dfs(db, body, l, &mut stack, &mut result);
}
for l in body.locals.iter().map(|it| it.0) {
db.unwind_if_cancelled();
if !result[body.start_block].contains_idx(l) {
result[body.start_block].insert(l, false);
dfs(db, body, body.start_block, l, &mut result);
stack.clear();
stack.push(body.start_block);
dfs(db, body, l, &mut stack, &mut result);
}
}
result

View file

@ -144,9 +144,9 @@ pub use {
hir_ty::{
consteval::ConstEvalError,
display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite},
dyn_compatibility::{DynCompatibilityViolation, MethodViolationCode},
layout::LayoutError,
mir::{MirEvalError, MirLowerError},
object_safety::{MethodViolationCode, ObjectSafetyViolation},
CastError, FnAbi, PointerCast, Safety,
},
// FIXME: Properly encapsulate mir
@ -497,10 +497,9 @@ impl Module {
/// Finds a parent module.
pub fn parent(self, db: &dyn HirDatabase) -> Option<Module> {
// FIXME: handle block expressions as modules (their parent is in a different DefMap)
let def_map = self.id.def_map(db.upcast());
let parent_id = def_map[self.id.local_id].parent?;
Some(Module { id: def_map.module_id(parent_id) })
let parent_id = def_map.containing_module(self.id.local_id)?;
Some(Module { id: parent_id })
}
/// Finds nearest non-block ancestor `Module` (`self` included).
@ -557,7 +556,7 @@ impl Module {
acc: &mut Vec<AnyDiagnostic>,
style_lints: bool,
) {
let _p = tracing::info_span!("Module::diagnostics", name = ?self.name(db)).entered();
let _p = tracing::info_span!("diagnostics", name = ?self.name(db)).entered();
let edition = db.crate_graph()[self.id.krate()].edition;
let def_map = self.id.def_map(db.upcast());
for diag in def_map.diagnostics() {
@ -2690,8 +2689,8 @@ impl Trait {
.count()
}
pub fn object_safety(&self, db: &dyn HirDatabase) -> Option<ObjectSafetyViolation> {
hir_ty::object_safety::object_safety(db, self.id)
pub fn dyn_compatibility(&self, db: &dyn HirDatabase) -> Option<DynCompatibilityViolation> {
hir_ty::dyn_compatibility::dyn_compatibility(db, self.id)
}
fn all_macro_calls(&self, db: &dyn HirDatabase) -> Box<[(AstId<ast::Item>, MacroCallId)]> {

View file

@ -24,6 +24,7 @@ use hir_expand::{
builtin::{BuiltinFnLikeExpander, EagerExpander},
db::ExpandDatabase,
files::InRealFile,
hygiene::SyntaxContextExt as _,
inert_attr_macro::find_builtin_attr_idx,
name::AsName,
FileRange, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
@ -32,13 +33,13 @@ use intern::Symbol;
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
use span::{EditionedFileId, FileId, HirFileIdRepr};
use span::{EditionedFileId, FileId, HirFileIdRepr, SyntaxContextId};
use stdx::TupleExt;
use syntax::{
algo::skip_trivia_token,
ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody, IsString as _},
match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken,
TextRange, TextSize,
ast::{self, HasAttrs as _, HasGenericParams, IsString as _},
AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange,
TextSize,
};
use crate::{
@ -608,7 +609,7 @@ impl<'db> SemanticsImpl<'db> {
let quote = string.open_quote_text_range()?;
let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?;
self.descend_into_macros_breakable(token, |token| {
self.descend_into_macros_breakable(token, |token, _| {
(|| {
let token = token.value;
let string = ast::String::cast(token)?;
@ -655,7 +656,7 @@ impl<'db> SemanticsImpl<'db> {
let original_string = ast::String::cast(original_token.clone())?;
let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?;
let quote = original_string.open_quote_text_range()?;
self.descend_into_macros_breakable(original_token, |token| {
self.descend_into_macros_breakable(original_token, |token, _| {
(|| {
let token = token.value;
self.resolve_offset_in_format_args(
@ -718,7 +719,7 @@ impl<'db> SemanticsImpl<'db> {
// node is just the token, so descend the token
self.descend_into_macros_impl(
InRealFile::new(file_id, first),
&mut |InFile { value, .. }| {
&mut |InFile { value, .. }, _ctx| {
if let Some(node) = value
.parent_ancestors()
.take_while(|it| it.text_range() == value.text_range())
@ -732,7 +733,7 @@ impl<'db> SemanticsImpl<'db> {
} else {
// Descend first and last token, then zip them to look for the node they belong to
let mut scratch: SmallVec<[_; 1]> = smallvec![];
self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token| {
self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token, _ctx| {
scratch.push(token);
CONTINUE_NO_BREAKS
});
@ -740,7 +741,7 @@ impl<'db> SemanticsImpl<'db> {
let mut scratch = scratch.into_iter();
self.descend_into_macros_impl(
InRealFile::new(file_id, last),
&mut |InFile { value: last, file_id: last_fid }| {
&mut |InFile { value: last, file_id: last_fid }, _ctx| {
if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
if first_fid == last_fid {
if let Some(p) = first.parent() {
@ -763,7 +764,9 @@ impl<'db> SemanticsImpl<'db> {
res
}
fn is_inside_macro_call(token: &SyntaxToken) -> bool {
// FIXME: This isn't quite right wrt to inner attributes
/// Does a syntactic traversal to check whether this token might be inside a macro call
pub fn might_be_inside_macro_call(&self, token: &SyntaxToken) -> bool {
token.parent_ancestors().any(|ancestor| {
if ast::MacroCall::can_cast(ancestor.kind()) {
return true;
@ -781,25 +784,14 @@ impl<'db> SemanticsImpl<'db> {
})
}
pub fn descend_into_macros_exact_if_in_macro(
&self,
token: SyntaxToken,
) -> SmallVec<[SyntaxToken; 1]> {
if Self::is_inside_macro_call(&token) {
self.descend_into_macros_exact(token)
} else {
smallvec![token]
}
}
pub fn descend_into_macros_cb(
&self,
token: SyntaxToken,
mut cb: impl FnMut(InFile<SyntaxToken>),
mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContextId),
) {
if let Ok(token) = self.wrap_token_infile(token).into_real_file() {
self.descend_into_macros_impl(token, &mut |t| {
cb(t);
self.descend_into_macros_impl(token, &mut |t, ctx| {
cb(t, ctx);
CONTINUE_NO_BREAKS
});
}
@ -808,7 +800,7 @@ impl<'db> SemanticsImpl<'db> {
pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
let mut res = smallvec![];
if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
self.descend_into_macros_impl(token, &mut |t| {
self.descend_into_macros_impl(token, &mut |t, _ctx| {
res.push(t.value);
CONTINUE_NO_BREAKS
});
@ -819,10 +811,27 @@ impl<'db> SemanticsImpl<'db> {
res
}
pub fn descend_into_macros_no_opaque(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
let mut res = smallvec![];
if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
self.descend_into_macros_impl(token, &mut |t, ctx| {
if !ctx.is_opaque(self.db.upcast()) {
// Don't descend into opaque contexts
res.push(t.value);
}
CONTINUE_NO_BREAKS
});
}
if res.is_empty() {
res.push(token);
}
res
}
pub fn descend_into_macros_breakable<T>(
&self,
token: InRealFile<SyntaxToken>,
mut cb: impl FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContextId) -> ControlFlow<T>,
) -> Option<T> {
self.descend_into_macros_impl(token.clone(), &mut cb)
}
@ -834,10 +843,12 @@ impl<'db> SemanticsImpl<'db> {
let text = token.text();
let kind = token.kind();
self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }| {
self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }, ctx| {
let mapped_kind = value.kind();
let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier();
let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
let matches = (kind == mapped_kind || any_ident_match())
&& text == value.text()
&& !ctx.is_opaque(self.db.upcast());
if matches {
r.push(value);
}
@ -854,17 +865,21 @@ impl<'db> SemanticsImpl<'db> {
let text = token.text();
let kind = token.kind();
if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| {
let mapped_kind = value.kind();
let any_ident_match =
|| kind.is_any_identifier() && value.kind().is_any_identifier();
let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
if matches {
ControlFlow::Break(value)
} else {
ControlFlow::Continue(())
}
})
self.descend_into_macros_breakable(
token.clone(),
|InFile { value, file_id: _ }, _ctx| {
let mapped_kind = value.kind();
let any_ident_match =
|| kind.is_any_identifier() && value.kind().is_any_identifier();
let matches =
(kind == mapped_kind || any_ident_match()) && text == value.text();
if matches {
ControlFlow::Break(value)
} else {
ControlFlow::Continue(())
}
},
)
} else {
None
}
@ -874,7 +889,7 @@ impl<'db> SemanticsImpl<'db> {
fn descend_into_macros_impl<T>(
&self,
InRealFile { value: token, file_id }: InRealFile<SyntaxToken>,
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContextId) -> ControlFlow<T>,
) -> Option<T> {
let _p = tracing::info_span!("descend_into_macros_impl").entered();
let (sa, span, file_id) = token
@ -898,7 +913,8 @@ impl<'db> SemanticsImpl<'db> {
// These are tracked to know which macro calls we still have to look into
// the tokens themselves aren't that interesting as the span that is being used to map
// things down never changes.
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])];
let mut stack: Vec<(_, SmallVec<[_; 2]>)> =
vec![(file_id, smallvec![(token, SyntaxContextId::ROOT)])];
// Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
@ -921,11 +937,11 @@ impl<'db> SemanticsImpl<'db> {
// Filters out all tokens that contain the given range (usually the macro call), any such
// token is redundant as the corresponding macro call has already been processed
let filter_duplicates = |tokens: &mut SmallVec<_>, range: TextRange| {
tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range()))
tokens.retain(|(t, _): &mut (SyntaxToken, _)| !range.contains_range(t.text_range()))
};
while let Some((expansion, ref mut tokens)) = stack.pop() {
while let Some(token) = tokens.pop() {
while let Some((token, ctx)) = tokens.pop() {
let was_not_remapped = (|| {
// First expand into attribute invocations
let containing_attribute_macro_call = self.with_ctx(|ctx| {
@ -1036,7 +1052,7 @@ impl<'db> SemanticsImpl<'db> {
let text_range = attr.syntax().text_range();
// remove any other token in this macro input, all their mappings are the
// same as this
tokens.retain(|t| {
tokens.retain(|(t, _)| {
!text_range.contains_range(t.text_range())
});
return process_expansion_for_token(
@ -1093,7 +1109,7 @@ impl<'db> SemanticsImpl<'db> {
.is_none();
if was_not_remapped {
if let ControlFlow::Break(b) = f(InFile::new(expansion, token)) {
if let ControlFlow::Break(b) = f(InFile::new(expansion, token), ctx) {
return Some(b);
}
}
@ -1221,26 +1237,10 @@ impl<'db> SemanticsImpl<'db> {
ToDef::to_def(self, src.as_ref())
}
pub fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> {
let text = lifetime.text();
let label = lifetime.syntax().ancestors().find_map(|syn| {
let label = match_ast! {
match syn {
ast::ForExpr(it) => it.label(),
ast::WhileExpr(it) => it.label(),
ast::LoopExpr(it) => it.label(),
ast::BlockExpr(it) => it.label(),
_ => None,
}
};
label.filter(|l| {
l.lifetime()
.and_then(|lt| lt.lifetime_ident_token())
.map_or(false, |lt| lt.text() == text)
})
})?;
let src = self.wrap_node_infile(label);
ToDef::to_def(self, src.as_ref())
pub fn resolve_label(&self, label: &ast::Lifetime) -> Option<Label> {
let (parent, label_id) = self
.with_ctx(|ctx| ctx.label_ref_to_def(self.wrap_node_infile(label.clone()).as_ref()))?;
Some(Label { parent, label_id })
}
pub fn resolve_type(&self, ty: &ast::Type) -> Option<Type> {

View file

@ -92,7 +92,7 @@ use hir_def::{
keys::{self, Key},
DynMap,
},
hir::{BindingId, LabelId},
hir::{BindingId, Expr, LabelId},
AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId,
FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, Lookup, MacroId,
ModuleId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId,
@ -343,6 +343,20 @@ impl SourceToDefCtx<'_, '_> {
Some((container, label_id))
}
pub(super) fn label_ref_to_def(
&mut self,
src: InFile<&ast::Lifetime>,
) -> Option<(DefWithBodyId, LabelId)> {
let break_or_continue = ast::Expr::cast(src.value.syntax().parent()?)?;
let container = self.find_pat_or_label_container(src.syntax_ref())?;
let (body, source_map) = self.db.body_with_source_map(container);
let break_or_continue = source_map.node_expr(src.with_value(&break_or_continue))?;
let (Expr::Break { label, .. } | Expr::Continue { label }) = body[break_or_continue] else {
return None;
};
Some((container, label?))
}
pub(super) fn item_to_macro_call(&mut self, src: InFile<&ast::Item>) -> Option<MacroCallId> {
let map = self.dyn_map(src)?;
map[keys::ATTR_MACRO_CALL].get(&AstPtr::new(src.value)).copied()

Some files were not shown because too many files have changed in this diff Show more