Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2025-02-27 21:40:02 +01:00
commit 02e812af4d
2562 changed files with 34500 additions and 15442 deletions

View file

@ -970,7 +970,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
},
InlineAsmOperand::SymFn { expr } => {
self.hash_expr(expr);
}
},
InlineAsmOperand::Const { anon_const } => {
self.hash_body(anon_const.body);
},

View file

@ -40,6 +40,7 @@ extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_errors;
extern crate rustc_hir;
extern crate rustc_hir_analysis;
extern crate rustc_hir_typeck;
extern crate rustc_index;
extern crate rustc_infer;
@ -92,8 +93,9 @@ use std::iter::{once, repeat_n};
use std::sync::{Mutex, MutexGuard, OnceLock};
use itertools::Itertools;
use rustc_abi::Integer;
use rustc_ast::ast::{self, LitKind, RangeLimits};
use rustc_attr_parsing::{find_attr, AttributeKind};
use rustc_attr_parsing::{AttributeKind, find_attr};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::packed::Pu128;
use rustc_data_structures::unhash::UnhashMap;
@ -107,24 +109,24 @@ use rustc_hir::{
self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, ConstContext,
Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat,
PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind,
PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind,
TraitItemRef, TraitRef, TyKind, UnOp, def,
};
use rustc_lexer::{TokenKind, tokenize};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::mir::{AggregateKind, Operand, RETURN_PLACE, Rvalue, StatementKind, TerminatorKind};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::fast_reject::SimplifiedType;
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgKind, GenericArgsRef, IntTy, Ty,
TyCtxt, TypeVisitableExt, UintTy, UpvarCapture,
TyCtxt, TypeFlags, TypeVisitableExt, UintTy, UpvarCapture,
};
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::symbol::{Ident, Symbol, kw};
use rustc_span::{InnerSpan, Span, sym};
use rustc_abi::Integer;
use visitors::{Visitable, for_each_unconsumed_temporary};
use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
@ -915,22 +917,101 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
}
/// Returns true if the expr is equal to `Default::default` when evaluated.
pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> bool {
pub fn is_default_equivalent_call(
cx: &LateContext<'_>,
repl_func: &Expr<'_>,
whole_call_expr: Option<&Expr<'_>>,
) -> bool {
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind
&& let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id()
&& (is_diag_trait_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath))
{
true
} else {
false
return true;
}
// Get the type of the whole method call expression, find the exact method definition, look at
// its body and check if it is similar to the corresponding `Default::default()` body.
let Some(e) = whole_call_expr else { return false };
let Some(default_fn_def_id) = cx.tcx.get_diagnostic_item(sym::default_fn) else {
return false;
};
let Some(ty) = cx.tcx.typeck(e.hir_id.owner.def_id).expr_ty_adjusted_opt(e) else {
return false;
};
let args = rustc_ty::GenericArgs::for_item(cx.tcx, default_fn_def_id, |param, _| {
if let rustc_ty::GenericParamDefKind::Lifetime = param.kind {
cx.tcx.lifetimes.re_erased.into()
} else if param.index == 0 && param.name == kw::SelfUpper {
ty.into()
} else {
param.to_error(cx.tcx)
}
});
let instance = rustc_ty::Instance::try_resolve(cx.tcx, cx.typing_env(), default_fn_def_id, args);
let Ok(Some(instance)) = instance else { return false };
if let rustc_ty::InstanceKind::Item(def) = instance.def
&& !cx.tcx.is_mir_available(def)
{
return false;
}
let ExprKind::Path(ref repl_func_qpath) = repl_func.kind else {
return false;
};
let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() else {
return false;
};
// Get the MIR Body for the `<Ty as Default>::default()` function.
// If it is a value or call (either fn or ctor), we compare its `DefId` against the one for the
// resolution of the expression we had in the path. This lets us identify, for example, that
// the body of `<Vec<T> as Default>::default()` is a `Vec::new()`, and the field was being
// initialized to `Vec::new()` as well.
let body = cx.tcx.instance_mir(instance.def);
for block_data in body.basic_blocks.iter() {
if block_data.statements.len() == 1
&& let StatementKind::Assign(assign) = &block_data.statements[0].kind
&& assign.0.local == RETURN_PLACE
&& let Rvalue::Aggregate(kind, _places) = &assign.1
&& let AggregateKind::Adt(did, variant_index, _, _, _) = &**kind
&& let def = cx.tcx.adt_def(did)
&& let variant = &def.variant(*variant_index)
&& variant.fields.is_empty()
&& let Some((_, did)) = variant.ctor
&& did == repl_def_id
{
return true;
} else if block_data.statements.is_empty()
&& let Some(term) = &block_data.terminator
{
match &term.kind {
TerminatorKind::Call {
func: Operand::Constant(c),
..
} if let rustc_ty::FnDef(did, _args) = c.ty().kind()
&& *did == repl_def_id =>
{
return true;
},
TerminatorKind::TailCall {
func: Operand::Constant(c),
..
} if let rustc_ty::FnDef(did, _args) = c.ty().kind()
&& *did == repl_def_id =>
{
return true;
},
_ => {},
}
}
}
false
}
/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated.
/// Returns true if the expr is equal to `Default::default()` of its type when evaluated.
///
/// It doesn't cover all cases, for example indirect function calls (some of std
/// functions are supported) but it is the best we have.
/// It doesn't cover all cases, like struct literals, but it is a close approximation.
pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
match &e.kind {
ExprKind::Lit(lit) => match lit.node {
@ -951,7 +1032,7 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
false
}
},
ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func),
ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func, Some(e)),
ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg),
ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone),
ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
@ -1421,6 +1502,10 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
| Node::ImplItem(&ImplItem {
kind: ImplItemKind::Fn(_, eid),
..
})
| Node::TraitItem(&TraitItem {
kind: TraitItemKind::Fn(_, TraitFn::Provided(eid)),
..
}) => match cx.tcx.hir_body(eid).value.kind {
ExprKind::Block(block, _) => Some(block),
_ => None,
@ -1971,8 +2056,7 @@ pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool
/// Checks if the given HIR node is inside an `impl` block with the `automatically_derived`
/// attribute.
pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx
.hir_parent_owner_iter(id)
tcx.hir_parent_owner_iter(id)
.filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_))))
.any(|(id, _)| {
has_attr(
@ -2618,9 +2702,7 @@ pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
/// Checks if any parent node of `HirId` has `#[cfg(test)]` attribute applied
pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool {
tcx
.hir_parent_id_iter(id)
.any(|parent_id| is_cfg_test(tcx, parent_id))
tcx.hir_parent_id_iter(id).any(|parent_id| is_cfg_test(tcx, parent_id))
}
/// Checks if the node is in a `#[test]` function or has any parent node marked `#[cfg(test)]`
@ -3504,3 +3586,111 @@ pub fn leaks_droppable_temporary_with_limited_lifetime<'tcx>(cx: &LateContext<'t
})
.is_break()
}
/// Returns true if the specified `expr` requires coercion,
/// meaning that it either has a coercion or propagates a coercion from one of its sub expressions.
///
/// Similar to [`is_adjusted`], this not only checks if an expression's type was adjusted,
/// but also going through extra steps to see if it fits the description of [coercion sites].
///
/// You should used this when you want to avoid suggesting replacing an expression that is currently
/// a coercion site or coercion propagating expression with one that is not.
///
/// [coercion sites]: https://doc.rust-lang.org/stable/reference/type-coercions.html#coercion-sites
pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
let expr_ty_is_adjusted = cx
.typeck_results()
.expr_adjustments(expr)
.iter()
// ignore `NeverToAny` adjustments, such as `panic!` call.
.any(|adj| !matches!(adj.kind, Adjust::NeverToAny));
if expr_ty_is_adjusted {
return true;
}
// Identify coercion sites and recursively check if those sites
// actually have type adjustments.
match expr.kind {
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) if let Some(def_id) = fn_def_id(cx, expr) => {
let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity();
if !fn_sig.output().skip_binder().has_type_flags(TypeFlags::HAS_TY_PARAM) {
return false;
}
let self_arg_count = usize::from(matches!(expr.kind, ExprKind::MethodCall(..)));
let mut args_with_ty_param = {
fn_sig
.inputs()
.skip_binder()
.iter()
.skip(self_arg_count)
.zip(args)
.filter_map(|(arg_ty, arg)| {
if arg_ty.has_type_flags(TypeFlags::HAS_TY_PARAM) {
Some(arg)
} else {
None
}
})
};
args_with_ty_param.any(|arg| expr_requires_coercion(cx, arg))
},
// Struct/union initialization.
ExprKind::Struct(qpath, _, _) => {
let res = cx.typeck_results().qpath_res(qpath, expr.hir_id);
if let Some((_, v_def)) = adt_and_variant_of_res(cx, res) {
let generic_args = cx.typeck_results().node_args(expr.hir_id);
v_def
.fields
.iter()
.any(|field| field.ty(cx.tcx, generic_args).has_type_flags(TypeFlags::HAS_TY_PARAM))
} else {
false
}
},
// Function results, including the final line of a block or a `return` expression.
ExprKind::Block(
&Block {
expr: Some(ret_expr), ..
},
_,
)
| ExprKind::Ret(Some(ret_expr)) => expr_requires_coercion(cx, ret_expr),
// ===== Coercion-propagation expressions =====
ExprKind::Array(elems) | ExprKind::Tup(elems) => elems.iter().any(|elem| expr_requires_coercion(cx, elem)),
// Array but with repeating syntax.
ExprKind::Repeat(rep_elem, _) => expr_requires_coercion(cx, rep_elem),
// Others that may contain coercion sites.
ExprKind::If(_, then, maybe_else) => {
expr_requires_coercion(cx, then) || maybe_else.is_some_and(|e| expr_requires_coercion(cx, e))
},
ExprKind::Match(_, arms, _) => arms
.iter()
.map(|arm| arm.body)
.any(|body| expr_requires_coercion(cx, body)),
_ => false,
}
}
/// Returns `true` if `expr` designates a mutable static, a mutable local binding, or an expression
/// that can be owned.
pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(hir_id) = path_to_local(expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
{
matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
} else if let ExprKind::Path(p) = &expr.kind
&& let Some(mutability) = cx
.qpath_res(p, expr.hir_id)
.opt_def_id()
.and_then(|id| cx.tcx.static_mutability(id))
{
mutability == Mutability::Mut
} else if let ExprKind::Field(parent, _) = expr.kind {
is_mutable(cx, parent)
} else {
true
}
}

View file

@ -30,6 +30,8 @@ const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
sym::print_macro,
sym::println_macro,
sym::std_panic_macro,
sym::todo_macro,
sym::unimplemented_macro,
sym::write_macro,
sym::writeln_macro,
];

View file

@ -27,7 +27,7 @@ msrv_aliases! {
1,77,0 { C_STR_LITERALS }
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
1,75,0 { OPTION_AS_SLICE }
1,74,0 { REPR_RUST }
1,74,0 { REPR_RUST, IO_ERROR_OTHER }
1,73,0 { MANUAL_DIV_CEIL }
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
@ -37,10 +37,11 @@ msrv_aliases! {
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
1,59,0 { THREAD_LOCAL_CONST_INIT }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
1,57,0 { MAP_WHILE }
1,56,0 { CONST_FN_UNION }
1,55,0 { SEEK_REWIND }
1,54,0 { INTO_KEYS }
1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
1,53,0 { OR_PATTERNS, INTEGER_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
1,50,0 { BOOL_THEN, CLAMP, SLICE_FILL }
@ -57,6 +58,7 @@ msrv_aliases! {
1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
1,34,0 { TRY_FROM }
1,33,0 { UNDERSCORE_IMPORTS }
1,31,0 { OPTION_REPLACE }
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
1,29,0 { ITER_FLATTEN }
1,28,0 { FROM_BOOL, REPEAT_WITH }

View file

@ -28,13 +28,9 @@ pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
// Paths in `core`/`alloc`/`std`. This should be avoided and cleaned up by adding diagnostic items.
pub const ABORT: [&str; 3] = ["std", "process", "abort"];
pub const CHILD: [&str; 3] = ["std", "process", "Child"];
pub const CHILD_ID: [&str; 4] = ["std", "process", "Child", "id"];
pub const CHILD_KILL: [&str; 4] = ["std", "process", "Child", "kill"];
pub const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
pub const CHAR_IS_ASCII: [&str; 5] = ["core", "char", "methods", "<impl char>", "is_ascii"];
pub const STDIN: [&str; 4] = ["std", "io", "stdio", "Stdin"];
pub const IO_ERROR_NEW: [&str; 5] = ["std", "io", "error", "Error", "new"];
pub const IO_ERRORKIND_OTHER: [&str; 5] = ["std", "io", "error", "ErrorKind", "Other"];
// Paths in clippy itself
pub const MSRV: [&str; 3] = ["clippy_utils", "msrvs", "Msrv"];

View file

@ -294,7 +294,7 @@ impl SourceFileRange {
}
}
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block` with no label.
pub fn expr_block(
sess: &impl HasSession,
expr: &Expr<'_>,
@ -305,10 +305,10 @@ pub fn expr_block(
) -> String {
let (code, from_macro) = snippet_block_with_context(sess, expr.span, outer, default, indent_relative_to, app);
if !from_macro
&& let ExprKind::Block(block, _) = expr.kind
&& let ExprKind::Block(block, None) = expr.kind
&& block.rules != BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
{
format!("{code}")
code
} else {
// FIXME: add extra indent for the unsafe blocks:
// original code: unsafe { ... }
@ -421,11 +421,10 @@ pub fn position_before_rarrow(s: &str) -> Option<usize> {
}
/// Reindent a multiline string with possibility of ignoring the first line.
#[expect(clippy::needless_pass_by_value)]
pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' ');
pub fn reindent_multiline(s: &str, ignore_first: bool, indent: Option<usize>) -> String {
let s_space = reindent_multiline_inner(s, ignore_first, indent, ' ');
let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t');
reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into()
reindent_multiline_inner(&s_tab, ignore_first, indent, ' ')
}
fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
@ -553,42 +552,37 @@ pub fn snippet_opt(sess: &impl HasSession, span: Span) -> Option<String> {
/// } // aligned with `if`
/// ```
/// Note that the first line of the snippet always has 0 indentation.
pub fn snippet_block<'a>(
sess: &impl HasSession,
span: Span,
default: &'a str,
indent_relative_to: Option<Span>,
) -> Cow<'a, str> {
pub fn snippet_block(sess: &impl HasSession, span: Span, default: &str, indent_relative_to: Option<Span>) -> String {
let snip = snippet(sess, span, default);
let indent = indent_relative_to.and_then(|s| indent_of(sess, s));
reindent_multiline(snip, true, indent)
reindent_multiline(&snip, true, indent)
}
/// Same as `snippet_block`, but adapts the applicability level by the rules of
/// `snippet_with_applicability`.
pub fn snippet_block_with_applicability<'a>(
pub fn snippet_block_with_applicability(
sess: &impl HasSession,
span: Span,
default: &'a str,
default: &str,
indent_relative_to: Option<Span>,
applicability: &mut Applicability,
) -> Cow<'a, str> {
) -> String {
let snip = snippet_with_applicability(sess, span, default, applicability);
let indent = indent_relative_to.and_then(|s| indent_of(sess, s));
reindent_multiline(snip, true, indent)
reindent_multiline(&snip, true, indent)
}
pub fn snippet_block_with_context<'a>(
pub fn snippet_block_with_context(
sess: &impl HasSession,
span: Span,
outer: SyntaxContext,
default: &'a str,
default: &str,
indent_relative_to: Option<Span>,
app: &mut Applicability,
) -> (Cow<'a, str>, bool) {
) -> (String, bool) {
let (snip, from_macro) = snippet_with_context(sess, span, outer, default, app);
let indent = indent_relative_to.and_then(|s| indent_of(sess, s));
(reindent_multiline(snip, true, indent), from_macro)
(reindent_multiline(&snip, true, indent), from_macro)
}
/// Same as `snippet_with_applicability`, but first walks the span up to the given context.
@ -749,11 +743,11 @@ mod test {
#[test]
fn test_reindent_multiline_single_line() {
assert_eq!("", reindent_multiline("".into(), false, None));
assert_eq!("...", reindent_multiline("...".into(), false, None));
assert_eq!("...", reindent_multiline(" ...".into(), false, None));
assert_eq!("...", reindent_multiline("\t...".into(), false, None));
assert_eq!("...", reindent_multiline("\t\t...".into(), false, None));
assert_eq!("", reindent_multiline("", false, None));
assert_eq!("...", reindent_multiline("...", false, None));
assert_eq!("...", reindent_multiline(" ...", false, None));
assert_eq!("...", reindent_multiline("\t...", false, None));
assert_eq!("...", reindent_multiline("\t\t...", false, None));
}
#[test]
@ -768,7 +762,7 @@ mod test {
y
} else {
z
}".into(), false, None));
}", false, None));
assert_eq!("\
if x {
\ty
@ -778,7 +772,7 @@ mod test {
\ty
} else {
\tz
}".into(), false, None));
}", false, None));
}
#[test]
@ -795,7 +789,7 @@ mod test {
} else {
z
}".into(), false, None));
}", false, None));
}
#[test]
@ -811,6 +805,6 @@ mod test {
y
} else {
z
}".into(), true, Some(8)));
}", true, Some(8)));
}
}

View file

@ -11,6 +11,7 @@ use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, FnDecl, LangItem, TyKind};
use rustc_hir_analysis::lower_ty;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::mir::ConstValue;
@ -36,6 +37,19 @@ use crate::{def_path_def_ids, match_def_path, path_res};
mod type_certainty;
pub use type_certainty::expr_type_is_certain;
/// Lower a [`hir::Ty`] to a [`rustc_middle::Ty`].
pub fn ty_from_hir_ty<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>) -> Ty<'tcx> {
cx.maybe_typeck_results()
.and_then(|results| {
if results.hir_owner == hir_ty.hir_id.owner {
results.node_type_opt(hir_ty.hir_id)
} else {
None
}
})
.unwrap_or_else(|| lower_ty(cx.tcx, hir_ty))
}
/// Checks if the given type implements copy.
pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
cx.type_is_copy_modulo_regions(ty)
@ -351,20 +365,26 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
/// Checks if `Ty` is normalizable. This function is useful
/// to avoid crashes on `layout_of`.
pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool {
is_normalizable_helper(cx, param_env, ty, &mut FxHashMap::default())
is_normalizable_helper(cx, param_env, ty, 0, &mut FxHashMap::default())
}
fn is_normalizable_helper<'tcx>(
cx: &LateContext<'tcx>,
param_env: ParamEnv<'tcx>,
ty: Ty<'tcx>,
depth: usize,
cache: &mut FxHashMap<Ty<'tcx>, bool>,
) -> bool {
if let Some(&cached_result) = cache.get(&ty) {
return cached_result;
}
// prevent recursive loops, false-negative is better than endless loop leading to stack overflow
cache.insert(ty, false);
if !cx.tcx.recursion_limit().value_within_limit(depth) {
return false;
}
// Prevent recursive loops by answering `true` to recursive requests with the same
// type. This will be adjusted when the outermost call analyzes all the type
// components.
cache.insert(ty, true);
let infcx = cx.tcx.infer_ctxt().build(cx.typing_mode());
let cause = ObligationCause::dummy();
let result = if infcx.at(&cause, param_env).query_normalize(ty).is_ok() {
@ -373,11 +393,11 @@ fn is_normalizable_helper<'tcx>(
variant
.fields
.iter()
.all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, args), cache))
.all(|field| is_normalizable_helper(cx, param_env, field.ty(cx.tcx, args), depth + 1, cache))
}),
_ => ty.walk().all(|generic_arg| match generic_arg.unpack() {
GenericArgKind::Type(inner_ty) if inner_ty != ty => {
is_normalizable_helper(cx, param_env, inner_ty, cache)
is_normalizable_helper(cx, param_env, inner_ty, depth + 1, cache)
},
_ => true, // if inner_ty == ty, we've already checked it
}),
@ -1227,6 +1247,10 @@ impl<'tcx> InteriorMut<'tcx> {
.find_map(|f| self.interior_mut_ty_chain(cx, f.ty(cx.tcx, args)))
}
},
ty::Alias(ty::Projection, _) => match cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty) {
Ok(normalized_ty) if ty != normalized_ty => self.interior_mut_ty_chain(cx, normalized_ty),
_ => None,
},
_ => None,
};