mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-30 14:01:51 +00:00
Auto merge of #70261 - Centril:angle-args-partition, r=varkor
Move arg/constraint partition check to validation & improve recovery - In the first commit, we move the check rejecting e.g., `<'a, Item = u8, String>` from the parser into AST validation. - We then use this to improve the code for parsing generic arguments. - And we add recovery for e.g., `<Item = >` (missing), `<Item = 42>` (constant), and `<Item = 'a>` (lifetime). This is also preparatory work for supporting https://github.com/rust-lang/rust/issues/70256. r? @varkor
This commit is contained in:
commit
b9d5ee5676
@ -214,11 +214,18 @@ impl GenericArg {
|
||||
pub struct AngleBracketedArgs {
|
||||
/// The overall span.
|
||||
pub span: Span,
|
||||
/// The arguments for this path segment.
|
||||
pub args: Vec<GenericArg>,
|
||||
/// Constraints on associated types, if any.
|
||||
/// E.g., `Foo<A = Bar, B: Baz>`.
|
||||
pub constraints: Vec<AssocTyConstraint>,
|
||||
/// The comma separated parts in the `<...>`.
|
||||
pub args: Vec<AngleBracketedArg>,
|
||||
}
|
||||
|
||||
/// Either an argument for a parameter e.g., `'a`, `Vec<u8>`, `0`,
|
||||
/// or a constraint on an associated item, e.g., `Item = String` or `Item: Bound`.
|
||||
#[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
|
||||
pub enum AngleBracketedArg {
|
||||
/// Argument for a generic parameter.
|
||||
Arg(GenericArg),
|
||||
/// Constraint for an associated item.
|
||||
Constraint(AssocTyConstraint),
|
||||
}
|
||||
|
||||
impl Into<Option<P<GenericArgs>>> for AngleBracketedArgs {
|
||||
@ -248,11 +255,13 @@ pub struct ParenthesizedArgs {
|
||||
|
||||
impl ParenthesizedArgs {
|
||||
pub fn as_angle_bracketed_args(&self) -> AngleBracketedArgs {
|
||||
AngleBracketedArgs {
|
||||
span: self.span,
|
||||
args: self.inputs.iter().cloned().map(GenericArg::Type).collect(),
|
||||
constraints: vec![],
|
||||
}
|
||||
let args = self
|
||||
.inputs
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|input| AngleBracketedArg::Arg(GenericArg::Type(input)))
|
||||
.collect();
|
||||
AngleBracketedArgs { span: self.span, args }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -546,9 +546,11 @@ pub fn noop_visit_angle_bracketed_parameter_data<T: MutVisitor>(
|
||||
data: &mut AngleBracketedArgs,
|
||||
vis: &mut T,
|
||||
) {
|
||||
let AngleBracketedArgs { args, constraints, span } = data;
|
||||
visit_vec(args, |arg| vis.visit_generic_arg(arg));
|
||||
visit_vec(constraints, |constraint| vis.visit_ty_constraint(constraint));
|
||||
let AngleBracketedArgs { args, span } = data;
|
||||
visit_vec(args, |arg| match arg {
|
||||
AngleBracketedArg::Arg(arg) => vis.visit_generic_arg(arg),
|
||||
AngleBracketedArg::Constraint(constraint) => vis.visit_ty_constraint(constraint),
|
||||
});
|
||||
vis.visit_span(span);
|
||||
}
|
||||
|
||||
|
@ -464,8 +464,12 @@ where
|
||||
{
|
||||
match *generic_args {
|
||||
GenericArgs::AngleBracketed(ref data) => {
|
||||
walk_list!(visitor, visit_generic_arg, &data.args);
|
||||
walk_list!(visitor, visit_assoc_ty_constraint, &data.constraints);
|
||||
for arg in &data.args {
|
||||
match arg {
|
||||
AngleBracketedArg::Arg(a) => visitor.visit_generic_arg(a),
|
||||
AngleBracketedArg::Constraint(c) => visitor.visit_assoc_ty_constraint(c),
|
||||
}
|
||||
}
|
||||
}
|
||||
GenericArgs::Parenthesized(ref data) => {
|
||||
walk_list!(visitor, visit_ty, &data.inputs);
|
||||
|
@ -34,6 +34,7 @@
|
||||
#![feature(crate_visibility_modifier)]
|
||||
#![feature(marker_trait_attr)]
|
||||
#![feature(specialization)]
|
||||
#![feature(or_patterns)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use rustc_ast::ast;
|
||||
|
@ -366,22 +366,27 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||
param_mode: ParamMode,
|
||||
mut itctx: ImplTraitContext<'_, 'hir>,
|
||||
) -> (GenericArgsCtor<'hir>, bool) {
|
||||
let &AngleBracketedArgs { ref args, ref constraints, .. } = data;
|
||||
let has_non_lt_args = args.iter().any(|arg| match arg {
|
||||
ast::GenericArg::Lifetime(_) => false,
|
||||
ast::GenericArg::Type(_) => true,
|
||||
ast::GenericArg::Const(_) => true,
|
||||
let has_non_lt_args = data.args.iter().any(|arg| match arg {
|
||||
AngleBracketedArg::Arg(ast::GenericArg::Lifetime(_))
|
||||
| AngleBracketedArg::Constraint(_) => false,
|
||||
AngleBracketedArg::Arg(ast::GenericArg::Type(_) | ast::GenericArg::Const(_)) => true,
|
||||
});
|
||||
(
|
||||
GenericArgsCtor {
|
||||
args: args.iter().map(|a| self.lower_generic_arg(a, itctx.reborrow())).collect(),
|
||||
bindings: self.arena.alloc_from_iter(
|
||||
constraints.iter().map(|b| self.lower_assoc_ty_constraint(b, itctx.reborrow())),
|
||||
),
|
||||
parenthesized: false,
|
||||
},
|
||||
!has_non_lt_args && param_mode == ParamMode::Optional,
|
||||
)
|
||||
let args = data
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
AngleBracketedArg::Arg(arg) => Some(self.lower_generic_arg(arg, itctx.reborrow())),
|
||||
AngleBracketedArg::Constraint(_) => None,
|
||||
})
|
||||
.collect();
|
||||
let bindings = self.arena.alloc_from_iter(data.args.iter().filter_map(|arg| match arg {
|
||||
AngleBracketedArg::Constraint(c) => {
|
||||
Some(self.lower_assoc_ty_constraint(c, itctx.reborrow()))
|
||||
}
|
||||
AngleBracketedArg::Arg(_) => None,
|
||||
}));
|
||||
let ctor = GenericArgsCtor { args, bindings, parenthesized: false };
|
||||
(ctor, !has_non_lt_args && param_mode == ParamMode::Optional)
|
||||
}
|
||||
|
||||
fn lower_parenthesized_parameter_data(
|
||||
|
@ -639,6 +639,34 @@ impl<'a> AstValidator<'a> {
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Enforce generic args coming before constraints in `<...>` of a path segment.
|
||||
fn check_generic_args_before_constraints(&self, data: &AngleBracketedArgs) {
|
||||
// Early exit in case it's partitioned as it should be.
|
||||
if data.args.iter().is_partitioned(|arg| matches!(arg, AngleBracketedArg::Arg(_))) {
|
||||
return;
|
||||
}
|
||||
// Find all generic argument coming after the first constraint...
|
||||
let mut misplaced_args = Vec::new();
|
||||
let mut first = None;
|
||||
for arg in &data.args {
|
||||
match (arg, first) {
|
||||
(AngleBracketedArg::Arg(a), Some(_)) => misplaced_args.push(a.span()),
|
||||
(AngleBracketedArg::Constraint(c), None) => first = Some(c.span),
|
||||
(AngleBracketedArg::Arg(_), None) | (AngleBracketedArg::Constraint(_), Some(_)) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
// ...and then error:
|
||||
self.err_handler()
|
||||
.struct_span_err(
|
||||
misplaced_args.clone(),
|
||||
"generic arguments must come before the first constraint",
|
||||
)
|
||||
.span_label(first.unwrap(), "the first constraint is provided here")
|
||||
.span_labels(misplaced_args, "generic argument")
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that generic parameters are in the correct order,
|
||||
@ -1008,17 +1036,20 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
|
||||
fn visit_generic_args(&mut self, _: Span, generic_args: &'a GenericArgs) {
|
||||
match *generic_args {
|
||||
GenericArgs::AngleBracketed(ref data) => {
|
||||
walk_list!(self, visit_generic_arg, &data.args);
|
||||
self.check_generic_args_before_constraints(data);
|
||||
|
||||
// Type bindings such as `Item = impl Debug` in `Iterator<Item = Debug>`
|
||||
// are allowed to contain nested `impl Trait`.
|
||||
self.with_impl_trait(None, |this| {
|
||||
walk_list!(
|
||||
this,
|
||||
visit_assoc_ty_constraint_from_generic_args,
|
||||
&data.constraints
|
||||
);
|
||||
});
|
||||
for arg in &data.args {
|
||||
match arg {
|
||||
AngleBracketedArg::Arg(arg) => self.visit_generic_arg(arg),
|
||||
// Type bindings such as `Item = impl Debug` in `Iterator<Item = Debug>`
|
||||
// are allowed to contain nested `impl Trait`.
|
||||
AngleBracketedArg::Constraint(constraint) => {
|
||||
self.with_impl_trait(None, |this| {
|
||||
this.visit_assoc_ty_constraint_from_generic_args(constraint);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GenericArgs::Parenthesized(ref data) => {
|
||||
walk_list!(self, visit_ty, &data.inputs);
|
||||
|
@ -1,10 +1,12 @@
|
||||
#![feature(bindings_after_at)]
|
||||
//! The `rustc_ast_passes` crate contains passes which validate the AST in `syntax`
|
||||
//! parsed by `rustc_parse` and then lowered, after the passes in this crate,
|
||||
//! by `rustc_ast_lowering`.
|
||||
//!
|
||||
//! The crate also contains other misc AST visitors, e.g. `node_count` and `show_span`.
|
||||
|
||||
#![feature(bindings_after_at)]
|
||||
#![feature(iter_is_partitioned)]
|
||||
|
||||
pub mod ast_validation;
|
||||
pub mod feature_gate;
|
||||
pub mod node_count;
|
||||
|
@ -796,31 +796,10 @@ impl<'a> PrintState<'a> for State<'a> {
|
||||
match *args {
|
||||
ast::GenericArgs::AngleBracketed(ref data) => {
|
||||
self.s.word("<");
|
||||
|
||||
self.commasep(Inconsistent, &data.args, |s, generic_arg| {
|
||||
s.print_generic_arg(generic_arg)
|
||||
self.commasep(Inconsistent, &data.args, |s, arg| match arg {
|
||||
ast::AngleBracketedArg::Arg(a) => s.print_generic_arg(a),
|
||||
ast::AngleBracketedArg::Constraint(c) => s.print_assoc_constraint(c),
|
||||
});
|
||||
|
||||
let mut comma = !data.args.is_empty();
|
||||
|
||||
for constraint in data.constraints.iter() {
|
||||
if comma {
|
||||
self.word_space(",")
|
||||
}
|
||||
self.print_ident(constraint.ident);
|
||||
self.s.space();
|
||||
match constraint.kind {
|
||||
ast::AssocTyConstraintKind::Equality { ref ty } => {
|
||||
self.word_space("=");
|
||||
self.print_type(ty);
|
||||
}
|
||||
ast::AssocTyConstraintKind::Bound { ref bounds } => {
|
||||
self.print_type_bounds(":", &*bounds);
|
||||
}
|
||||
}
|
||||
comma = true;
|
||||
}
|
||||
|
||||
self.s.word(">")
|
||||
}
|
||||
|
||||
@ -891,6 +870,20 @@ impl<'a> State<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn print_assoc_constraint(&mut self, constraint: &ast::AssocTyConstraint) {
|
||||
self.print_ident(constraint.ident);
|
||||
self.s.space();
|
||||
match &constraint.kind {
|
||||
ast::AssocTyConstraintKind::Equality { ty } => {
|
||||
self.word_space("=");
|
||||
self.print_type(ty);
|
||||
}
|
||||
ast::AssocTyConstraintKind::Bound { bounds } => {
|
||||
self.print_type_bounds(":", &*bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate fn print_generic_arg(&mut self, generic_arg: &GenericArg) {
|
||||
match generic_arg {
|
||||
GenericArg::Lifetime(lt) => self.print_lifetime(*lt),
|
||||
|
@ -36,7 +36,8 @@ impl<'a> ExtCtxt<'a> {
|
||||
idents.into_iter().map(|ident| ast::PathSegment::from_ident(ident.with_span_pos(span))),
|
||||
);
|
||||
let args = if !args.is_empty() {
|
||||
ast::AngleBracketedArgs { args, constraints: Vec::new(), span }.into()
|
||||
let args = args.into_iter().map(ast::AngleBracketedArg::Arg).collect();
|
||||
ast::AngleBracketedArgs { args, span }.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -634,17 +634,19 @@ impl<'a, 'b> ReplaceBodyWithLoop<'a, 'b> {
|
||||
match seg.args.as_ref().map(|generic_arg| &**generic_arg) {
|
||||
None => false,
|
||||
Some(&ast::GenericArgs::AngleBracketed(ref data)) => {
|
||||
let types = data.args.iter().filter_map(|arg| match arg {
|
||||
ast::GenericArg::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
});
|
||||
any_involves_impl_trait(types)
|
||||
|| data.constraints.iter().any(|c| match c.kind {
|
||||
data.args.iter().any(|arg| match arg {
|
||||
ast::AngleBracketedArg::Arg(arg) => match arg {
|
||||
ast::GenericArg::Type(ty) => involves_impl_trait(ty),
|
||||
ast::GenericArg::Lifetime(_)
|
||||
| ast::GenericArg::Const(_) => false,
|
||||
},
|
||||
ast::AngleBracketedArg::Constraint(c) => match c.kind {
|
||||
ast::AssocTyConstraintKind::Bound { .. } => true,
|
||||
ast::AssocTyConstraintKind::Equality { ref ty } => {
|
||||
involves_impl_trait(ty)
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
Some(&ast::GenericArgs::Parenthesized(ref data)) => {
|
||||
any_involves_impl_trait(data.inputs.iter())
|
||||
|
@ -1,12 +1,10 @@
|
||||
use super::ty::{AllowPlus, RecoverQPath};
|
||||
use super::{Parser, TokenType};
|
||||
use crate::maybe_whole;
|
||||
use rustc_ast::ast::{
|
||||
self, AngleBracketedArgs, Ident, ParenthesizedArgs, Path, PathSegment, QSelf,
|
||||
};
|
||||
use rustc_ast::ast::{
|
||||
AnonConst, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode, GenericArg,
|
||||
};
|
||||
use rustc_ast::ast::{self, AngleBracketedArg, AngleBracketedArgs, GenericArg, ParenthesizedArgs};
|
||||
use rustc_ast::ast::{AnonConst, AssocTyConstraint, AssocTyConstraintKind, BlockCheckMode};
|
||||
use rustc_ast::ast::{Ident, Path, PathSegment, QSelf};
|
||||
use rustc_ast::ptr::P;
|
||||
use rustc_ast::token::{self, Token};
|
||||
use rustc_errors::{pluralize, Applicability, PResult};
|
||||
use rustc_span::source_map::{BytePos, Span};
|
||||
@ -218,11 +216,11 @@ impl<'a> Parser<'a> {
|
||||
let lo = self.token.span;
|
||||
let args = if self.eat_lt() {
|
||||
// `<'a, T, A = U>`
|
||||
let (args, constraints) =
|
||||
self.parse_generic_args_with_leading_angle_bracket_recovery(style, lo)?;
|
||||
let args =
|
||||
self.parse_angle_args_with_leading_angle_bracket_recovery(style, lo)?;
|
||||
self.expect_gt()?;
|
||||
let span = lo.to(self.prev_token.span);
|
||||
AngleBracketedArgs { args, constraints, span }.into()
|
||||
AngleBracketedArgs { args, span }.into()
|
||||
} else {
|
||||
// `(T, U) -> R`
|
||||
let (inputs, _) = self.parse_paren_comma_seq(|p| p.parse_ty())?;
|
||||
@ -251,18 +249,18 @@ impl<'a> Parser<'a> {
|
||||
|
||||
/// Parses generic args (within a path segment) with recovery for extra leading angle brackets.
|
||||
/// For the purposes of understanding the parsing logic of generic arguments, this function
|
||||
/// can be thought of being the same as just calling `self.parse_generic_args()` if the source
|
||||
/// can be thought of being the same as just calling `self.parse_angle_args()` if the source
|
||||
/// had the correct amount of leading angle brackets.
|
||||
///
|
||||
/// ```ignore (diagnostics)
|
||||
/// bar::<<<<T as Foo>::Output>();
|
||||
/// ^^ help: remove extra angle brackets
|
||||
/// ```
|
||||
fn parse_generic_args_with_leading_angle_bracket_recovery(
|
||||
fn parse_angle_args_with_leading_angle_bracket_recovery(
|
||||
&mut self,
|
||||
style: PathStyle,
|
||||
lo: Span,
|
||||
) -> PResult<'a, (Vec<GenericArg>, Vec<AssocTyConstraint>)> {
|
||||
) -> PResult<'a, Vec<AngleBracketedArg>> {
|
||||
// We need to detect whether there are extra leading left angle brackets and produce an
|
||||
// appropriate error and suggestion. This cannot be implemented by looking ahead at
|
||||
// upcoming tokens for a matching `>` character - if there are unmatched `<` tokens
|
||||
@ -337,8 +335,8 @@ impl<'a> Parser<'a> {
|
||||
let snapshot = if is_first_invocation { Some(self.clone()) } else { None };
|
||||
|
||||
debug!("parse_generic_args_with_leading_angle_bracket_recovery: (snapshotting)");
|
||||
match self.parse_generic_args() {
|
||||
Ok(value) => Ok(value),
|
||||
match self.parse_angle_args() {
|
||||
Ok(args) => Ok(args),
|
||||
Err(ref mut e) if is_first_invocation && self.unmatched_angle_bracket_count > 0 => {
|
||||
// Cancel error from being unable to find `>`. We know the error
|
||||
// must have been this due to a non-zero unmatched angle bracket
|
||||
@ -381,110 +379,136 @@ impl<'a> Parser<'a> {
|
||||
.emit();
|
||||
|
||||
// Try again without unmatched angle bracket characters.
|
||||
self.parse_generic_args()
|
||||
self.parse_angle_args()
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses (possibly empty) list of lifetime and type arguments and associated type bindings,
|
||||
/// Parses (possibly empty) list of generic arguments / associated item constraints,
|
||||
/// possibly including trailing comma.
|
||||
fn parse_generic_args(&mut self) -> PResult<'a, (Vec<GenericArg>, Vec<AssocTyConstraint>)> {
|
||||
fn parse_angle_args(&mut self) -> PResult<'a, Vec<AngleBracketedArg>> {
|
||||
let mut args = Vec::new();
|
||||
let mut constraints = Vec::new();
|
||||
let mut misplaced_assoc_ty_constraints: Vec<Span> = Vec::new();
|
||||
let mut assoc_ty_constraints: Vec<Span> = Vec::new();
|
||||
|
||||
let args_lo = self.token.span;
|
||||
|
||||
loop {
|
||||
if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
|
||||
// Parse lifetime argument.
|
||||
args.push(GenericArg::Lifetime(self.expect_lifetime()));
|
||||
misplaced_assoc_ty_constraints.append(&mut assoc_ty_constraints);
|
||||
} else if self.check_ident()
|
||||
&& self.look_ahead(1, |t| t == &token::Eq || t == &token::Colon)
|
||||
{
|
||||
// Parse associated type constraint.
|
||||
let lo = self.token.span;
|
||||
let ident = self.parse_ident()?;
|
||||
let kind = if self.eat(&token::Eq) {
|
||||
AssocTyConstraintKind::Equality { ty: self.parse_ty()? }
|
||||
} else if self.eat(&token::Colon) {
|
||||
AssocTyConstraintKind::Bound {
|
||||
bounds: self.parse_generic_bounds(Some(self.prev_token.span))?,
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let span = lo.to(self.prev_token.span);
|
||||
|
||||
// Gate associated type bounds, e.g., `Iterator<Item: Ord>`.
|
||||
if let AssocTyConstraintKind::Bound { .. } = kind {
|
||||
self.sess.gated_spans.gate(sym::associated_type_bounds, span);
|
||||
}
|
||||
|
||||
constraints.push(AssocTyConstraint { id: ast::DUMMY_NODE_ID, ident, kind, span });
|
||||
assoc_ty_constraints.push(span);
|
||||
} else if self.check_const_arg() {
|
||||
// Parse const argument.
|
||||
let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
|
||||
self.parse_block_expr(
|
||||
None,
|
||||
self.token.span,
|
||||
BlockCheckMode::Default,
|
||||
ast::AttrVec::new(),
|
||||
)?
|
||||
} else if self.token.is_ident() {
|
||||
// FIXME(const_generics): to distinguish between idents for types and consts,
|
||||
// we should introduce a GenericArg::Ident in the AST and distinguish when
|
||||
// lowering to the HIR. For now, idents for const args are not permitted.
|
||||
if self.token.is_bool_lit() {
|
||||
self.parse_literal_maybe_minus()?
|
||||
} else {
|
||||
let span = self.token.span;
|
||||
let msg = "identifiers may currently not be used for const generics";
|
||||
self.struct_span_err(span, msg).emit();
|
||||
let block = self.mk_block_err(span);
|
||||
self.mk_expr(span, ast::ExprKind::Block(block, None), ast::AttrVec::new())
|
||||
}
|
||||
} else {
|
||||
self.parse_literal_maybe_minus()?
|
||||
};
|
||||
let value = AnonConst { id: ast::DUMMY_NODE_ID, value: expr };
|
||||
args.push(GenericArg::Const(value));
|
||||
misplaced_assoc_ty_constraints.append(&mut assoc_ty_constraints);
|
||||
} else if self.check_type() {
|
||||
// Parse type argument.
|
||||
args.push(GenericArg::Type(self.parse_ty()?));
|
||||
misplaced_assoc_ty_constraints.append(&mut assoc_ty_constraints);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
while let Some(arg) = self.parse_angle_arg()? {
|
||||
args.push(arg);
|
||||
if !self.eat(&token::Comma) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
// FIXME: we would like to report this in ast_validation instead, but we currently do not
|
||||
// preserve ordering of generic parameters with respect to associated type binding, so we
|
||||
// lose that information after parsing.
|
||||
if !misplaced_assoc_ty_constraints.is_empty() {
|
||||
let mut err = self.struct_span_err(
|
||||
args_lo.to(self.prev_token.span),
|
||||
"associated type bindings must be declared after generic parameters",
|
||||
);
|
||||
for span in misplaced_assoc_ty_constraints {
|
||||
err.span_label(
|
||||
span,
|
||||
"this associated type binding should be moved after the generic parameters",
|
||||
);
|
||||
/// Parses a single argument in the angle arguments `<...>` of a path segment.
|
||||
fn parse_angle_arg(&mut self) -> PResult<'a, Option<AngleBracketedArg>> {
|
||||
if self.check_ident() && self.look_ahead(1, |t| matches!(t.kind, token::Eq | token::Colon))
|
||||
{
|
||||
// Parse associated type constraint.
|
||||
let lo = self.token.span;
|
||||
let ident = self.parse_ident()?;
|
||||
let kind = if self.eat(&token::Eq) {
|
||||
let ty = self.parse_assoc_equality_term(ident, self.prev_token.span)?;
|
||||
AssocTyConstraintKind::Equality { ty }
|
||||
} else if self.eat(&token::Colon) {
|
||||
let bounds = self.parse_generic_bounds(Some(self.prev_token.span))?;
|
||||
AssocTyConstraintKind::Bound { bounds }
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let span = lo.to(self.prev_token.span);
|
||||
|
||||
// Gate associated type bounds, e.g., `Iterator<Item: Ord>`.
|
||||
if let AssocTyConstraintKind::Bound { .. } = kind {
|
||||
self.sess.gated_spans.gate(sym::associated_type_bounds, span);
|
||||
}
|
||||
err.emit();
|
||||
}
|
||||
|
||||
Ok((args, constraints))
|
||||
let constraint = AssocTyConstraint { id: ast::DUMMY_NODE_ID, ident, kind, span };
|
||||
Ok(Some(AngleBracketedArg::Constraint(constraint)))
|
||||
} else {
|
||||
Ok(self.parse_generic_arg()?.map(AngleBracketedArg::Arg))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the term to the right of an associated item equality constraint.
|
||||
/// That is, parse `<term>` in `Item = <term>`.
|
||||
/// Right now, this only admits types in `<term>`.
|
||||
fn parse_assoc_equality_term(&mut self, ident: Ident, eq: Span) -> PResult<'a, P<ast::Ty>> {
|
||||
let arg = self.parse_generic_arg()?;
|
||||
let span = ident.span.to(self.prev_token.span);
|
||||
match arg {
|
||||
Some(GenericArg::Type(ty)) => return Ok(ty),
|
||||
Some(GenericArg::Const(expr)) => {
|
||||
self.struct_span_err(span, "cannot constrain an associated constant to a value")
|
||||
.span_label(ident.span, "this associated constant...")
|
||||
.span_label(expr.value.span, "...cannot be constrained to this value")
|
||||
.emit();
|
||||
}
|
||||
Some(GenericArg::Lifetime(lt)) => {
|
||||
self.struct_span_err(span, "associated lifetimes are not supported")
|
||||
.span_label(lt.ident.span, "the lifetime is given here")
|
||||
.help("if you meant to specify a trait object, write `dyn Trait + 'lifetime`")
|
||||
.emit();
|
||||
}
|
||||
None => {
|
||||
let after_eq = eq.shrink_to_hi();
|
||||
let before_next = self.token.span.shrink_to_lo();
|
||||
self.struct_span_err(after_eq.to(before_next), "missing type to the right of `=`")
|
||||
.span_suggestion(
|
||||
self.sess.source_map().next_point(eq).to(before_next),
|
||||
"to constrain the associated type, add a type after `=`",
|
||||
" TheType".to_string(),
|
||||
Applicability::HasPlaceholders,
|
||||
)
|
||||
.span_suggestion(
|
||||
eq.to(before_next),
|
||||
&format!("remove the `=` if `{}` is a type", ident),
|
||||
String::new(),
|
||||
Applicability::MaybeIncorrect,
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
Ok(self.mk_ty(span, ast::TyKind::Err))
|
||||
}
|
||||
|
||||
/// Parse a generic argument in a path segment.
|
||||
/// This does not include constraints, e.g., `Item = u8`, which is handled in `parse_angle_arg`.
|
||||
fn parse_generic_arg(&mut self) -> PResult<'a, Option<GenericArg>> {
|
||||
let arg = if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
|
||||
// Parse lifetime argument.
|
||||
GenericArg::Lifetime(self.expect_lifetime())
|
||||
} else if self.check_const_arg() {
|
||||
// Parse const argument.
|
||||
let expr = if let token::OpenDelim(token::Brace) = self.token.kind {
|
||||
self.parse_block_expr(
|
||||
None,
|
||||
self.token.span,
|
||||
BlockCheckMode::Default,
|
||||
ast::AttrVec::new(),
|
||||
)?
|
||||
} else if self.token.is_ident() {
|
||||
// FIXME(const_generics): to distinguish between idents for types and consts,
|
||||
// we should introduce a GenericArg::Ident in the AST and distinguish when
|
||||
// lowering to the HIR. For now, idents for const args are not permitted.
|
||||
if self.token.is_bool_lit() {
|
||||
self.parse_literal_maybe_minus()?
|
||||
} else {
|
||||
let span = self.token.span;
|
||||
let msg = "identifiers may currently not be used for const generics";
|
||||
self.struct_span_err(span, msg).emit();
|
||||
let block = self.mk_block_err(span);
|
||||
self.mk_expr(span, ast::ExprKind::Block(block, None), ast::AttrVec::new())
|
||||
}
|
||||
} else {
|
||||
self.parse_literal_maybe_minus()?
|
||||
};
|
||||
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value: expr })
|
||||
} else if self.check_type() {
|
||||
// Parse type argument.
|
||||
GenericArg::Type(self.parse_ty()?)
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(arg))
|
||||
}
|
||||
}
|
||||
|
@ -783,7 +783,7 @@ impl<'l, 'tcx> DumpVisitor<'l, 'tcx> {
|
||||
match **generic_args {
|
||||
ast::GenericArgs::AngleBracketed(ref data) => {
|
||||
for arg in &data.args {
|
||||
if let ast::GenericArg::Type(ty) = arg {
|
||||
if let ast::AngleBracketedArg::Arg(ast::GenericArg::Type(ty)) = arg {
|
||||
self.visit_ty(ty);
|
||||
}
|
||||
}
|
||||
@ -849,7 +849,7 @@ impl<'l, 'tcx> DumpVisitor<'l, 'tcx> {
|
||||
if let ast::GenericArgs::AngleBracketed(ref data) = **generic_args {
|
||||
for arg in &data.args {
|
||||
match arg {
|
||||
ast::GenericArg::Type(ty) => self.visit_ty(ty),
|
||||
ast::AngleBracketedArg::Arg(ast::GenericArg::Type(ty)) => self.visit_ty(ty),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
// check-pass
|
||||
|
||||
#[cfg(FALSE)]
|
||||
fn syntax() {
|
||||
foo::<T = u8, T: Ord, String>();
|
||||
foo::<T = u8, 'a, T: Ord>();
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -1,6 +1,6 @@
|
||||
trait Trait<T> { type Item; }
|
||||
|
||||
pub fn test<W, I: Trait<Item=(), W> >() {}
|
||||
//~^ ERROR associated type bindings must be declared after generic parameters
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
|
||||
fn main() { }
|
||||
|
@ -1,10 +1,10 @@
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/issue-32214.rs:3:25
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/issue-32214.rs:3:34
|
||||
|
|
||||
LL | pub fn test<W, I: Trait<Item=(), W> >() {}
|
||||
| -------^^^
|
||||
| ------- ^ generic argument
|
||||
| |
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| the first constraint is provided here
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
7
src/test/ui/parser/recover-assoc-const-constraint.rs
Normal file
7
src/test/ui/parser/recover-assoc-const-constraint.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[cfg(FALSE)]
|
||||
fn syntax() {
|
||||
bar::<Item = 42>(); //~ ERROR cannot constrain an associated constant to a value
|
||||
bar::<Item = { 42 }>(); //~ ERROR cannot constrain an associated constant to a value
|
||||
}
|
||||
|
||||
fn main() {}
|
20
src/test/ui/parser/recover-assoc-const-constraint.stderr
Normal file
20
src/test/ui/parser/recover-assoc-const-constraint.stderr
Normal file
@ -0,0 +1,20 @@
|
||||
error: cannot constrain an associated constant to a value
|
||||
--> $DIR/recover-assoc-const-constraint.rs:3:11
|
||||
|
|
||||
LL | bar::<Item = 42>();
|
||||
| ----^^^--
|
||||
| | |
|
||||
| | ...cannot be constrained to this value
|
||||
| this associated constant...
|
||||
|
||||
error: cannot constrain an associated constant to a value
|
||||
--> $DIR/recover-assoc-const-constraint.rs:4:11
|
||||
|
|
||||
LL | bar::<Item = { 42 }>();
|
||||
| ----^^^------
|
||||
| | |
|
||||
| | ...cannot be constrained to this value
|
||||
| this associated constant...
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
6
src/test/ui/parser/recover-assoc-eq-missing-term.rs
Normal file
6
src/test/ui/parser/recover-assoc-eq-missing-term.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#[cfg(FALSE)]
|
||||
fn syntax() {
|
||||
bar::<Item = >(); //~ ERROR missing type to the right of `=`
|
||||
}
|
||||
|
||||
fn main() {}
|
17
src/test/ui/parser/recover-assoc-eq-missing-term.stderr
Normal file
17
src/test/ui/parser/recover-assoc-eq-missing-term.stderr
Normal file
@ -0,0 +1,17 @@
|
||||
error: missing type to the right of `=`
|
||||
--> $DIR/recover-assoc-eq-missing-term.rs:3:17
|
||||
|
|
||||
LL | bar::<Item = >();
|
||||
| ^^^
|
||||
|
|
||||
help: to constrain the associated type, add a type after `=`
|
||||
|
|
||||
LL | bar::<Item = TheType>();
|
||||
| ^^^^^^^
|
||||
help: remove the `=` if `Item` is a type
|
||||
|
|
||||
LL | bar::<Item >();
|
||||
| --
|
||||
|
||||
error: aborting due to previous error
|
||||
|
6
src/test/ui/parser/recover-assoc-lifetime-constraint.rs
Normal file
6
src/test/ui/parser/recover-assoc-lifetime-constraint.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#[cfg(FALSE)]
|
||||
fn syntax() {
|
||||
bar::<Item = 'a>(); //~ ERROR associated lifetimes are not supported
|
||||
}
|
||||
|
||||
fn main() {}
|
12
src/test/ui/parser/recover-assoc-lifetime-constraint.stderr
Normal file
12
src/test/ui/parser/recover-assoc-lifetime-constraint.stderr
Normal file
@ -0,0 +1,12 @@
|
||||
error: associated lifetimes are not supported
|
||||
--> $DIR/recover-assoc-lifetime-constraint.rs:3:11
|
||||
|
|
||||
LL | bar::<Item = 'a>();
|
||||
| ^^^^^^^--
|
||||
| |
|
||||
| the lifetime is given here
|
||||
|
|
||||
= help: if you meant to specify a trait object, write `dyn Trait + 'lifetime`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
@ -1,5 +1,3 @@
|
||||
// ignore-tidy-linelength
|
||||
|
||||
#![allow(warnings)]
|
||||
|
||||
// This test verifies that the suggestion to move types before associated type bindings
|
||||
@ -25,20 +23,22 @@ trait ThreeWithLifetime<'a, 'b, 'c, T, U, V> {
|
||||
type C;
|
||||
}
|
||||
|
||||
struct A<T, M: One<A=(), T>> { //~ ERROR associated type bindings must be declared after generic parameters
|
||||
struct A<T, M: One<A=(), T>> {
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
m: M,
|
||||
t: T,
|
||||
}
|
||||
|
||||
|
||||
struct Al<'a, T, M: OneWithLifetime<A=(), T, 'a>> {
|
||||
//~^ ERROR associated type bindings must be declared after generic parameters
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
//~^^ ERROR type provided when a lifetime was expected
|
||||
m: M,
|
||||
t: &'a T,
|
||||
}
|
||||
|
||||
struct B<T, U, V, M: Three<A=(), B=(), C=(), T, U, V>> { //~ ERROR associated type bindings must be declared after generic parameters
|
||||
struct B<T, U, V, M: Three<A=(), B=(), C=(), T, U, V>> {
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
m: M,
|
||||
t: T,
|
||||
u: U,
|
||||
@ -46,7 +46,7 @@ struct B<T, U, V, M: Three<A=(), B=(), C=(), T, U, V>> { //~ ERROR associated ty
|
||||
}
|
||||
|
||||
struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<A=(), B=(), C=(), T, U, V, 'a, 'b, 'c>> {
|
||||
//~^ ERROR associated type bindings must be declared after generic parameters
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
//~^^ ERROR type provided when a lifetime was expected
|
||||
m: M,
|
||||
t: &'a T,
|
||||
@ -54,7 +54,8 @@ struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<A=(), B=(), C=(), T, U, V, '
|
||||
v: &'c V,
|
||||
}
|
||||
|
||||
struct C<T, U, V, M: Three<T, A=(), B=(), C=(), U, V>> { //~ ERROR associated type bindings must be declared after generic parameters
|
||||
struct C<T, U, V, M: Three<T, A=(), B=(), C=(), U, V>> {
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
m: M,
|
||||
t: T,
|
||||
u: U,
|
||||
@ -62,7 +63,7 @@ struct C<T, U, V, M: Three<T, A=(), B=(), C=(), U, V>> { //~ ERROR associated ty
|
||||
}
|
||||
|
||||
struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), C=(), U, 'b, V, 'c>> {
|
||||
//~^ ERROR associated type bindings must be declared after generic parameters
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
//~^^ ERROR lifetime provided when a type was expected
|
||||
m: M,
|
||||
t: &'a T,
|
||||
@ -70,7 +71,8 @@ struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), C=(), U,
|
||||
v: &'c V,
|
||||
}
|
||||
|
||||
struct D<T, U, V, M: Three<T, A=(), B=(), U, C=(), V>> { //~ ERROR associated type bindings must be declared after generic parameters
|
||||
struct D<T, U, V, M: Three<T, A=(), B=(), U, C=(), V>> {
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
m: M,
|
||||
t: T,
|
||||
u: U,
|
||||
@ -78,7 +80,7 @@ struct D<T, U, V, M: Three<T, A=(), B=(), U, C=(), V>> { //~ ERROR associated ty
|
||||
}
|
||||
|
||||
struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), U, 'b, C=(), V, 'c>> {
|
||||
//~^ ERROR associated type bindings must be declared after generic parameters
|
||||
//~^ ERROR generic arguments must come before the first constraint
|
||||
//~^^ ERROR lifetime provided when a type was expected
|
||||
m: M,
|
||||
t: &'a T,
|
||||
|
@ -1,81 +1,85 @@
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:28:20
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:26:26
|
||||
|
|
||||
LL | struct A<T, M: One<A=(), T>> {
|
||||
| ----^^^
|
||||
| ---- ^ generic argument
|
||||
| |
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| the first constraint is provided here
|
||||
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:34:37
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:33:43
|
||||
|
|
||||
LL | struct Al<'a, T, M: OneWithLifetime<A=(), T, 'a>> {
|
||||
| ----^^^^^^^
|
||||
| |
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| ---- ^ ^^ generic argument
|
||||
| | |
|
||||
| | generic argument
|
||||
| the first constraint is provided here
|
||||
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:41:28
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:40:46
|
||||
|
|
||||
LL | struct B<T, U, V, M: Three<A=(), B=(), C=(), T, U, V>> {
|
||||
| ----^^----^^----^^^^^^^^^
|
||||
| | | |
|
||||
| | | this associated type binding should be moved after the generic parameters
|
||||
| | this associated type binding should be moved after the generic parameters
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| ---- ^ ^ ^ generic argument
|
||||
| | | |
|
||||
| | | generic argument
|
||||
| | generic argument
|
||||
| the first constraint is provided here
|
||||
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:48:53
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:48:71
|
||||
|
|
||||
LL | struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<A=(), B=(), C=(), T, U, V, 'a, 'b, 'c>> {
|
||||
| ----^^----^^----^^^^^^^^^^^^^^^^^^^^^
|
||||
| | | |
|
||||
| | | this associated type binding should be moved after the generic parameters
|
||||
| | this associated type binding should be moved after the generic parameters
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| ---- ^ ^ ^ ^^ ^^ ^^ generic argument
|
||||
| | | | | | |
|
||||
| | | | | | generic argument
|
||||
| | | | | generic argument
|
||||
| | | | generic argument
|
||||
| | | generic argument
|
||||
| | generic argument
|
||||
| the first constraint is provided here
|
||||
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:57:28
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:57:49
|
||||
|
|
||||
LL | struct C<T, U, V, M: Three<T, A=(), B=(), C=(), U, V>> {
|
||||
| ^^^----^^----^^----^^^^^^
|
||||
| | | |
|
||||
| | | this associated type binding should be moved after the generic parameters
|
||||
| | this associated type binding should be moved after the generic parameters
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| ---- ^ ^ generic argument
|
||||
| | |
|
||||
| | generic argument
|
||||
| the first constraint is provided here
|
||||
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:64:53
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:65:78
|
||||
|
|
||||
LL | struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), C=(), U, 'b, V, 'c>> {
|
||||
| ^^^^^^^----^^----^^----^^^^^^^^^^^^^^
|
||||
| | | |
|
||||
| | | this associated type binding should be moved after the generic parameters
|
||||
| | this associated type binding should be moved after the generic parameters
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| ---- ^ ^^ ^ ^^ generic argument
|
||||
| | | | |
|
||||
| | | | generic argument
|
||||
| | | generic argument
|
||||
| | generic argument
|
||||
| the first constraint is provided here
|
||||
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:73:28
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:74:43
|
||||
|
|
||||
LL | struct D<T, U, V, M: Three<T, A=(), B=(), U, C=(), V>> {
|
||||
| ^^^----^^----^^^^^----^^^
|
||||
| | | |
|
||||
| | | this associated type binding should be moved after the generic parameters
|
||||
| | this associated type binding should be moved after the generic parameters
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| ---- ^ ^ generic argument
|
||||
| | |
|
||||
| | generic argument
|
||||
| the first constraint is provided here
|
||||
|
||||
error: associated type bindings must be declared after generic parameters
|
||||
--> $DIR/suggest-move-types.rs:80:53
|
||||
error: generic arguments must come before the first constraint
|
||||
--> $DIR/suggest-move-types.rs:82:72
|
||||
|
|
||||
LL | struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), U, 'b, C=(), V, 'c>> {
|
||||
| ^^^^^^^----^^----^^^^^^^^^----^^^^^^^
|
||||
| | | |
|
||||
| | | this associated type binding should be moved after the generic parameters
|
||||
| | this associated type binding should be moved after the generic parameters
|
||||
| this associated type binding should be moved after the generic parameters
|
||||
| ---- ^ ^^ ^ ^^ generic argument
|
||||
| | | | |
|
||||
| | | | generic argument
|
||||
| | | generic argument
|
||||
| | generic argument
|
||||
| the first constraint is provided here
|
||||
|
||||
error[E0747]: type provided when a lifetime was expected
|
||||
--> $DIR/suggest-move-types.rs:34:43
|
||||
--> $DIR/suggest-move-types.rs:33:43
|
||||
|
|
||||
LL | struct Al<'a, T, M: OneWithLifetime<A=(), T, 'a>> {
|
||||
| ^
|
||||
@ -91,7 +95,7 @@ LL | struct Bl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<A=(), B=(), C=(), T, U,
|
||||
= note: lifetime arguments must be provided before type arguments
|
||||
|
||||
error[E0747]: lifetime provided when a type was expected
|
||||
--> $DIR/suggest-move-types.rs:64:56
|
||||
--> $DIR/suggest-move-types.rs:65:56
|
||||
|
|
||||
LL | struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), C=(), U, 'b, V, 'c>> {
|
||||
| ^^
|
||||
@ -99,7 +103,7 @@ LL | struct Cl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), C=()
|
||||
= note: type arguments must be provided before lifetime arguments
|
||||
|
||||
error[E0747]: lifetime provided when a type was expected
|
||||
--> $DIR/suggest-move-types.rs:80:56
|
||||
--> $DIR/suggest-move-types.rs:82:56
|
||||
|
|
||||
LL | struct Dl<'a, 'b, 'c, T, U, V, M: ThreeWithLifetime<T, 'a, A=(), B=(), U, 'b, C=(), V, 'c>> {
|
||||
| ^^
|
||||
|
Loading…
Reference in New Issue
Block a user