@@ -78,13 +78,17 @@ impl<'ast> ProjectLintPass<'ast> for InternalFunctionUsedOnce {
7878 if name. as_str ( ) . starts_with ( '_' ) {
7979 continue ;
8080 }
81- // Exactly one reference: zero references is dead code, a different concern.
82- // Self-references do not count and mark the function recursive, which rules it
83- // out entirely: a recursive function cannot be inlined into its caller. A single
84- // reference that only enters through a reference cycle (mutually recursive
85- // helpers with no external caller) has no caller to inline into either.
81+ // Exactly one reference, and it must be a direct call: zero references is dead
82+ // code, a different concern, and a reference used as a value (assigned to a
83+ // function pointer, returned, or passed as a callback) has no call site to inline
84+ // into, so it is not an inlining candidate. Self-references do not count and mark
85+ // the function recursive, which rules it out entirely: a recursive function cannot
86+ // be inlined into its caller. A single reference that only enters through a
87+ // reference cycle (mutually recursive helpers with no external caller) has no
88+ // caller to inline into either.
8689 let Some ( info) = counts. get ( & function_id) else { continue } ;
8790 if info. count == 1
91+ && info. value_count == 0
8892 && !info. self_referencing
8993 && !only_referenced_within_cycle ( & counts, function_id)
9094 {
@@ -122,6 +126,7 @@ fn operator_bound_functions(hir: &hir::Hir<'_>) -> HashSet<hir::FunctionId> {
122126#[ derive( Default ) ]
123127struct RefInfo {
124128 count : usize ,
129+ value_count : usize ,
125130 self_referencing : bool ,
126131 first_from : Option < hir:: FunctionId > ,
127132}
@@ -194,23 +199,58 @@ impl<'gcx> hir::Visit<'gcx> for ReferenceCounter<'gcx> {
194199 }
195200
196201 fn visit_expr ( & mut self , expr : & ' gcx hir:: Expr < ' gcx > ) -> ControlFlow < Self :: BreakValue > {
197- // A name or member expression typed as a function is one resolved reference. A
198- // self-reference marks the function recursive instead of counting.
199- if matches ! ( expr. kind, hir:: ExprKind :: Ident ( ..) | hir:: ExprKind :: Member ( ..) )
200- && let Some ( ty) = self . gcx . type_of_expr ( expr. peel_parens ( ) . id )
201- && let TyKind :: Fn ( function_ty) = ty. kind
202- && let Some ( function_id) = function_ty. function_id
203- {
204- let info = self . refs . entry ( function_id) . or_default ( ) ;
205- if self . current == Some ( function_id) {
206- info. self_referencing = true ;
207- } else {
208- info. count += 1 ;
209- if info. count == 1 {
210- info. first_from = self . current ;
202+ // The callee of a call is a direct, inlinable use: record it as a call and descend into
203+ // the callee's own sub-expressions (a member receiver, a nested call) without also
204+ // counting the callee node as a value reference. Every other name or member expression
205+ // typed as a function is a value-position reference, such as a function pointer that is
206+ // assigned, returned or passed as a callback, which has no call site to inline into.
207+ if let hir:: ExprKind :: Call ( callee, args, opts) = & expr. kind {
208+ let peeled = callee. peel_parens ( ) ;
209+ match peeled. kind {
210+ hir:: ExprKind :: Ident ( _) => self . record_reference ( peeled, true ) ,
211+ hir:: ExprKind :: Member ( receiver, _) => {
212+ self . record_reference ( peeled, true ) ;
213+ let _ = self . visit_expr ( receiver) ;
214+ }
215+ _ => {
216+ let _ = self . visit_expr ( peeled) ;
217+ }
218+ }
219+ if let Some ( opts) = opts {
220+ for arg in opts. args {
221+ let _ = self . visit_expr ( & arg. value ) ;
211222 }
212223 }
224+ return self . visit_call_args ( args) ;
225+ }
226+ if matches ! ( expr. kind, hir:: ExprKind :: Ident ( ..) | hir:: ExprKind :: Member ( ..) ) {
227+ self . record_reference ( expr, false ) ;
213228 }
214229 self . walk_expr ( expr)
215230 }
216231}
232+
233+ impl < ' gcx > ReferenceCounter < ' gcx > {
234+ /// Records one resolved function reference from `expr`, marking whether it is a direct call
235+ /// or a value-position use. `type_of_expr` gives the single declaration the type checker
236+ /// selected, so overloads, the qualified and `using for` forms and import aliases are all
237+ /// attributed to the right function. A self-reference marks the function recursive rather
238+ /// than counting.
239+ fn record_reference ( & mut self , expr : & hir:: Expr < ' _ > , is_call : bool ) {
240+ let Some ( ty) = self . gcx . type_of_expr ( expr. peel_parens ( ) . id ) else { return } ;
241+ let TyKind :: Fn ( function_ty) = ty. kind else { return } ;
242+ let Some ( function_id) = function_ty. function_id else { return } ;
243+ let info = self . refs . entry ( function_id) . or_default ( ) ;
244+ if self . current == Some ( function_id) {
245+ info. self_referencing = true ;
246+ } else {
247+ info. count += 1 ;
248+ if info. count == 1 {
249+ info. first_from = self . current ;
250+ }
251+ if !is_call {
252+ info. value_count += 1 ;
253+ }
254+ }
255+ }
256+ }
0 commit comments