zed/tooling/lints/src/entity_update_in_render.rs
Miguel Raz Guzmán Macedo 4b7369481d
Add dylint lint library for Zed-specific patterns (#58496)
Adds a dylint library under tooling/lints that flags Zed-specific
anti-patterns:

* shared_string_from_str_literal, 
* async_block_without_await, 
* entity_update_in_render, 
* notify_in_render, 
* owned_string_into_shared, 
* len_in_loop_condition, and 
* blocking_io_on_foreground. 

Includes UI tests, a single-lint helper, and workspace.metadata.dylint
registration so cargo dylint --all discovers it. The library pins its
own nightly toolchain (kept out of the main workspace) and tracks dylint
6.

Release Notes:

- N/A

Self-Review Checklist:

- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable

Closes #ISSUE

Release Notes:

- N/A or Added/Fixed/Improved ...
2026-07-03 22:05:34 +00:00

66 lines
2 KiB
Rust

use clippy_utils::diagnostics::span_lint;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use crate::render_helpers::{
is_directly_in_render_method, is_gpui_entity_or_weak, is_unit_or_result_unit,
};
rustc_session::declare_lint! {
/// ### What it does
///
/// Flags calls to `Entity::update` or `WeakEntity::update` that execute
/// synchronously inside a `Render::render` or `RenderOnce::render` method
/// and whose closure returns `()` (indicating mutation rather than reading).
///
/// ### Why is this bad?
///
/// The `render` method should be a pure function of state. Calling
/// `.update()` mutates an entity during the render pass, which can trigger
/// re-renders mid-render and lead to inconsistent UI state or infinite
/// render loops.
pub ENTITY_UPDATE_IN_RENDER,
Warn,
"mutating an entity via `.update()` during render"
}
pub(crate) struct EntityUpdateInRender;
rustc_session::impl_lint_pass!(EntityUpdateInRender => [ENTITY_UPDATE_IN_RENDER]);
impl<'tcx> LateLintPass<'tcx> for EntityUpdateInRender {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if expr.span.from_expansion() {
return;
}
let ExprKind::MethodCall(segment, receiver, _args, _span) = &expr.kind else {
return;
};
if segment.ident.name.as_str() != "update" {
return;
}
let receiver_ty = cx.typeck_results().expr_ty(receiver);
if !is_gpui_entity_or_weak(cx, receiver_ty) {
return;
}
let call_ty = cx.typeck_results().expr_ty(expr);
if !is_unit_or_result_unit(cx, call_ty) {
return;
}
if !is_directly_in_render_method(cx, expr.hir_id) {
return;
}
span_lint(
cx,
ENTITY_UPDATE_IN_RENDER,
expr.span,
"entity `.update()` called during render mutates state in the render pass",
);
}
}