Skip to content

Commit 1bc4f16

Browse files
committed
feat: elaborate on function lints
1 parent fe1009e commit 1bc4f16

File tree

7 files changed

+180
-0
lines changed

7 files changed

+180
-0
lines changed

cargo_pup_lint_config/src/function_lint/builder.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ impl<'a> FunctionConstraintBuilder<'a> {
123123
self
124124
}
125125

126+
/// Require that no function matching the selector exists
127+
pub fn must_not_exist(mut self) -> Self {
128+
self.add_rule_internal(FunctionRule::MustNotExist(self.current_severity));
129+
self
130+
}
131+
126132
/// Create a new MaxLength rule with the current severity
127133
pub fn create_max_length_rule(&self, length: usize) -> FunctionRule {
128134
FunctionRule::MaxLength(length, self.current_severity)

cargo_pup_lint_config/src/function_lint/matcher.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ impl FunctionMatcher {
6060
pattern.into(),
6161
)))
6262
}
63+
64+
/// Matches functions that return `Self` by value
65+
pub fn returns_self(&self) -> FunctionMatchNode {
66+
FunctionMatchNode::Leaf(FunctionMatch::ReturnsType(ReturnTypePattern::SelfValue))
67+
}
68+
69+
/// Matches functions that return `&Self`
70+
pub fn returns_self_ref(&self) -> FunctionMatchNode {
71+
FunctionMatchNode::Leaf(FunctionMatch::ReturnsType(ReturnTypePattern::SelfRef))
72+
}
73+
74+
/// Matches functions that return `&mut Self`
75+
pub fn returns_self_mut_ref(&self) -> FunctionMatchNode {
76+
FunctionMatchNode::Leaf(FunctionMatch::ReturnsType(ReturnTypePattern::SelfMutRef))
77+
}
6378
}
6479

6580
/// Node in the matcher expression tree

cargo_pup_lint_config/src/function_lint/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ pub enum ReturnTypePattern {
1616
Regex(String),
1717
/// Match Result<T, E> where E implements Error trait
1818
ResultWithErrorImpl,
19+
/// Match when the function returns `Self` by value (e.g., a consuming builder-style method)
20+
SelfValue,
21+
/// Match when the function returns `&Self` (immutable reference, e.g., fluent interface)
22+
SelfRef,
23+
/// Match when the function returns `&mut Self` (mutable reference, e.g., classic builder setter)
24+
SelfMutRef,
1925
}
2026

2127
/// Specifies how to match functions for linting
@@ -52,6 +58,8 @@ pub enum FunctionRule {
5258
MaxLength(usize, Severity),
5359
/// Enforces that Result error types must implement the Error trait
5460
ResultErrorMustImplementError(Severity),
61+
/// Enforces that a function matching the selector must not exist at all
62+
MustNotExist(Severity),
5563
}
5664

5765
// Helper methods for FunctionRule

cargo_pup_lint_impl/src/lints/function_lint/lint.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ use rustc_middle::ty::TyKind;
1212
use rustc_session::impl_lint_pass;
1313
use rustc_span::BytePos;
1414

15+
// Helper: retrieve the concrete Self type of the impl the method belongs to, if any
16+
fn get_self_type<'tcx>(
17+
ctx: &LateContext<'tcx>,
18+
fn_def_id: rustc_hir::def_id::DefId,
19+
) -> Option<rustc_middle::ty::Ty<'tcx>> {
20+
ctx.tcx
21+
.impl_of_method(fn_def_id)
22+
.map(|impl_def_id| ctx.tcx.type_of(impl_def_id).instantiate_identity())
23+
}
24+
1525
pub struct FunctionLint {
1626
name: String,
1727
matches: FunctionMatch,
@@ -143,6 +153,23 @@ fn evaluate_function_match(
143153
Err(_) => false,
144154
}
145155
}
156+
ReturnTypePattern::SelfValue => get_self_type(ctx, fn_def_id) == Some(return_ty),
157+
ReturnTypePattern::SelfRef => {
158+
match (get_self_type(ctx, fn_def_id), return_ty.kind()) {
159+
(Some(self_ty), &TyKind::Ref(_, inner, rustc_hir::Mutability::Not)) => {
160+
inner == self_ty
161+
}
162+
_ => false,
163+
}
164+
}
165+
ReturnTypePattern::SelfMutRef => {
166+
match (get_self_type(ctx, fn_def_id), return_ty.kind()) {
167+
(Some(self_ty), &TyKind::Ref(_, inner, rustc_hir::Mutability::Mut)) => {
168+
inner == self_ty
169+
}
170+
_ => false,
171+
}
172+
}
146173
}
147174
}
148175
FunctionMatch::AndMatches(left, right) => {
@@ -284,6 +311,21 @@ impl<'tcx> LateLintPass<'tcx> for FunctionLint {
284311
}
285312
}
286313
}
314+
FunctionRule::MustNotExist(severity) => {
315+
let sig_span = item
316+
.span
317+
.with_hi(item.span.lo() + BytePos((item_name.len() + 5) as u32));
318+
319+
span_lint_and_help(
320+
ctx,
321+
FUNCTION_LINT::get_by_severity(*severity),
322+
self.name().as_str(),
323+
sig_span,
324+
format!("Function '{item_name}' is forbidden by lint rule"),
325+
None,
326+
"Remove this function to satisfy the architectural rule",
327+
);
328+
}
287329
}
288330
}
289331
}
@@ -372,6 +414,21 @@ impl<'tcx> LateLintPass<'tcx> for FunctionLint {
372414
}
373415
}
374416
}
417+
FunctionRule::MustNotExist(severity) => {
418+
let sig_span = impl_item
419+
.span
420+
.with_hi(impl_item.span.lo() + BytePos((item_name.len() + 5) as u32));
421+
422+
span_lint_and_help(
423+
ctx,
424+
FUNCTION_LINT::get_by_severity(*severity),
425+
self.name().as_str(),
426+
sig_span,
427+
format!("Function '{item_name}' is forbidden by lint rule"),
428+
None,
429+
"Remove this function to satisfy the architectural rule",
430+
);
431+
}
375432
}
376433
}
377434
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//@compile-flags: --crate-name test_builder_style
2+
//@compile-flags: --crate-type lib
3+
4+
// This test verifies builder-style lints: "with_" methods returning `Self` are forbidden,
5+
// while "set_" methods returning `&mut Self` are allowed.
6+
7+
pub struct WidgetBuilder {
8+
val: i32,
9+
}
10+
11+
impl WidgetBuilder {
12+
// This should trigger the MustNotExist rule
13+
pub fn with_val(mut self, val: i32) -> Self { //~ ERROR: Function 'with_val' is forbidden by lint rule
14+
self.val = val;
15+
self
16+
}
17+
18+
// This is the preferred style and should compile cleanly
19+
pub fn set_val(&mut self, val: i32) -> &mut Self {
20+
self.val = val;
21+
self
22+
}
23+
24+
// Name matches the forbidden prefix but uses an allowed return type (&mut Self) – should be OK
25+
pub fn with_val_ref(&mut self, val: i32) -> &mut Self {
26+
self.val = val;
27+
self
28+
}
29+
30+
// Opposite rule: name starts with "set_" but returns Self – should trigger error
31+
pub fn set_val_value(self, val: i32) -> Self { //~ ERROR: Function 'set_val_value' is forbidden by lint rule
32+
Self { val }
33+
}
34+
35+
// Control method that matches neither rule
36+
pub fn touch(&self) {
37+
let _ = self.val;
38+
}
39+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: Function 'with_val' is forbidden by lint rule
2+
--> tests/ui/function_lint/builder_style.rs:13:5
3+
|
4+
LL | pub fn with_val(mut self, val: i32) -> Self {
5+
| ^^^^^^^^^^^^^
6+
|
7+
= help: Remove this function to satisfy the architectural rule
8+
= note: Applied by cargo-pup rule 'builder_style_with_methods_forbidden'.
9+
= note: `#[deny(function_lint)]` on by default
10+
11+
error: Function 'set_val_value' is forbidden by lint rule
12+
--> tests/ui/function_lint/builder_style.rs:31:5
13+
|
14+
LL | pub fn set_val_value(self, val: i32) -> Self {
15+
| ^^^^^^^^^^^^^^^^^^
16+
|
17+
= help: Remove this function to satisfy the architectural rule
18+
= note: Applied by cargo-pup rule 'builder_style_set_methods_forbid_self_value'.
19+
20+
error: aborting due to 2 previous errors
21+

tests/ui/function_lint/pup.ron

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,40 @@
300300
)
301301
]
302302
)
303+
),
304+
305+
// ======================================================================
306+
// SECTION: Builder Style Tests (for builder_style.rs)
307+
// ======================================================================
308+
309+
Function(
310+
(
311+
name: "builder_style_with_methods_forbidden",
312+
matches: AndMatches(
313+
NameRegex("^with_.*"),
314+
ReturnsType(SelfValue)
315+
),
316+
rules: [
317+
MustNotExist(
318+
Error,
319+
)
320+
]
321+
)
322+
),
323+
324+
Function(
325+
(
326+
name: "builder_style_set_methods_forbid_self_value",
327+
matches: AndMatches(
328+
NameRegex("^set_.*"),
329+
ReturnsType(SelfValue)
330+
),
331+
rules: [
332+
MustNotExist(
333+
Error,
334+
)
335+
]
336+
)
303337
)
304338
]
305339
)

0 commit comments

Comments
 (0)