Skip to main content

forge_lint/sol/info/
redundant_base_constructor_call.rs

1use super::RedundantBaseConstructorCall;
2use crate::{
3    linter::{LateLintPass, LintContext, Suggestion},
4    sol::{Severity, SolLint},
5};
6use solar::{
7    interface::{BytePos, Span, diagnostics::Applicability},
8    sema::{Gcx, hir},
9};
10
11declare_forge_lint!(
12    REDUNDANT_BASE_CONSTRUCTOR_CALL,
13    Severity::Info,
14    "redundant-base-constructor-call",
15    "explicit empty base-constructor arguments are redundant"
16);
17
18impl<'gcx> LateLintPass<'gcx> for RedundantBaseConstructorCall {
19    fn check_contract(
20        &mut self,
21        ctx: &LintContext,
22        gcx: Gcx<'gcx>,
23        contract: &'gcx hir::Contract<'gcx>,
24    ) {
25        // `contract X is A(), B()` clauses: removing only the `()` is enough, `is A` is valid.
26        for m in contract.bases_args {
27            try_emit(ctx, &gcx.hir, m, m.args.span);
28        }
29    }
30
31    fn check_function(
32        &mut self,
33        ctx: &LintContext,
34        gcx: Gcx<'gcx>,
35        func: &'gcx hir::Function<'gcx>,
36    ) {
37        // `constructor() A() {}` modifier-style base calls. The bare base name `A` is not valid
38        // in a constructor's modifier list, so the whole `A()` must be removed, along with one
39        // leading whitespace char to avoid leaving a double space.
40        if func.kind == hir::FunctionKind::Constructor {
41            for m in func.modifiers {
42                try_emit(ctx, &gcx.hir, m, expand_to_leading_ws(ctx, m.span));
43            }
44        }
45    }
46}
47
48fn try_emit(ctx: &LintContext, hir: &hir::Hir<'_>, m: &hir::Modifier<'_>, fix_span: Span) {
49    // Base-constructor invocations resolve to a contract; real modifiers resolve to functions.
50    // `is A` (no parens written) and `A(args...)` with real arguments are not redundant.
51    let hir::ItemId::Contract(base_id) = m.id else { return };
52    if m.args.is_dummy() || !m.args.is_empty() {
53        return;
54    }
55    // Empty `()` is redundant only when the base constructor takes no parameters (or the base
56    // declares no constructor at all).
57    if hir.contract(base_id).ctor.is_some_and(|c| !hir.function(c).parameters.is_empty()) {
58        return;
59    }
60    // Only emit a machine-applicable fix if the args span really is just `()` (no comments,
61    // whitespace, etc. that would silently be dropped).
62    if ctx.span_to_snippet(m.args.span).is_some_and(|s| s.trim() == "()") {
63        ctx.emit_with_suggestion(
64            &REDUNDANT_BASE_CONSTRUCTOR_CALL,
65            m.args.span,
66            Suggestion::fix(String::new(), Applicability::MachineApplicable)
67                .with_span(fix_span)
68                .with_desc("remove redundant base-constructor call"),
69        );
70    } else {
71        ctx.emit(&REDUNDANT_BASE_CONSTRUCTOR_CALL, m.args.span);
72    }
73}
74
75/// Extends `span` to start one byte earlier when that byte is an ASCII space or tab.
76fn expand_to_leading_ws(ctx: &LintContext, span: Span) -> Span {
77    if span.is_dummy() || span.lo() == BytePos(0) {
78        return span;
79    }
80    let lo = span.lo() - BytePos(1);
81    match ctx.span_to_snippet(Span::new(lo, span.lo())).as_deref() {
82        Some(" " | "\t") => span.with_lo(lo),
83        _ => span,
84    }
85}