Skip to content

Commit 2c412f1

Browse files
committed
Parse impl restriction on trait definitions
1 parent 206f423 commit 2c412f1

File tree

10 files changed

+210
-15
lines changed

10 files changed

+210
-15
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3530,6 +3530,7 @@ impl Default for FnHeader {
35303530

35313531
#[derive(Clone, Encodable, Decodable, Debug)]
35323532
pub struct Trait {
3533+
pub impl_restriction: Restriction,
35333534
pub safety: Safety,
35343535
pub is_auto: IsAuto,
35353536
pub ident: Ident,

compiler/rustc_ast/src/visit.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,16 @@ macro_rules! common_visitor_and_walkers {
557557
<V as Visitor<$lt>>::Result::output()
558558
)?
559559
}
560-
ItemKind::Trait(box Trait { safety, is_auto: _, ident, generics, bounds, items }) => {
560+
ItemKind::Trait(box Trait {
561+
impl_restriction,
562+
safety,
563+
is_auto: _,
564+
ident,
565+
generics,
566+
bounds,
567+
items,
568+
}) => {
569+
try_visit!(vis.visit_restriction(impl_restriction));
561570
try_visit!(visit_safety(vis, safety));
562571
try_visit!(vis.visit_ident(ident));
563572
try_visit!(vis.visit_generics(generics));

compiler/rustc_ast_lowering/src/item.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,15 @@ impl<'hir> LoweringContext<'_, 'hir> {
415415
items: new_impl_items,
416416
}))
417417
}
418-
ItemKind::Trait(box Trait { is_auto, safety, ident, generics, bounds, items }) => {
418+
ItemKind::Trait(box Trait {
419+
impl_restriction: _,
420+
is_auto,
421+
safety,
422+
ident,
423+
generics,
424+
bounds,
425+
items,
426+
}) => {
419427
let ident = self.lower_ident(*ident);
420428
let (generics, (safety, items, bounds)) = self.lower_generics(
421429
generics,

compiler/rustc_ast_pretty/src/pprust/state/item.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ impl<'a> State<'a> {
357357
self.bclose(item.span, empty, cb);
358358
}
359359
ast::ItemKind::Trait(box ast::Trait {
360+
impl_restriction,
360361
safety,
361362
is_auto,
362363
ident,
@@ -366,6 +367,7 @@ impl<'a> State<'a> {
366367
}) => {
367368
let (cb, ib) = self.head("");
368369
self.print_visibility(&item.vis);
370+
self.print_restriction("impl", impl_restriction);
369371
self.print_safety(*safety);
370372
self.print_is_auto(*is_auto);
371373
self.word_nbsp("trait");

compiler/rustc_parse/messages.ftl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,13 @@ parse_inclusive_range_no_end = inclusive range with no end
377377
parse_incorrect_parens_trait_bounds = incorrect parentheses around trait bounds
378378
parse_incorrect_parens_trait_bounds_sugg = fix the parentheses
379379
380+
parse_incorrect_restriction = incorrect {$noun} restriction
381+
.help = some possible {$noun} restrictions are:
382+
`{$keyword}(crate)`: {$adjective} only in the current crate
383+
`{$keyword}(super)`: {$adjective} only in the current module's parent
384+
`{$keyword}(in path::to::module)`: {$adjective} only in the specified path
385+
.suggestion = make this {$adjective} only to module `{$inner_str}` with `in`
386+
380387
parse_incorrect_semicolon =
381388
expected item, found `;`
382389
.suggestion = remove this semicolon
@@ -853,6 +860,7 @@ parse_trailing_vert_not_allowed = a trailing `|` is not allowed in an or-pattern
853860
parse_trait_alias_cannot_be_auto = trait aliases cannot be `auto`
854861
parse_trait_alias_cannot_be_unsafe = trait aliases cannot be `unsafe`
855862
863+
parse_trait_alias_cannot_have_impl_restriction = trait alias cannot have `impl` restriction
856864
parse_transpose_dyn_or_impl = `for<...>` expected after `{$kw}`, not before
857865
.suggestion = move `{$kw}` before the `for<...>`
858866

compiler/rustc_parse/src/errors.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,19 @@ pub(crate) struct IncorrectVisibilityRestriction {
10411041
pub inner_str: String,
10421042
}
10431043

1044+
#[derive(Diagnostic)]
1045+
#[diag(parse_incorrect_restriction, code = E0704)]
1046+
#[help]
1047+
pub(crate) struct IncorrectRestriction<'kw> {
1048+
#[primary_span]
1049+
#[suggestion(code = "in {inner_str}", applicability = "machine-applicable", style = "verbose")]
1050+
pub span: Span,
1051+
pub inner_str: String,
1052+
pub keyword: &'kw str,
1053+
pub adjective: &'static str,
1054+
pub noun: &'static str,
1055+
}
1056+
10441057
#[derive(Diagnostic)]
10451058
#[diag(parse_assignment_else_not_allowed)]
10461059
pub(crate) struct AssignmentElseNotAllowed {
@@ -1946,6 +1959,16 @@ pub(crate) struct BoundsNotAllowedOnTraitAliases {
19461959
pub span: Span,
19471960
}
19481961

1962+
#[derive(Diagnostic)]
1963+
#[diag(parse_trait_alias_cannot_have_impl_restriction)]
1964+
pub(crate) struct TraitAliasCannotHaveImplRestriction {
1965+
#[primary_span]
1966+
#[label(parse_trait_alias_cannot_have_impl_restriction)]
1967+
pub span: Span,
1968+
#[suggestion(code = "", applicability = "machine-applicable", style = "short")]
1969+
pub restriction: Span,
1970+
}
1971+
19491972
#[derive(Diagnostic)]
19501973
#[diag(parse_trait_alias_cannot_be_auto)]
19511974
pub(crate) struct TraitAliasCannotBeAuto {

compiler/rustc_parse/src/parser/item.rs

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ impl<'a> Parser<'a> {
262262
define_opaque: None,
263263
}))
264264
}
265-
} else if self.check_keyword(exp!(Trait)) || self.check_auto_or_unsafe_trait_item() {
265+
} else if self.check_keyword(exp!(Trait)) || self.check_trait_item_with_modifiers() {
266266
// TRAIT ITEM
267267
self.parse_item_trait(attrs, lo)?
268268
} else if self.check_keyword(exp!(Impl))
@@ -373,7 +373,7 @@ impl<'a> Parser<'a> {
373373
pub(super) fn is_path_start_item(&mut self) -> bool {
374374
self.is_kw_followed_by_ident(kw::Union) // no: `union::b`, yes: `union U { .. }`
375375
|| self.is_reuse_path_item()
376-
|| self.check_auto_or_unsafe_trait_item() // no: `auto::b`, yes: `auto trait X { .. }`
376+
|| self.check_trait_item_with_modifiers() // no: `auto::b`, yes: `auto trait X { .. }`
377377
|| self.is_async_fn() // no(2015): `async::b`, yes: `async fn`
378378
|| matches!(self.is_macro_rules_item(), IsMacroRulesItem::Yes{..}) // no: `macro_rules::b`, yes: `macro_rules! mac`
379379
}
@@ -872,16 +872,67 @@ impl<'a> Parser<'a> {
872872
}
873873
}
874874

875-
/// Is this an `(unsafe auto? | auto) trait` item?
876-
fn check_auto_or_unsafe_trait_item(&mut self) -> bool {
877-
// auto trait
878-
self.check_keyword(exp!(Auto)) && self.is_keyword_ahead(1, &[kw::Trait])
879-
// unsafe auto trait
880-
|| self.check_keyword(exp!(Unsafe)) && self.is_keyword_ahead(1, &[kw::Trait, kw::Auto])
875+
/// Is this an `[ impl(in path) ]? unsafe? auto? trait` item?
876+
fn check_trait_item_with_modifiers(&mut self) -> bool {
877+
let current_token = TokenTree::Token(self.token.clone(), self.token_spacing);
878+
let look_ahead = |index| match index {
879+
0 => Some(&current_token),
880+
_ => self.token_cursor.curr.look_ahead(index - 1),
881+
};
882+
883+
let mut has_impl_restriction = false;
884+
let mut has_unsafe = false;
885+
let mut has_auto = false;
886+
let mut has_trait = false;
887+
888+
let mut index = 0;
889+
890+
if let Some(TokenTree::Token(token, _)) = look_ahead(index)
891+
&& token.is_keyword(kw::Impl)
892+
{
893+
has_impl_restriction = true;
894+
index += 1;
895+
}
896+
// impl restrictions require parens, but we enforce this later.
897+
if has_impl_restriction
898+
&& matches!(
899+
look_ahead(index),
900+
Some(TokenTree::Delimited(_, _, Delimiter::Parenthesis, _))
901+
)
902+
{
903+
index += 1;
904+
}
905+
if let Some(TokenTree::Token(token, _)) = look_ahead(index)
906+
&& token.is_keyword(kw::Unsafe)
907+
{
908+
has_unsafe = true;
909+
index += 1;
910+
}
911+
if let Some(TokenTree::Token(token, _)) = look_ahead(index)
912+
&& token.is_keyword(kw::Auto)
913+
{
914+
has_auto = true;
915+
index += 1;
916+
}
917+
if let Some(TokenTree::Token(token, _)) = look_ahead(index)
918+
&& token.is_keyword(kw::Trait)
919+
{
920+
has_trait = true;
921+
}
922+
923+
let ret = (has_impl_restriction || has_unsafe || has_auto) && has_trait;
924+
if !ret {
925+
self.expected_token_types.insert(exp!(Impl).token_type);
926+
self.expected_token_types.insert(exp!(Unsafe).token_type);
927+
self.expected_token_types.insert(exp!(Auto).token_type);
928+
}
929+
ret
881930
}
882931

883-
/// Parses `unsafe? auto? trait Foo { ... }` or `trait Foo = Bar;`.
932+
/// Parses `[ impl(in path) ]? unsafe? auto? trait Foo { ... }` or `trait Foo = Bar;`.
884933
fn parse_item_trait(&mut self, attrs: &mut AttrVec, lo: Span) -> PResult<'a, ItemKind> {
934+
let impl_restriction =
935+
self.parse_restriction(exp!(Impl), "implementable", "impl", FollowedByType::No)?;
885936
let safety = self.parse_safety(Case::Sensitive);
886937
// Parse optional `auto` prefix.
887938
let is_auto = if self.eat_keyword(exp!(Auto)) {
@@ -913,6 +964,12 @@ impl<'a> Parser<'a> {
913964
self.expect_semi()?;
914965

915966
let whole_span = lo.to(self.prev_token.span);
967+
if !matches!(impl_restriction.kind, RestrictionKind::Implied) {
968+
self.dcx().emit_err(errors::TraitAliasCannotHaveImplRestriction {
969+
span: whole_span,
970+
restriction: impl_restriction.span,
971+
});
972+
}
916973
if is_auto == IsAuto::Yes {
917974
self.dcx().emit_err(errors::TraitAliasCannotBeAuto { span: whole_span });
918975
}
@@ -927,7 +984,15 @@ impl<'a> Parser<'a> {
927984
// It's a normal trait.
928985
generics.where_clause = self.parse_where_clause()?;
929986
let items = self.parse_item_list(attrs, |p| p.parse_trait_item(ForceCollect::No))?;
930-
Ok(ItemKind::Trait(Box::new(Trait { is_auto, safety, ident, generics, bounds, items })))
987+
Ok(ItemKind::Trait(Box::new(Trait {
988+
impl_restriction,
989+
is_auto,
990+
safety,
991+
ident,
992+
generics,
993+
bounds,
994+
items,
995+
})))
931996
}
932997
}
933998

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ use rustc_ast::tokenstream::{
3131
use rustc_ast::util::case::Case;
3232
use rustc_ast::{
3333
self as ast, AnonConst, AttrArgs, AttrId, ByRef, Const, CoroutineKind, DUMMY_NODE_ID,
34-
DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, Mutability, Recovered, Safety, StrLit,
35-
Visibility, VisibilityKind,
34+
DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, Mutability, Recovered, Restriction,
35+
Safety, StrLit, Visibility, VisibilityKind,
3636
};
3737
use rustc_ast_pretty::pprust;
3838
use rustc_data_structures::fx::FxHashMap;
@@ -45,7 +45,9 @@ use token_type::TokenTypeSet;
4545
pub use token_type::{ExpKeywordPair, ExpTokenPair, TokenType};
4646
use tracing::debug;
4747

48-
use crate::errors::{self, IncorrectVisibilityRestriction, NonStringAbiLiteral};
48+
use crate::errors::{
49+
self, IncorrectRestriction, IncorrectVisibilityRestriction, NonStringAbiLiteral,
50+
};
4951
use crate::exp;
5052

5153
#[cfg(test)]
@@ -1514,6 +1516,80 @@ impl<'a> Parser<'a> {
15141516
Ok(())
15151517
}
15161518

1519+
/// Parses `kw`, `kw(in path)` plus shortcuts `kw(crate)` for `kw(in crate)`, `kw(self)` for
1520+
/// `kw(in self)` and `kw(super)` for `kw(in super)`.
1521+
fn parse_restriction(
1522+
&mut self,
1523+
kw: ExpKeywordPair,
1524+
action: &'static str,
1525+
description: &'static str,
1526+
fbt: FollowedByType,
1527+
) -> PResult<'a, Restriction> {
1528+
if !self.eat_keyword(kw) {
1529+
// We need a span, but there's inherently no keyword to grab a span from for an implied
1530+
// restriction. An empty span at the beginning of the current token is a reasonable
1531+
// fallback.
1532+
return Ok(Restriction::implied().with_span(self.token.span.shrink_to_lo()));
1533+
}
1534+
1535+
let lo = self.prev_token.span;
1536+
1537+
if self.check(exp!(OpenParen)) {
1538+
// We don't `self.bump()` the `(` yet because this might be a struct definition where
1539+
// `()` or a tuple might be allowed. For example, `struct Struct(kw (), kw (usize));`.
1540+
// Because of this, we only `bump` the `(` if we're assured it is appropriate to do so
1541+
// by the following tokens.
1542+
if self.is_keyword_ahead(1, &[kw::In]) {
1543+
// Parse `kw(in path)`.
1544+
self.bump(); // `(`
1545+
self.bump(); // `in`
1546+
let path = self.parse_path(PathStyle::Mod)?; // `path`
1547+
self.expect(exp!(CloseParen))?; // `)`
1548+
return Ok(Restriction::restricted(P(path), ast::DUMMY_NODE_ID, false)
1549+
.with_span(lo.to(self.prev_token.span)));
1550+
} else if self.look_ahead(2, |t| t == &TokenKind::CloseParen)
1551+
&& self.is_keyword_ahead(1, &[kw::Crate, kw::Super, kw::SelfLower])
1552+
{
1553+
// Parse `kw(crate)`, `kw(self)`, or `kw(super)`.
1554+
self.bump(); // `(`
1555+
let path = self.parse_path(PathStyle::Mod)?; // `crate`/`super`/`self`
1556+
self.expect(exp!(CloseParen))?; // `)`
1557+
return Ok(Restriction::restricted(P(path), ast::DUMMY_NODE_ID, false)
1558+
.with_span(lo.to(self.prev_token.span)));
1559+
} else if let FollowedByType::No = fbt {
1560+
// Provide this diagnostic if a type cannot follow;
1561+
// in particular, if this is not a tuple struct.
1562+
self.recover_incorrect_restriction(kw.kw.as_str(), action, description)?;
1563+
// Emit diagnostic, but continue unrestricted.
1564+
}
1565+
}
1566+
1567+
Ok(Restriction::unrestricted().with_span(lo))
1568+
}
1569+
1570+
/// Recovery for e.g. `kw(something) fn ...` or `struct X { kw(something) y: Z }`
1571+
fn recover_incorrect_restriction<'kw>(
1572+
&mut self,
1573+
kw: &'kw str,
1574+
action: &'static str,
1575+
description: &'static str,
1576+
) -> PResult<'a, ()> {
1577+
self.bump(); // `(`
1578+
let path = self.parse_path(PathStyle::Mod)?;
1579+
self.expect(exp!(CloseParen))?; // `)`
1580+
1581+
let path_str = pprust::path_to_string(&path);
1582+
self.dcx().emit_err(IncorrectRestriction {
1583+
span: path.span,
1584+
inner_str: path_str,
1585+
keyword: kw,
1586+
adjective: action,
1587+
noun: description,
1588+
});
1589+
1590+
Ok(())
1591+
}
1592+
15171593
/// Parses `extern string_literal?`.
15181594
fn parse_extern(&mut self, case: Case) -> Extern {
15191595
if self.eat_keyword_case(exp!(Extern), case) {

src/tools/clippy/clippy_utils/src/ast_utils/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
444444
},
445445
(
446446
Trait(box ast::Trait {
447+
impl_restriction: _, // does not affect item kind
447448
is_auto: la,
448449
safety: lu,
449450
ident: li,
@@ -452,6 +453,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
452453
items: lis,
453454
}),
454455
Trait(box ast::Trait {
456+
impl_restriction: _, // does not affect item kind
455457
is_auto: ra,
456458
safety: ru,
457459
ident: ri,

src/tools/rustfmt/src/items.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,7 @@ pub(crate) fn format_trait(
11721172
unreachable!();
11731173
};
11741174
let ast::Trait {
1175+
impl_restriction: _,
11751176
is_auto,
11761177
safety,
11771178
ident,

0 commit comments

Comments
 (0)