Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
@@ -2,13 +2,16 @@
|
||||
|
||||
#![deny(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use crate::{is_expn_of, match_def_path, paths};
|
||||
use crate::ty::is_type_diagnostic_item;
|
||||
use crate::{is_expn_of, last_path_segment, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, LitKind};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::{Arm, Block, BorrowKind, Expr, ExprKind, LoopSource, MatchSource, Node, Pat, StmtKind, UnOp};
|
||||
use rustc_hir::{
|
||||
Arm, Block, BorrowKind, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath, StmtKind, UnOp,
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::{sym, ExpnKind, Span, Symbol};
|
||||
use rustc_span::{sym, symbol, ExpnKind, Span, Symbol};
|
||||
|
||||
/// The essential nodes of a desugared for loop as well as the entire span:
|
||||
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
|
||||
@@ -569,6 +572,106 @@ impl FormatArgsExpn<'tcx> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a vector of `FormatArgsArg`.
|
||||
pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
|
||||
if let Some(expr) = self.fmt_expr {
|
||||
if_chain! {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
|
||||
if let ExprKind::Array(exprs) = expr.kind;
|
||||
then {
|
||||
exprs.iter().map(|fmt| {
|
||||
if_chain! {
|
||||
// struct `core::fmt::rt::v1::Argument`
|
||||
if let ExprKind::Struct(_, fields, _) = fmt.kind;
|
||||
if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
|
||||
if let ExprKind::Lit(lit) = &position_field.expr.kind;
|
||||
if let LitKind::Int(position, _) = lit.node;
|
||||
then {
|
||||
let i = usize::try_from(position).unwrap();
|
||||
Some(FormatArgsArg { value: self.value_args[i], arg: &self.args[i], fmt: Some(fmt) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}).collect()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(
|
||||
self.value_args
|
||||
.iter()
|
||||
.zip(self.args.iter())
|
||||
.map(|(value, arg)| FormatArgsArg { value, arg, fmt: None })
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type representing a `FormatArgsExpn`'s format arguments
|
||||
pub struct FormatArgsArg<'tcx> {
|
||||
/// An element of `value_args` according to `position`
|
||||
pub value: &'tcx Expr<'tcx>,
|
||||
/// An element of `args` according to `position`
|
||||
pub arg: &'tcx Expr<'tcx>,
|
||||
/// An element of `fmt_expn`
|
||||
pub fmt: Option<&'tcx Expr<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatArgsArg<'tcx> {
|
||||
/// Returns true if any formatting parameters are used that would have an effect on strings,
|
||||
/// like `{:+2}` instead of just `{}`.
|
||||
pub fn has_string_formatting(&self) -> bool {
|
||||
self.fmt.map_or(false, |fmt| {
|
||||
// `!` because these conditions check that `self` is unformatted.
|
||||
!if_chain! {
|
||||
// struct `core::fmt::rt::v1::Argument`
|
||||
if let ExprKind::Struct(_, fields, _) = fmt.kind;
|
||||
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
|
||||
// struct `core::fmt::rt::v1::FormatSpec`
|
||||
if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind;
|
||||
let mut precision_found = false;
|
||||
let mut width_found = false;
|
||||
if subfields.iter().all(|field| {
|
||||
match field.ident.name {
|
||||
sym::precision => {
|
||||
precision_found = true;
|
||||
if let ExprKind::Path(ref precision_path) = field.expr.kind {
|
||||
last_path_segment(precision_path).ident.name == sym::Implied
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
sym::width => {
|
||||
width_found = true;
|
||||
if let ExprKind::Path(ref width_qpath) = field.expr.kind {
|
||||
last_path_segment(width_qpath).ident.name == sym::Implied
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
});
|
||||
if precision_found && width_found;
|
||||
then { true } else { false }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns true if the argument is formatted using `Display::fmt`.
|
||||
pub fn is_display(&self) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(_, [_, format_field]) = self.arg.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = format_field.kind;
|
||||
if let [.., t, _] = path.segments;
|
||||
if t.ident.name == sym::Display;
|
||||
then { true } else { false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a `let` statement is from a `for` loop desugaring.
|
||||
@@ -631,3 +734,51 @@ impl PanicExpn<'tcx> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A parsed `Vec` initialization expression
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum VecInitKind {
|
||||
/// `Vec::new()`
|
||||
New,
|
||||
/// `Vec::default()` or `Default::default()`
|
||||
Default,
|
||||
/// `Vec::with_capacity(123)`
|
||||
WithLiteralCapacity(u64),
|
||||
/// `Vec::with_capacity(slice.len())`
|
||||
WithExprCapacity(HirId),
|
||||
}
|
||||
|
||||
/// Checks if given expression is an initialization of `Vec` and returns its kind.
|
||||
pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
|
||||
if let ExprKind::Call(func, args) = expr.kind {
|
||||
match func.kind {
|
||||
ExprKind::Path(QPath::TypeRelative(ty, name))
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
|
||||
{
|
||||
if name.ident.name == sym::new {
|
||||
return Some(VecInitKind::New);
|
||||
} else if name.ident.name == symbol::kw::Default {
|
||||
return Some(VecInitKind::Default);
|
||||
} else if name.ident.name.as_str() == "with_capacity" {
|
||||
let arg = args.get(0)?;
|
||||
if_chain! {
|
||||
if let ExprKind::Lit(lit) = &arg.kind;
|
||||
if let LitKind::Int(num, _) = lit.node;
|
||||
then {
|
||||
return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?))
|
||||
}
|
||||
}
|
||||
return Some(VecInitKind::WithExprCapacity(arg.hir_id));
|
||||
}
|
||||
}
|
||||
ExprKind::Path(QPath::Resolved(_, path))
|
||||
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
|
||||
{
|
||||
return Some(VecInitKind::Default);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -69,11 +69,13 @@ use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
|
||||
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::itemlikevisit::ItemLikeVisitor;
|
||||
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
|
||||
use rustc_hir::{
|
||||
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
|
||||
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, Param, Pat,
|
||||
PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
|
||||
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, ForeignItem, GenericArgs,
|
||||
HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node,
|
||||
Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
|
||||
UnOp,
|
||||
};
|
||||
use rustc_lint::{LateContext, Level, Lint, LintContext};
|
||||
use rustc_middle::hir::exports::Export;
|
||||
@@ -251,11 +253,7 @@ pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem
|
||||
/// Returns `true` if this `span` was expanded by any macro.
|
||||
#[must_use]
|
||||
pub fn in_macro(span: Span) -> bool {
|
||||
if span.from_expansion() {
|
||||
!matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
|
||||
}
|
||||
|
||||
pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
|
||||
@@ -1285,10 +1283,9 @@ pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool
|
||||
}
|
||||
let enclosing_body = cx.tcx.hir().local_def_id(cx.tcx.hir().enclosing_body_owner(e.hir_id));
|
||||
if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
|
||||
value == v
|
||||
} else {
|
||||
false
|
||||
return value == v;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks whether the given expression is a constant literal of the given value.
|
||||
@@ -1315,7 +1312,7 @@ pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
|
||||
/// Returns the pre-expansion span if is this comes from an expansion of the
|
||||
/// macro `name`.
|
||||
/// See also `is_direct_expn_of`.
|
||||
/// See also [`is_direct_expn_of`].
|
||||
#[must_use]
|
||||
pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
|
||||
loop {
|
||||
@@ -1338,13 +1335,13 @@ pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
|
||||
|
||||
/// Returns the pre-expansion span if the span directly comes from an expansion
|
||||
/// of the macro `name`.
|
||||
/// The difference with `is_expn_of` is that in
|
||||
/// ```rust,ignore
|
||||
/// The difference with [`is_expn_of`] is that in
|
||||
/// ```rust
|
||||
/// # macro_rules! foo { ($e:tt) => { $e } }; macro_rules! bar { ($e:expr) => { $e } }
|
||||
/// foo!(bar!(42));
|
||||
/// ```
|
||||
/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
|
||||
/// `bar!` by
|
||||
/// `is_direct_expn_of`.
|
||||
/// from `bar!` by `is_direct_expn_of`.
|
||||
#[must_use]
|
||||
pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
|
||||
if span.from_expansion() {
|
||||
@@ -1467,11 +1464,9 @@ pub fn is_self(slf: &Param<'_>) -> bool {
|
||||
}
|
||||
|
||||
pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind;
|
||||
if let Res::SelfTy(..) = path.res;
|
||||
then {
|
||||
return true
|
||||
if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind {
|
||||
if let Res::SelfTy(..) = path.res {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -2062,27 +2057,80 @@ macro_rules! unwrap_cargo_metadata {
|
||||
}
|
||||
|
||||
pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
|
||||
if let Res::Def(_, def_id) = path.res;
|
||||
then {
|
||||
cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr)
|
||||
} else {
|
||||
false
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
|
||||
if let Res::Def(_, def_id) = path.res {
|
||||
return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Checks whether item either has `test` attribute applied, or
|
||||
/// is a module with `test` in its name.
|
||||
pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
|
||||
if let Some(def_id) = tcx.hir().opt_local_def_id(item.hir_id()) {
|
||||
if tcx.has_attr(def_id.to_def_id(), sym::test) {
|
||||
return true;
|
||||
struct VisitConstTestStruct<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
names: Vec<Symbol>,
|
||||
found: bool,
|
||||
}
|
||||
impl<'hir> ItemLikeVisitor<'hir> for VisitConstTestStruct<'hir> {
|
||||
fn visit_item(&mut self, item: &Item<'_>) {
|
||||
if let ItemKind::Const(ty, _body) = item.kind {
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
|
||||
// We could also check for the type name `test::TestDescAndFn`
|
||||
// and the `#[rustc_test_marker]` attribute?
|
||||
if let Res::Def(DefKind::Struct, _) = path.res {
|
||||
let has_test_marker = self
|
||||
.tcx
|
||||
.hir()
|
||||
.attrs(item.hir_id())
|
||||
.iter()
|
||||
.any(|a| a.has_name(sym::rustc_test_marker));
|
||||
if has_test_marker && self.names.contains(&item.ident.name) {
|
||||
self.found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn visit_trait_item(&mut self, _: &TraitItem<'_>) {}
|
||||
fn visit_impl_item(&mut self, _: &ImplItem<'_>) {}
|
||||
fn visit_foreign_item(&mut self, _: &ForeignItem<'_>) {}
|
||||
}
|
||||
|
||||
matches!(item.kind, ItemKind::Mod(..)) && item.ident.name.as_str().contains("test")
|
||||
/// Checks if the function containing the given `HirId` is a `#[test]` function
|
||||
///
|
||||
/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
|
||||
pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||
let names: Vec<_> = tcx
|
||||
.hir()
|
||||
.parent_iter(id)
|
||||
// Since you can nest functions we need to collect all until we leave
|
||||
// function scope
|
||||
.filter_map(|(_id, node)| {
|
||||
if let Node::Item(item) = node {
|
||||
if let ItemKind::Fn(_, _, _) = item.kind {
|
||||
return Some(item.ident.name);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect();
|
||||
let parent_mod = tcx.parent_module(id);
|
||||
let mut vis = VisitConstTestStruct {
|
||||
tcx,
|
||||
names,
|
||||
found: false,
|
||||
};
|
||||
tcx.hir().visit_item_likes_in_module(parent_mod, &mut vis);
|
||||
vis.found
|
||||
}
|
||||
|
||||
/// Checks whether item either has `test` attribute appelied, or
|
||||
/// is a module with `test` in its name.
|
||||
///
|
||||
/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
|
||||
pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
|
||||
is_in_test_function(tcx, item.hir_id())
|
||||
|| matches!(item.kind, ItemKind::Mod(..))
|
||||
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
|
||||
}
|
||||
|
||||
macro_rules! op_utils {
|
||||
|
||||
@@ -17,6 +17,12 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
|
||||
#[cfg(feature = "metadata-collector-lint")]
|
||||
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
|
||||
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const ASSERT_EQ_MACRO: [&str; 3] = ["core", "macros", "assert_eq"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const ASSERT_MACRO: [&str; 4] = ["core", "macros", "builtin", "assert"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const ASSERT_NE_MACRO: [&str; 3] = ["core", "macros", "assert_ne"];
|
||||
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
|
||||
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
|
||||
pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
|
||||
@@ -41,11 +47,17 @@ pub const DROP: [&str; 3] = ["core", "mem", "drop"];
|
||||
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
|
||||
#[cfg(feature = "internal-lints")]
|
||||
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const EPRINT_MACRO: [&str; 3] = ["std", "macros", "eprint"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const EPRINTLN_MACRO: [&str; 3] = ["std", "macros", "eprintln"];
|
||||
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
|
||||
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
|
||||
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
|
||||
pub const FILE: [&str; 3] = ["std", "fs", "File"];
|
||||
pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const FORMAT_ARGS_MACRO: [&str; 4] = ["core", "macros", "builtin", "format_args"];
|
||||
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
|
||||
pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
|
||||
pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
|
||||
@@ -108,6 +120,10 @@ pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "Permis
|
||||
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
|
||||
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
|
||||
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const PRINT_MACRO: [&str; 3] = ["std", "macros", "print"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const PRINTLN_MACRO: [&str; 3] = ["std", "macros", "println"];
|
||||
pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
|
||||
pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
|
||||
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
|
||||
@@ -184,3 +200,7 @@ pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
|
||||
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
|
||||
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
|
||||
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const WRITE_MACRO: [&str; 3] = ["core", "macros", "write"];
|
||||
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
|
||||
pub const WRITELN_MACRO: [&str; 3] = ["core", "macros", "writeln"];
|
||||
|
||||
@@ -367,3 +367,13 @@ pub fn same_type_and_consts(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a given type looks safe to be uninitialized.
|
||||
pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
|
||||
ty::Tuple(types) => types.types().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
|
||||
ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user