Skip to content

no_std_fs_operations ignores in-source allow/expect attributes #270

Description

@leynos

Summary

no_std_fs_operations ignores in-source lint-level attributes entirely. Neither #[allow(no_std_fs_operations)] nor #[expect(no_std_fs_operations)] (gated via cfg_attr(dylint_lib = "whitaker_suite", …) per ADR-002) suppresses the diagnostic. With expect, the outcome is contradictory: the original diagnostic and an unfulfilled_lint_expectations error are emitted for the same site.

Environment

  • whitaker-installer / suite v0.2.5, pinned toolchain nightly-2025-09-18
  • cargo-dylint 5.0.0, invoked via the whitaker wrapper: RUSTFLAGS="-D warnings" whitaker --all -- --all-targets --all-features
  • Observed while adopting the suite in leynos/netsuke (branch adopt-whitaker)

Reproduction

In any linted crate:

/// Sync a temporary file via its open handle.
#[cfg_attr(
    dylint_lib = "whitaker_suite",
    expect(
        no_std_fs_operations,
        reason = "NamedTempFile lives in the ambient system temp directory by design"
    )
)]
fn sync_temp_file(tmp: &tempfile::NamedTempFile) -> std::io::Result<()> {
    tmp.as_file().sync_all()
}

Result:

error: std::fs operation `std::fs::File::sync_all` bypasses the capability-based filesystem policy.
  --> src/runner/process/file_io.rs:47:5

error: this lint expectation is unfulfilled
  --> src/runner/process/file_io.rs:41:9
   |
41 |         no_std_fs_operations,

The pair of errors proves the cfg matched, the attribute was active, and rustc registered the expectation against the correctly-resolved lint name — yet the diagnostic was still emitted at its default Deny level, and the expectation was never fulfilled. Swapping expect for allow removes the unfulfilled-expectation error but the primary diagnostic still fires. A module-level inner attribute (#![cfg_attr(…)]) behaves identically. The same behaviour was observed on function-scoped attributes.

Expected

Standard rustc lint-level semantics: an enclosing allow/expect attribute suppresses the diagnostic (and fulfils the expectation), as ADR-002 ("Attribute macro for conditional Dylint expect") assumes when it prescribes the cfg_attr(dylint_lib = "…", expect(…)) pattern.

Suspected area

emit_diagnostic in crates/no_std_fs_operations/src/diagnostics.rs uses cx.span_lint(NO_STD_FS_OPERATIONS, span, …), which looks up the lint level at the visitor's current lint node rather than the HIR node that owns the offending expression/item. Other lints in the suite may share the pattern; no_expect_outside_tests and no_unwrap_or_else_panic were not exercised with attributes during the netsuke adoption, so their behaviour is unverified. Emitting via cx.tcx.node_span_lint / span_lint_hir (or the clippy_utils helpers that take a HirId) should restore attribute handling. A UI fixture asserting that allow-annotated code produces no diagnostic would prevent regression — the current UI suites only cover positive findings and excluded_crates.

Impact

Crate-level excluded_crates in dylint.toml is currently the only working escape hatch. Single-crate consumers with legitimately-ambient code (e.g. a which-style PATH resolver) cannot scope a suppression tighter than the whole crate; netsuke worked around it by extracting an ambient_fs boundary crate. This blocks the estate-wide rollout pattern for other single-crate repositories, and it makes ADR-002's proposed dylint_expect macro moot until the underlying level lookup is fixed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtestingTest coverage, test infrastructure, and verification tooling work.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions