Auto merge of #89970 - jackh726:gats_diagnostics, r=nikomatsakis
Implementation of GATs outlives lint See #87479 for background. Closes #87479 The basic premise of this lint/error is to require the user to write where clauses on a GAT when those bounds can be implied or proven from any function on the trait returning that GAT. ## Intuitive Explanation (Attempt) ## Let's take this trait definition as an example: ```rust trait Iterable { type Item<'x>; fn iter<'a>(&'a self) -> Self::Item<'a>; } ``` Let's focus on the `iter` function. The first thing to realize is that we know that `Self: 'a` because of `&'a self`. If an impl wants `Self::Item` to contain any data with references, then those references must be derived from `&'a self`. Thus, they must live only as long as `'a`. Furthermore, because of the `Self: 'a` implied bound, they must live only as long as `Self`. Since it's `'a` is used in place of `'x`, it is reasonable to assume that any value of `Self::Item<'x>`, and thus `'x`, will only be able to live as long as `Self`. Therefore, we require this bound on `Item` in the trait. As another example: ```rust trait Deserializer<T> { type Out<'x>; fn deserialize<'a>(&self, input: &'a T) -> Self::Out<'a>; } ``` The intuition is similar here, except rather than a `Self: 'a` implied bound, we have a `T: 'a` implied bound. Thus, the data on `Self::Out<'a>` is derived from `&'a T`, and thus it is reasonable to expect that the lifetime `'x` will always be less than `T`. ## Implementation Algorithm ## * Given a GAT `<P0 as Trait<P1..Pi>>::G<Pi...Pn>` declared as `trait T<A1..Ai> for A0 { type G<Ai...An>; }` used in return type of one associated function `F` * Given env `E` (including implied bounds) for `F` * For each lifetime parameter `'a` in `P0...Pn`: * For each other type parameter `Pi != 'a` in `P0...Pn`: // FIXME: this include of lifetime parameters too * If `E => (P: 'a)`: * Require where clause `Ai: 'a` ## Follow-up questions ## * What should we do when we don't pass params exactly? For this example: ```rust trait Des { type Out<'x, D>; fn des<'z, T>(&self, data: &'z Wrap<T>) -> Self::Out<'z, Wrap<T>>; } ``` Should we be requiring a `D: 'x` clause? We pass `Wrap<T>` as `D` and `'z` as `'x`, and should be able to prove that `Wrap<T>: 'z`. r? `@nikomatsakis`
This commit is contained in:
commit
9d39f6ab7d
@ -1252,16 +1252,16 @@ pub fn set_tainted_by_errors(&self) {
|
||||
self.tainted_by_errors_flag.set(true)
|
||||
}
|
||||
|
||||
/// Process the region constraints and report any errors that
|
||||
/// Process the region constraints and return any any errors that
|
||||
/// result. After this, no more unification operations should be
|
||||
/// done -- or the compiler will panic -- but it is legal to use
|
||||
/// `resolve_vars_if_possible` as well as `fully_resolve`.
|
||||
pub fn resolve_regions_and_report_errors(
|
||||
pub fn resolve_regions(
|
||||
&self,
|
||||
region_context: DefId,
|
||||
outlives_env: &OutlivesEnvironment<'tcx>,
|
||||
mode: RegionckMode,
|
||||
) {
|
||||
) -> Vec<RegionResolutionError<'tcx>> {
|
||||
let (var_infos, data) = {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let inner = &mut *inner;
|
||||
@ -1287,6 +1287,21 @@ pub fn resolve_regions_and_report_errors(
|
||||
let old_value = self.lexical_region_resolutions.replace(Some(lexical_region_resolutions));
|
||||
assert!(old_value.is_none());
|
||||
|
||||
errors
|
||||
}
|
||||
|
||||
/// Process the region constraints and report any errors that
|
||||
/// result. After this, no more unification operations should be
|
||||
/// done -- or the compiler will panic -- but it is legal to use
|
||||
/// `resolve_vars_if_possible` as well as `fully_resolve`.
|
||||
pub fn resolve_regions_and_report_errors(
|
||||
&self,
|
||||
region_context: DefId,
|
||||
outlives_env: &OutlivesEnvironment<'tcx>,
|
||||
mode: RegionckMode,
|
||||
) {
|
||||
let errors = self.resolve_regions(region_context, outlives_env, mode);
|
||||
|
||||
if !self.is_tainted_by_errors() {
|
||||
// As a heuristic, just skip reporting region errors
|
||||
// altogether if other errors have been reported while
|
||||
|
@ -103,7 +103,7 @@ macro_rules! ignore_err {
|
||||
};
|
||||
}
|
||||
|
||||
trait OutlivesEnvironmentExt<'tcx> {
|
||||
pub(crate) trait OutlivesEnvironmentExt<'tcx> {
|
||||
fn add_implied_bounds(
|
||||
&mut self,
|
||||
infcx: &InferCtxt<'a, 'tcx>,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::check::regionck::OutlivesEnvironmentExt;
|
||||
use crate::check::{FnCtxt, Inherited};
|
||||
use crate::constrained_generic_params::{identify_constrained_generic_params, Parameter};
|
||||
|
||||
@ -11,16 +12,21 @@
|
||||
use rustc_hir::itemlikevisit::ParItemLikeVisitor;
|
||||
use rustc_hir::lang_items::LangItem;
|
||||
use rustc_hir::ItemKind;
|
||||
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
|
||||
use rustc_infer::infer::outlives::obligations::TypeOutlives;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_infer::infer::{self, RegionckMode, SubregionOrigin};
|
||||
use rustc_middle::hir::map as hir_map;
|
||||
use rustc_middle::ty::subst::{InternalSubsts, Subst};
|
||||
use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts, Subst};
|
||||
use rustc_middle::ty::trait_def::TraitSpecializationKind;
|
||||
use rustc_middle::ty::{
|
||||
self, AdtKind, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness,
|
||||
self, AdtKind, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeVisitor,
|
||||
WithConstness,
|
||||
};
|
||||
use rustc_session::parse::feature_err;
|
||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
|
||||
use rustc_span::{Span, DUMMY_SP};
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode, WellFormedLoc};
|
||||
|
||||
use std::convert::TryInto;
|
||||
@ -253,6 +259,364 @@ pub fn check_trait_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
check_gat_where_clauses(tcx, trait_item, encl_trait_def_id);
|
||||
}
|
||||
|
||||
/// Require that the user writes where clauses on GATs for the implicit
|
||||
/// outlives bounds involving trait parameters in trait functions and
|
||||
/// lifetimes passed as GAT substs. See `self-outlives-lint` test.
|
||||
///
|
||||
/// This trait will be our running example. We are currently WF checking the `Item` item...
|
||||
///
|
||||
/// ```rust
|
||||
/// trait LendingIterator {
|
||||
/// type Item<'me>; // <-- WF checking this trait item
|
||||
///
|
||||
/// fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
|
||||
/// }
|
||||
/// ```
|
||||
fn check_gat_where_clauses(
|
||||
tcx: TyCtxt<'_>,
|
||||
trait_item: &hir::TraitItem<'_>,
|
||||
encl_trait_def_id: DefId,
|
||||
) {
|
||||
let item = tcx.associated_item(trait_item.def_id);
|
||||
// If the current trait item isn't a type, it isn't a GAT
|
||||
if !matches!(item.kind, ty::AssocKind::Type) {
|
||||
return;
|
||||
}
|
||||
let generics: &ty::Generics = tcx.generics_of(trait_item.def_id);
|
||||
// If the current associated type doesn't have any (own) params, it's not a GAT
|
||||
// FIXME(jackh726): we can also warn in the more general case
|
||||
if generics.params.len() == 0 {
|
||||
return;
|
||||
}
|
||||
let associated_items: &ty::AssocItems<'_> = tcx.associated_items(encl_trait_def_id);
|
||||
let mut clauses: Option<FxHashSet<ty::Predicate<'_>>> = None;
|
||||
// For every function in this trait...
|
||||
// In our example, this would be the `next` method
|
||||
for item in
|
||||
associated_items.in_definition_order().filter(|item| matches!(item.kind, ty::AssocKind::Fn))
|
||||
{
|
||||
// The clauses we that we would require from this function
|
||||
let mut function_clauses = FxHashSet::default();
|
||||
|
||||
let id = hir::HirId::make_owner(item.def_id.expect_local());
|
||||
let param_env = tcx.param_env(item.def_id.expect_local());
|
||||
|
||||
let sig = tcx.fn_sig(item.def_id);
|
||||
// Get the signature using placeholders. In our example, this would
|
||||
// convert the late-bound 'a into a free region.
|
||||
let sig = tcx.liberate_late_bound_regions(item.def_id, sig);
|
||||
// Collect the arguments that are given to this GAT in the return type
|
||||
// of the function signature. In our example, the GAT in the return
|
||||
// type is `<Self as LendingIterator>::Item<'a>`, so 'a and Self are arguments.
|
||||
let (regions, types) =
|
||||
GATSubstCollector::visit(tcx, trait_item.def_id.to_def_id(), sig.output());
|
||||
|
||||
// If both regions and types are empty, then this GAT isn't in the
|
||||
// return type, and we shouldn't try to do clause analysis
|
||||
// (particularly, doing so would end up with an empty set of clauses,
|
||||
// since the current method would require none, and we take the
|
||||
// intersection of requirements of all methods)
|
||||
if types.is_empty() && regions.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The types we can assume to be well-formed. In our example, this
|
||||
// would be &'a mut Self, from the first argument.
|
||||
let mut wf_tys = FxHashSet::default();
|
||||
wf_tys.extend(sig.inputs());
|
||||
|
||||
// For each region argument (e.g., 'a in our example), check for a
|
||||
// relationship to the type arguments (e.g., Self). If there is an
|
||||
// outlives relationship (`Self: 'a`), then we want to ensure that is
|
||||
// reflected in a where clause on the GAT itself.
|
||||
for (region, region_idx) in ®ions {
|
||||
for (ty, ty_idx) in &types {
|
||||
// In our example, requires that Self: 'a
|
||||
if ty_known_to_outlive(tcx, id, param_env, &wf_tys, *ty, *region) {
|
||||
debug!(?ty_idx, ?region_idx);
|
||||
debug!("required clause: {} must outlive {}", ty, region);
|
||||
// Translate into the generic parameters of the GAT. In
|
||||
// our example, the type was Self, which will also be
|
||||
// Self in the GAT.
|
||||
let ty_param = generics.param_at(*ty_idx, tcx);
|
||||
let ty_param = tcx.mk_ty(ty::Param(ty::ParamTy {
|
||||
index: ty_param.index,
|
||||
name: ty_param.name,
|
||||
}));
|
||||
// Same for the region. In our example, 'a corresponds
|
||||
// to the 'me parameter.
|
||||
let region_param = generics.param_at(*region_idx, tcx);
|
||||
let region_param =
|
||||
tcx.mk_region(ty::RegionKind::ReEarlyBound(ty::EarlyBoundRegion {
|
||||
def_id: region_param.def_id,
|
||||
index: region_param.index,
|
||||
name: region_param.name,
|
||||
}));
|
||||
// The predicate we expect to see. (In our example,
|
||||
// `Self: 'me`.)
|
||||
let clause = ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(
|
||||
ty_param,
|
||||
region_param,
|
||||
));
|
||||
let clause = tcx.mk_predicate(ty::Binder::dummy(clause));
|
||||
function_clauses.insert(clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each region argument (e.g., 'a in our example), also check for a
|
||||
// relationship to the other region arguments. If there is an
|
||||
// outlives relationship, then we want to ensure that is
|
||||
// reflected in a where clause on the GAT itself.
|
||||
for (region_a, region_a_idx) in ®ions {
|
||||
for (region_b, region_b_idx) in ®ions {
|
||||
if region_a == region_b {
|
||||
continue;
|
||||
}
|
||||
|
||||
if region_known_to_outlive(tcx, id, param_env, &wf_tys, *region_a, *region_b) {
|
||||
debug!(?region_a_idx, ?region_b_idx);
|
||||
debug!("required clause: {} must outlive {}", region_a, region_b);
|
||||
// Translate into the generic parameters of the GAT.
|
||||
let region_a_param = generics.param_at(*region_a_idx, tcx);
|
||||
let region_a_param =
|
||||
tcx.mk_region(ty::RegionKind::ReEarlyBound(ty::EarlyBoundRegion {
|
||||
def_id: region_a_param.def_id,
|
||||
index: region_a_param.index,
|
||||
name: region_a_param.name,
|
||||
}));
|
||||
// Same for the region.
|
||||
let region_b_param = generics.param_at(*region_b_idx, tcx);
|
||||
let region_b_param =
|
||||
tcx.mk_region(ty::RegionKind::ReEarlyBound(ty::EarlyBoundRegion {
|
||||
def_id: region_b_param.def_id,
|
||||
index: region_b_param.index,
|
||||
name: region_b_param.name,
|
||||
}));
|
||||
// The predicate we expect to see.
|
||||
let clause = ty::PredicateKind::RegionOutlives(ty::OutlivesPredicate(
|
||||
region_a_param,
|
||||
region_b_param,
|
||||
));
|
||||
let clause = tcx.mk_predicate(ty::Binder::dummy(clause));
|
||||
function_clauses.insert(clause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Imagine we have:
|
||||
// ```
|
||||
// trait Foo {
|
||||
// type Bar<'me>;
|
||||
// fn gimme(&self) -> Self::Bar<'_>;
|
||||
// fn gimme_default(&self) -> Self::Bar<'static>;
|
||||
// }
|
||||
// ```
|
||||
// We only want to require clauses on `Bar` that we can prove from *all* functions (in this
|
||||
// case, `'me` can be `static` from `gimme_default`)
|
||||
match clauses.as_mut() {
|
||||
Some(clauses) => {
|
||||
clauses.drain_filter(|p| !function_clauses.contains(p));
|
||||
}
|
||||
None => {
|
||||
clauses = Some(function_clauses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any missing clauses, emit an error
|
||||
let mut clauses = clauses.unwrap_or_default();
|
||||
debug!(?clauses);
|
||||
if !clauses.is_empty() {
|
||||
let written_predicates: ty::GenericPredicates<'_> =
|
||||
tcx.explicit_predicates_of(trait_item.def_id);
|
||||
let mut clauses: Vec<_> = clauses
|
||||
.drain_filter(|clause| {
|
||||
written_predicates.predicates.iter().find(|p| &p.0 == clause).is_none()
|
||||
})
|
||||
.map(|clause| format!("{}", clause))
|
||||
.collect();
|
||||
// We sort so that order is predictable
|
||||
clauses.sort();
|
||||
if !clauses.is_empty() {
|
||||
let mut err = tcx.sess.struct_span_err(
|
||||
trait_item.span,
|
||||
&format!("Missing required bounds on {}", trait_item.ident),
|
||||
);
|
||||
|
||||
let suggestion = format!(
|
||||
"{} {}",
|
||||
if !trait_item.generics.where_clause.predicates.is_empty() {
|
||||
","
|
||||
} else {
|
||||
" where"
|
||||
},
|
||||
clauses.join(", "),
|
||||
);
|
||||
err.span_suggestion(
|
||||
trait_item.generics.where_clause.tail_span_for_suggestion(),
|
||||
"add the required where clauses",
|
||||
suggestion,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
|
||||
err.emit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(jackh726): refactor some of the shared logic between the two functions below
|
||||
|
||||
/// Given a known `param_env` and a set of well formed types, can we prove that
|
||||
/// `ty` outlives `region`.
|
||||
fn ty_known_to_outlive<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
id: hir::HirId,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
wf_tys: &FxHashSet<Ty<'tcx>>,
|
||||
ty: Ty<'tcx>,
|
||||
region: ty::Region<'tcx>,
|
||||
) -> bool {
|
||||
// Unfortunately, we have to use a new `InferCtxt` each call, because
|
||||
// region constraints get added and solved there and we need to test each
|
||||
// call individually.
|
||||
tcx.infer_ctxt().enter(|infcx| {
|
||||
let mut outlives_environment = OutlivesEnvironment::new(param_env);
|
||||
outlives_environment.add_implied_bounds(&infcx, wf_tys.clone(), id, DUMMY_SP);
|
||||
outlives_environment.save_implied_bounds(id);
|
||||
let region_bound_pairs = outlives_environment.region_bound_pairs_map().get(&id).unwrap();
|
||||
|
||||
let cause = ObligationCause::new(DUMMY_SP, id, ObligationCauseCode::MiscObligation);
|
||||
|
||||
let sup_type = ty;
|
||||
let sub_region = region;
|
||||
|
||||
let origin = SubregionOrigin::from_obligation_cause(&cause, || {
|
||||
infer::RelateParamBound(cause.span, sup_type, None)
|
||||
});
|
||||
|
||||
let outlives = &mut TypeOutlives::new(
|
||||
&infcx,
|
||||
tcx,
|
||||
®ion_bound_pairs,
|
||||
Some(infcx.tcx.lifetimes.re_root_empty),
|
||||
param_env,
|
||||
);
|
||||
outlives.type_must_outlive(origin, sup_type, sub_region);
|
||||
|
||||
let errors = infcx.resolve_regions(
|
||||
id.expect_owner().to_def_id(),
|
||||
&outlives_environment,
|
||||
RegionckMode::default(),
|
||||
);
|
||||
|
||||
debug!(?errors, "errors");
|
||||
|
||||
// If we were able to prove that the type outlives the region without
|
||||
// an error, it must be because of the implied or explicit bounds...
|
||||
errors.is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
fn region_known_to_outlive<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
id: hir::HirId,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
wf_tys: &FxHashSet<Ty<'tcx>>,
|
||||
region_a: ty::Region<'tcx>,
|
||||
region_b: ty::Region<'tcx>,
|
||||
) -> bool {
|
||||
// Unfortunately, we have to use a new `InferCtxt` each call, because
|
||||
// region constraints get added and solved there and we need to test each
|
||||
// call individually.
|
||||
tcx.infer_ctxt().enter(|infcx| {
|
||||
let mut outlives_environment = OutlivesEnvironment::new(param_env);
|
||||
outlives_environment.add_implied_bounds(&infcx, wf_tys.clone(), id, DUMMY_SP);
|
||||
outlives_environment.save_implied_bounds(id);
|
||||
|
||||
let cause = ObligationCause::new(DUMMY_SP, id, ObligationCauseCode::MiscObligation);
|
||||
|
||||
let origin = SubregionOrigin::from_obligation_cause(&cause, || {
|
||||
infer::RelateRegionParamBound(cause.span)
|
||||
});
|
||||
|
||||
use rustc_infer::infer::outlives::obligations::TypeOutlivesDelegate;
|
||||
(&infcx).push_sub_region_constraint(origin, region_a, region_b);
|
||||
|
||||
let errors = infcx.resolve_regions(
|
||||
id.expect_owner().to_def_id(),
|
||||
&outlives_environment,
|
||||
RegionckMode::default(),
|
||||
);
|
||||
|
||||
debug!(?errors, "errors");
|
||||
|
||||
// If we were able to prove that the type outlives the region without
|
||||
// an error, it must be because of the implied or explicit bounds...
|
||||
errors.is_empty()
|
||||
})
|
||||
}
|
||||
|
||||
/// TypeVisitor that looks for uses of GATs like
|
||||
/// `<P0 as Trait<P1..Pn>>::GAT<Pn..Pm>` and adds the arguments `P0..Pm` into
|
||||
/// the two vectors, `regions` and `types` (depending on their kind). For each
|
||||
/// parameter `Pi` also track the index `i`.
|
||||
struct GATSubstCollector<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
gat: DefId,
|
||||
// Which region appears and which parameter index its subsituted for
|
||||
regions: FxHashSet<(ty::Region<'tcx>, usize)>,
|
||||
// Which params appears and which parameter index its subsituted for
|
||||
types: FxHashSet<(Ty<'tcx>, usize)>,
|
||||
}
|
||||
|
||||
impl<'tcx> GATSubstCollector<'tcx> {
|
||||
fn visit<T: TypeFoldable<'tcx>>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
gat: DefId,
|
||||
t: T,
|
||||
) -> (FxHashSet<(ty::Region<'tcx>, usize)>, FxHashSet<(Ty<'tcx>, usize)>) {
|
||||
let mut visitor = GATSubstCollector {
|
||||
tcx,
|
||||
gat,
|
||||
regions: FxHashSet::default(),
|
||||
types: FxHashSet::default(),
|
||||
};
|
||||
t.visit_with(&mut visitor);
|
||||
(visitor.regions, visitor.types)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> TypeVisitor<'tcx> for GATSubstCollector<'tcx> {
|
||||
type BreakTy = !;
|
||||
|
||||
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
|
||||
match t.kind() {
|
||||
ty::Projection(p) if p.item_def_id == self.gat => {
|
||||
for (idx, subst) in p.substs.iter().enumerate() {
|
||||
match subst.unpack() {
|
||||
GenericArgKind::Lifetime(lt) => {
|
||||
self.regions.insert((lt, idx));
|
||||
}
|
||||
GenericArgKind::Type(t) => {
|
||||
self.types.insert((t, idx));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
t.super_visit_with(self)
|
||||
}
|
||||
|
||||
fn tcx_for_anon_const_substs(&self) -> Option<TyCtxt<'tcx>> {
|
||||
Some(self.tcx)
|
||||
}
|
||||
}
|
||||
|
||||
fn could_be_self(trait_def_id: LocalDefId, ty: &hir::Ty<'_>) -> bool {
|
||||
|
@ -70,6 +70,7 @@
|
||||
#![feature(never_type)]
|
||||
#![feature(slice_partition_dedup)]
|
||||
#![feature(control_flow_enum)]
|
||||
#![feature(hash_drain_filter)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
#[macro_use]
|
||||
|
@ -8,7 +8,7 @@
|
||||
// check that we don't normalize with trait defaults.
|
||||
|
||||
trait Collection<T> {
|
||||
type Iter<'iter>: Iterator<Item=&'iter T> where T: 'iter;
|
||||
type Iter<'iter>: Iterator<Item=&'iter T> where T: 'iter, Self: 'iter;
|
||||
type Family: CollectionFamily;
|
||||
// Test associated type defaults with parameters
|
||||
type Sibling<U>: Collection<U> =
|
||||
|
@ -8,7 +8,7 @@
|
||||
// run-pass
|
||||
|
||||
trait Collection<T> {
|
||||
type Iter<'iter>: Iterator<Item=&'iter T> where T: 'iter;
|
||||
type Iter<'iter>: Iterator<Item=&'iter T> where T: 'iter, Self: 'iter;
|
||||
type Family: CollectionFamily;
|
||||
// Test associated type defaults with parameters
|
||||
type Sibling<U>: Collection<U> =
|
||||
|
@ -3,7 +3,7 @@
|
||||
#![feature(generic_associated_types)]
|
||||
|
||||
pub trait X {
|
||||
type Y<'a>;
|
||||
type Y<'a> where Self: 'a;
|
||||
fn m(&self) -> Self::Y<'_>;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
#![feature(generic_associated_types)]
|
||||
|
||||
trait Document {
|
||||
type Cursor<'a>: DocCursor<'a>;
|
||||
type Cursor<'a>: DocCursor<'a> where Self: 'a;
|
||||
|
||||
fn cursor(&self) -> Self::Cursor<'_>;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
pub trait SubTrait {}
|
||||
|
||||
pub trait SuperTrait {
|
||||
type SubType<'a>: SubTrait;
|
||||
type SubType<'a>: SubTrait where Self: 'a;
|
||||
|
||||
fn get_sub<'a>(&'a mut self) -> Self::SubType<'a>;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ LL | let sub: Box<dyn SuperTrait<SubType = SubStruct>> = Box::new(SuperStruc
|
||||
note: associated type defined here, with 1 lifetime parameter: `'a`
|
||||
--> $DIR/issue-76535.rs:6:10
|
||||
|
|
||||
LL | type SubType<'a>: SubTrait;
|
||||
LL | type SubType<'a>: SubTrait where Self: 'a;
|
||||
| ^^^^^^^ --
|
||||
help: add missing lifetime argument
|
||||
|
|
||||
@ -25,7 +25,7 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
|
||||
|
|
||||
LL | pub trait SuperTrait {
|
||||
| ---------- this trait cannot be made into an object...
|
||||
LL | type SubType<'a>: SubTrait;
|
||||
LL | type SubType<'a>: SubTrait where Self: 'a;
|
||||
| ^^^^^^^ ...because it contains the generic associated type `SubType`
|
||||
= help: consider moving `SubType` to another trait
|
||||
|
||||
@ -40,7 +40,7 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
|
||||
|
|
||||
LL | pub trait SuperTrait {
|
||||
| ---------- this trait cannot be made into an object...
|
||||
LL | type SubType<'a>: SubTrait;
|
||||
LL | type SubType<'a>: SubTrait where Self: 'a;
|
||||
| ^^^^^^^ ...because it contains the generic associated type `SubType`
|
||||
= help: consider moving `SubType` to another trait
|
||||
= note: required because of the requirements on the impl of `CoerceUnsized<Box<dyn SuperTrait<SubType = SubStruct<'_>>>>` for `Box<SuperStruct>`
|
||||
|
@ -17,12 +17,12 @@ fn t(&'a self) -> &'a T {
|
||||
}
|
||||
|
||||
trait MapLike<K, V> {
|
||||
type VRefCont<'a>: RefCont<'a, V>;
|
||||
type VRefCont<'a>: RefCont<'a, V> where Self: 'a;
|
||||
fn get<'a>(&'a self, key: &K) -> Option<Self::VRefCont<'a>>;
|
||||
}
|
||||
|
||||
impl<K: Ord, V: 'static> MapLike<K, V> for std::collections::BTreeMap<K, V> {
|
||||
type VRefCont<'a> = &'a V;
|
||||
type VRefCont<'a> where Self: 'a = &'a V;
|
||||
fn get<'a>(&'a self, key: &K) -> Option<&'a V> {
|
||||
std::collections::BTreeMap::get(self, key)
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ LL | as Box<dyn MapLike<u8, u8, VRefCont = dyn RefCont<'_, u8>>>;
|
||||
note: associated type defined here, with 1 lifetime parameter: `'a`
|
||||
--> $DIR/issue-79422.rs:20:10
|
||||
|
|
||||
LL | type VRefCont<'a>: RefCont<'a, V>;
|
||||
LL | type VRefCont<'a>: RefCont<'a, V> where Self: 'a;
|
||||
| ^^^^^^^^ --
|
||||
help: add missing lifetime argument
|
||||
|
|
||||
@ -25,7 +25,7 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
|
||||
|
|
||||
LL | trait MapLike<K, V> {
|
||||
| ------- this trait cannot be made into an object...
|
||||
LL | type VRefCont<'a>: RefCont<'a, V>;
|
||||
LL | type VRefCont<'a>: RefCont<'a, V> where Self: 'a;
|
||||
| ^^^^^^^^ ...because it contains the generic associated type `VRefCont`
|
||||
= help: consider moving `VRefCont` to another trait
|
||||
|
||||
@ -40,7 +40,7 @@ note: for a trait to be "object safe" it needs to allow building a vtable to all
|
||||
|
|
||||
LL | trait MapLike<K, V> {
|
||||
| ------- this trait cannot be made into an object...
|
||||
LL | type VRefCont<'a>: RefCont<'a, V>;
|
||||
LL | type VRefCont<'a>: RefCont<'a, V> where Self: 'a;
|
||||
| ^^^^^^^^ ...because it contains the generic associated type `VRefCont`
|
||||
= help: consider moving `VRefCont` to another trait
|
||||
= note: required because of the requirements on the impl of `CoerceUnsized<Box<dyn MapLike<u8, u8, VRefCont = (dyn RefCont<'_, u8> + 'static)>>>` for `Box<BTreeMap<u8, u8>>`
|
||||
|
@ -9,6 +9,7 @@ enum Either<L, R> {
|
||||
pub trait HasChildrenOf {
|
||||
type T;
|
||||
type TRef<'a>;
|
||||
//~^ Missing required bounds
|
||||
|
||||
fn ref_children<'a>(&'a self) -> Vec<Self::TRef<'a>>;
|
||||
fn take_children(self) -> Vec<Self::T>;
|
||||
@ -20,9 +21,9 @@ impl<Left, Right> HasChildrenOf for Either<Left, Right>
|
||||
Right: HasChildrenOf,
|
||||
{
|
||||
type T = Either<Left::T, Right::T>;
|
||||
// We used to error below because the where clause doesn't match the trait.
|
||||
// Now, we error early on the trait itself.
|
||||
type TRef<'a>
|
||||
//~^ `impl` associated type signature
|
||||
//~^^ `impl` associated type signature
|
||||
where
|
||||
<Left as HasChildrenOf>::T: 'a,
|
||||
<Right as HasChildrenOf>::T: 'a
|
||||
|
@ -1,32 +1,10 @@
|
||||
error: `impl` associated type signature for `TRef` doesn't match `trait` associated type signature
|
||||
--> $DIR/issue-86787.rs:23:5
|
||||
error: Missing required bounds on TRef
|
||||
--> $DIR/issue-86787.rs:11:5
|
||||
|
|
||||
LL | type TRef<'a>;
|
||||
| -------------- expected
|
||||
...
|
||||
LL | / type TRef<'a>
|
||||
LL | |
|
||||
LL | |
|
||||
LL | | where
|
||||
LL | | <Left as HasChildrenOf>::T: 'a,
|
||||
LL | | <Right as HasChildrenOf>::T: 'a
|
||||
LL | | = Either<&'a Left::T, &'a Right::T>;
|
||||
| |________________________________________^ found
|
||||
LL | type TRef<'a>;
|
||||
| ^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where Self: 'a`
|
||||
|
||||
error: `impl` associated type signature for `TRef` doesn't match `trait` associated type signature
|
||||
--> $DIR/issue-86787.rs:23:5
|
||||
|
|
||||
LL | type TRef<'a>;
|
||||
| -------------- expected
|
||||
...
|
||||
LL | / type TRef<'a>
|
||||
LL | |
|
||||
LL | |
|
||||
LL | | where
|
||||
LL | | <Left as HasChildrenOf>::T: 'a,
|
||||
LL | | <Right as HasChildrenOf>::T: 'a
|
||||
LL | | = Either<&'a Left::T, &'a Right::T>;
|
||||
| |________________________________________^ found
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -13,7 +13,8 @@ trait SearchableResource<Criteria> {
|
||||
trait SearchableResourceExt<Criteria>: SearchableResource<Criteria> {
|
||||
type Future<'f, A: 'f + ?Sized, B: 'f>: Future<Output = Result<Vec<A::SearchResult>, ()>> + 'f
|
||||
where
|
||||
A: SearchableResource<B>;
|
||||
A: SearchableResource<B>,
|
||||
Self: 'f;
|
||||
|
||||
fn search<'c>(&'c self, client: &'c ()) -> Self::Future<'c, Self, Criteria>;
|
||||
}
|
||||
@ -29,6 +30,7 @@ impl<T, Criteria> SearchableResourceExt<Criteria> for T
|
||||
type Future<'f, A, B: 'f>
|
||||
where
|
||||
A: SearchableResource<B> + ?Sized + 'f,
|
||||
Self: 'f,
|
||||
= SearchFutureTy<'f, A, B>;
|
||||
|
||||
fn search<'c>(&'c self, _client: &'c ()) -> Self::Future<'c, Self, Criteria> {
|
||||
|
@ -1,13 +1,14 @@
|
||||
#![feature(generic_associated_types)]
|
||||
|
||||
trait GatTrait {
|
||||
type Gat<'a>;
|
||||
type Gat<'a> where Self: 'a;
|
||||
|
||||
fn test(&self) -> Self::Gat<'_>;
|
||||
}
|
||||
|
||||
trait SuperTrait<T>
|
||||
where
|
||||
Self: 'static,
|
||||
for<'a> Self: GatTrait<Gat<'a> = &'a T>,
|
||||
{
|
||||
fn copy(&self) -> Self::Gat<'_> where T: Copy {
|
||||
|
@ -1,5 +1,5 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/issue-88360.rs:14:9
|
||||
--> $DIR/issue-88360.rs:15:9
|
||||
|
|
||||
LL | trait SuperTrait<T>
|
||||
| - this type parameter
|
||||
|
@ -1,7 +1,7 @@
|
||||
#![feature(generic_associated_types)]
|
||||
|
||||
pub trait X {
|
||||
type Y<'a>;
|
||||
type Y<'a> where Self: 'a;
|
||||
fn m(&self) -> Self::Y<'_>;
|
||||
}
|
||||
|
||||
|
173
src/test/ui/generic-associated-types/self-outlives-lint.rs
Normal file
173
src/test/ui/generic-associated-types/self-outlives-lint.rs
Normal file
@ -0,0 +1,173 @@
|
||||
#![feature(generic_associated_types)]
|
||||
|
||||
// check-fail
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
// We have a `&'a self`, so we need a `Self: 'a`
|
||||
trait Iterable {
|
||||
type Item<'x>;
|
||||
//~^ Missing required bounds
|
||||
fn iter<'a>(&'a self) -> Self::Item<'a>;
|
||||
}
|
||||
|
||||
/*
|
||||
impl<T> Iterable for T {
|
||||
type Item<'a> = &'a T;
|
||||
fn iter<'a>(&'a self) -> Self::Item<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// We have a `&'a T`, so we need a `T: 'x`
|
||||
trait Deserializer<T> {
|
||||
type Out<'x>;
|
||||
//~^ Missing required bounds
|
||||
fn deserialize<'a>(&self, input: &'a T) -> Self::Out<'a>;
|
||||
}
|
||||
|
||||
/*
|
||||
impl<T> Deserializer<T> for () {
|
||||
type Out<'a> = &'a T;
|
||||
fn deserialize<'a>(&self, input: &'a T) -> Self::Out<'a> { input }
|
||||
}
|
||||
*/
|
||||
|
||||
// We have a `&'b T` and a `'b: 'a`, so it is implied that `T: 'a`. Therefore, we need a `T: 'x`
|
||||
trait Deserializer2<T> {
|
||||
type Out<'x>;
|
||||
//~^ Missing required bounds
|
||||
fn deserialize2<'a, 'b: 'a>(&self, input1: &'b T) -> Self::Out<'a>;
|
||||
}
|
||||
|
||||
// We have a `&'a T` and a `&'b U`, so we need a `T: 'x` and a `U: 'y`
|
||||
trait Deserializer3<T, U> {
|
||||
type Out<'x, 'y>;
|
||||
//~^ Missing required bounds
|
||||
fn deserialize2<'a, 'b>(&self, input: &'a T, input2: &'b U) -> Self::Out<'a, 'b>;
|
||||
}
|
||||
|
||||
// `T` is a param on the function, so it can't be named by the associated type
|
||||
trait Deserializer4 {
|
||||
type Out<'x>;
|
||||
fn deserialize<'a, T>(&self, input: &'a T) -> Self::Out<'a>;
|
||||
}
|
||||
|
||||
struct Wrap<T>(T);
|
||||
|
||||
// We pass `Wrap<T>` and we see `&'z Wrap<T>`, so we require `D: 'x`
|
||||
trait Des {
|
||||
type Out<'x, D>;
|
||||
//~^ Missing required bounds
|
||||
fn des<'z, T>(&self, data: &'z Wrap<T>) -> Self::Out<'z, Wrap<T>>;
|
||||
}
|
||||
/*
|
||||
impl Des for () {
|
||||
type Out<'x, D> = &'x D; // Not okay
|
||||
fn des<'a, T>(&self, data: &'a Wrap<T>) -> Self::Out<'a, Wrap<T>> {
|
||||
data
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// We have `T` and `'z` as GAT substs. Because of `&'z Wrap<T>`, there is an
|
||||
// implied bound that `T: 'z`, so we require `D: 'x`
|
||||
trait Des2 {
|
||||
type Out<'x, D>;
|
||||
//~^ Missing required bounds
|
||||
fn des<'z, T>(&self, data: &'z Wrap<T>) -> Self::Out<'z, T>;
|
||||
}
|
||||
/*
|
||||
impl Des2 for () {
|
||||
type Out<'x, D> = &'x D;
|
||||
fn des<'a, T>(&self, data: &'a Wrap<T>) -> Self::Out<'a, T> {
|
||||
&data.0
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// We see `&'z T`, so we require `D: 'x`
|
||||
trait Des3 {
|
||||
type Out<'x, D>;
|
||||
//~^ Missing required bounds
|
||||
fn des<'z, T>(&self, data: &'z T) -> Self::Out<'z, T>;
|
||||
}
|
||||
/*
|
||||
impl Des3 for () {
|
||||
type Out<'x, D> = &'x D;
|
||||
fn des<'a, T>(&self, data: &'a T) -> Self::Out<'a, T> {
|
||||
data
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Similar case to before, except with GAT.
|
||||
trait NoGat<'a> {
|
||||
type Bar;
|
||||
fn method(&'a self) -> Self::Bar;
|
||||
}
|
||||
|
||||
// Lifetime is not on function; except `Self: 'a`
|
||||
// FIXME: we require two bounds (`where Self: 'a, Self: 'b`) when we should only require one
|
||||
trait TraitLifetime<'a> {
|
||||
type Bar<'b>;
|
||||
//~^ Missing required bounds
|
||||
fn method(&'a self) -> Self::Bar<'a>;
|
||||
}
|
||||
|
||||
// Like above, but we have a where clause that can prove what we want
|
||||
// FIXME: we require two bounds (`where Self: 'a, Self: 'b`) when we should only require one
|
||||
trait TraitLifetimeWhere<'a> where Self: 'a {
|
||||
type Bar<'b>;
|
||||
//~^ Missing required bounds
|
||||
fn method(&'a self) -> Self::Bar<'a>;
|
||||
}
|
||||
|
||||
// Explicit bound instead of implicit; we want to still error
|
||||
trait ExplicitBound {
|
||||
type Bar<'b>;
|
||||
//~^ Missing required bounds
|
||||
fn method<'b>(&self, token: &'b ()) -> Self::Bar<'b> where Self: 'b;
|
||||
}
|
||||
|
||||
// The use of the GAT here is not in the return, we don't want to error
|
||||
trait NotInReturn {
|
||||
type Bar<'b>;
|
||||
fn method<'b>(&'b self) where Self::Bar<'b>: Debug;
|
||||
}
|
||||
|
||||
// We obviously error for `Iterator`, but we should also error for `Item`
|
||||
trait IterableTwo {
|
||||
type Item<'a>;
|
||||
type Iterator<'a>: Iterator<Item = Self::Item<'a>>;
|
||||
//~^ Missing required bounds
|
||||
fn iter<'a>(&'a self) -> Self::Iterator<'a>;
|
||||
}
|
||||
|
||||
// We also should report region outlives clauses
|
||||
trait RegionOutlives {
|
||||
type Bar<'a, 'b>;
|
||||
//~^ Missing required bounds
|
||||
fn foo<'x, 'y>(&self, input: &'x &'y ()) -> Self::Bar<'x, 'y>;
|
||||
}
|
||||
|
||||
/*
|
||||
impl Foo for () {
|
||||
type Bar<'a, 'b> = &'a &'b ();
|
||||
fn foo<'x, 'y>(&self, input: &'x &'y ()) -> Self::Bar<'x, 'y> {
|
||||
input
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// If there are multiple methods that return the GAT, require a set of clauses
|
||||
// that can be satisfied by *all* methods
|
||||
trait MultipleMethods {
|
||||
type Bar<'me>;
|
||||
|
||||
fn gimme<'a>(&'a self) -> Self::Bar<'a>;
|
||||
fn gimme_default(&self) -> Self::Bar<'static>;
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,98 @@
|
||||
error: Missing required bounds on Item
|
||||
--> $DIR/self-outlives-lint.rs:9:5
|
||||
|
|
||||
LL | type Item<'x>;
|
||||
| ^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where Self: 'x`
|
||||
|
||||
error: Missing required bounds on Out
|
||||
--> $DIR/self-outlives-lint.rs:25:5
|
||||
|
|
||||
LL | type Out<'x>;
|
||||
| ^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where T: 'x`
|
||||
|
||||
error: Missing required bounds on Out
|
||||
--> $DIR/self-outlives-lint.rs:39:5
|
||||
|
|
||||
LL | type Out<'x>;
|
||||
| ^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where T: 'x`
|
||||
|
||||
error: Missing required bounds on Out
|
||||
--> $DIR/self-outlives-lint.rs:46:5
|
||||
|
|
||||
LL | type Out<'x, 'y>;
|
||||
| ^^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where T: 'x, U: 'y`
|
||||
|
||||
error: Missing required bounds on Out
|
||||
--> $DIR/self-outlives-lint.rs:61:5
|
||||
|
|
||||
LL | type Out<'x, D>;
|
||||
| ^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where D: 'x`
|
||||
|
||||
error: Missing required bounds on Out
|
||||
--> $DIR/self-outlives-lint.rs:77:5
|
||||
|
|
||||
LL | type Out<'x, D>;
|
||||
| ^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where D: 'x`
|
||||
|
||||
error: Missing required bounds on Out
|
||||
--> $DIR/self-outlives-lint.rs:92:5
|
||||
|
|
||||
LL | type Out<'x, D>;
|
||||
| ^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where D: 'x`
|
||||
|
||||
error: Missing required bounds on Bar
|
||||
--> $DIR/self-outlives-lint.rs:114:5
|
||||
|
|
||||
LL | type Bar<'b>;
|
||||
| ^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where Self: 'a, Self: 'b`
|
||||
|
||||
error: Missing required bounds on Bar
|
||||
--> $DIR/self-outlives-lint.rs:122:5
|
||||
|
|
||||
LL | type Bar<'b>;
|
||||
| ^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where Self: 'a, Self: 'b`
|
||||
|
||||
error: Missing required bounds on Bar
|
||||
--> $DIR/self-outlives-lint.rs:129:5
|
||||
|
|
||||
LL | type Bar<'b>;
|
||||
| ^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where Self: 'b`
|
||||
|
||||
error: Missing required bounds on Iterator
|
||||
--> $DIR/self-outlives-lint.rs:143:5
|
||||
|
|
||||
LL | type Iterator<'a>: Iterator<Item = Self::Item<'a>>;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where Self: 'a`
|
||||
|
||||
error: Missing required bounds on Bar
|
||||
--> $DIR/self-outlives-lint.rs:150:5
|
||||
|
|
||||
LL | type Bar<'a, 'b>;
|
||||
| ^^^^^^^^^^^^^^^^-
|
||||
| |
|
||||
| help: add the required where clauses: `where 'a: 'b`
|
||||
|
||||
error: aborting due to 12 previous errors
|
||||
|
@ -5,12 +5,12 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
trait StreamingIterator {
|
||||
type Item<'a>;
|
||||
type Item<'a> where Self: 'a;
|
||||
// Applying the lifetime parameter `'a` to `Self::Item` inside the trait.
|
||||
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
|
||||
}
|
||||
|
||||
struct Foo<T: StreamingIterator> {
|
||||
struct Foo<T: StreamingIterator + 'static> {
|
||||
// Applying a concrete lifetime to the constructor outside the trait.
|
||||
bar: <T as StreamingIterator>::Item<'static>,
|
||||
}
|
||||
@ -30,7 +30,7 @@ struct StreamEnumerate<I> {
|
||||
}
|
||||
|
||||
impl<I: StreamingIterator> StreamingIterator for StreamEnumerate<I> {
|
||||
type Item<'a> = (usize, I::Item<'a>);
|
||||
type Item<'a> where Self: 'a = (usize, I::Item<'a>);
|
||||
fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> {
|
||||
match self.iter.next() {
|
||||
None => None,
|
||||
@ -44,7 +44,7 @@ fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> {
|
||||
}
|
||||
|
||||
impl<I: Iterator> StreamingIterator for I {
|
||||
type Item<'a> = <I as Iterator>::Item;
|
||||
type Item<'a> where Self: 'a = <I as Iterator>::Item;
|
||||
fn next(&mut self) -> Option<<I as StreamingIterator>::Item<'_>> {
|
||||
Iterator::next(self)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
#![feature(generic_associated_types)]
|
||||
|
||||
trait A {
|
||||
type B<'a>;
|
||||
type B<'a> where Self: 'a;
|
||||
|
||||
fn make_b<'a>(&'a self) -> Self::B<'a>;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user