Skip to content

Implementation of GATs outlives lint #89970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 6, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1255,16 +1255,16 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
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;
Expand All @@ -1290,6 +1290,21 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
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
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_typeck/src/check/regionck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ macro_rules! ignore_err {
};
}

trait OutlivesEnvironmentExt<'tcx> {
pub(crate) trait OutlivesEnvironmentExt<'tcx> {
fn add_implied_bounds(
&mut self,
infcx: &InferCtxt<'a, 'tcx>,
Expand Down
192 changes: 188 additions & 4 deletions compiler/rustc_typeck/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::check::regionck::OutlivesEnvironmentExt;
use crate::check::{FnCtxt, Inherited};
use crate::constrained_generic_params::{identify_constrained_generic_params, Parameter};

Expand All @@ -11,16 +12,21 @@ use rustc_hir::intravisit::Visitor;
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;
Expand Down Expand Up @@ -253,6 +259,184 @@ 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 as where clauses on GATs the implicit
/// outlives bounds involving trait parameters in trait functions and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Require that the user writes as where clauses on GATs the implicit
/// outlives bounds involving trait parameters in trait functions and
/// 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.
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
if generics.params.len() == 0 {
return;
}
let associated_items: &ty::AssocItems<'_> = tcx.associated_items(encl_trait_def_id);
let mut clauses = FxHashSet::default();
// For every function in this trait...
for item in
associated_items.in_definition_order().filter(|item| matches!(item.kind, ty::AssocKind::Fn))
{
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);
let sig = tcx.liberate_late_bound_regions(item.def_id, sig);
let mut visitor = GATSubstCollector {
tcx,
gat: trait_item.def_id.to_def_id(),
regions: FxHashSet::default(),
types: FxHashSet::default(),
};
sig.output().visit_with(&mut visitor);
let mut wf_tys = FxHashSet::default();
wf_tys.extend(sig.inputs());

for (region, region_idx) in &visitor.regions {
for (ty, ty_idx) in &visitor.types {
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,
&region_bound_pairs,
Some(tcx.lifetimes.re_root_empty),
param_env,
);
outlives.type_must_outlive(origin, sup_type, sub_region);

let errors = infcx.resolve_regions(
trait_item.def_id.to_def_id(),
&outlives_environment,
RegionckMode::default(),
);

debug!(?errors, "errors");

if errors.is_empty() {
debug!(?ty_idx, ?region_idx);
debug!("required clause: {} must outlive {}", ty, region);
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,
}));
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,
}));
let clause = ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(
ty_param,
region_param,
));
let clause = tcx.mk_predicate(ty::Binder::dummy(clause));
clauses.insert(clause);
}
});
}
}
}

// If there are any missing clauses, emit an error
debug!(?clauses);
if !clauses.is_empty() {
let written_predicates: ty::GenericPredicates<'_> = tcx.predicates_of(trait_item.def_id);
let clauses: Vec<_> = clauses
.drain_filter(|clause| {
written_predicates.predicates.iter().find(|p| &p.0 == clause).is_none()
})
.map(|clause| format!("{}", clause))
.collect();
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()
}
}
}

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> 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 {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_typeck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ This API is completely unstable and subject to change.
#![feature(never_type)]
#![feature(slice_partition_dedup)]
#![feature(control_flow_enum)]
#![feature(hash_drain_filter)]
#![recursion_limit = "256"]

#[macro_use]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> =
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/generic-associated-types/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_>;
}

Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/generic-associated-types/issue-70303.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<'_>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/generic-associated-types/issue-76535.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/ui/generic-associated-types/issue-76535.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
|
Expand All @@ -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

Expand All @@ -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>`
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/generic-associated-types/issue-79422.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ impl<'a, T> RefCont<'a, T> for Box<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)
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/ui/generic-associated-types/issue-79422.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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
|
Expand All @@ -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

Expand All @@ -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>>`
Expand Down
Loading