use crate::diag::span_lint_and_help;
use crate::utils::*;
use rustc_hir::Generics;
use rustc_hir::intravisit::FnKind;
use rustc_lint::LateContext;
use rustc_span::Span;
use crate::{
BLOCKING_SUFFIX_RESERVED_FOR_RUNNERS, EFFECT_ERROR_BOUND_MUST_USE_FROM,
EFFECT_FN_REQUIRES_WHERE_CLAUSE, MULTI_STEP_MUST_USE_EFFECT_MACRO, NO_ASYNC_FN_IN_EFFECT_CRATE,
NO_CONCRETE_CONTEXT_IN_PUB_API, NO_CONCRETE_ENV_IN_PUB_API, NO_EFFECT_SUFFIX_ON_GRAPH_BUILDERS,
NO_RESULT_RETURN_IN_PUB_FN, PREFER_NEEDS_X_OVER_RAW_GET_BOUNDS,
};
pub fn check_no_async_fn<'tcx>(
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
span: Span,
def_id: rustc_hir::def_id::LocalDefId,
) {
if !is_async_fn_kind(&kind) {
return;
}
if in_test_context(cx, def_id) {
return;
}
span_lint_and_help(
cx,
NO_ASYNC_FN_IN_EFFECT_CRATE,
span,
"`async fn` is forbidden in Effect.rs code",
None,
"convert to `fn ... -> Effect<A, E, R>` and wrap async calls with `from_async`",
);
}
pub fn check_no_result_return_pub<'tcx>(
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
is_pub: bool,
span: Span,
def_id: rustc_hir::def_id::LocalDefId,
) {
if in_test_context(cx, def_id) || !is_pub {
return;
}
if let FnKind::ItemFn(ident, _, _) = kind
&& ident.name.as_str() == "main"
{
return;
}
if !returns_result(decl) {
return;
}
span_lint_and_help(
cx,
NO_RESULT_RETURN_IN_PUB_FN,
span,
"public functions must not return `Result<T, E>`",
None,
"return `Effect<A, E, R>` instead; run the effect from `main` or test harnesses",
);
}
pub fn check_effect_fn_where_clause<'tcx>(
cx: &LateContext<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
generics: &'tcx Generics<'tcx>,
span: Span,
) {
if !returns_effect(decl) {
return;
}
let has_generics = generics
.params
.iter()
.any(|p| matches!(p.kind, rustc_hir::GenericParamKind::Type { .. }));
if has_generics && !has_where_clause(generics) {
span_lint_and_help(
cx,
EFFECT_FN_REQUIRES_WHERE_CLAUSE,
span,
"Effect-returning function has generic parameters but no `where` clause",
None,
"add `where A: Send + 'static, E: From<…> + Send + 'static, R: Selector<Key, I> + Send + Sync + 'static`",
);
}
}
pub fn check_effect_error_from_bound<'tcx>(
cx: &LateContext<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
generics: &'tcx Generics<'tcx>,
span: Span,
) {
if !returns_effect(decl) {
return;
}
if !has_where_clause(generics) {
return; }
let names = generic_param_names(generics);
let Some(e_name) = names.iter().find(|n| n.as_str() == "E").copied() else {
return;
};
if !e_has_from_bound(generics, e_name) {
span_lint_and_help(
cx,
EFFECT_ERROR_BOUND_MUST_USE_FROM,
span,
"`E` parameter in Effect-returning function has no `E: From<_>` bound",
None,
"add `E: From<SpecificError> + 'static` to the `where` clause for each service used",
);
}
}
pub fn check_no_concrete_context<'tcx>(
cx: &LateContext<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
is_pub: bool,
span: Span,
) {
if !is_pub {
return;
}
if let rustc_hir::FnRetTy::Return(ty) = &decl.output
&& ty_last_segment_is(ty, "Effect")
{
if let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) = &ty.kind
&& let Some(last) = path.segments.last()
&& let Some(args) = last.args
{
if let Some(rustc_hir::GenericArg::Type(r_ty)) = args.args.get(2) {
let r = r_ty.as_unambig_ty();
if ty_last_segment_is(r, "Context") || ty_last_segment_is(r, "Cons") {
span_lint_and_help(
cx,
NO_CONCRETE_CONTEXT_IN_PUB_API,
span,
"public API exposes a 0.6 `Context<Cons<…>>` as `R` type",
None,
"spell `R` with `caps!(Key, …)` (an `HCons` list), or take a generic `R: Selector<Key, I>`",
);
} else if ty_last_segment_is(r, "Env") {
span_lint_and_help(
cx,
NO_CONCRETE_ENV_IN_PUB_API,
span,
"public API exposes a 0.6 `Env` as `R` type",
None,
"spell `R` with `caps!(Key, …)`, or take a generic `R: Selector<Key, I>`",
);
}
}
}
}
}
pub fn check_prefer_needs_x<'tcx>(
cx: &LateContext<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
generics: &'tcx Generics<'tcx>,
span: Span,
) {
if !returns_effect(decl) {
return;
}
if has_raw_get_bound(generics) {
span_lint_and_help(
cx,
PREFER_NEEDS_X_OVER_RAW_GET_BOUNDS,
span,
"Effect-returning function uses 0.6 `Get<Key, …>` bounds",
None,
"spell `R` with `caps!(Key, …)` and read with `~Key` / `need::<Key>()`, or bound a generic `R: Selector<Key, I>`",
);
}
}
pub fn check_no_effect_suffix<'tcx>(
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
_span: Span,
) {
if !returns_effect(decl) {
return;
}
if let FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) = kind
&& ident.name.as_str().ends_with("_effect")
{
span_lint_and_help(
cx,
NO_EFFECT_SUFFIX_ON_GRAPH_BUILDERS,
ident.span,
"Effect-returning function name ends with `_effect`",
None,
"remove the `_effect` suffix; the return type already communicates effectfulness",
);
}
}
pub fn check_blocking_suffix_misuse<'tcx>(
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
_span: Span,
) {
if !returns_effect(decl) {
return;
}
if let FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) = kind
&& ident.name.as_str().ends_with("_blocking")
{
span_lint_and_help(
cx,
BLOCKING_SUFFIX_RESERVED_FOR_RUNNERS,
ident.span,
"function named `*_blocking` returns `Effect`; the name implies it runs the effect",
None,
"rename, or call `effect.block_on()` inside and return the `Exit` (or a `Result`)",
);
}
}
pub fn check_multi_step_flat_map(cx: &LateContext<'_>, expr: &rustc_hir::Expr<'_>) {
use rustc_hir::ExprKind;
if let ExprKind::MethodCall(seg, recv, _, _) = &expr.kind {
if seg.ident.name.as_str() != "flat_map" {
return;
}
if let ExprKind::MethodCall(inner_seg, _, _, _) = &recv.kind
&& inner_seg.ident.name.as_str() == "flat_map"
{
span_lint_and_help(
cx,
MULTI_STEP_MUST_USE_EFFECT_MACRO,
expr.span,
"chained `.flat_map(…).flat_map(…)` found",
None,
"use `effect!({ let a = ~step_a(); let b = ~step_b(a); … })` instead",
);
}
}
}