Skip to content
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

Generic associated types in trait paths #79554

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
39 changes: 32 additions & 7 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,16 +1076,40 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
fn lower_assoc_ty_constraint(
&mut self,
constraint: &AssocTyConstraint,
itctx: ImplTraitContext<'_, 'hir>,
mut itctx: ImplTraitContext<'_, 'hir>,
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
) -> hir::TypeBinding<'hir> {
debug!("lower_assoc_ty_constraint(constraint={:?}, itctx={:?})", constraint, itctx);

if let Some(ref gen_args) = constraint.gen_args {
self.sess.span_fatal(
gen_args.span(),
"generic associated types in trait paths are currently not implemented",
);
}
// lower generic arguments of identifier in constraint
let gen_args = if let Some(ref gen_args) = constraint.gen_args {
let gen_args_ctor = match gen_args {
GenericArgs::AngleBracketed(ref data) => {
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
self.lower_angle_bracketed_parameter_data(
data,
ParamMode::Explicit,
itctx.reborrow(),
)
.0
}
GenericArgs::Parenthesized(ref data) => {
let mut err = self.sess.struct_span_err(
gen_args.span(),
"parenthesized generic arguments cannot be used in associated type constraints"
);
// FIXME: try to write a suggestion here
err.emit();
self.lower_angle_bracketed_parameter_data(
&data.as_angle_bracketed_args(),
ParamMode::Explicit,
itctx.reborrow(),
)
.0
}
};
self.arena.alloc(gen_args_ctor.into_generic_args(&self.arena))
} else {
self.arena.alloc(hir::GenericArgs::none())
};

let kind = match constraint.kind {
AssocTyConstraintKind::Equality { ref ty } => {
Expand Down Expand Up @@ -1182,6 +1206,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
hir::TypeBinding {
hir_id: self.lower_node_id(constraint.id),
ident: constraint.ident,
gen_args,
kind,
span: constraint.span,
}
Expand Down
7 changes: 5 additions & 2 deletions compiler/rustc_ast_lowering/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
}
}

fn lower_angle_bracketed_parameter_data(
pub(crate) fn lower_angle_bracketed_parameter_data(
&mut self,
data: &AngleBracketedArgs,
param_mode: ParamMode,
Expand Down Expand Up @@ -426,6 +426,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
) -> hir::TypeBinding<'hir> {
let ident = Ident::with_dummy_span(hir::FN_OUTPUT_NAME);
let kind = hir::TypeBindingKind::Equality { ty };
hir::TypeBinding { hir_id: self.next_id(), span, ident, kind }
let args = arena_vec![self;];
let bindings = arena_vec![self;];
let gen_args = self.arena.alloc(hir::GenericArgs { args, bindings, parenthesized: false });
hir::TypeBinding { hir_id: self.next_id(), gen_args, span, ident, kind }
}
}
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,7 @@ pub struct TypeBinding<'hir> {
pub hir_id: HirId,
#[stable_hasher(project(name))]
pub ident: Ident,
pub gen_args: &'hir GenericArgs<'hir>,
pub kind: TypeBindingKind<'hir>,
pub span: Span,
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,7 @@ pub fn walk_assoc_type_binding<'v, V: Visitor<'v>>(
) {
visitor.visit_id(type_binding.hir_id);
visitor.visit_ident(type_binding.ident);
visitor.visit_generic_args(type_binding.span, type_binding.gen_args);
match type_binding.kind {
TypeBindingKind::Equality { ref ty } => {
visitor.visit_ty(ty);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_pretty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1840,6 +1840,7 @@ impl<'a> State<'a> {
for binding in generic_args.bindings.iter() {
start_or_comma(self);
self.print_ident(binding.ident);
self.print_generic_args(binding.gen_args, false, false);
self.s.space();
match generic_args.bindings[0].kind {
hir::TypeBindingKind::Equality { ref ty } => {
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,8 +1132,16 @@ impl<'tcx> ProjectionTy<'tcx> {
/// For example, if this is a projection of `<T as Iterator>::Item`,
/// then this function would return a `T: Iterator` trait reference.
pub fn trait_ref(&self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx> {
// FIXME: This method probably shouldn't exist at all, since it's not
// clear what this method really intends to do. Be careful when
// using this method since the resulting TraitRef additionally
// contains the substs for the assoc_item, which strictly speaking
// is not correct
let def_id = tcx.associated_item(self.item_def_id).container.id();
ty::TraitRef { def_id, substs: self.substs.truncate_to(tcx, tcx.generics_of(def_id)) }
// Include substitutions for generic arguments of associated types
let assoc_item = tcx.associated_item(self.item_def_id);
let substs_assoc_item = self.substs.truncate_to(tcx, tcx.generics_of(assoc_item.def_id));
ty::TraitRef { def_id, substs: substs_assoc_item }
jackh726 marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn self_ty(&self) -> Ty<'tcx> {
Expand Down
4 changes: 1 addition & 3 deletions compiler/rustc_trait_selection/src/traits/object_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,11 @@ fn predicates_reference_self(
}

fn bounds_reference_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span; 1]> {
let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(tcx, trait_def_id));
tcx.associated_items(trait_def_id)
.in_definition_order()
.filter(|item| item.kind == ty::AssocKind::Type)
.flat_map(|item| tcx.explicit_item_bounds(item.def_id))
.map(|&(predicate, sp)| (predicate.subst_supertrait(tcx, &trait_ref), sp))
.filter_map(|predicate| predicate_references_self(tcx, predicate))
.filter_map(|pred_span| predicate_references_self(tcx, *pred_span))
.collect()
}

Expand Down
166 changes: 104 additions & 62 deletions compiler/rustc_typeck/src/astconv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,15 @@ pub enum SizedByDefault {
No,
}

#[derive(Debug)]
struct ConvertedBinding<'a, 'tcx> {
item_name: Ident,
kind: ConvertedBindingKind<'a, 'tcx>,
gen_args: &'a GenericArgs<'a>,
span: Span,
}

#[derive(Debug)]
enum ConvertedBindingKind<'a, 'tcx> {
Equality(Ty<'tcx>),
Constraint(&'a [hir::GenericBound<'a>]),
Expand Down Expand Up @@ -323,6 +326,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {

let tcx = self.tcx();
let generics = tcx.generics_of(def_id);
debug!("generics: {:?}", generics);

if generics.has_self {
if generics.parent.is_some() {
Expand Down Expand Up @@ -557,7 +561,12 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
ConvertedBindingKind::Constraint(bounds)
}
};
ConvertedBinding { item_name: binding.ident, kind, span: binding.span }
ConvertedBinding {
item_name: binding.ident,
kind,
gen_args: binding.gen_args,
span: binding.span,
}
})
.collect();

Expand Down Expand Up @@ -918,60 +927,27 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
dup_bindings: &mut FxHashMap<DefId, Span>,
path_span: Span,
) -> Result<(), ErrorReported> {
let tcx = self.tcx();

if !speculative {
// Given something like `U: SomeTrait<T = X>`, we want to produce a
// predicate like `<U as SomeTrait>::T = X`. This is somewhat
// subtle in the event that `T` is defined in a supertrait of
// `SomeTrait`, because in that case we need to upcast.
//
// That is, consider this case:
//
// ```
// trait SubTrait: SuperTrait<i32> { }
// trait SuperTrait<A> { type T; }
//
// ... B: SubTrait<T = foo> ...
// ```
//
// We want to produce `<B as SuperTrait<i32>>::T == foo`.

// Find any late-bound regions declared in `ty` that are not
// declared in the trait-ref. These are not well-formed.
//
// Example:
//
// for<'a> <T as Iterator>::Item = &'a str // <-- 'a is bad
// for<'a> <T as FnMut<(&'a u32,)>>::Output = &'a str // <-- 'a is ok
if let ConvertedBindingKind::Equality(ty) = binding.kind {
let late_bound_in_trait_ref =
tcx.collect_constrained_late_bound_regions(&trait_ref);
let late_bound_in_ty =
tcx.collect_referenced_late_bound_regions(&ty::Binder::bind(ty));
debug!("late_bound_in_trait_ref = {:?}", late_bound_in_trait_ref);
debug!("late_bound_in_ty = {:?}", late_bound_in_ty);
// Given something like `U: SomeTrait<T = X>`, we want to produce a
// predicate like `<U as SomeTrait>::T = X`. This is somewhat
// subtle in the event that `T` is defined in a supertrait of
// `SomeTrait`, because in that case we need to upcast.
//
// That is, consider this case:
//
// ```
// trait SubTrait: SuperTrait<i32> { }
// trait SuperTrait<A> { type T; }
//
// ... B: SubTrait<T = foo> ...
// ```
//
// We want to produce `<B as SuperTrait<i32>>::T == foo`.

// FIXME: point at the type params that don't have appropriate lifetimes:
// struct S1<F: for<'a> Fn(&i32, &i32) -> &'a i32>(F);
// ---- ---- ^^^^^^^
self.validate_late_bound_regions(
late_bound_in_trait_ref,
late_bound_in_ty,
|br_name| {
struct_span_err!(
tcx.sess,
binding.span,
E0582,
"binding for associated type `{}` references {}, \
which does not appear in the trait input types",
binding.item_name,
br_name
)
},
);
}
}
debug!(
"add_predicates_for_ast_type_binding(hir_ref_id {:?}, trait_ref {:?}, binding {:?}, bounds {:?}",
hir_ref_id, trait_ref, binding, bounds
);
let tcx = self.tcx();

let candidate =
if self.trait_defines_associated_type_named(trait_ref.def_id(), binding.item_name) {
Expand Down Expand Up @@ -1030,20 +1006,85 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
.or_insert(binding.span);
}

// Include substitutions for generic parameters of associated types
let projection_ty = candidate.map_bound(|trait_ref| {
let item_segment = hir::PathSegment {
ident: assoc_ty.ident,
hir_id: None,
res: None,
args: Some(binding.gen_args),
infer_args: false,
};

let substs_trait_ref_and_assoc_item = self.create_substs_for_associated_item(
tcx,
path_span,
assoc_ty.def_id,
&item_segment,
trait_ref.substs,
);

debug!(
"add_predicates_for_ast_type_binding: substs for trait-ref and assoc_item: {:?}",
substs_trait_ref_and_assoc_item
);

ty::ProjectionTy {
item_def_id: assoc_ty.def_id,
substs: substs_trait_ref_and_assoc_item,
}
});

if !speculative {
// Find any late-bound regions declared in `ty` that are not
// declared in the trait-ref or assoc_ty. These are not well-formed.
//
// Example:
//
// for<'a> <T as Iterator>::Item = &'a str // <-- 'a is bad
// for<'a> <T as FnMut<(&'a u32,)>>::Output = &'a str // <-- 'a is ok
if let ConvertedBindingKind::Equality(ty) = binding.kind {
let late_bound_in_trait_ref =
tcx.collect_constrained_late_bound_regions(&projection_ty);
let late_bound_in_ty =
tcx.collect_referenced_late_bound_regions(&ty::Binder::bind(ty));
debug!("late_bound_in_trait_ref = {:?}", late_bound_in_trait_ref);
debug!("late_bound_in_ty = {:?}", late_bound_in_ty);

// FIXME: point at the type params that don't have appropriate lifetimes:
// struct S1<F: for<'a> Fn(&i32, &i32) -> &'a i32>(F);
// ---- ---- ^^^^^^^
self.validate_late_bound_regions(
late_bound_in_trait_ref,
late_bound_in_ty,
|br_name| {
struct_span_err!(
tcx.sess,
binding.span,
E0582,
"binding for associated type `{}` references {}, \
which does not appear in the trait input types",
binding.item_name,
br_name
)
},
);
}
}

match binding.kind {
ConvertedBindingKind::Equality(ref ty) => {
// "Desugar" a constraint like `T: Iterator<Item = u32>` this to
// the "projection predicate" for:
//
// `<T as Iterator>::Item = u32`
bounds.projection_bounds.push((
candidate.map_bound(|trait_ref| ty::ProjectionPredicate {
projection_ty: ty::ProjectionTy::from_ref_and_name(
tcx,
trait_ref,
binding.item_name,
),
ty,
projection_ty.map_bound(|projection_ty| {
debug!(
"add_predicates_for_ast_type_binding: projection_ty {:?}, substs: {:?}",
projection_ty, projection_ty.substs
);
ty::ProjectionPredicate { projection_ty, ty }
}),
binding.span,
));
Expand All @@ -1055,7 +1096,8 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
//
// Calling `skip_binder` is okay, because `add_bounds` expects the `param_ty`
// parameter to have a skipped binder.
let param_ty = tcx.mk_projection(assoc_ty.def_id, candidate.skip_binder().substs);
let param_ty =
tcx.mk_projection(assoc_ty.def_id, projection_ty.skip_binder().substs);
self.add_bounds(param_ty, ast_bounds, bounds);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#![feature(generic_associated_types)]
//~^ WARNING: the feature `generic_associated_types` is incomplete

trait X {
type Y<'x>;
}

fn main() {
fn _f(arg : Box<dyn for<'a> X<Y<'x> = &'a [u32]>>) {}
//~^ ERROR: use of undeclared lifetime name `'x`
//~| ERROR: binding for associated type `Y` references lifetime
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
warning: the feature `generic_associated_types` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/gat-in-trait-path-undeclared-lifetime.rs:1:12
|
LL | #![feature(generic_associated_types)]
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(incomplete_features)]` on by default
= note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information

error[E0261]: use of undeclared lifetime name `'x`
--> $DIR/gat-in-trait-path-undeclared-lifetime.rs:9:35
|
LL | fn _f(arg : Box<dyn for<'a> X<Y<'x> = &'a [u32]>>) {}
| - ^^ undeclared lifetime
| |
| help: consider introducing lifetime `'x` here: `<'x>`
|
= help: if you want to experiment with in-band lifetime bindings, add `#![feature(in_band_lifetimes)]` to the crate attributes

error[E0582]: binding for associated type `Y` references lifetime `'a`, which does not appear in the trait input types
--> $DIR/gat-in-trait-path-undeclared-lifetime.rs:9:33
|
LL | fn _f(arg : Box<dyn for<'a> X<Y<'x> = &'a [u32]>>) {}
| ^^^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors; 1 warning emitted

Some errors have detailed explanations: E0261, E0582.
For more information about an error, try `rustc --explain E0261`.
Loading