1 //! A group of attributes that can be attached to Rust code in order
2 //! to generate a clippy lint detecting said code automatically.
4 use clippy_utils
::{get_attr, higher}
;
5 use rustc_ast
::ast
::{LitFloatType, LitKind}
;
6 use rustc_ast
::LitIntType
;
7 use rustc_data_structures
::fx
::FxHashMap
;
10 ArrayLen
, BindingAnnotation
, Closure
, ExprKind
, FnRetTy
, HirId
, Lit
, PatKind
, QPath
, StmtKind
, TyKind
,
12 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
13 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
14 use rustc_span
::symbol
::{Ident, Symbol}
;
16 use std
::fmt
::{Display, Formatter, Write as _}
;
18 declare_clippy_lint
! {
20 /// Generates clippy code that detects the offending pattern
24 /// // ./tests/ui/my_lint.rs
26 /// // detect the following pattern
29 /// // but ignore everything from here on
30 /// #![clippy::author = "ignore"]
36 /// Running `TESTNAME=ui/my_lint cargo uitest` will produce
37 /// a `./tests/ui/new_lint.stdout` file with the generated code:
40 /// // ./tests/ui/new_lint.stdout
41 /// if ExprKind::If(ref cond, ref then, None) = item.kind
42 /// && let ExprKind::Binary(BinOp::Eq, ref left, ref right) = cond.kind
43 /// && let ExprKind::Path(ref path) = left.kind
44 /// && let ExprKind::Lit(ref lit) = right.kind
45 /// && let LitKind::Int(42, _) = lit.node
47 /// // report your lint here
52 "helper for writing lints"
55 declare_lint_pass
!(Author
=> [LINT_AUTHOR
]);
57 /// Writes a line of output with indentation added
60 println
!(" {}", format_args
!($
($t
)*))
64 /// The variables passed in are replaced with `&Binding`s where the `value` field is set
65 /// to the original value of the variable. The `name` field is set to the name of the variable
66 /// (using `stringify!`) and is adjusted to avoid duplicate names.
67 /// Note that the `Binding` may be printed directly to output the `name`.
69 ($
self:ident $
(, $name
:ident
)+) => {
70 $
(let $name
= & $
self.bind(stringify
!($name
), $name
);)+
74 /// Transforms the given `Option<T>` variables into `OptionPat<Binding<T>>`.
75 /// This displays as `Some($name)` or `None` when printed. The name of the inner binding
76 /// is set to the name of the variable passed to the macro.
77 macro_rules
! opt_bind
{
78 ($
self:ident $
(, $name
:ident
)+) => {
79 $
(let $name
= OptionPat
::new($name
.map(|o
| $
self.bind(stringify
!($name
), o
)));)+
83 /// Creates a `Binding` that accesses the field of an existing `Binding`
85 ($binding
:ident
.$field
:ident
) => {
87 name
: $binding
.name
.to_string() + stringify
!(.$field
),
88 value
: $binding
.value
.$field
,
93 /// Print a condition of a let chain, `chain!(self, "let Some(x) = y")` will print
94 /// `if let Some(x) = y` on the first call and ` && let Some(x) = y` thereafter
96 ($
self:ident
, $
($t
:tt
)*) => {
97 if $
self.first
.take() {
98 println
!("if {}", format_args
!($
($t
)*));
100 println
!(" && {}", format_args
!($
($t
)*));
105 impl<'tcx
> LateLintPass
<'tcx
> for Author
{
106 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::Item
<'_
>) {
107 check_item(cx
, item
.hir_id());
110 fn check_impl_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::ImplItem
<'_
>) {
111 check_item(cx
, item
.hir_id());
114 fn check_trait_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx hir
::TraitItem
<'_
>) {
115 check_item(cx
, item
.hir_id());
118 fn check_arm(&mut self, cx
: &LateContext
<'tcx
>, arm
: &'tcx hir
::Arm
<'_
>) {
119 check_node(cx
, arm
.hir_id
, |v
| {
120 v
.arm(&v
.bind("arm", arm
));
124 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'_
>) {
125 check_node(cx
, expr
.hir_id
, |v
| {
126 v
.expr(&v
.bind("expr", expr
));
130 fn check_stmt(&mut self, cx
: &LateContext
<'tcx
>, stmt
: &'tcx hir
::Stmt
<'_
>) {
132 StmtKind
::Expr(e
) | StmtKind
::Semi(e
) if has_attr(cx
, e
.hir_id
) => return,
135 check_node(cx
, stmt
.hir_id
, |v
| {
136 v
.stmt(&v
.bind("stmt", stmt
));
141 fn check_item(cx
: &LateContext
<'_
>, hir_id
: HirId
) {
142 let hir
= cx
.tcx
.hir();
143 if let Some(body_id
) = hir
.maybe_body_owned_by(hir_id
.expect_owner().def_id
) {
144 check_node(cx
, hir_id
, |v
| {
145 v
.expr(&v
.bind("expr", hir
.body(body_id
).value
));
150 fn check_node(cx
: &LateContext
<'_
>, hir_id
: HirId
, f
: impl Fn(&PrintVisitor
<'_
, '_
>)) {
151 if has_attr(cx
, hir_id
) {
152 f(&PrintVisitor
::new(cx
));
154 println
!(" // report your lint here");
164 impl<T
> Display
for Binding
<T
> {
165 fn fmt(&self, f
: &mut Formatter
<'_
>) -> std
::fmt
::Result
{
166 f
.write_str(&self.name
)
170 struct OptionPat
<T
> {
174 impl<T
> OptionPat
<T
> {
175 fn new(opt
: Option
<T
>) -> Self {
179 fn if_some(&self, f
: impl Fn(&T
)) {
180 if let Some(t
) = &self.opt
{
186 impl<T
: Display
> Display
for OptionPat
<T
> {
187 fn fmt(&self, f
: &mut Formatter
<'_
>) -> std
::fmt
::Result
{
189 None
=> f
.write_str("None"),
190 Some(node
) => write
!(f
, "Some({node})"),
195 struct PrintVisitor
<'a
, 'tcx
> {
196 cx
: &'a LateContext
<'tcx
>,
197 /// Fields are the current index that needs to be appended to pattern
199 ids
: Cell
<FxHashMap
<&'
static str, u32>>,
200 /// Currently at the first condition in the if chain
204 #[allow(clippy::unused_self)]
205 impl<'a
, 'tcx
> PrintVisitor
<'a
, 'tcx
> {
206 fn new(cx
: &'a LateContext
<'tcx
>) -> Self {
209 ids
: Cell
::default(),
210 first
: Cell
::new(true),
214 fn next(&self, s
: &'
static str) -> String
{
215 let mut ids
= self.ids
.take();
216 let out
= match *ids
.entry(s
).and_modify(|n
| *n
+= 1).or_default() {
217 // first usage of the name, use it as is
219 // append a number starting with 1
220 n
=> format
!("{s}{n}"),
226 fn bind
<T
>(&self, name
: &'
static str, value
: T
) -> Binding
<T
> {
227 let name
= self.next(name
);
228 Binding { name, value }
231 fn option
<T
: Copy
>(&self, option
: &Binding
<Option
<T
>>, name
: &'
static str, f
: impl Fn(&Binding
<T
>)) {
233 None
=> chain
!(self, "{option}.is_none()"),
235 let value
= &self.bind(name
, value
);
236 chain
!(self, "let Some({value}) = {option}");
242 fn slice
<T
>(&self, slice
: &Binding
<&[T
]>, f
: impl Fn(&Binding
<&T
>)) {
243 if slice
.value
.is_empty() {
244 chain
!(self, "{slice}.is_empty()");
246 chain
!(self, "{slice}.len() == {}", slice
.value
.len());
247 for (i
, value
) in slice
.value
.iter().enumerate() {
248 let name
= format
!("{slice}[{i}]");
249 f(&Binding { name, value }
);
254 fn destination(&self, destination
: &Binding
<hir
::Destination
>) {
255 self.option(field
!(destination
.label
), "label", |label
| {
256 self.ident(field
!(label
.ident
));
260 fn ident(&self, ident
: &Binding
<Ident
>) {
261 chain
!(self, "{ident}.as_str() == {:?}", ident
.value
.as_str());
264 fn symbol(&self, symbol
: &Binding
<Symbol
>) {
265 chain
!(self, "{symbol}.as_str() == {:?}", symbol
.value
.as_str());
268 fn qpath(&self, qpath
: &Binding
<&QPath
<'_
>>) {
269 if let QPath
::LangItem(lang_item
, ..) = *qpath
.value
{
270 chain
!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
272 chain
!(self, "match_qpath({qpath}, &[{}])", path_to_string(qpath
.value
));
276 fn lit(&self, lit
: &Binding
<&Lit
>) {
277 let kind
= |kind
| chain
!(self, "let LitKind::{kind} = {lit}.node");
279 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
282 match lit
.value
.node
{
283 LitKind
::Bool(val
) => kind
!("Bool({val:?})"),
284 LitKind
::Char(c
) => kind
!("Char({c:?})"),
285 LitKind
::Err
=> kind
!("Err"),
286 LitKind
::Byte(b
) => kind
!("Byte({b})"),
287 LitKind
::Int(i
, suffix
) => {
288 let int_ty
= match suffix
{
289 LitIntType
::Signed(int_ty
) => format
!("LitIntType::Signed(IntTy::{int_ty:?})"),
290 LitIntType
::Unsigned(uint_ty
) => format
!("LitIntType::Unsigned(UintTy::{uint_ty:?})"),
291 LitIntType
::Unsuffixed
=> String
::from("LitIntType::Unsuffixed"),
293 kind
!("Int({i}, {int_ty})");
295 LitKind
::Float(_
, suffix
) => {
296 let float_ty
= match suffix
{
297 LitFloatType
::Suffixed(suffix_ty
) => format
!("LitFloatType::Suffixed(FloatTy::{suffix_ty:?})"),
298 LitFloatType
::Unsuffixed
=> String
::from("LitFloatType::Unsuffixed"),
300 kind
!("Float(_, {float_ty})");
302 LitKind
::ByteStr(ref vec
) => {
304 kind
!("ByteStr(ref {vec})");
305 chain
!(self, "let [{:?}] = **{vec}", vec
.value
);
307 LitKind
::Str(s
, _
) => {
309 kind
!("Str({s}, _)");
315 fn arm(&self, arm
: &Binding
<&hir
::Arm
<'_
>>) {
316 self.pat(field
!(arm
.pat
));
317 match arm
.value
.guard
{
318 None
=> chain
!(self, "{arm}.guard.is_none()"),
319 Some(hir
::Guard
::If(expr
)) => {
321 chain
!(self, "let Some(Guard::If({expr})) = {arm}.guard");
324 Some(hir
::Guard
::IfLet(let_expr
)) => {
325 bind
!(self, let_expr
);
326 chain
!(self, "let Some(Guard::IfLet({let_expr}) = {arm}.guard");
327 self.pat(field
!(let_expr
.pat
));
328 self.expr(field
!(let_expr
.init
));
331 self.expr(field
!(arm
.body
));
334 #[allow(clippy::too_many_lines)]
335 fn expr(&self, expr
: &Binding
<&hir
::Expr
<'_
>>) {
336 if let Some(higher
::While { condition, body }
) = higher
::While
::hir(expr
.value
) {
337 bind
!(self, condition
, body
);
340 "let Some(higher::While {{ condition: {condition}, body: {body} }}) \
341 = higher::While::hir({expr})"
343 self.expr(condition
);
348 if let Some(higher
::WhileLet
{
352 }) = higher
::WhileLet
::hir(expr
.value
)
354 bind
!(self, let_pat
, let_expr
, if_then
);
357 "let Some(higher::WhileLet {{ let_pat: {let_pat}, let_expr: {let_expr}, if_then: {if_then} }}) \
358 = higher::WhileLet::hir({expr})"
366 if let Some(higher
::ForLoop { pat, arg, body, .. }
) = higher
::ForLoop
::hir(expr
.value
) {
367 bind
!(self, pat
, arg
, body
);
370 "let Some(higher::ForLoop {{ pat: {pat}, arg: {arg}, body: {body}, .. }}) \
371 = higher::ForLoop::hir({expr})"
379 let kind
= |kind
| chain
!(self, "let ExprKind::{kind} = {expr}.kind");
381 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
384 match expr
.value
.kind
{
385 ExprKind
::Let(let_expr
) => {
386 bind
!(self, let_expr
);
387 kind
!("Let({let_expr})");
388 self.pat(field
!(let_expr
.pat
));
389 // Does what ExprKind::Cast does, only adds a clause for the type
391 if let Some(TyKind
::Path(ref qpath
)) = let_expr
.value
.ty
.as_ref().map(|ty
| &ty
.kind
) {
393 chain
!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind");
396 self.expr(field
!(let_expr
.init
));
398 ExprKind
::Box(inner
) => {
400 kind
!("Box({inner})");
403 ExprKind
::Array(elements
) => {
404 bind
!(self, elements
);
405 kind
!("Array({elements})");
406 self.slice(elements
, |e
| self.expr(e
));
408 ExprKind
::Call(func
, args
) => {
409 bind
!(self, func
, args
);
410 kind
!("Call({func}, {args})");
412 self.slice(args
, |e
| self.expr(e
));
414 ExprKind
::MethodCall(method_name
, receiver
, args
, _
) => {
415 bind
!(self, method_name
, receiver
, args
);
416 kind
!("MethodCall({method_name}, {receiver}, {args}, _)");
417 self.ident(field
!(method_name
.ident
));
419 self.slice(args
, |e
| self.expr(e
));
421 ExprKind
::Tup(elements
) => {
422 bind
!(self, elements
);
423 kind
!("Tup({elements})");
424 self.slice(elements
, |e
| self.expr(e
));
426 ExprKind
::Binary(op
, left
, right
) => {
427 bind
!(self, op
, left
, right
);
428 kind
!("Binary({op}, {left}, {right})");
429 chain
!(self, "BinOpKind::{:?} == {op}.node", op
.value
.node
);
433 ExprKind
::Unary(op
, inner
) => {
435 kind
!("Unary(UnOp::{op:?}, {inner})");
438 ExprKind
::Lit(ref lit
) => {
440 kind
!("Lit(ref {lit})");
443 ExprKind
::Cast(expr
, cast_ty
) => {
444 bind
!(self, expr
, cast_ty
);
445 kind
!("Cast({expr}, {cast_ty})");
446 if let TyKind
::Path(ref qpath
) = cast_ty
.value
.kind
{
448 chain
!(self, "let TyKind::Path(ref {qpath}) = {cast_ty}.kind");
453 ExprKind
::Type(expr
, _ty
) => {
455 kind
!("Type({expr}, _)");
458 ExprKind
::Loop(body
, label
, des
, _
) => {
460 opt_bind
!(self, label
);
461 kind
!("Loop({body}, {label}, LoopSource::{des:?}, _)");
463 label
.if_some(|l
| self.ident(field
!(l
.ident
)));
465 ExprKind
::If(cond
, then
, else_expr
) => {
466 bind
!(self, cond
, then
);
467 opt_bind
!(self, else_expr
);
468 kind
!("If({cond}, {then}, {else_expr})");
471 else_expr
.if_some(|e
| self.expr(e
));
473 ExprKind
::Match(scrutinee
, arms
, des
) => {
474 bind
!(self, scrutinee
, arms
);
475 kind
!("Match({scrutinee}, {arms}, MatchSource::{des:?})");
476 self.expr(scrutinee
);
477 self.slice(arms
, |arm
| self.arm(arm
));
479 ExprKind
::Closure(&Closure
{
486 let movability
= OptionPat
::new(movability
.map(|m
| format
!("Movability::{m:?}")));
488 let ret_ty
= match fn_decl
.output
{
489 FnRetTy
::DefaultReturn(_
) => "FnRetTy::DefaultReturn(_)",
490 FnRetTy
::Return(_
) => "FnRetTy::Return(_ty)",
493 bind
!(self, fn_decl
, body_id
);
494 kind
!("Closure(CaptureBy::{capture_clause:?}, {fn_decl}, {body_id}, _, {movability})");
495 chain
!(self, "let {ret_ty} = {fn_decl}.output");
498 ExprKind
::Yield(sub
, source
) => {
500 kind
!("Yield(sub, YieldSource::{source:?})");
503 ExprKind
::Block(block
, label
) => {
505 opt_bind
!(self, label
);
506 kind
!("Block({block}, {label})");
508 label
.if_some(|l
| self.ident(field
!(l
.ident
)));
510 ExprKind
::Assign(target
, value
, _
) => {
511 bind
!(self, target
, value
);
512 kind
!("Assign({target}, {value}, _span)");
516 ExprKind
::AssignOp(op
, target
, value
) => {
517 bind
!(self, op
, target
, value
);
518 kind
!("AssignOp({op}, {target}, {value})");
519 chain
!(self, "BinOpKind::{:?} == {op}.node", op
.value
.node
);
523 ExprKind
::Field(object
, field_name
) => {
524 bind
!(self, object
, field_name
);
525 kind
!("Field({object}, {field_name})");
526 self.ident(field_name
);
529 ExprKind
::Index(object
, index
) => {
530 bind
!(self, object
, index
);
531 kind
!("Index({object}, {index})");
535 ExprKind
::Path(ref qpath
) => {
537 kind
!("Path(ref {qpath})");
540 ExprKind
::AddrOf(kind
, mutability
, inner
) => {
542 kind
!("AddrOf(BorrowKind::{kind:?}, Mutability::{mutability:?}, {inner})");
545 ExprKind
::Break(destination
, value
) => {
546 bind
!(self, destination
);
547 opt_bind
!(self, value
);
548 kind
!("Break({destination}, {value})");
549 self.destination(destination
);
550 value
.if_some(|e
| self.expr(e
));
552 ExprKind
::Continue(destination
) => {
553 bind
!(self, destination
);
554 kind
!("Continue({destination})");
555 self.destination(destination
);
557 ExprKind
::Ret(value
) => {
558 opt_bind
!(self, value
);
559 kind
!("Ret({value})");
560 value
.if_some(|e
| self.expr(e
));
562 ExprKind
::InlineAsm(_
) => {
563 kind
!("InlineAsm(_)");
564 out
!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
566 ExprKind
::Struct(qpath
, fields
, base
) => {
567 bind
!(self, qpath
, fields
);
568 opt_bind
!(self, base
);
569 kind
!("Struct({qpath}, {fields}, {base})");
571 self.slice(fields
, |field
| {
572 self.ident(field
!(field
.ident
));
573 self.expr(field
!(field
.expr
));
575 base
.if_some(|e
| self.expr(e
));
577 ExprKind
::ConstBlock(_
) => kind
!("ConstBlock(_)"),
578 ExprKind
::Repeat(value
, length
) => {
579 bind
!(self, value
, length
);
580 kind
!("Repeat({value}, {length})");
583 ArrayLen
::Infer(..) => chain
!(self, "let ArrayLen::Infer(..) = length"),
584 ArrayLen
::Body(anon_const
) => {
585 bind
!(self, anon_const
);
586 chain
!(self, "let ArrayLen::Body({anon_const}) = {length}");
587 self.body(field
!(anon_const
.body
));
591 ExprKind
::Err
=> kind
!("Err"),
592 ExprKind
::DropTemps(expr
) => {
594 kind
!("DropTemps({expr})");
600 fn block(&self, block
: &Binding
<&hir
::Block
<'_
>>) {
601 self.slice(field
!(block
.stmts
), |stmt
| self.stmt(stmt
));
602 self.option(field
!(block
.expr
), "trailing_expr", |expr
| {
607 fn body(&self, body_id
: &Binding
<hir
::BodyId
>) {
608 let expr
= self.cx
.tcx
.hir().body(body_id
.value
).value
;
610 chain
!(self, "{expr} = &cx.tcx.hir().body({body_id}).value");
614 fn pat(&self, pat
: &Binding
<&hir
::Pat
<'_
>>) {
615 let kind
= |kind
| chain
!(self, "let PatKind::{kind} = {pat}.kind");
617 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
620 match pat
.value
.kind
{
621 PatKind
::Wild
=> kind
!("Wild"),
622 PatKind
::Binding(ann
, _
, name
, sub
) => {
624 opt_bind
!(self, sub
);
625 let ann
= match ann
{
626 BindingAnnotation
::NONE
=> "NONE",
627 BindingAnnotation
::REF
=> "REF",
628 BindingAnnotation
::MUT
=> "MUT",
629 BindingAnnotation
::REF_MUT
=> "REF_MUT",
631 kind
!("Binding(BindingAnnotation::{ann}, _, {name}, {sub})");
633 sub
.if_some(|p
| self.pat(p
));
635 PatKind
::Struct(ref qpath
, fields
, ignore
) => {
636 bind
!(self, qpath
, fields
);
637 kind
!("Struct(ref {qpath}, {fields}, {ignore})");
639 self.slice(fields
, |field
| {
640 self.ident(field
!(field
.ident
));
641 self.pat(field
!(field
.pat
));
644 PatKind
::Or(fields
) => {
646 kind
!("Or({fields})");
647 self.slice(fields
, |pat
| self.pat(pat
));
649 PatKind
::TupleStruct(ref qpath
, fields
, skip_pos
) => {
650 bind
!(self, qpath
, fields
);
651 kind
!("TupleStruct(ref {qpath}, {fields}, {skip_pos:?})");
653 self.slice(fields
, |pat
| self.pat(pat
));
655 PatKind
::Path(ref qpath
) => {
657 kind
!("Path(ref {qpath})");
660 PatKind
::Tuple(fields
, skip_pos
) => {
662 kind
!("Tuple({fields}, {skip_pos:?})");
663 self.slice(fields
, |field
| self.pat(field
));
665 PatKind
::Box(pat
) => {
670 PatKind
::Ref(pat
, muta
) => {
672 kind
!("Ref({pat}, Mutability::{muta:?})");
675 PatKind
::Lit(lit_expr
) => {
676 bind
!(self, lit_expr
);
677 kind
!("Lit({lit_expr})");
680 PatKind
::Range(start
, end
, end_kind
) => {
681 opt_bind
!(self, start
, end
);
682 kind
!("Range({start}, {end}, RangeEnd::{end_kind:?})");
683 start
.if_some(|e
| self.expr(e
));
684 end
.if_some(|e
| self.expr(e
));
686 PatKind
::Slice(start
, middle
, end
) => {
687 bind
!(self, start
, end
);
688 opt_bind
!(self, middle
);
689 kind
!("Slice({start}, {middle}, {end})");
690 middle
.if_some(|p
| self.pat(p
));
691 self.slice(start
, |pat
| self.pat(pat
));
692 self.slice(end
, |pat
| self.pat(pat
));
697 fn stmt(&self, stmt
: &Binding
<&hir
::Stmt
<'_
>>) {
698 let kind
= |kind
| chain
!(self, "let StmtKind::{kind} = {stmt}.kind");
700 ($
($t
:tt
)*) => (kind(format_args
!($
($t
)*)));
703 match stmt
.value
.kind
{
704 StmtKind
::Local(local
) => {
706 kind
!("Local({local})");
707 self.option(field
!(local
.init
), "init", |init
| {
710 self.pat(field
!(local
.pat
));
712 StmtKind
::Item(_
) => kind
!("Item(item_id)"),
713 StmtKind
::Expr(e
) => {
718 StmtKind
::Semi(e
) => {
727 fn has_attr(cx
: &LateContext
<'_
>, hir_id
: hir
::HirId
) -> bool
{
728 let attrs
= cx
.tcx
.hir().attrs(hir_id
);
729 get_attr(cx
.sess(), attrs
, "author").count() > 0
732 fn path_to_string(path
: &QPath
<'_
>) -> String
{
733 fn inner(s
: &mut String
, path
: &QPath
<'_
>) {
735 QPath
::Resolved(_
, path
) => {
736 for (i
, segment
) in path
.segments
.iter().enumerate() {
740 write
!(s
, "{:?}", segment
.ident
.as_str()).unwrap();
743 QPath
::TypeRelative(ty
, segment
) => match &ty
.kind
{
744 hir
::TyKind
::Path(inner_path
) => {
745 inner(s
, inner_path
);
747 write
!(s
, "{:?}", segment
.ident
.as_str()).unwrap();
749 other
=> write
!(s
, "/* unimplemented: {other:?}*/").unwrap(),
751 QPath
::LangItem(..) => panic
!("path_to_string: called for lang item qpath"),
754 let mut s
= String
::new();