Move ast::Item::ident into ast::ItemKind.

`ast::Item` has an `ident` field.

- It's always non-empty for these item kinds: `ExternCrate`, `Static`,
  `Const`, `Fn`, `Mod`, `TyAlias`, `Enum`, `Struct`, `Union`,
  `Trait`, `TraitAlias`, `MacroDef`, `Delegation`.

- It's always empty for these item kinds: `Use`, `ForeignMod`,
  `GlobalAsm`, `Impl`, `MacCall`, `DelegationMac`.

There is a similar story for `AssocItemKind` and `ForeignItemKind`.

Some sites that handle items check for an empty ident, some don't. This
is a very C-like way of doing things, but this is Rust, we have sum
types, we can do this properly and never forget to check for the
exceptional case and never YOLO possibly empty identifiers (or possibly
dummy spans) around and hope that things will work out.

The commit is large but it's mostly obvious plumbing work. Some notable
things.

- `ast::Item` got 8 bytes bigger. This could be avoided by boxing the
  fields within some of the `ast::ItemKind` variants (specifically:
  `Struct`, `Union`, `Enum`). I might do that in a follow-up; this
  commit is big enough already.

- For the visitors: `FnKind` no longer needs an `ident` field because
  the `Fn` within how has one.

- In the parser, the `ItemInfo` typedef is no longer needed. It was used
  in various places to return an `Ident` alongside an `ItemKind`, but
  now the `Ident` (if present) is within the `ItemKind`.

- In a few places I renamed identifier variables called `name` (or
  `foo_name`) as `ident` (or `foo_ident`), to better match the type, and
  because `name` is normally used for `Symbol`s. It's confusing to see
  something like `foo_name.name`.
This commit is contained in:
Nicholas Nethercote 2025-03-21 09:47:43 +11:00
parent 43018eacb6
commit df247968f2
54 changed files with 1072 additions and 864 deletions

View file

@ -34,10 +34,10 @@ impl<'a> Parser<'a> {
}
/// Parses a `mod <foo> { ... }` or `mod <foo>;` item.
fn parse_item_mod(&mut self, attrs: &mut AttrVec) -> PResult<'a, ItemInfo> {
fn parse_item_mod(&mut self, attrs: &mut AttrVec) -> PResult<'a, ItemKind> {
let safety = self.parse_safety(Case::Sensitive);
self.expect_keyword(exp!(Mod))?;
let id = self.parse_ident()?;
let ident = self.parse_ident()?;
let mod_kind = if self.eat(exp!(Semi)) {
ModKind::Unloaded
} else {
@ -46,7 +46,7 @@ impl<'a> Parser<'a> {
attrs.extend(inner_attrs);
ModKind::Loaded(items, Inline::Yes, inner_span, Ok(()))
};
Ok((id, ItemKind::Mod(safety, mod_kind)))
Ok(ItemKind::Mod(safety, ident, mod_kind))
}
/// Parses the contents of a module (inner attributes followed by module items).
@ -115,8 +115,6 @@ impl<'a> Parser<'a> {
}
}
pub(super) type ItemInfo = (Ident, ItemKind);
impl<'a> Parser<'a> {
pub fn parse_item(&mut self, force_collect: ForceCollect) -> PResult<'a, Option<P<Item>>> {
let fn_parse_mode = FnParseMode { req_name: |_| true, req_body: true };
@ -163,11 +161,11 @@ impl<'a> Parser<'a> {
fn_parse_mode,
Case::Sensitive,
)?;
if let Some((ident, kind)) = kind {
if let Some(kind) = kind {
this.error_on_unconsumed_default(def, &kind);
let span = lo.to(this.prev_token.span);
let id = DUMMY_NODE_ID;
let item = Item { ident, attrs, id, kind, vis, span, tokens: None };
let item = Item { attrs, id, kind, vis, span, tokens: None };
return Ok((Some(item), Trailing::No, UsePreAttrPos::No));
}
@ -208,7 +206,7 @@ impl<'a> Parser<'a> {
def: &mut Defaultness,
fn_parse_mode: FnParseMode,
case: Case,
) -> PResult<'a, Option<ItemInfo>> {
) -> PResult<'a, Option<ItemKind>> {
let check_pub = def == &Defaultness::Final;
let mut def_ = || mem::replace(def, Defaultness::Final);
@ -218,17 +216,15 @@ impl<'a> Parser<'a> {
// FUNCTION ITEM
let (ident, sig, generics, contract, body) =
self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?;
(
ItemKind::Fn(Box::new(Fn {
defaultness: def_(),
ident,
ItemKind::Fn(Box::new(Fn {
defaultness: def_(),
sig,
generics,
contract,
body,
define_opaque: None,
})),
)
sig,
generics,
contract,
body,
define_opaque: None,
}))
} else if self.eat_keyword(exp!(Extern)) {
if self.eat_keyword(exp!(Crate)) {
// EXTERN CRATE
@ -247,8 +243,8 @@ impl<'a> Parser<'a> {
// STATIC ITEM
self.bump(); // `static`
let mutability = self.parse_mutability();
let (ident, item) = self.parse_static_item(safety, mutability)?;
(ident, ItemKind::Static(Box::new(item)))
let item = self.parse_static_item(safety, mutability)?;
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) {
@ -258,16 +254,14 @@ impl<'a> Parser<'a> {
self.recover_const_mut(const_span);
self.recover_missing_kw_before_item()?;
let (ident, generics, ty, expr) = self.parse_const_item()?;
(
ItemKind::Const(Box::new(ConstItem {
defaultness: def_(),
ident,
ItemKind::Const(Box::new(ConstItem {
defaultness: def_(),
generics,
ty,
expr,
define_opaque: None,
})),
)
generics,
ty,
expr,
define_opaque: None,
}))
}
} else if self.check_keyword(exp!(Trait)) || self.check_auto_or_unsafe_trait_item() {
// TRAIT ITEM
@ -334,14 +328,14 @@ impl<'a> Parser<'a> {
self.recover_missing_kw_before_item()?;
}
// MACRO INVOCATION ITEM
(Ident::empty(), ItemKind::MacCall(P(self.parse_item_macro(vis)?)))
ItemKind::MacCall(P(self.parse_item_macro(vis)?))
} else {
return Ok(None);
};
Ok(Some(info))
}
fn recover_import_as_use(&mut self) -> PResult<'a, Option<ItemInfo>> {
fn recover_import_as_use(&mut self) -> PResult<'a, Option<ItemKind>> {
let span = self.token.span;
let token_name = super::token_descr(&self.token);
let snapshot = self.create_snapshot_for_diagnostic();
@ -359,7 +353,7 @@ impl<'a> Parser<'a> {
}
}
fn parse_use_item(&mut self) -> PResult<'a, ItemInfo> {
fn parse_use_item(&mut self) -> PResult<'a, ItemKind> {
let tree = self.parse_use_tree()?;
if let Err(mut e) = self.expect_semi() {
match tree.kind {
@ -373,7 +367,7 @@ impl<'a> Parser<'a> {
}
return Err(e);
}
Ok((Ident::empty(), ItemKind::Use(tree)))
Ok(ItemKind::Use(tree))
}
/// When parsing a statement, would the start of a path be an item?
@ -483,7 +477,7 @@ impl<'a> Parser<'a> {
if let Some(err) = err { Err(self.dcx().create_err(err)) } else { Ok(()) }
}
fn parse_item_builtin(&mut self) -> PResult<'a, Option<ItemInfo>> {
fn parse_item_builtin(&mut self) -> PResult<'a, Option<ItemKind>> {
// To be expanded
Ok(None)
}
@ -577,7 +571,7 @@ impl<'a> Parser<'a> {
&mut self,
attrs: &mut AttrVec,
defaultness: Defaultness,
) -> PResult<'a, ItemInfo> {
) -> PResult<'a, ItemKind> {
let safety = self.parse_safety(Case::Sensitive);
self.expect_keyword(exp!(Impl))?;
@ -698,10 +692,10 @@ impl<'a> Parser<'a> {
items: impl_items,
}));
Ok((Ident::empty(), item_kind))
Ok(item_kind)
}
fn parse_item_delegation(&mut self) -> PResult<'a, ItemInfo> {
fn parse_item_delegation(&mut self) -> PResult<'a, ItemKind> {
let span = self.token.span;
self.expect_keyword(exp!(Reuse))?;
@ -724,7 +718,7 @@ impl<'a> Parser<'a> {
})
};
let (ident, item_kind) = if self.eat_path_sep() {
let item_kind = if self.eat_path_sep() {
let suffixes = if self.eat(exp!(Star)) {
None
} else {
@ -732,7 +726,7 @@ impl<'a> Parser<'a> {
Some(self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), parse_suffix)?.0)
};
let deleg = DelegationMac { qself, prefix: path, suffixes, body: body(self)? };
(Ident::empty(), ItemKind::DelegationMac(Box::new(deleg)))
ItemKind::DelegationMac(Box::new(deleg))
} else {
let rename = rename(self)?;
let ident = rename.unwrap_or_else(|| path.segments.last().unwrap().ident);
@ -740,17 +734,18 @@ impl<'a> Parser<'a> {
id: DUMMY_NODE_ID,
qself,
path,
ident,
rename,
body: body(self)?,
from_glob: false,
};
(ident, ItemKind::Delegation(Box::new(deleg)))
ItemKind::Delegation(Box::new(deleg))
};
let span = span.to(self.prev_token.span);
self.psess.gated_spans.gate(sym::fn_delegation, span);
Ok((ident, item_kind))
Ok(item_kind)
}
fn parse_item_list<T>(
@ -900,7 +895,7 @@ impl<'a> Parser<'a> {
}
/// Parses `unsafe? auto? trait Foo { ... }` or `trait Foo = Bar;`.
fn parse_item_trait(&mut self, attrs: &mut AttrVec, lo: Span) -> PResult<'a, ItemInfo> {
fn parse_item_trait(&mut self, attrs: &mut AttrVec, lo: Span) -> PResult<'a, ItemKind> {
let safety = self.parse_safety(Case::Sensitive);
// Parse optional `auto` prefix.
let is_auto = if self.eat_keyword(exp!(Auto)) {
@ -941,15 +936,12 @@ impl<'a> Parser<'a> {
self.psess.gated_spans.gate(sym::trait_alias, whole_span);
Ok((ident, ItemKind::TraitAlias(generics, bounds)))
Ok(ItemKind::TraitAlias(ident, generics, bounds))
} else {
// It's a normal trait.
generics.where_clause = self.parse_where_clause()?;
let items = self.parse_item_list(attrs, |p| p.parse_trait_item(ForceCollect::No))?;
Ok((
ident,
ItemKind::Trait(Box::new(Trait { is_auto, safety, generics, bounds, items })),
))
Ok(ItemKind::Trait(Box::new(Trait { is_auto, safety, ident, generics, bounds, items })))
}
}
@ -977,11 +969,12 @@ impl<'a> Parser<'a> {
force_collect: ForceCollect,
) -> PResult<'a, Option<Option<P<AssocItem>>>> {
Ok(self.parse_item_(fn_parse_mode, force_collect)?.map(
|Item { attrs, id, span, vis, ident, kind, tokens }| {
|Item { attrs, id, span, vis, kind, tokens }| {
let kind = match AssocItemKind::try_from(kind) {
Ok(kind) => kind,
Err(kind) => match kind {
ItemKind::Static(box StaticItem {
ident,
ty,
safety: _,
mutability: _,
@ -991,6 +984,7 @@ impl<'a> Parser<'a> {
self.dcx().emit_err(errors::AssociatedStaticItemNotAllowed { span });
AssocItemKind::Const(Box::new(ConstItem {
defaultness: Defaultness::Final,
ident,
generics: Generics::default(),
ty,
expr,
@ -1000,7 +994,7 @@ impl<'a> Parser<'a> {
_ => return self.error_bad_item_kind(span, &kind, "`trait`s or `impl`s"),
},
};
Some(P(Item { attrs, id, span, vis, ident, kind, tokens }))
Some(P(Item { attrs, id, span, vis, kind, tokens }))
},
))
}
@ -1010,7 +1004,7 @@ impl<'a> Parser<'a> {
/// 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> {
fn parse_type_alias(&mut self, defaultness: Defaultness) -> PResult<'a, ItemKind> {
let ident = self.parse_ident()?;
let mut generics = self.parse_generics()?;
@ -1045,16 +1039,14 @@ impl<'a> Parser<'a> {
self.expect_semi()?;
Ok((
Ok(ItemKind::TyAlias(Box::new(TyAlias {
defaultness,
ident,
ItemKind::TyAlias(Box::new(TyAlias {
defaultness,
generics,
where_clauses,
bounds,
ty,
})),
))
generics,
where_clauses,
bounds,
ty,
})))
}
/// Parses a `UseTree`.
@ -1158,16 +1150,16 @@ impl<'a> Parser<'a> {
/// extern crate foo;
/// extern crate bar as foo;
/// ```
fn parse_item_extern_crate(&mut self) -> PResult<'a, ItemInfo> {
fn parse_item_extern_crate(&mut self) -> PResult<'a, ItemKind> {
// Accept `extern crate name-like-this` for better diagnostics
let orig_name = self.parse_crate_name_with_dashes()?;
let (item_name, orig_name) = if let Some(rename) = self.parse_rename()? {
(rename, Some(orig_name.name))
let orig_ident = self.parse_crate_name_with_dashes()?;
let (orig_name, item_ident) = if let Some(rename) = self.parse_rename()? {
(Some(orig_ident.name), rename)
} else {
(orig_name, None)
(None, orig_ident)
};
self.expect_semi()?;
Ok((item_name, ItemKind::ExternCrate(orig_name)))
Ok(ItemKind::ExternCrate(orig_name, item_ident))
}
fn parse_crate_name_with_dashes(&mut self) -> PResult<'a, Ident> {
@ -1218,7 +1210,7 @@ impl<'a> Parser<'a> {
&mut self,
attrs: &mut AttrVec,
mut safety: Safety,
) -> PResult<'a, ItemInfo> {
) -> PResult<'a, ItemKind> {
let extern_span = self.prev_token.uninterpolated_span();
let abi = self.parse_abi(); // ABI?
// FIXME: This recovery should be tested better.
@ -1236,7 +1228,7 @@ impl<'a> Parser<'a> {
abi,
items: self.parse_item_list(attrs, |p| p.parse_foreign_item(ForceCollect::No))?,
};
Ok((Ident::empty(), ItemKind::ForeignMod(module)))
Ok(ItemKind::ForeignMod(module))
}
/// Parses a foreign item (one in an `extern { ... }` block).
@ -1246,11 +1238,11 @@ impl<'a> Parser<'a> {
) -> PResult<'a, Option<Option<P<ForeignItem>>>> {
let fn_parse_mode = FnParseMode { req_name: |_| true, req_body: false };
Ok(self.parse_item_(fn_parse_mode, force_collect)?.map(
|Item { attrs, id, span, vis, ident, kind, tokens }| {
|Item { attrs, id, span, vis, kind, tokens }| {
let kind = match ForeignItemKind::try_from(kind) {
Ok(kind) => kind,
Err(kind) => match kind {
ItemKind::Const(box ConstItem { ty, expr, .. }) => {
ItemKind::Const(box ConstItem { ident, ty, expr, .. }) => {
let const_span = Some(span.with_hi(ident.span.lo()))
.filter(|span| span.can_be_used_for_suggestions());
self.dcx().emit_err(errors::ExternItemCannotBeConst {
@ -1258,6 +1250,7 @@ impl<'a> Parser<'a> {
const_span,
});
ForeignItemKind::Static(Box::new(StaticItem {
ident,
ty,
mutability: Mutability::Not,
expr,
@ -1268,7 +1261,7 @@ impl<'a> Parser<'a> {
_ => return self.error_bad_item_kind(span, &kind, "`extern` blocks"),
},
};
Some(P(Item { attrs, id, span, vis, ident, kind, tokens }))
Some(P(Item { attrs, id, span, vis, kind, tokens }))
},
))
}
@ -1343,13 +1336,13 @@ impl<'a> Parser<'a> {
const_span: Span,
attrs: &mut AttrVec,
defaultness: Defaultness,
) -> PResult<'a, ItemInfo> {
) -> PResult<'a, ItemKind> {
let impl_span = self.token.span;
let err = self.expected_ident_found_err();
// Only try to recover if this is implementing a trait for a type
let mut impl_info = match self.parse_item_impl(attrs, defaultness) {
Ok(impl_info) => impl_info,
let mut item_kind = match self.parse_item_impl(attrs, defaultness) {
Ok(item_kind) => item_kind,
Err(recovery_error) => {
// Recovery failed, raise the "expected identifier" error
recovery_error.cancel();
@ -1357,7 +1350,7 @@ impl<'a> Parser<'a> {
}
};
match &mut impl_info.1 {
match &mut item_kind {
ItemKind::Impl(box Impl { of_trait: Some(trai), constness, .. }) => {
*constness = Const::Yes(const_span);
@ -1374,7 +1367,7 @@ impl<'a> Parser<'a> {
_ => unreachable!(),
}
Ok(impl_info)
Ok(item_kind)
}
/// Parse a static item with the prefix `"static" "mut"?` already parsed and stored in `mutability`.
@ -1386,7 +1379,7 @@ impl<'a> Parser<'a> {
&mut self,
safety: Safety,
mutability: Mutability,
) -> PResult<'a, (Ident, StaticItem)> {
) -> PResult<'a, StaticItem> {
let ident = self.parse_ident()?;
if self.token == TokenKind::Lt && self.may_recover() {
@ -1406,7 +1399,7 @@ impl<'a> Parser<'a> {
self.expect_semi()?;
Ok((ident, StaticItem { ty, safety, mutability, expr, define_opaque: None }))
Ok(StaticItem { ident, ty, safety, mutability, expr, define_opaque: None })
}
/// Parse a constant item with the prefix `"const"` already parsed.
@ -1531,7 +1524,7 @@ impl<'a> Parser<'a> {
}
/// Parses an enum declaration.
fn parse_item_enum(&mut self) -> PResult<'a, ItemInfo> {
fn parse_item_enum(&mut self) -> PResult<'a, ItemKind> {
if self.token.is_keyword(kw::Struct) {
let span = self.prev_token.span.to(self.token.span);
let err = errors::EnumStructMutuallyExclusive { span };
@ -1584,7 +1577,7 @@ impl<'a> Parser<'a> {
};
let enum_definition = EnumDef { variants: variants.into_iter().flatten().collect() };
Ok((id, ItemKind::Enum(enum_definition, generics)))
Ok(ItemKind::Enum(id, enum_definition, generics))
}
fn parse_enum_variant(&mut self, span: Span) -> PResult<'a, Option<Variant>> {
@ -1676,8 +1669,8 @@ impl<'a> Parser<'a> {
}
/// Parses `struct Foo { ... }`.
fn parse_item_struct(&mut self) -> PResult<'a, ItemInfo> {
let class_name = self.parse_ident()?;
fn parse_item_struct(&mut self) -> PResult<'a, ItemKind> {
let ident = self.parse_ident()?;
let mut generics = self.parse_generics()?;
@ -1698,7 +1691,7 @@ impl<'a> Parser<'a> {
let vdata = if self.token.is_keyword(kw::Where) {
let tuple_struct_body;
(generics.where_clause, tuple_struct_body) =
self.parse_struct_where_clause(class_name, generics.span)?;
self.parse_struct_where_clause(ident, generics.span)?;
if let Some(body) = tuple_struct_body {
// If we see a misplaced tuple struct body: `struct Foo<T> where T: Copy, (T);`
@ -1712,7 +1705,7 @@ impl<'a> Parser<'a> {
// If we see: `struct Foo<T> where T: Copy { ... }`
let (fields, recovered) = self.parse_record_struct_body(
"struct",
class_name.span,
ident.span,
generics.where_clause.has_where_token,
)?;
VariantData::Struct { fields, recovered }
@ -1724,7 +1717,7 @@ impl<'a> Parser<'a> {
} else if self.token == token::OpenDelim(Delimiter::Brace) {
let (fields, recovered) = self.parse_record_struct_body(
"struct",
class_name.span,
ident.span,
generics.where_clause.has_where_token,
)?;
VariantData::Struct { fields, recovered }
@ -1740,12 +1733,12 @@ impl<'a> Parser<'a> {
return Err(self.dcx().create_err(err));
};
Ok((class_name, ItemKind::Struct(vdata, generics)))
Ok(ItemKind::Struct(ident, vdata, generics))
}
/// Parses `union Foo { ... }`.
fn parse_item_union(&mut self) -> PResult<'a, ItemInfo> {
let class_name = self.parse_ident()?;
fn parse_item_union(&mut self) -> PResult<'a, ItemKind> {
let ident = self.parse_ident()?;
let mut generics = self.parse_generics()?;
@ -1753,14 +1746,14 @@ impl<'a> Parser<'a> {
generics.where_clause = self.parse_where_clause()?;
let (fields, recovered) = self.parse_record_struct_body(
"union",
class_name.span,
ident.span,
generics.where_clause.has_where_token,
)?;
VariantData::Struct { fields, recovered }
} else if self.token == token::OpenDelim(Delimiter::Brace) {
let (fields, recovered) = self.parse_record_struct_body(
"union",
class_name.span,
ident.span,
generics.where_clause.has_where_token,
)?;
VariantData::Struct { fields, recovered }
@ -1772,7 +1765,7 @@ impl<'a> Parser<'a> {
return Err(err);
};
Ok((class_name, ItemKind::Union(vdata, generics)))
Ok(ItemKind::Union(ident, vdata, generics))
}
/// This function parses the fields of record structs:
@ -2124,15 +2117,17 @@ impl<'a> Parser<'a> {
}
} else if self.eat_keyword(exp!(Struct)) {
match self.parse_item_struct() {
Ok((ident, _)) => self
.dcx()
.struct_span_err(
lo.with_hi(ident.span.hi()),
format!("structs are not allowed in {adt_ty} definitions"),
)
.with_help(
"consider creating a new `struct` definition instead of nesting",
),
Ok(item) => {
let ItemKind::Struct(ident, ..) = item else { unreachable!() };
self.dcx()
.struct_span_err(
lo.with_hi(ident.span.hi()),
format!("structs are not allowed in {adt_ty} definitions"),
)
.with_help(
"consider creating a new `struct` definition instead of nesting",
)
}
Err(err) => {
err.cancel();
self.restore_snapshot(snapshot);
@ -2177,7 +2172,7 @@ impl<'a> Parser<'a> {
/// MacParams = "(" TOKEN_STREAM ")" ;
/// DeclMac = "macro" Ident MacParams? MacBody ;
/// ```
fn parse_item_decl_macro(&mut self, lo: Span) -> PResult<'a, ItemInfo> {
fn parse_item_decl_macro(&mut self, lo: Span) -> PResult<'a, ItemKind> {
let ident = self.parse_ident()?;
let body = if self.check(exp!(OpenBrace)) {
self.parse_delim_args()? // `MacBody`
@ -2199,7 +2194,7 @@ impl<'a> Parser<'a> {
};
self.psess.gated_spans.gate(sym::decl_macro, lo.to(self.prev_token.span));
Ok((ident, ItemKind::MacroDef(ast::MacroDef { body, macro_rules: false })))
Ok(ItemKind::MacroDef(ident, ast::MacroDef { body, macro_rules: false }))
}
/// Is this a possibly malformed start of a `macro_rules! foo` item definition?
@ -2228,7 +2223,7 @@ impl<'a> Parser<'a> {
&mut self,
vis: &Visibility,
has_bang: bool,
) -> PResult<'a, ItemInfo> {
) -> PResult<'a, ItemKind> {
self.expect_keyword(exp!(MacroRules))?; // `macro_rules`
if has_bang {
@ -2246,7 +2241,7 @@ impl<'a> Parser<'a> {
self.eat_semi_for_macro_if_needed(&body);
self.complain_if_pub_macro(vis, true);
Ok((ident, ItemKind::MacroDef(ast::MacroDef { body, macro_rules: true })))
Ok(ItemKind::MacroDef(ident, ast::MacroDef { body, macro_rules: true }))
}
/// Item macro invocations or `macro_rules!` definitions need inherited visibility.

View file

@ -2922,7 +2922,7 @@ fn out_of_line_mod() {
.unwrap()
.unwrap();
let ast::ItemKind::Mod(_, mod_kind) = &item.kind else { panic!() };
let ast::ItemKind::Mod(_, _, mod_kind) = &item.kind else { panic!() };
assert_matches!(mod_kind, ast::ModKind::Loaded(items, ..) if items.len() == 2);
});
}