use crate::diag::span_lint_and_help;
use crate::utils::*;
use rustc_hir::ItemKind;
use rustc_lint::LateContext;
use crate::{
NO_NESTED_RESULT_IN_PUB_API, NO_STATIC_SERVICE_GLOBALS, NO_THREAD_LOCAL_FOR_REQUEST_CONTEXT,
};
pub fn check_no_static_service_globals(cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
if let ItemKind::Static(_, _, ty, _) = &item.kind
&& !is_primitive_ty(ty)
{
span_lint_and_help(
cx,
NO_STATIC_SERVICE_GLOBALS,
item.span,
"`static` item found; services must not live in global statics",
None,
"list the service in `R` (`caps!(Key, …)`), provide it with a `Layer`, and read it with `~Key` in `effect!` or `need::<Key>()`",
);
}
}
pub fn check_no_thread_local(cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
let def_id = item.owner_id.to_def_id();
let span = match item.kind {
ItemKind::Static(..)
if cx
.tcx
.codegen_fn_attrs(def_id)
.flags
.contains(CodegenFnAttrFlags::THREAD_LOCAL) =>
{
item.span
}
ItemKind::Const(..)
if ty_is_adt_named(cx, cx.tcx.type_of(def_id).skip_binder(), "LocalKey") =>
{
item.span.source_callsite()
}
_ => return,
};
span_lint_and_help(
cx,
NO_THREAD_LOCAL_FOR_REQUEST_CONTEXT,
span,
"`thread_local!` storage found in Effect code",
None,
"use `FiberRef::new(initial)` for fiber-scoped dynamic state",
);
}
pub fn check_no_nested_result_type(cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) {
let is_pub = cx.tcx.visibility(item.owner_id.def_id).is_public();
if !is_pub {
return;
}
if let ItemKind::TyAlias(_, _, ty) = &item.kind
&& is_nested_result(ty)
{
span_lint_and_help(
cx,
NO_NESTED_RESULT_IN_PUB_API,
item.span,
"public type alias is `Result<Result<_, _>, _>`",
None,
"use `Or<E1, E2>` or a flat `E: From<E1> + From<E2>` channel",
);
}
}
pub fn check_no_nested_result_fn<'tcx>(
cx: &LateContext<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
is_pub: bool,
span: rustc_span::Span,
) {
if !is_pub {
return;
}
if let rustc_hir::FnRetTy::Return(ty) = &decl.output
&& is_nested_result(ty)
{
span_lint_and_help(
cx,
NO_NESTED_RESULT_IN_PUB_API,
span,
"public function returns `Result<Result<_, _>, _>`",
None,
"use `Or<E1, E2>` or flatten errors via `E: From<E1> + From<E2>`",
);
}
}
fn is_nested_result(ty: &rustc_hir::Ty<'_>) -> bool {
if !ty_last_segment_is(ty, "Result") {
return false;
}
if let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) = &ty.kind
&& let Some(last) = path.segments.last()
&& let Some(args) = last.args
&& let Some(rustc_hir::GenericArg::Type(ok_ty)) = args.args.first()
{
return ty_last_segment_is(ok_ty.as_unambig_ty(), "Result");
}
false
}
fn is_primitive_ty(ty: &rustc_hir::Ty<'_>) -> bool {
if let rustc_hir::TyKind::Ref(_, inner) = &ty.kind {
return is_primitive_ty(inner.ty);
}
matches!(
&ty.kind,
rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path))
if path.segments.last().is_some_and(|s| matches!(
s.ident.name.as_str(),
"u8" | "u16" | "u32" | "u64" | "u128" | "usize"
| "i8" | "i16" | "i32" | "i64" | "i128" | "isize"
| "f32" | "f64" | "bool" | "char" | "str" | "String"
))
) || matches!(
ty.kind,
rustc_hir::TyKind::Tup(_) | rustc_hir::TyKind::Never
)
}