1
Fork 0

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)] #[derive(Clone, Encodable, Decodable, Debug)]
pub struct ConstItem { pub struct ConstItem {
pub defaultness: Defaultness, pub defaultness: Defaultness,
pub generics: Generics,
pub ty: P<Ty>, pub ty: P<Ty>,
pub expr: Option<P<Expr>>, pub expr: Option<P<Expr>>,
} }
@ -3058,6 +3059,7 @@ impl ItemKind {
match self { match self {
Self::Fn(box Fn { generics, .. }) Self::Fn(box Fn { generics, .. })
| Self::TyAlias(box TyAlias { generics, .. }) | Self::TyAlias(box TyAlias { generics, .. })
| Self::Const(box ConstItem { generics, .. })
| Self::Enum(_, generics) | Self::Enum(_, generics)
| Self::Struct(_, generics) | Self::Struct(_, generics)
| Self::Union(_, 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>( fn visit_const_item<T: MutVisitor>(
ConstItem { defaultness, ty, expr }: &mut ConstItem, ConstItem { defaultness, generics, ty, expr }: &mut ConstItem,
visitor: &mut T, visitor: &mut T,
) { ) {
visit_defaultness(defaultness, visitor); visit_defaultness(defaultness, visitor);
visitor.visit_generics(generics);
visitor.visit_ty(ty); visitor.visit_ty(ty);
visit_opt(expr, |expr| visitor.visit_expr(expr)); 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 { match &item.kind {
ItemKind::ExternCrate(_) => {} ItemKind::ExternCrate(_) => {}
ItemKind::Use(use_tree) => visitor.visit_use_tree(use_tree, item.id, false), ItemKind::Use(use_tree) => visitor.visit_use_tree(use_tree, item.id, false),
ItemKind::Static(box StaticItem { ty, mutability: _, expr }) ItemKind::Static(box StaticItem { ty, mutability: _, expr }) => {
| ItemKind::Const(box ConstItem { ty, 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); visitor.visit_ty(ty);
walk_list!(visitor, visit_expr, expr); 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); visitor.visit_ident(ident);
walk_list!(visitor, visit_attribute, attrs); walk_list!(visitor, visit_attribute, attrs);
match kind { match kind {
AssocItemKind::Const(box ConstItem { ty, expr, .. }) => { AssocItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => {
visitor.visit_generics(generics);
visitor.visit_ty(ty); visitor.visit_ty(ty);
walk_list!(visitor, visit_expr, expr); walk_list!(visitor, visit_expr, expr);
} }

View file

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

View file

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

View file

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

View file

@ -643,7 +643,16 @@ impl<'a> ExtCtxt<'a> {
span, span,
name, name,
AttrVec::new(), 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 @@ declare_features! (
(incomplete, generic_associated_types_extended, "1.61.0", Some(95451), None), (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 /// Allows non-trivial generic constants which have to have wfness manually propagated to callers
(incomplete, generic_const_exprs, "1.56.0", Some(76560), None), (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. /// Allows using `..=X` as a patterns in slices.
(active, half_open_range_patterns_in_slices, "1.66.0", Some(67264), None), (active, half_open_range_patterns_in_slices, "1.66.0", Some(67264), None),
/// Allows `if let` guard in match arms. /// 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 parse_single_colon_struct_type = found single colon in a struct field type path
.suggestion = write a path separator here .suggestion = write a path separator here
parse_static_with_generics = static items may not have generic parameters
parse_struct_literal_body_without_path = parse_struct_literal_body_without_path =
struct literal body without path struct literal body without path
.suggestion = you might have forgotten to add the struct literal inside the block .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 .label = the visibility
.help = you likely meant to define an item, e.g., `{$vis} fn foo() {"{}"}` .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 parse_where_clause_before_tuple_struct_body = where clauses are not allowed before tuple struct bodies
.label = unexpected where clause .label = unexpected where clause
.name_label = while parsing this tuple struct .name_label = while parsing this tuple struct

View file

@ -2692,3 +2692,34 @@ pub(crate) struct ExpectedBuiltinIdent {
#[primary_span] #[primary_span]
pub span: 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 @@ impl<'a> Parser<'a> {
} else if self.is_static_global() { } else if self.is_static_global() {
// STATIC ITEM // STATIC ITEM
self.bump(); // `static` self.bump(); // `static`
let m = self.parse_mutability(); let mutability = self.parse_mutability();
let (ident, ty, expr) = self.parse_item_global(Some(m))?; let (ident, item) = self.parse_static_item(mutability)?;
(ident, ItemKind::Static(Box::new(StaticItem { ty, mutability: m, expr }))) (ident, ItemKind::Static(Box::new(item)))
} else if let Const::Yes(const_span) = self.parse_constness(Case::Sensitive) { } else if let Const::Yes(const_span) = self.parse_constness(Case::Sensitive) {
// CONST ITEM // CONST ITEM
if self.token.is_keyword(kw::Impl) { if self.token.is_keyword(kw::Impl) {
@ -236,8 +236,16 @@ impl<'a> Parser<'a> {
self.recover_const_impl(const_span, attrs, def_())? self.recover_const_impl(const_span, attrs, def_())?
} else { } else {
self.recover_const_mut(const_span); self.recover_const_mut(const_span);
let (ident, ty, expr) = self.parse_item_global(None)?; let (ident, generics, ty, expr) = self.parse_const_item()?;
(ident, ItemKind::Const(Box::new(ConstItem { defaultness: def_(), ty, expr }))) (
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() { } else if self.check_keyword(kw::Trait) || self.check_auto_or_unsafe_trait_item() {
// TRAIT ITEM // TRAIT ITEM
@ -878,6 +886,7 @@ impl<'a> Parser<'a> {
self.sess.emit_err(errors::AssociatedStaticItemNotAllowed { span }); self.sess.emit_err(errors::AssociatedStaticItemNotAllowed { span });
AssocItemKind::Const(Box::new(ConstItem { AssocItemKind::Const(Box::new(ConstItem {
defaultness: Defaultness::Final, defaultness: Defaultness::Final,
generics: Generics::default(),
ty, ty,
expr, expr,
})) }))
@ -892,7 +901,7 @@ impl<'a> Parser<'a> {
/// Parses a `type` alias with the following grammar: /// Parses a `type` alias with the following grammar:
/// ```ebnf /// ```ebnf
/// TypeAlias = "type" Ident Generics {":" GenericBounds}? {"=" Ty}? ";" ; /// TypeAlias = "type" Ident Generics (":" GenericBounds)? WhereClause ("=" Ty)? WhereClause ";" ;
/// ``` /// ```
/// The `"type"` has already been eaten. /// The `"type"` has already been eaten.
fn parse_type_alias(&mut self, defaultness: Defaultness) -> PResult<'a, ItemInfo> { fn parse_type_alias(&mut self, defaultness: Defaultness) -> PResult<'a, ItemInfo> {
@ -1220,33 +1229,132 @@ impl<'a> Parser<'a> {
Ok(impl_info) Ok(impl_info)
} }
/// Parse `["const" | ("static" "mut"?)] $ident ":" $ty (= $expr)?` with /// Parse a static item with the prefix `"static" "mut"?` already parsed and stored in `mutability`.
/// `["const" | ("static" "mut"?)]` already parsed and stored in `m`.
/// ///
/// When `m` is `"const"`, `$ident` may also be `"_"`. /// ```ebnf
fn parse_item_global( /// Static = "static" "mut"? $ident ":" $ty (= $expr)? ";" ;
&mut self, /// ```
m: Option<Mutability>, fn parse_static_item(&mut self, mutability: Mutability) -> PResult<'a, (Ident, StaticItem)> {
) -> PResult<'a, (Ident, P<Ty>, Option<P<ast::Expr>>)> { let ident = self.parse_ident()?;
let id = if m.is_none() { self.parse_ident_or_underscore() } else { self.parse_ident() }?;
// Parse the type of a `const` or `static mut?` item. if self.token.kind == TokenKind::Lt && self.may_recover() {
// That is, the `":" $ty` fragment. 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)) 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()?, (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 }; let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None };
self.expect_semi()?; 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. /// We were supposed to parse `":" $ty` but the `:` or the type was missing.
/// This means that the type is 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 // Construct the error and stash it away with the hope
// that typeck will later enrich the error with a type. // that typeck will later enrich the error with a type.
let kind = match m { let kind = match m {

View file

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