rustc_hir_analysis/impl_wf_check/min_specialization.rs
1//! # Minimal Specialization
2//!
3//! This module contains the checks for sound specialization used when the
4//! `min_specialization` feature is enabled. This requires that the impl is
5//! *always applicable*.
6//!
7//! If `impl1` specializes `impl2` then `impl1` is always applicable if we know
8//! that all the bounds of `impl2` are satisfied, and all of the bounds of
9//! `impl1` are satisfied for some choice of lifetimes then we know that
10//! `impl1` applies for any choice of lifetimes.
11//!
12//! ## Basic approach
13//!
14//! To enforce this requirement on specializations we take the following
15//! approach:
16//!
17//! 1. Match up the args for `impl2` so that the implemented trait and
18//! self-type match those for `impl1`.
19//! 2. Check for any direct use of `'static` in the args of `impl2`.
20//! 3. Check that all of the generic parameters of `impl1` occur at most once
21//! in the *unconstrained* args for `impl2`. A parameter is constrained if
22//! its value is completely determined by an associated type projection
23//! predicate.
24//! 4. Check that all predicates on `impl1` either exist on `impl2` (after
25//! matching args), or are well-formed predicates for the trait's type
26//! arguments.
27//!
28//! ## Example
29//!
30//! Suppose we have the following always applicable impl:
31//!
32//! ```ignore (illustrative)
33//! impl<T> SpecExtend<T> for std::vec::IntoIter<T> { /* specialized impl */ }
34//! impl<T, I: Iterator<Item=T>> SpecExtend<T> for I { /* default impl */ }
35//! ```
36//!
37//! We get that the generic parameters for `impl2` are `[T, std::vec::IntoIter<T>]`.
38//! `T` is constrained to be `<I as Iterator>::Item`, so we check only
39//! `std::vec::IntoIter<T>` for repeated parameters, which it doesn't have. The
40//! predicates of `impl1` are only `T: Sized`, which is also a predicate of
41//! `impl2`. So this specialization is sound.
42//!
43//! ## Extensions
44//!
45//! Unfortunately not all specializations in the standard library are allowed
46//! by this. So there are two extensions to these rules that allow specializing
47//! on some traits: that is, using them as bounds on the specializing impl,
48//! even when they don't occur in the base impl.
49//!
50//! ### rustc_specialization_trait
51//!
52//! If a trait is always applicable, then it's sound to specialize on it. We
53//! check trait is always applicable in the same way as impls, except that step
54//! 4 is now "all predicates on `impl1` are always applicable". We require that
55//! `specialization` or `min_specialization` is enabled to implement these
56//! traits.
57//!
58//! ### rustc_unsafe_specialization_marker
59//!
60//! There are also some specialization on traits with no methods, including the
61//! stable `FusedIterator` trait. We allow marking marker traits with an
62//! unstable attribute that means we ignore them in point 3 of the checks
63//! above. This is unsound, in the sense that the specialized impl may be used
64//! when it doesn't apply, but we allow it in the short term since it can't
65//! cause use after frees with purely safe code in the same way as specializing
66//! on traits with methods can.
67
68use rustc_data_structures::fx::FxHashSet;
69use rustc_hir::def_id::{DefId, LocalDefId};
70use rustc_infer::infer::TyCtxtInferExt;
71use rustc_infer::traits::ObligationCause;
72use rustc_infer::traits::specialization_graph::Node;
73use rustc_middle::ty::trait_def::TraitSpecializationKind;
74use rustc_middle::ty::{
75 self, GenericArg, GenericArgs, GenericArgsRef, TyCtxt, TypeVisitableExt, TypingMode,
76};
77use rustc_span::{ErrorGuaranteed, Span};
78use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
79use rustc_trait_selection::traits::{self, ObligationCtxt, translate_args_with_cause, wf};
80use tracing::{debug, instrument};
81
82use crate::errors::GenericArgsOnOverriddenImpl;
83use crate::{constrained_generic_params as cgp, errors};
84
85pub(super) fn check_min_specialization(
86 tcx: TyCtxt<'_>,
87 impl_def_id: LocalDefId,
88) -> Result<(), ErrorGuaranteed> {
89 if let Some(node) = parent_specialization_node(tcx, impl_def_id) {
90 check_always_applicable(tcx, impl_def_id, node)?;
91 }
92 Ok(())
93}
94
95fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId) -> Option<Node> {
96 let trait_ref = tcx.impl_trait_ref(impl1_def_id)?;
97 let trait_def = tcx.trait_def(trait_ref.skip_binder().def_id);
98
99 let impl2_node = trait_def.ancestors(tcx, impl1_def_id.to_def_id()).ok()?.nth(1)?;
100
101 let always_applicable_trait =
102 matches!(trait_def.specialization_kind, TraitSpecializationKind::AlwaysApplicable);
103 if impl2_node.is_from_trait() && !always_applicable_trait {
104 // Implementing a normal trait isn't a specialization.
105 return None;
106 }
107 if trait_def.is_marker {
108 // Overlapping marker implementations are not really specializations.
109 return None;
110 }
111 Some(impl2_node)
112}
113
114/// Check that `impl1` is a sound specialization
115#[instrument(level = "debug", skip(tcx))]
116fn check_always_applicable(
117 tcx: TyCtxt<'_>,
118 impl1_def_id: LocalDefId,
119 impl2_node: Node,
120) -> Result<(), ErrorGuaranteed> {
121 let span = tcx.def_span(impl1_def_id);
122
123 let (impl1_args, impl2_args) = get_impl_args(tcx, impl1_def_id, impl2_node)?;
124 let impl2_def_id = impl2_node.def_id();
125 debug!(?impl2_def_id, ?impl2_args);
126
127 let parent_args = if impl2_node.is_from_trait() {
128 impl2_args.to_vec()
129 } else {
130 unconstrained_parent_impl_args(tcx, impl2_def_id, impl2_args)
131 };
132
133 check_has_items(tcx, impl1_def_id, impl2_node, span)
134 .and(check_static_lifetimes(tcx, &parent_args, span))
135 .and(check_duplicate_params(tcx, impl1_args, parent_args, span))
136 .and(check_predicates(tcx, impl1_def_id, impl1_args, impl2_node, impl2_args, span))
137}
138
139fn check_has_items(
140 tcx: TyCtxt<'_>,
141 impl1_def_id: LocalDefId,
142 impl2_node: Node,
143 span: Span,
144) -> Result<(), ErrorGuaranteed> {
145 if let Node::Impl(impl2_id) = impl2_node
146 && tcx.associated_item_def_ids(impl1_def_id).is_empty()
147 {
148 let base_impl_span = tcx.def_span(impl2_id);
149 return Err(tcx.dcx().emit_err(errors::EmptySpecialization { span, base_impl_span }));
150 }
151 Ok(())
152}
153
154/// Given a specializing impl `impl1`, and the base impl `impl2`, returns two
155/// generic parameters `(S1, S2)` that equate their trait references.
156/// The returned types are expressed in terms of the generics of `impl1`.
157///
158/// Example
159///
160/// ```ignore (illustrative)
161/// impl<A, B> Foo<A> for B { /* impl2 */ }
162/// impl<C> Foo<Vec<C>> for C { /* impl1 */ }
163/// ```
164///
165/// Would return `S1 = [C]` and `S2 = [Vec<C>, C]`.
166fn get_impl_args(
167 tcx: TyCtxt<'_>,
168 impl1_def_id: LocalDefId,
169 impl2_node: Node,
170) -> Result<(GenericArgsRef<'_>, GenericArgsRef<'_>), ErrorGuaranteed> {
171 let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis());
172 let ocx = ObligationCtxt::new_with_diagnostics(infcx);
173 let param_env = tcx.param_env(impl1_def_id);
174 let impl1_span = tcx.def_span(impl1_def_id);
175
176 let impl1_args = GenericArgs::identity_for_item(tcx, impl1_def_id);
177 let impl2_args = translate_args_with_cause(
178 infcx,
179 param_env,
180 impl1_def_id.to_def_id(),
181 impl1_args,
182 impl2_node,
183 &ObligationCause::misc(impl1_span, impl1_def_id),
184 );
185
186 let errors = ocx.select_all_or_error();
187 if !errors.is_empty() {
188 let guar = ocx.infcx.err_ctxt().report_fulfillment_errors(errors);
189 return Err(guar);
190 }
191
192 let assumed_wf_types = ocx.assumed_wf_types_and_report_errors(param_env, impl1_def_id)?;
193 let _ = ocx.resolve_regions_and_report_errors(impl1_def_id, param_env, assumed_wf_types);
194 let Ok(impl2_args) = infcx.fully_resolve(impl2_args) else {
195 let span = tcx.def_span(impl1_def_id);
196 let guar = tcx.dcx().emit_err(GenericArgsOnOverriddenImpl { span });
197 return Err(guar);
198 };
199 Ok((impl1_args, impl2_args))
200}
201
202/// Returns a list of all of the unconstrained generic parameters of the given impl.
203///
204/// For example given the impl:
205///
206/// impl<'a, T, I> ... where &'a I: IntoIterator<Item=&'a T>
207///
208/// This would return the args corresponding to `['a, I]`, because knowing
209/// `'a` and `I` determines the value of `T`.
210fn unconstrained_parent_impl_args<'tcx>(
211 tcx: TyCtxt<'tcx>,
212 impl_def_id: DefId,
213 impl_args: GenericArgsRef<'tcx>,
214) -> Vec<GenericArg<'tcx>> {
215 let impl_generic_predicates = tcx.predicates_of(impl_def_id);
216 let mut unconstrained_parameters = FxHashSet::default();
217 let mut constrained_params = FxHashSet::default();
218 let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity);
219
220 // Unfortunately the functions in `constrained_generic_parameters` don't do
221 // what we want here. We want only a list of constrained parameters while
222 // the functions in `cgp` add the constrained parameters to a list of
223 // unconstrained parameters.
224 for (clause, _) in impl_generic_predicates.predicates.iter() {
225 if let ty::ClauseKind::Projection(proj) = clause.kind().skip_binder() {
226 let unbound_trait_ref = proj.projection_term.trait_ref(tcx);
227 if Some(unbound_trait_ref) == impl_trait_ref {
228 continue;
229 }
230
231 unconstrained_parameters.extend(cgp::parameters_for(tcx, proj.projection_term, true));
232
233 for param in cgp::parameters_for(tcx, proj.term, false) {
234 if !unconstrained_parameters.contains(¶m) {
235 constrained_params.insert(param.0);
236 }
237 }
238
239 unconstrained_parameters.extend(cgp::parameters_for(tcx, proj.term, true));
240 }
241 }
242
243 impl_args
244 .iter()
245 .enumerate()
246 .filter(|&(idx, _)| !constrained_params.contains(&(idx as u32)))
247 .map(|(_, arg)| arg)
248 .collect()
249}
250
251/// Check that parameters of the derived impl don't occur more than once in the
252/// equated args of the base impl.
253///
254/// For example forbid the following:
255///
256/// ```ignore (illustrative)
257/// impl<A> Tr for A { }
258/// impl<B> Tr for (B, B) { }
259/// ```
260///
261/// Note that only consider the unconstrained parameters of the base impl:
262///
263/// ```ignore (illustrative)
264/// impl<S, I: IntoIterator<Item = S>> Tr<S> for I { }
265/// impl<T> Tr<T> for Vec<T> { }
266/// ```
267///
268/// The args for the parent impl here are `[T, Vec<T>]`, which repeats `T`,
269/// but `S` is constrained in the parent impl, so `parent_args` is only
270/// `[Vec<T>]`. This means we allow this impl.
271fn check_duplicate_params<'tcx>(
272 tcx: TyCtxt<'tcx>,
273 impl1_args: GenericArgsRef<'tcx>,
274 parent_args: Vec<GenericArg<'tcx>>,
275 span: Span,
276) -> Result<(), ErrorGuaranteed> {
277 let mut base_params = cgp::parameters_for(tcx, parent_args, true);
278 base_params.sort_by_key(|param| param.0);
279 if let (_, [duplicate, ..]) = base_params.partition_dedup() {
280 let param = impl1_args[duplicate.0 as usize];
281 return Err(tcx
282 .dcx()
283 .struct_span_err(span, format!("specializing impl repeats parameter `{param}`"))
284 .emit());
285 }
286 Ok(())
287}
288
289/// Check that `'static` lifetimes are not introduced by the specializing impl.
290///
291/// For example forbid the following:
292///
293/// ```ignore (illustrative)
294/// impl<A> Tr for A { }
295/// impl Tr for &'static i32 { }
296/// ```
297fn check_static_lifetimes<'tcx>(
298 tcx: TyCtxt<'tcx>,
299 parent_args: &Vec<GenericArg<'tcx>>,
300 span: Span,
301) -> Result<(), ErrorGuaranteed> {
302 if tcx.any_free_region_meets(parent_args, |r| r.is_static()) {
303 return Err(tcx.dcx().emit_err(errors::StaticSpecialize { span }));
304 }
305 Ok(())
306}
307
308/// Check whether predicates on the specializing impl (`impl1`) are allowed.
309///
310/// Each predicate `P` must be one of:
311///
312/// * Global (not reference any parameters).
313/// * A `T: Tr` predicate where `Tr` is an always-applicable trait.
314/// * Present on the base impl `impl2`.
315/// * This check is done using the `trait_predicates_eq` function below.
316/// * A well-formed predicate of a type argument of the trait being implemented,
317/// including the `Self`-type.
318#[instrument(level = "debug", skip(tcx))]
319fn check_predicates<'tcx>(
320 tcx: TyCtxt<'tcx>,
321 impl1_def_id: LocalDefId,
322 impl1_args: GenericArgsRef<'tcx>,
323 impl2_node: Node,
324 impl2_args: GenericArgsRef<'tcx>,
325 span: Span,
326) -> Result<(), ErrorGuaranteed> {
327 let impl1_predicates: Vec<_> = traits::elaborate(
328 tcx,
329 tcx.predicates_of(impl1_def_id).instantiate(tcx, impl1_args).into_iter(),
330 )
331 .collect();
332
333 let mut impl2_predicates = if impl2_node.is_from_trait() {
334 // Always applicable traits have to be always applicable without any
335 // assumptions.
336 Vec::new()
337 } else {
338 traits::elaborate(
339 tcx,
340 tcx.predicates_of(impl2_node.def_id())
341 .instantiate(tcx, impl2_args)
342 .into_iter()
343 .map(|(c, _s)| c.as_predicate()),
344 )
345 .collect()
346 };
347 debug!(?impl1_predicates, ?impl2_predicates);
348
349 // Since impls of always applicable traits don't get to assume anything, we
350 // can also assume their supertraits apply.
351 //
352 // For example, we allow:
353 //
354 // #[rustc_specialization_trait]
355 // trait AlwaysApplicable: Debug { }
356 //
357 // impl<T> Tr for T { }
358 // impl<T: AlwaysApplicable> Tr for T { }
359 //
360 // Specializing on `AlwaysApplicable` allows also specializing on `Debug`
361 // which is sound because we forbid impls like the following
362 //
363 // impl<D: Debug> AlwaysApplicable for D { }
364 let always_applicable_traits = impl1_predicates
365 .iter()
366 .copied()
367 .filter(|&(clause, _span)| {
368 matches!(
369 trait_specialization_kind(tcx, clause),
370 Some(TraitSpecializationKind::AlwaysApplicable)
371 )
372 })
373 .map(|(c, _span)| c.as_predicate());
374
375 // Include the well-formed predicates of the type parameters of the impl.
376 for arg in tcx.impl_trait_ref(impl1_def_id).unwrap().instantiate_identity().args {
377 let infcx = &tcx.infer_ctxt().build(TypingMode::non_body_analysis());
378 let obligations =
379 wf::obligations(infcx, tcx.param_env(impl1_def_id), impl1_def_id, 0, arg, span)
380 .unwrap();
381
382 assert!(!obligations.has_infer());
383 impl2_predicates
384 .extend(traits::elaborate(tcx, obligations).map(|obligation| obligation.predicate))
385 }
386 impl2_predicates.extend(traits::elaborate(tcx, always_applicable_traits));
387
388 let mut res = Ok(());
389 for (clause, span) in impl1_predicates {
390 if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(clause.as_predicate(), *pred2))
391 {
392 res = res.and(check_specialization_on(tcx, clause, span))
393 }
394 }
395 res
396}
397
398/// Checks if some predicate on the specializing impl (`predicate1`) is the same
399/// as some predicate on the base impl (`predicate2`).
400///
401/// This basically just checks syntactic equivalence, but is a little more
402/// forgiving since we want to equate `T: Tr` with `T: ~const Tr` so this can work:
403///
404/// ```ignore (illustrative)
405/// #[rustc_specialization_trait]
406/// trait Specialize { }
407///
408/// impl<T: Bound> Tr for T { }
409/// impl<T: ~const Bound + Specialize> const Tr for T { }
410/// ```
411///
412/// However, we *don't* want to allow the reverse, i.e., when the bound on the
413/// specializing impl is not as const as the bound on the base impl:
414///
415/// ```ignore (illustrative)
416/// impl<T: ~const Bound> const Tr for T { }
417/// impl<T: Bound + Specialize> const Tr for T { } // should be T: ~const Bound
418/// ```
419///
420/// So we make that check in this function and try to raise a helpful error message.
421fn trait_predicates_eq<'tcx>(
422 predicate1: ty::Predicate<'tcx>,
423 predicate2: ty::Predicate<'tcx>,
424) -> bool {
425 // FIXME(const_trait_impl)
426 predicate1 == predicate2
427}
428
429#[instrument(level = "debug", skip(tcx))]
430fn check_specialization_on<'tcx>(
431 tcx: TyCtxt<'tcx>,
432 clause: ty::Clause<'tcx>,
433 span: Span,
434) -> Result<(), ErrorGuaranteed> {
435 match clause.kind().skip_binder() {
436 // Global predicates are either always true or always false, so we
437 // are fine to specialize on.
438 _ if clause.is_global() => Ok(()),
439 // We allow specializing on explicitly marked traits with no associated
440 // items.
441 ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, polarity: _ }) => {
442 if matches!(
443 trait_specialization_kind(tcx, clause),
444 Some(TraitSpecializationKind::Marker)
445 ) {
446 Ok(())
447 } else {
448 Err(tcx
449 .dcx()
450 .struct_span_err(
451 span,
452 format!(
453 "cannot specialize on trait `{}`",
454 tcx.def_path_str(trait_ref.def_id),
455 ),
456 )
457 .emit())
458 }
459 }
460 ty::ClauseKind::Projection(ty::ProjectionPredicate { projection_term, term }) => Err(tcx
461 .dcx()
462 .struct_span_err(
463 span,
464 format!("cannot specialize on associated type `{projection_term} == {term}`",),
465 )
466 .emit()),
467 ty::ClauseKind::ConstArgHasType(..) => {
468 // FIXME(min_specialization), FIXME(const_generics):
469 // It probably isn't right to allow _every_ `ConstArgHasType` but I am somewhat unsure
470 // about the actual rules that would be sound. Can't just always error here because otherwise
471 // std/core doesn't even compile as they have `const N: usize` in some specializing impls.
472 //
473 // While we do not support constructs like `<T, const N: T>` there is probably no risk of
474 // soundness bugs, but when we support generic const parameter types this will need to be
475 // revisited.
476 Ok(())
477 }
478 _ => Err(tcx
479 .dcx()
480 .struct_span_err(span, format!("cannot specialize on predicate `{clause}`"))
481 .emit()),
482 }
483}
484
485fn trait_specialization_kind<'tcx>(
486 tcx: TyCtxt<'tcx>,
487 clause: ty::Clause<'tcx>,
488) -> Option<TraitSpecializationKind> {
489 match clause.kind().skip_binder() {
490 ty::ClauseKind::Trait(ty::TraitPredicate { trait_ref, polarity: _ }) => {
491 Some(tcx.trait_def(trait_ref.def_id).specialization_kind)
492 }
493 ty::ClauseKind::RegionOutlives(_)
494 | ty::ClauseKind::TypeOutlives(_)
495 | ty::ClauseKind::Projection(_)
496 | ty::ClauseKind::ConstArgHasType(..)
497 | ty::ClauseKind::WellFormed(_)
498 | ty::ClauseKind::ConstEvaluatable(..)
499 | ty::ClauseKind::HostEffect(..) => None,
500 }
501}