Skip to content

Commit 3af0280

Browse files
committed
Extend QueryStability to handle IntoIterator implementations
1 parent 00095b3 commit 3af0280

File tree

3 files changed

+147
-5
lines changed

3 files changed

+147
-5
lines changed

compiler/rustc_lint/src/internal.rs

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
use rustc_hir::HirId;
55
use rustc_hir::def::Res;
66
use rustc_hir::def_id::DefId;
7-
use rustc_middle::ty::{self, GenericArgsRef, Ty as MiddleTy};
7+
use rustc_hir::{Expr, ExprKind};
8+
use rustc_middle::ty::{
9+
self, ClauseKind, GenericArgsRef, ParamTy, ProjectionPredicate, TraitPredicate, Ty as MiddleTy,
10+
};
811
use rustc_session::{declare_lint_pass, declare_tool_lint};
912
use rustc_span::hygiene::{ExpnKind, MacroKind};
1013
use rustc_span::{Span, sym};
@@ -101,10 +104,11 @@ declare_tool_lint! {
101104

102105
declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY, UNTRACKED_QUERY_INFORMATION]);
103106

104-
impl LateLintPass<'_> for QueryStability {
105-
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
106-
let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return };
107-
if let Ok(Some(instance)) = ty::Instance::try_resolve(cx.tcx, cx.typing_env(), def_id, args)
107+
impl<'tcx> LateLintPass<'tcx> for QueryStability {
108+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
109+
if let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr)
110+
&& let Ok(Some(instance)) =
111+
ty::Instance::try_resolve(cx.tcx, cx.typing_env(), def_id, args)
108112
{
109113
let def_id = instance.def_id();
110114
if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
@@ -122,7 +126,119 @@ impl LateLintPass<'_> for QueryStability {
122126
);
123127
}
124128
}
129+
check_into_iter_stability(cx, expr);
130+
}
131+
}
132+
133+
fn check_into_iter_stability<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
134+
let Some(into_iterator_def_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else {
135+
return;
136+
};
137+
// Is `expr` a function or method call?
138+
let Some((callee_def_id, generic_args, recv, args)) =
139+
get_callee_generic_args_and_args(cx, expr)
140+
else {
141+
return;
142+
};
143+
let fn_sig = cx.tcx.fn_sig(callee_def_id).instantiate_identity().skip_binder();
144+
let (_, inputs) = fn_sig.inputs_and_output.as_slice().split_last().unwrap();
145+
for (arg_index, &input) in inputs.iter().enumerate() {
146+
let &ty::Param(ParamTy { index: param_index, .. }) = input.kind() else {
147+
continue;
148+
};
149+
let (trait_predicates, _) = get_input_traits_and_projections(cx, callee_def_id, input);
150+
for TraitPredicate { trait_ref, .. } in trait_predicates {
151+
// Does the function or method require any of its arguments to implement `IntoIterator`?
152+
if trait_ref.def_id != into_iterator_def_id {
153+
continue;
154+
}
155+
let self_ty = generic_args[param_index as usize].expect_ty();
156+
let Some(self_ty_adt_def) = self_ty.peel_refs().ty_adt_def() else {
157+
return;
158+
};
159+
cx.tcx.for_each_relevant_impl(into_iterator_def_id, self_ty, |impl_id| {
160+
let impl_ty = cx.tcx.type_of(impl_id).skip_binder();
161+
let Some(impl_ty_adt_def) = impl_ty.peel_refs().ty_adt_def() else {
162+
return;
163+
};
164+
// To reduce false positives, verify that `self_ty` and `impl_ty` refer to the same ADT.
165+
if self_ty_adt_def != impl_ty_adt_def {
166+
return;
167+
}
168+
let Some(into_iter_item) = cx
169+
.tcx
170+
.associated_items(impl_id)
171+
.filter_by_name_unhygienic(sym::into_iter)
172+
.next()
173+
else {
174+
return;
175+
};
176+
// Does the input type's `IntoIterator` implementation have the
177+
// `rustc_lint_query_instability` attribute on its `into_iter` method?
178+
if !cx.tcx.has_attr(into_iter_item.def_id, sym::rustc_lint_query_instability) {
179+
return;
180+
}
181+
let span = if let Some(recv) = recv {
182+
if arg_index == 0 { recv.span } else { args[arg_index - 1].span }
183+
} else {
184+
args[arg_index].span
185+
};
186+
cx.emit_span_lint(
187+
POTENTIAL_QUERY_INSTABILITY,
188+
span,
189+
QueryInstability { query: cx.tcx.item_name(into_iter_item.def_id) },
190+
);
191+
});
192+
}
193+
}
194+
}
195+
196+
/// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
197+
/// `GenericArgs`, and arguments.
198+
fn get_callee_generic_args_and_args<'tcx>(
199+
cx: &LateContext<'tcx>,
200+
expr: &'tcx Expr<'tcx>,
201+
) -> Option<(DefId, GenericArgsRef<'tcx>, Option<&'tcx Expr<'tcx>>, &'tcx [Expr<'tcx>])> {
202+
if let ExprKind::Call(callee, args) = expr.kind
203+
&& let callee_ty = cx.typeck_results().expr_ty(callee)
204+
&& let ty::FnDef(callee_def_id, _) = callee_ty.kind()
205+
{
206+
let generic_args = cx.typeck_results().node_args(callee.hir_id);
207+
return Some((*callee_def_id, generic_args, None, args));
208+
}
209+
if let ExprKind::MethodCall(_, recv, args, _) = expr.kind
210+
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
211+
{
212+
let generic_args = cx.typeck_results().node_args(expr.hir_id);
213+
return Some((method_def_id, generic_args, Some(recv), args));
214+
}
215+
None
216+
}
217+
218+
/// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
219+
fn get_input_traits_and_projections<'tcx>(
220+
cx: &LateContext<'tcx>,
221+
callee_def_id: DefId,
222+
input: MiddleTy<'tcx>,
223+
) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
224+
let mut trait_predicates = Vec::new();
225+
let mut projection_predicates = Vec::new();
226+
for predicate in cx.tcx.param_env(callee_def_id).caller_bounds() {
227+
match predicate.kind().skip_binder() {
228+
ClauseKind::Trait(trait_predicate) => {
229+
if trait_predicate.trait_ref.self_ty() == input {
230+
trait_predicates.push(trait_predicate);
231+
}
232+
}
233+
ClauseKind::Projection(projection_predicate) => {
234+
if projection_predicate.projection_term.self_ty() == input {
235+
projection_predicates.push(projection_predicate);
236+
}
237+
}
238+
_ => {}
239+
}
125240
}
241+
(trait_predicates, projection_predicates)
126242
}
127243

128244
declare_tool_lint! {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//@ compile-flags: -Z unstable-options
2+
3+
#![deny(rustc::potential_query_instability)]
4+
5+
use std::collections::HashSet;
6+
7+
fn main() {
8+
let set = HashSet::<u32>::default();
9+
HashSet::<u32>::default().extend(set);
10+
//~^ ERROR using `into_iter` can result in unstable query results
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: using `into_iter` can result in unstable query results
2+
--> $DIR/query_stability_into_iter.rs:9:38
3+
|
4+
LL | HashSet::<u32>::default().extend(set);
5+
| ^^^
6+
|
7+
= note: if you believe this case to be fine, allow this lint and add a comment explaining your rationale
8+
note: the lint level is defined here
9+
--> $DIR/query_stability_into_iter.rs:3:9
10+
|
11+
LL | #![deny(rustc::potential_query_instability)]
12+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
13+
14+
error: aborting due to 1 previous error
15+

0 commit comments

Comments
 (0)