Parse generic const items

This commit is contained in:
León Orell Valerian Liehr 2023-05-04 16:08:33 +02:00
parent b6dd153fbc
commit afd009a8d8
No known key found for this signature in database
GPG key ID: D17A07215F68E713
12 changed files with 220 additions and 34 deletions

View file

@ -2947,6 +2947,7 @@ pub struct StaticItem {
#[derive(Clone, Encodable, Decodable, Debug)]
pub struct ConstItem {
pub defaultness: Defaultness,
pub generics: Generics,
pub ty: P<Ty>,
pub expr: Option<P<Expr>>,
}
@ -3058,6 +3059,7 @@ pub fn generics(&self) -> Option<&Generics> {
match self {
Self::Fn(box Fn { generics, .. })
| Self::TyAlias(box TyAlias { generics, .. })
| Self::Const(box ConstItem { generics, .. })
| Self::Enum(_, generics)
| Self::Struct(_, generics)
| Self::Union(_, generics)

View file

@ -1149,10 +1149,11 @@ pub fn noop_flat_map_assoc_item<T: MutVisitor>(
}
fn visit_const_item<T: MutVisitor>(
ConstItem { defaultness, ty, expr }: &mut ConstItem,
ConstItem { defaultness, generics, ty, expr }: &mut ConstItem,
visitor: &mut T,
) {
visit_defaultness(defaultness, visitor);
visitor.visit_generics(generics);
visitor.visit_ty(ty);
visit_opt(expr, |expr| visitor.visit_expr(expr));
}

View file

@ -308,8 +308,12 @@ pub fn walk_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a Item) {
match &item.kind {
ItemKind::ExternCrate(_) => {}
ItemKind::Use(use_tree) => visitor.visit_use_tree(use_tree, item.id, false),
ItemKind::Static(box StaticItem { ty, mutability: _, expr })
| ItemKind::Const(box ConstItem { ty, expr, .. }) => {
ItemKind::Static(box StaticItem { ty, mutability: _, expr }) => {
visitor.visit_ty(ty);
walk_list!(visitor, visit_expr, expr);
}
ItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => {
visitor.visit_generics(generics);
visitor.visit_ty(ty);
walk_list!(visitor, visit_expr, expr);
}
@ -677,7 +681,8 @@ pub fn walk_assoc_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a AssocItem,
visitor.visit_ident(ident);
walk_list!(visitor, visit_attribute, attrs);
match kind {
AssocItemKind::Const(box ConstItem { ty, expr, .. }) => {
AssocItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => {
visitor.visit_generics(generics);
visitor.visit_ty(ty);
walk_list!(visitor, visit_expr, expr);
}

View file

@ -569,6 +569,7 @@ macro_rules! gate_all {
gate_all!(const_closures, "const closures are experimental");
gate_all!(builtin_syntax, "`builtin #` syntax is unstable");
gate_all!(explicit_tail_calls, "`become` expression is experimental");
gate_all!(generic_const_items, "generic const items are experimental");
if !visitor.features.negative_bounds {
for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() {

View file

@ -30,10 +30,15 @@ pub(crate) fn print_foreign_item(&mut self, item: &ast::ForeignItem) {
ast::ForeignItemKind::Fn(box ast::Fn { defaultness, sig, generics, body }) => {
self.print_fn_full(sig, ident, generics, vis, *defaultness, body.as_deref(), attrs);
}
ast::ForeignItemKind::Static(ty, mutbl, body) => {
let def = ast::Defaultness::Final;
self.print_item_const(ident, Some(*mutbl), ty, body.as_deref(), vis, def);
}
ast::ForeignItemKind::Static(ty, mutbl, body) => self.print_item_const(
ident,
Some(*mutbl),
&ast::Generics::default(),
ty,
body.as_deref(),
vis,
ast::Defaultness::Final,
),
ast::ForeignItemKind::TyAlias(box ast::TyAlias {
defaultness,
generics,
@ -67,6 +72,7 @@ fn print_item_const(
&mut self,
ident: Ident,
mutbl: Option<ast::Mutability>,
generics: &ast::Generics,
ty: &ast::Ty,
body: Option<&ast::Expr>,
vis: &ast::Visibility,
@ -82,6 +88,7 @@ fn print_item_const(
};
self.word_space(leading);
self.print_ident(ident);
self.print_generic_params(&generics.params);
self.word_space(":");
self.print_type(ty);
if body.is_some() {
@ -92,6 +99,7 @@ fn print_item_const(
self.word_space("=");
self.print_expr(body);
}
self.print_where_clause(&generics.where_clause);
self.word(";");
self.end(); // end the outer cbox
}
@ -158,20 +166,21 @@ pub(crate) fn print_item(&mut self, item: &ast::Item) {
self.word(";");
}
ast::ItemKind::Static(box StaticItem { ty, mutability: mutbl, expr: body }) => {
let def = ast::Defaultness::Final;
self.print_item_const(
item.ident,
Some(*mutbl),
&ast::Generics::default(),
ty,
body.as_deref(),
&item.vis,
def,
ast::Defaultness::Final,
);
}
ast::ItemKind::Const(box ast::ConstItem { defaultness, ty, expr }) => {
ast::ItemKind::Const(box ast::ConstItem { defaultness, generics, ty, expr }) => {
self.print_item_const(
item.ident,
None,
generics,
ty,
expr.as_deref(),
&item.vis,
@ -515,8 +524,16 @@ pub(crate) fn print_assoc_item(&mut self, item: &ast::AssocItem) {
ast::AssocItemKind::Fn(box ast::Fn { defaultness, sig, generics, body }) => {
self.print_fn_full(sig, ident, generics, vis, *defaultness, body.as_deref(), attrs);
}
ast::AssocItemKind::Const(box ast::ConstItem { defaultness, ty, expr }) => {
self.print_item_const(ident, None, ty, expr.as_deref(), vis, *defaultness);
ast::AssocItemKind::Const(box ast::ConstItem { defaultness, generics, ty, expr }) => {
self.print_item_const(
ident,
None,
generics,
ty,
expr.as_deref(),
vis,
*defaultness,
);
}
ast::AssocItemKind::Type(box ast::TyAlias {
defaultness,

View file

@ -255,6 +255,7 @@ pub fn expand_test_or_bench(
ast::ItemKind::Const(
ast::ConstItem {
defaultness: ast::Defaultness::Final,
generics: ast::Generics::default(),
ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
// test::TestDescAndFn {
expr: Some(

View file

@ -643,7 +643,16 @@ pub fn item_const(
span,
name,
AttrVec::new(),
ast::ItemKind::Const(ast::ConstItem { defaultness, ty, expr: Some(expr) }.into()),
ast::ItemKind::Const(
ast::ConstItem {
defaultness,
// FIXME(generic_const_items): Pass the generics as a parameter.
generics: ast::Generics::default(),
ty,
expr: Some(expr),
}
.into(),
),
)
}

View file

@ -424,6 +424,8 @@ pub fn set(&self, features: &mut Features, span: Span) {
(incomplete, generic_associated_types_extended, "1.61.0", Some(95451), None),
/// Allows non-trivial generic constants which have to have wfness manually propagated to callers
(incomplete, generic_const_exprs, "1.56.0", Some(76560), None),
/// Allows generic parameters and where-clauses on free & associated const items.
(incomplete, generic_const_items, "CURRENT_RUSTC_VERSION", Some(113521), None),
/// Allows using `..=X` as a patterns in slices.
(active, half_open_range_patterns_in_slices, "1.66.0", Some(67264), None),
/// Allows `if let` guard in match arms.

View file

@ -690,6 +690,8 @@ parse_single_colon_import_path = expected `::`, found `:`
parse_single_colon_struct_type = found single colon in a struct field type path
.suggestion = write a path separator here
parse_static_with_generics = static items may not have generic parameters
parse_struct_literal_body_without_path =
struct literal body without path
.suggestion = you might have forgotten to add the struct literal inside the block
@ -847,6 +849,12 @@ parse_visibility_not_followed_by_item = visibility `{$vis}` is not followed by a
.label = the visibility
.help = you likely meant to define an item, e.g., `{$vis} fn foo() {"{}"}`
parse_where_clause_before_const_body = where clauses are not allowed before const item bodies
.label = unexpected where clause
.name_label = while parsing this const item
.body_label = the item body
.suggestion = move the body before the where clause
parse_where_clause_before_tuple_struct_body = where clauses are not allowed before tuple struct bodies
.label = unexpected where clause
.name_label = while parsing this tuple struct

View file

@ -2692,3 +2692,34 @@ pub(crate) struct ExpectedBuiltinIdent {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parse_static_with_generics)]
pub(crate) struct StaticWithGenerics {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parse_where_clause_before_const_body)]
pub(crate) struct WhereClauseBeforeConstBody {
#[primary_span]
#[label]
pub span: Span,
#[label(parse_name_label)]
pub name: Span,
#[label(parse_body_label)]
pub body: Span,
#[subdiagnostic]
pub sugg: Option<WhereClauseBeforeConstBodySugg>,
}
#[derive(Subdiagnostic)]
#[multipart_suggestion(parse_suggestion, applicability = "machine-applicable")]
pub(crate) struct WhereClauseBeforeConstBodySugg {
#[suggestion_part(code = "= {snippet} ")]
pub left: Span,
pub snippet: String,
#[suggestion_part(code = "")]
pub right: Span,
}

View file

@ -226,9 +226,9 @@ fn parse_item_kind(
} else if self.is_static_global() {
// STATIC ITEM
self.bump(); // `static`
let m = self.parse_mutability();
let (ident, ty, expr) = self.parse_item_global(Some(m))?;
(ident, ItemKind::Static(Box::new(StaticItem { ty, mutability: m, expr })))
let mutability = self.parse_mutability();
let (ident, item) = self.parse_static_item(mutability)?;
(ident, ItemKind::Static(Box::new(item)))
} else if let Const::Yes(const_span) = self.parse_constness(Case::Sensitive) {
// CONST ITEM
if self.token.is_keyword(kw::Impl) {
@ -236,8 +236,16 @@ fn parse_item_kind(
self.recover_const_impl(const_span, attrs, def_())?
} else {
self.recover_const_mut(const_span);
let (ident, ty, expr) = self.parse_item_global(None)?;
(ident, ItemKind::Const(Box::new(ConstItem { defaultness: def_(), ty, expr })))
let (ident, generics, ty, expr) = self.parse_const_item()?;
(
ident,
ItemKind::Const(Box::new(ConstItem {
defaultness: def_(),
generics,
ty,
expr,
})),
)
}
} else if self.check_keyword(kw::Trait) || self.check_auto_or_unsafe_trait_item() {
// TRAIT ITEM
@ -878,6 +886,7 @@ fn parse_assoc_item(
self.sess.emit_err(errors::AssociatedStaticItemNotAllowed { span });
AssocItemKind::Const(Box::new(ConstItem {
defaultness: Defaultness::Final,
generics: Generics::default(),
ty,
expr,
}))
@ -892,7 +901,7 @@ fn parse_assoc_item(
/// Parses a `type` alias with the following grammar:
/// ```ebnf
/// TypeAlias = "type" Ident Generics {":" GenericBounds}? {"=" Ty}? ";" ;
/// TypeAlias = "type" Ident Generics (":" GenericBounds)? WhereClause ("=" Ty)? WhereClause ";" ;
/// ```
/// The `"type"` has already been eaten.
fn parse_type_alias(&mut self, defaultness: Defaultness) -> PResult<'a, ItemInfo> {
@ -1220,33 +1229,132 @@ fn recover_const_impl(
Ok(impl_info)
}
/// Parse `["const" | ("static" "mut"?)] $ident ":" $ty (= $expr)?` with
/// `["const" | ("static" "mut"?)]` already parsed and stored in `m`.
/// Parse a static item with the prefix `"static" "mut"?` already parsed and stored in `mutability`.
///
/// When `m` is `"const"`, `$ident` may also be `"_"`.
fn parse_item_global(
&mut self,
m: Option<Mutability>,
) -> PResult<'a, (Ident, P<Ty>, Option<P<ast::Expr>>)> {
let id = if m.is_none() { self.parse_ident_or_underscore() } else { self.parse_ident() }?;
/// ```ebnf
/// Static = "static" "mut"? $ident ":" $ty (= $expr)? ";" ;
/// ```
fn parse_static_item(&mut self, mutability: Mutability) -> PResult<'a, (Ident, StaticItem)> {
let ident = self.parse_ident()?;
// Parse the type of a `const` or `static mut?` item.
// That is, the `":" $ty` fragment.
if self.token.kind == TokenKind::Lt && self.may_recover() {
let generics = self.parse_generics()?;
self.sess.emit_err(errors::StaticWithGenerics { span: generics.span });
}
// Parse the type of a static item. That is, the `":" $ty` fragment.
// FIXME: This could maybe benefit from `.may_recover()`?
let ty = match (self.eat(&token::Colon), self.check(&token::Eq) | self.check(&token::Semi))
{
// If there wasn't a `:` or the colon was followed by a `=` or `;` recover a missing type.
(true, false) => self.parse_ty()?,
(colon, _) => self.recover_missing_const_type(colon, m),
// If there wasn't a `:` or the colon was followed by a `=` or `;`, recover a missing type.
(colon, _) => self.recover_missing_global_item_type(colon, Some(mutability)),
};
let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None };
self.expect_semi()?;
Ok((id, ty, expr))
Ok((ident, StaticItem { ty, mutability, expr }))
}
/// Parse a constant item with the prefix `"const"` already parsed.
///
/// ```ebnf
/// Const = "const" ($ident | "_") Generics ":" $ty (= $expr)? WhereClause ";" ;
/// ```
fn parse_const_item(&mut self) -> PResult<'a, (Ident, Generics, P<Ty>, Option<P<ast::Expr>>)> {
let ident = self.parse_ident_or_underscore()?;
let mut generics = self.parse_generics()?;
// Check the span for emptiness instead of the list of parameters in order to correctly
// recognize and subsequently flag empty parameter lists (`<>`) as unstable.
if !generics.span.is_empty() {
self.sess.gated_spans.gate(sym::generic_const_items, generics.span);
}
// Parse the type of a constant item. That is, the `":" $ty` fragment.
// FIXME: This could maybe benefit from `.may_recover()`?
let ty = match (
self.eat(&token::Colon),
self.check(&token::Eq) | self.check(&token::Semi) | self.check_keyword(kw::Where),
) {
(true, false) => self.parse_ty()?,
// If there wasn't a `:` or the colon was followed by a `=`, `;` or `where`, recover a missing type.
(colon, _) => self.recover_missing_global_item_type(colon, None),
};
// Proactively parse a where-clause to be able to provide a good error message in case we
// encounter the item body following it.
let before_where_clause =
if self.may_recover() { self.parse_where_clause()? } else { WhereClause::default() };
let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None };
let after_where_clause = self.parse_where_clause()?;
// Provide a nice error message if the user placed a where-clause before the item body.
// Users may be tempted to write such code if they are still used to the deprecated
// where-clause location on type aliases and associated types. See also #89122.
if before_where_clause.has_where_token && let Some(expr) = &expr {
self.sess.emit_err(errors::WhereClauseBeforeConstBody {
span: before_where_clause.span,
name: ident.span,
body: expr.span,
sugg: if !after_where_clause.has_where_token {
self.sess.source_map().span_to_snippet(expr.span).ok().map(|body| {
errors::WhereClauseBeforeConstBodySugg {
left: before_where_clause.span.shrink_to_lo(),
snippet: body,
right: before_where_clause.span.shrink_to_hi().to(expr.span),
}
})
} else {
// FIXME(generic_const_items): Provide a structured suggestion to merge the first
// where-clause into the second one.
None
},
});
}
// Merge the predicates of both where-clauses since either one can be relevant.
// If we didn't parse a body (which is valid for associated consts in traits) and we were
// allowed to recover, `before_where_clause` contains the predicates, otherwise they are
// in `after_where_clause`. Further, both of them might contain predicates iff two
// where-clauses were provided which is syntactically ill-formed but we want to recover from
// it and treat them as one large where-clause.
let mut predicates = before_where_clause.predicates;
predicates.extend(after_where_clause.predicates);
let where_clause = WhereClause {
has_where_token: before_where_clause.has_where_token
|| after_where_clause.has_where_token,
predicates,
span: if after_where_clause.has_where_token {
after_where_clause.span
} else {
before_where_clause.span
},
};
if where_clause.has_where_token {
self.sess.gated_spans.gate(sym::generic_const_items, where_clause.span);
}
generics.where_clause = where_clause;
self.expect_semi()?;
Ok((ident, generics, ty, expr))
}
/// We were supposed to parse `":" $ty` but the `:` or the type was missing.
/// This means that the type is missing.
fn recover_missing_const_type(&mut self, colon_present: bool, m: Option<Mutability>) -> P<Ty> {
fn recover_missing_global_item_type(
&mut self,
colon_present: bool,
m: Option<Mutability>,
) -> P<Ty> {
// Construct the error and stash it away with the hope
// that typeck will later enrich the error with a type.
let kind = match m {

View file

@ -783,6 +783,7 @@
generic_associated_types,
generic_associated_types_extended,
generic_const_exprs,
generic_const_items,
generic_param_attrs,
get_context,
global_allocator,