@@ -80,6 +80,13 @@ impl<'tcx> LateLintPass<'tcx> for ConstPathJoin {
8080 let all_const_or_literal = components
8181 . iter ( )
8282 . all ( |c| !matches ! ( c, ComponentType :: Other ) ) ;
83+
84+ // Only consider constants if there are multiple constants
85+ // If there's just one constant with string literals, treat as normal joins
86+ let has_multiple_constants = components
87+ . iter ( )
88+ . filter ( |c| matches ! ( c, ComponentType :: Constant ( _) ) )
89+ . count ( ) > 1 ;
8390
8491 if all_literals {
8592 // Suggest joining into a single string literal
@@ -111,8 +118,8 @@ impl<'tcx> LateLintPass<'tcx> for ConstPathJoin {
111118 sugg,
112119 Applicability :: MachineApplicable ,
113120 ) ;
114- } else if all_const_or_literal {
115- // Suggest using concat!()
121+ } else if all_const_or_literal && has_multiple_constants {
122+ // Suggest using concat!() only when there are multiple constant expressions
116123 let concat_args = components
117124 . iter ( )
118125 . enumerate ( )
@@ -132,20 +139,57 @@ impl<'tcx> LateLintPass<'tcx> for ConstPathJoin {
132139 . collect :: < Vec < String > > ( )
133140 . join ( ", " ) ;
134141
135- let sugg = format ! ( "concat!({concat_args})" ) ;
136-
137- let span = match ty_or_partial_span {
138- TyOrPartialSpan :: Ty ( _) => expr. span ,
139- // For partial joins, we need a wider span
140- TyOrPartialSpan :: PartialSpan ( partial_span) => expr. span . with_lo ( partial_span. lo ( ) ) ,
142+ let ( span, sugg) = match ty_or_partial_span {
143+ TyOrPartialSpan :: Ty ( ty) => (
144+ expr. span ,
145+ format ! ( r#"{}::from(concat!({concat_args}))"# , ty. join( "::" ) )
146+ ) ,
147+ TyOrPartialSpan :: PartialSpan ( partial_span) => {
148+ // We need to replace the entire chain of joins with a single join of the concat
149+ let full_span = expr. span . with_lo ( partial_span. lo ( ) ) ;
150+ ( full_span, format ! ( r#".join(concat!({concat_args}))"# ) )
151+ }
141152 } ;
142153
143154 span_lint_and_sugg (
144155 cx,
145156 CONST_PATH_JOIN ,
146157 span,
158+ "path could be constructed with concat!" ,
159+ "consider using" ,
160+ sugg,
161+ Applicability :: MachineApplicable ,
162+ ) ;
163+ } else if all_const_or_literal {
164+ // For a single constant expression with string literals, treat like string literals
165+ let joined_path = components
166+ . iter ( )
167+ . map ( |c| match c {
168+ ComponentType :: Literal ( s) => s. as_str ( ) ,
169+ ComponentType :: Constant ( _) => ".." , // We know it's a path component
170+ _ => unreachable ! ( ) , // Already checked all are const or literals
171+ } )
172+ . collect :: < Vec < & str > > ( )
173+ . join ( "/" ) ;
174+
175+ let ( span, sugg) = match ty_or_partial_span {
176+ TyOrPartialSpan :: Ty ( ty) => (
177+ expr. span ,
178+ format ! ( r#"{}::from("{joined_path}")"# , ty. join( "::" ) )
179+ ) ,
180+ TyOrPartialSpan :: PartialSpan ( partial_span) => {
181+ // Use a wider span to replace all the joins
182+ let full_span = expr. span . with_lo ( partial_span. lo ( ) ) ;
183+ ( full_span, format ! ( r#".join("{joined_path}")"# ) )
184+ }
185+ } ;
186+
187+ span_lint_and_sugg (
188+ cx,
189+ CONST_PATH_JOIN ,
190+ span,
147191 "path could be constructed from a string literal" ,
148- "consider using concat! " ,
192+ "use " ,
149193 sugg,
150194 Applicability :: MachineApplicable ,
151195 ) ;
@@ -242,18 +286,26 @@ fn check_component_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) ->
242286 return ComponentType :: Literal ( s) ;
243287 }
244288
289+ // Check if it's a concat!() or env!() macro
290+ if let ExprKind :: Call ( callee, _) = expr. kind {
291+ if is_expr_path_def_path ( cx, callee, & [ "std" , "concat" ] ) ||
292+ is_expr_path_def_path ( cx, callee, & [ "std" , "env" ] ) {
293+ if let Some ( snippet) = snippet_opt ( cx, expr. span ) {
294+ return ComponentType :: Constant ( snippet) ;
295+ }
296+ }
297+ }
298+
245299 // Check if it's a constant-evaluatable string expression
246300 if !expr. span . from_expansion ( )
247301 && is_const_evaluatable ( cx, expr)
248302 && matches ! ( cx. typeck_results( ) . expr_ty( expr) . kind( ) , ty:: Str )
249303 {
250304 if let Some ( snippet) = snippet_opt ( cx, expr. span ) {
251- // Consider it a constant component if we can get the snippet
252305 return ComponentType :: Constant ( snippet) ;
253306 }
254307 }
255308
256- // Otherwise, it's neither a literal nor a recognized constant string
257309 ComponentType :: Other
258310}
259311
0 commit comments