Treat macros as HIR items
This commit is contained in:
parent
ac50a53359
commit
8c62fa0575
31 changed files with 162 additions and 256 deletions
|
@ -4146,6 +4146,7 @@ dependencies = [
|
|||
name = "rustc_privacy"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"rustc_ast",
|
||||
"rustc_attr",
|
||||
"rustc_data_structures",
|
||||
"rustc_errors",
|
||||
|
|
|
@ -170,7 +170,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
self.lower_item_id_use_tree(use_tree, i.id, &mut vec);
|
||||
vec
|
||||
}
|
||||
ItemKind::MacroDef(..) => SmallVec::new(),
|
||||
ItemKind::Fn(..) | ItemKind::Impl(box ImplKind { of_trait: None, .. }) => {
|
||||
smallvec![i.id]
|
||||
}
|
||||
|
@ -212,28 +211,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
pub fn lower_item(&mut self, i: &Item) -> Option<hir::Item<'hir>> {
|
||||
let mut ident = i.ident;
|
||||
let mut vis = self.lower_visibility(&i.vis, None);
|
||||
|
||||
if let ItemKind::MacroDef(MacroDef { ref body, macro_rules }) = i.kind {
|
||||
if !macro_rules || self.sess.contains_name(&i.attrs, sym::macro_export) {
|
||||
let hir_id = self.lower_node_id(i.id);
|
||||
self.lower_attrs(hir_id, &i.attrs);
|
||||
let body = P(self.lower_mac_args(body));
|
||||
self.insert_macro_def(hir::MacroDef {
|
||||
ident,
|
||||
vis,
|
||||
def_id: hir_id.expect_owner(),
|
||||
span: i.span,
|
||||
ast: MacroDef { body, macro_rules },
|
||||
});
|
||||
} else {
|
||||
for a in i.attrs.iter() {
|
||||
let a = self.lower_attr(a);
|
||||
self.non_exported_macro_attrs.push(a);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
let hir_id = self.lower_node_id(i.id);
|
||||
let attrs = self.lower_attrs(hir_id, &i.attrs);
|
||||
let kind = self.lower_item_kind(i.span, i.id, hir_id, &mut ident, attrs, &mut vis, &i.kind);
|
||||
|
@ -465,7 +442,12 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
|||
self.lower_generics(generics, ImplTraitContext::disallowed()),
|
||||
self.lower_param_bounds(bounds, ImplTraitContext::disallowed()),
|
||||
),
|
||||
ItemKind::MacroDef(..) | ItemKind::MacCall(..) => {
|
||||
ItemKind::MacroDef(MacroDef { ref body, macro_rules }) => {
|
||||
let body = P(self.lower_mac_args(body));
|
||||
|
||||
hir::ItemKind::Macro(ast::MacroDef { body, macro_rules })
|
||||
}
|
||||
ItemKind::MacCall(..) => {
|
||||
panic!("`TyMac` should have been expanded by now")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,6 @@ struct LoweringContext<'a, 'hir: 'a> {
|
|||
/// The items being lowered are collected here.
|
||||
owners: IndexVec<LocalDefId, Option<hir::OwnerNode<'hir>>>,
|
||||
bodies: BTreeMap<hir::BodyId, hir::Body<'hir>>,
|
||||
non_exported_macro_attrs: Vec<ast::Attribute>,
|
||||
|
||||
trait_impls: BTreeMap<DefId, Vec<LocalDefId>>,
|
||||
|
||||
|
@ -330,7 +329,6 @@ pub fn lower_crate<'a, 'hir>(
|
|||
trait_impls: BTreeMap::new(),
|
||||
modules: BTreeMap::new(),
|
||||
attrs: BTreeMap::default(),
|
||||
non_exported_macro_attrs: Vec::new(),
|
||||
catch_scopes: Vec::new(),
|
||||
loop_scopes: Vec::new(),
|
||||
is_in_loop_condition: false,
|
||||
|
@ -551,7 +549,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
}
|
||||
|
||||
let krate = hir::Crate {
|
||||
non_exported_macro_attrs: self.arena.alloc_from_iter(self.non_exported_macro_attrs),
|
||||
owners: self.owners,
|
||||
bodies: self.bodies,
|
||||
body_ids,
|
||||
|
@ -600,13 +597,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
|||
id
|
||||
}
|
||||
|
||||
fn insert_macro_def(&mut self, item: hir::MacroDef<'hir>) {
|
||||
let def_id = item.def_id;
|
||||
let item = self.arena.alloc(item);
|
||||
self.owners.ensure_contains_elem(def_id, || None);
|
||||
self.owners[def_id] = Some(hir::OwnerNode::MacroDef(item));
|
||||
}
|
||||
|
||||
fn allocate_hir_id_counter(&mut self, owner: NodeId) -> hir::HirId {
|
||||
// Set up the counter if needed.
|
||||
self.item_local_id_counters.entry(owner).or_insert(0);
|
||||
|
|
|
@ -578,6 +578,33 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
|
|||
}
|
||||
}
|
||||
|
||||
fn print_mac_def(
|
||||
&mut self,
|
||||
macro_def: &ast::MacroDef,
|
||||
ident: &Ident,
|
||||
sp: &Span,
|
||||
print_visibility: impl FnOnce(&mut Self),
|
||||
) {
|
||||
let (kw, has_bang) = if macro_def.macro_rules {
|
||||
("macro_rules", true)
|
||||
} else {
|
||||
print_visibility(self);
|
||||
("macro", false)
|
||||
};
|
||||
self.print_mac_common(
|
||||
Some(MacHeader::Keyword(kw)),
|
||||
has_bang,
|
||||
Some(*ident),
|
||||
macro_def.body.delim(),
|
||||
¯o_def.body.inner_tokens(),
|
||||
true,
|
||||
*sp,
|
||||
);
|
||||
if macro_def.body.need_semicolon() {
|
||||
self.word(";");
|
||||
}
|
||||
}
|
||||
|
||||
fn print_path(&mut self, path: &ast::Path, colons_before_params: bool, depth: usize) {
|
||||
self.maybe_print_comment(path.span.lo());
|
||||
|
||||
|
@ -1305,24 +1332,9 @@ impl<'a> State<'a> {
|
|||
}
|
||||
}
|
||||
ast::ItemKind::MacroDef(ref macro_def) => {
|
||||
let (kw, has_bang) = if macro_def.macro_rules {
|
||||
("macro_rules", true)
|
||||
} else {
|
||||
self.print_visibility(&item.vis);
|
||||
("macro", false)
|
||||
};
|
||||
self.print_mac_common(
|
||||
Some(MacHeader::Keyword(kw)),
|
||||
has_bang,
|
||||
Some(item.ident),
|
||||
macro_def.body.delim(),
|
||||
¯o_def.body.inner_tokens(),
|
||||
true,
|
||||
item.span,
|
||||
);
|
||||
if macro_def.body.need_semicolon() {
|
||||
self.word(";");
|
||||
}
|
||||
self.print_mac_def(macro_def, &item.ident, &item.span, |state| {
|
||||
state.print_visibility(&item.vis)
|
||||
});
|
||||
}
|
||||
}
|
||||
self.ann.post(self, AnnNode::Item(item))
|
||||
|
|
|
@ -35,7 +35,6 @@ macro_rules! arena_types {
|
|||
[few] inline_asm: rustc_hir::InlineAsm<$tcx>,
|
||||
[few] llvm_inline_asm: rustc_hir::LlvmInlineAsm<$tcx>,
|
||||
[] local: rustc_hir::Local<$tcx>,
|
||||
[few] macro_def: rustc_hir::MacroDef<$tcx>,
|
||||
[few] mod_: rustc_hir::Mod<$tcx>,
|
||||
[] param: rustc_hir::Param<$tcx>,
|
||||
[] pat: rustc_hir::Pat<$tcx>,
|
||||
|
|
|
@ -670,9 +670,6 @@ pub struct ModuleItems {
|
|||
/// [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/hir.html
|
||||
#[derive(Debug)]
|
||||
pub struct Crate<'hir> {
|
||||
// Attributes from non-exported macros, kept only for collecting the library feature list.
|
||||
pub non_exported_macro_attrs: &'hir [Attribute],
|
||||
|
||||
pub owners: IndexVec<LocalDefId, Option<OwnerNode<'hir>>>,
|
||||
pub bodies: BTreeMap<BodyId, Body<'hir>>,
|
||||
pub trait_impls: BTreeMap<DefId, Vec<LocalDefId>>,
|
||||
|
@ -768,32 +765,6 @@ impl Crate<'_> {
|
|||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exported_macros<'hir>(&'hir self) -> impl Iterator<Item = &'hir MacroDef<'hir>> + 'hir {
|
||||
self.owners.iter().filter_map(|owner| match owner {
|
||||
Some(OwnerNode::MacroDef(macro_def)) => Some(*macro_def),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro definition, in this crate or imported from another.
|
||||
///
|
||||
/// Not parsed directly, but created on macro import or `macro_rules!` expansion.
|
||||
#[derive(Debug)]
|
||||
pub struct MacroDef<'hir> {
|
||||
pub ident: Ident,
|
||||
pub vis: Visibility<'hir>,
|
||||
pub def_id: LocalDefId,
|
||||
pub span: Span,
|
||||
pub ast: ast::MacroDef,
|
||||
}
|
||||
|
||||
impl MacroDef<'_> {
|
||||
#[inline]
|
||||
pub fn hir_id(&self) -> HirId {
|
||||
HirId::make_owner(self.def_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A block of statements `{ .. }`, which may have a label (in this case the
|
||||
|
@ -2602,7 +2573,7 @@ pub struct PolyTraitRef<'hir> {
|
|||
|
||||
pub type Visibility<'hir> = Spanned<VisibilityKind<'hir>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum VisibilityKind<'hir> {
|
||||
Public,
|
||||
Crate(CrateSugar),
|
||||
|
@ -2791,6 +2762,8 @@ pub enum ItemKind<'hir> {
|
|||
Const(&'hir Ty<'hir>, BodyId),
|
||||
/// A function declaration.
|
||||
Fn(FnSig<'hir>, Generics<'hir>, BodyId),
|
||||
/// A MBE macro definition (`macro_rules!` or `macro`).
|
||||
Macro(ast::MacroDef),
|
||||
/// A module.
|
||||
Mod(Mod<'hir>),
|
||||
/// An external module, e.g. `extern { .. }`.
|
||||
|
@ -2856,6 +2829,7 @@ impl ItemKind<'_> {
|
|||
ItemKind::Static(..) => "static item",
|
||||
ItemKind::Const(..) => "constant item",
|
||||
ItemKind::Fn(..) => "function",
|
||||
ItemKind::Macro(..) => "macro",
|
||||
ItemKind::Mod(..) => "module",
|
||||
ItemKind::ForeignMod { .. } => "extern block",
|
||||
ItemKind::GlobalAsm(..) => "global asm item",
|
||||
|
|
|
@ -466,9 +466,6 @@ pub trait Visitor<'v>: Sized {
|
|||
walk_assoc_type_binding(self, type_binding)
|
||||
}
|
||||
fn visit_attribute(&mut self, _id: HirId, _attr: &'v Attribute) {}
|
||||
fn visit_macro_def(&mut self, macro_def: &'v MacroDef<'v>) {
|
||||
walk_macro_def(self, macro_def)
|
||||
}
|
||||
fn visit_vis(&mut self, vis: &'v Visibility<'v>) {
|
||||
walk_vis(self, vis)
|
||||
}
|
||||
|
@ -484,7 +481,6 @@ pub trait Visitor<'v>: Sized {
|
|||
pub fn walk_crate<'v, V: Visitor<'v>>(visitor: &mut V, krate: &'v Crate<'v>) {
|
||||
let top_mod = krate.module();
|
||||
visitor.visit_mod(top_mod, top_mod.inner, CRATE_HIR_ID);
|
||||
walk_list!(visitor, visit_macro_def, krate.exported_macros());
|
||||
for (&id, attrs) in krate.attrs.iter() {
|
||||
for a in *attrs {
|
||||
visitor.visit_attribute(id, a)
|
||||
|
@ -492,11 +488,6 @@ pub fn walk_crate<'v, V: Visitor<'v>>(visitor: &mut V, krate: &'v Crate<'v>) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn walk_macro_def<'v, V: Visitor<'v>>(visitor: &mut V, macro_def: &'v MacroDef<'v>) {
|
||||
visitor.visit_id(macro_def.hir_id());
|
||||
visitor.visit_ident(macro_def.ident);
|
||||
}
|
||||
|
||||
pub fn walk_mod<'v, V: Visitor<'v>>(visitor: &mut V, module: &'v Mod<'v>, mod_hir_id: HirId) {
|
||||
visitor.visit_id(mod_hir_id);
|
||||
for &item_id in module.item_ids {
|
||||
|
@ -586,6 +577,9 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) {
|
|||
item.span,
|
||||
item.hir_id(),
|
||||
),
|
||||
ItemKind::Macro(_) => {
|
||||
visitor.visit_id(item.hir_id());
|
||||
}
|
||||
ItemKind::Mod(ref module) => {
|
||||
// `visit_mod()` takes care of visiting the `Item`'s `HirId`.
|
||||
visitor.visit_mod(module, item.span, item.hir_id())
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
|
||||
|
||||
use crate::hir::{
|
||||
BodyId, Expr, ForeignItem, ForeignItemId, ImplItem, ImplItemId, Item, ItemId, MacroDef, Mod,
|
||||
TraitItem, TraitItemId, Ty, VisibilityKind,
|
||||
BodyId, Expr, ForeignItem, ForeignItemId, ImplItem, ImplItemId, Item, ItemId, Mod, TraitItem,
|
||||
TraitItemId, Ty, VisibilityKind,
|
||||
};
|
||||
use crate::hir_id::{HirId, ItemLocalId};
|
||||
use rustc_span::def_id::DefPathHash;
|
||||
|
@ -190,16 +190,3 @@ impl<HirCtx: crate::HashStableContext> HashStable<HirCtx> for Item<'_> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<HirCtx: crate::HashStableContext> HashStable<HirCtx> for MacroDef<'_> {
|
||||
fn hash_stable(&self, hcx: &mut HirCtx, hasher: &mut StableHasher) {
|
||||
let MacroDef { ident, def_id: _, ref ast, ref vis, span } = *self;
|
||||
|
||||
hcx.hash_hir_item_like(|hcx| {
|
||||
ident.name.hash_stable(hcx, hasher);
|
||||
ast.hash_stable(hcx, hasher);
|
||||
vis.hash_stable(hcx, hasher);
|
||||
span.hash_stable(hcx, hasher);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ impl Target {
|
|||
ItemKind::Static(..) => Target::Static,
|
||||
ItemKind::Const(..) => Target::Const,
|
||||
ItemKind::Fn(..) => Target::Fn,
|
||||
ItemKind::Macro(..) => Target::MacroDef,
|
||||
ItemKind::Mod(..) => Target::Mod,
|
||||
ItemKind::ForeignMod { .. } => Target::ForeignMod,
|
||||
ItemKind::GlobalAsm(..) => Target::GlobalAsm,
|
||||
|
|
|
@ -642,6 +642,11 @@ impl<'a> State<'a> {
|
|||
self.end(); // need to close a box
|
||||
self.ann.nested(self, Nested::Body(body));
|
||||
}
|
||||
hir::ItemKind::Macro(ref macro_def) => {
|
||||
self.print_mac_def(macro_def, &item.ident, &item.span, |state| {
|
||||
state.print_visibility(&item.vis)
|
||||
});
|
||||
}
|
||||
hir::ItemKind::Mod(ref _mod) => {
|
||||
self.head(visibility_qualified(&item.vis, "mod"));
|
||||
self.print_ident(item.ident);
|
||||
|
|
|
@ -585,24 +585,6 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
|
|||
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>, krate: &hir::Crate<'_>) {
|
||||
self.check_missing_docs_attrs(cx, CRATE_DEF_ID, krate.module().inner, "the", "crate");
|
||||
|
||||
for macro_def in krate.exported_macros() {
|
||||
// Non exported macros should be skipped, since `missing_docs` only
|
||||
// applies to externally visible items.
|
||||
if !cx.access_levels.is_exported(macro_def.def_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let attrs = cx.tcx.hir().attrs(macro_def.hir_id());
|
||||
let has_doc = attrs.iter().any(has_doc);
|
||||
if !has_doc {
|
||||
cx.struct_span_lint(
|
||||
MISSING_DOCS,
|
||||
cx.tcx.sess.source_map().guess_head_span(macro_def.span),
|
||||
|lint| lint.build("missing documentation for macro").emit(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
|
||||
|
@ -636,6 +618,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
|
|||
|
||||
hir::ItemKind::TyAlias(..)
|
||||
| hir::ItemKind::Fn(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::Mod(..)
|
||||
| hir::ItemKind::Enum(..)
|
||||
| hir::ItemKind::Struct(..)
|
||||
|
|
|
@ -453,10 +453,6 @@ fn late_lint_pass_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, pass: T)
|
|||
lint_callback!(cx, check_crate, krate);
|
||||
|
||||
hir_visit::walk_crate(cx, krate);
|
||||
for attr in krate.non_exported_macro_attrs {
|
||||
// This HIR ID is a lie, since the macro ID isn't available.
|
||||
cx.visit_attribute(hir::CRATE_HIR_ID, attr);
|
||||
}
|
||||
|
||||
lint_callback!(cx, check_crate_post, krate);
|
||||
})
|
||||
|
|
|
@ -37,9 +37,6 @@ fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
|
|||
|
||||
let push = builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), &store, true);
|
||||
builder.levels.register_id(hir::CRATE_HIR_ID);
|
||||
for macro_def in krate.exported_macros() {
|
||||
builder.levels.register_id(macro_def.hir_id());
|
||||
}
|
||||
intravisit::walk_crate(&mut builder, krate);
|
||||
builder.levels.pop(push);
|
||||
|
||||
|
|
|
@ -1100,7 +1100,13 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
|
|||
let vis = self.get_visibility(child_index);
|
||||
let def_id = self.local_def_id(child_index);
|
||||
let res = Res::Def(kind, def_id);
|
||||
|
||||
// FIXME: Macros are currently encoded twice, once as items and once as
|
||||
// reexports. We ignore the items here and only use the reexports.
|
||||
if !matches!(kind, DefKind::Macro(..)) {
|
||||
callback(Export { res, ident, vis, span });
|
||||
}
|
||||
|
||||
// For non-re-export structs and variants add their constructors to children.
|
||||
// Re-export lists automatically contain constructors when necessary.
|
||||
match kind {
|
||||
|
|
|
@ -448,9 +448,6 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
|||
}
|
||||
|
||||
krate.visit_all_item_likes(&mut self.as_deep_visitor());
|
||||
for macro_def in krate.exported_macros() {
|
||||
self.visit_macro_def(macro_def);
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_def_path_table(&mut self) {
|
||||
|
@ -1385,6 +1382,9 @@ impl EncodeContext<'a, 'tcx> {
|
|||
|
||||
EntryKind::Fn(self.lazy(data))
|
||||
}
|
||||
hir::ItemKind::Macro(ref macro_def) => {
|
||||
EntryKind::MacroDef(self.lazy(macro_def.clone()))
|
||||
}
|
||||
hir::ItemKind::Mod(ref m) => {
|
||||
return self.encode_info_for_mod(item.def_id, m);
|
||||
}
|
||||
|
@ -1539,13 +1539,6 @@ impl EncodeContext<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Serialize the text of exported macros
|
||||
fn encode_info_for_macro_def(&mut self, macro_def: &hir::MacroDef<'_>) {
|
||||
let def_id = macro_def.def_id.to_def_id();
|
||||
record!(self.tables.kind[def_id] <- EntryKind::MacroDef(self.lazy(macro_def.ast.clone())));
|
||||
self.encode_ident_span(def_id, macro_def.ident);
|
||||
}
|
||||
|
||||
fn encode_info_for_generic_param(&mut self, def_id: DefId, kind: EntryKind, encode_type: bool) {
|
||||
record!(self.tables.kind[def_id] <- kind);
|
||||
if encode_type {
|
||||
|
@ -1915,9 +1908,6 @@ impl Visitor<'tcx> for EncodeContext<'a, 'tcx> {
|
|||
intravisit::walk_generics(self, generics);
|
||||
self.encode_info_for_generics(generics);
|
||||
}
|
||||
fn visit_macro_def(&mut self, macro_def: &'tcx hir::MacroDef<'tcx>) {
|
||||
self.encode_info_for_macro_def(macro_def);
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodeContext<'a, 'tcx> {
|
||||
|
@ -1972,6 +1962,7 @@ impl EncodeContext<'a, 'tcx> {
|
|||
hir::ItemKind::Static(..)
|
||||
| hir::ItemKind::Const(..)
|
||||
| hir::ItemKind::Fn(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::Mod(..)
|
||||
| hir::ItemKind::ForeignMod { .. }
|
||||
| hir::ItemKind::GlobalAsm(..)
|
||||
|
|
|
@ -394,20 +394,6 @@ impl<'a, 'hir> Visitor<'hir> for NodeCollector<'a, 'hir> {
|
|||
}
|
||||
}
|
||||
|
||||
fn visit_macro_def(&mut self, macro_def: &'hir MacroDef<'hir>) {
|
||||
// Exported macros are visited directly from the crate root,
|
||||
// so they do not have `parent_node` set.
|
||||
// Find the correct enclosing module from their DefKey.
|
||||
let def_key = self.definitions.def_key(macro_def.def_id);
|
||||
let parent = def_key.parent.map_or(hir::CRATE_HIR_ID, |local_def_index| {
|
||||
self.definitions.local_def_id_to_hir_id(LocalDefId { local_def_index })
|
||||
});
|
||||
self.insert_owner(macro_def.def_id, OwnerNode::MacroDef(macro_def));
|
||||
self.with_parent(parent, |this| {
|
||||
this.insert_nested(macro_def.def_id);
|
||||
});
|
||||
}
|
||||
|
||||
fn visit_variant(&mut self, v: &'hir Variant<'hir>, g: &'hir Generics<'hir>, item_id: HirId) {
|
||||
self.insert(v.span, v.id, Node::Variant(v));
|
||||
self.with_parent(v.id, |this| {
|
||||
|
|
|
@ -10,7 +10,6 @@ use rustc_hir::def::{DefKind, Res};
|
|||
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE};
|
||||
use rustc_hir::definitions::{DefKey, DefPath, DefPathHash};
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_hir::intravisit::Visitor;
|
||||
use rustc_hir::itemlikevisit::ItemLikeVisitor;
|
||||
use rustc_hir::*;
|
||||
use rustc_index::vec::Idx;
|
||||
|
@ -218,6 +217,7 @@ impl<'hir> Map<'hir> {
|
|||
ItemKind::Static(..) => DefKind::Static,
|
||||
ItemKind::Const(..) => DefKind::Const,
|
||||
ItemKind::Fn(..) => DefKind::Fn,
|
||||
ItemKind::Macro(..) => DefKind::Macro(MacroKind::Bang),
|
||||
ItemKind::Mod(..) => DefKind::Mod,
|
||||
ItemKind::OpaqueTy(..) => DefKind::OpaqueTy,
|
||||
ItemKind::TyAlias(..) => DefKind::TyAlias,
|
||||
|
@ -543,15 +543,6 @@ impl<'hir> Map<'hir> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn visit_exported_macros_in_krate<V>(&self, visitor: &mut V)
|
||||
where
|
||||
V: Visitor<'hir>,
|
||||
{
|
||||
for macro_def in self.krate().exported_macros() {
|
||||
visitor.visit_macro_def(macro_def);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator for the nodes in the ancestor tree of the `current_id`
|
||||
/// until the crate root is reached. Prefer this over your own loop using `get_parent_node`.
|
||||
pub fn parent_iter(&self, current_id: HirId) -> ParentHirIterator<'_, 'hir> {
|
||||
|
@ -1013,7 +1004,6 @@ pub(super) fn crate_hash(tcx: TyCtxt<'_>, crate_num: CrateNum) -> Svh {
|
|||
source_file_names.hash_stable(&mut hcx, &mut stable_hasher);
|
||||
tcx.sess.opts.dep_tracking_hash(true).hash_stable(&mut hcx, &mut stable_hasher);
|
||||
tcx.sess.local_stable_crate_id().hash_stable(&mut hcx, &mut stable_hasher);
|
||||
tcx.untracked_crate.non_exported_macro_attrs.hash_stable(&mut hcx, &mut stable_hasher);
|
||||
|
||||
let crate_hash: Fingerprint = stable_hasher.finish();
|
||||
Svh::new(crate_hash.to_smaller_hash())
|
||||
|
@ -1062,6 +1052,7 @@ fn hir_id_to_string(map: &Map<'_>, id: HirId) -> String {
|
|||
ItemKind::Static(..) => "static",
|
||||
ItemKind::Const(..) => "const",
|
||||
ItemKind::Fn(..) => "fn",
|
||||
ItemKind::Macro(..) => "macro",
|
||||
ItemKind::Mod(..) => "mod",
|
||||
ItemKind::ForeignMod { .. } => "foreign mod",
|
||||
ItemKind::GlobalAsm(..) => "global asm",
|
||||
|
|
|
@ -1149,6 +1149,7 @@ impl ItemLikeVisitor<'v> for RootCollector<'_, 'v> {
|
|||
match item.kind {
|
||||
hir::ItemKind::ExternCrate(..)
|
||||
| hir::ItemKind::Use(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::ForeignMod { .. }
|
||||
| hir::ItemKind::TyAlias(..)
|
||||
| hir::ItemKind::Trait(..)
|
||||
|
|
|
@ -1723,6 +1723,16 @@ impl Visitor<'tcx> for CheckAttrVisitor<'tcx> {
|
|||
}
|
||||
|
||||
fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
|
||||
// Historically we've run more checks on non-exported than exported macros,
|
||||
// so this lets us continue to run them while maintaining backwards compatibility.
|
||||
// In the long run, the checks should be harmonized.
|
||||
if let ItemKind::Macro(ref macro_def) = item.kind {
|
||||
let def_id = item.def_id.to_def_id();
|
||||
if macro_def.macro_rules && !self.tcx.has_attr(def_id, sym::macro_export) {
|
||||
check_non_exported_macro_for_invalid_attrs(self.tcx, item);
|
||||
}
|
||||
}
|
||||
|
||||
let target = Target::from_item(item);
|
||||
self.check_attributes(item.hir_id(), &item.span, target, Some(ItemLike::Item(item)));
|
||||
intravisit::walk_item(self, item)
|
||||
|
@ -1795,11 +1805,6 @@ impl Visitor<'tcx> for CheckAttrVisitor<'tcx> {
|
|||
intravisit::walk_variant(self, variant, generics, item_id)
|
||||
}
|
||||
|
||||
fn visit_macro_def(&mut self, macro_def: &'tcx hir::MacroDef<'tcx>) {
|
||||
self.check_attributes(macro_def.hir_id(), ¯o_def.span, Target::MacroDef, None);
|
||||
intravisit::walk_macro_def(self, macro_def);
|
||||
}
|
||||
|
||||
fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
|
||||
self.check_attributes(param.hir_id, ¶m.span, Target::Param, None);
|
||||
|
||||
|
@ -1848,7 +1853,9 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_invalid_macro_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
|
||||
fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) {
|
||||
let attrs = tcx.hir().attrs(item.hir_id());
|
||||
|
||||
for attr in attrs {
|
||||
if attr.has_name(sym::inline) {
|
||||
struct_span_err!(
|
||||
|
@ -1869,8 +1876,6 @@ fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
|
|||
if module_def_id.is_top_level_module() {
|
||||
check_attr_visitor.check_attributes(CRATE_HIR_ID, &DUMMY_SP, Target::Mod, None);
|
||||
check_invalid_crate_level_attr(tcx, tcx.hir().krate_attrs());
|
||||
tcx.hir().visit_exported_macros_in_krate(check_attr_visitor);
|
||||
check_invalid_macro_level_attr(tcx, tcx.hir().krate().non_exported_macro_attrs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,10 +107,6 @@ fn diagnostic_items<'tcx>(tcx: TyCtxt<'tcx>, cnum: CrateNum) -> FxHashMap<Symbol
|
|||
// Collect diagnostic items in this crate.
|
||||
tcx.hir().krate().visit_all_item_likes(&mut collector);
|
||||
|
||||
for m in tcx.hir().krate().exported_macros() {
|
||||
collector.observe_item(m.def_id);
|
||||
}
|
||||
|
||||
collector.items
|
||||
}
|
||||
|
||||
|
|
|
@ -244,11 +244,6 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
|
|||
fn visit_attribute(&mut self, _: hir::HirId, attr: &'v ast::Attribute) {
|
||||
self.record("Attribute", Id::Attr(attr.id), attr);
|
||||
}
|
||||
|
||||
fn visit_macro_def(&mut self, macro_def: &'v hir::MacroDef<'v>) {
|
||||
self.record("MacroDef", Id::Node(macro_def.hir_id()), macro_def);
|
||||
hir_visit::walk_macro_def(self, macro_def)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
|
||||
|
|
|
@ -127,9 +127,7 @@ impl Visitor<'tcx> for LibFeatureCollector<'tcx> {
|
|||
fn get_lib_features(tcx: TyCtxt<'_>, (): ()) -> LibFeatures {
|
||||
let mut collector = LibFeatureCollector::new(tcx);
|
||||
let krate = tcx.hir().krate();
|
||||
for attr in krate.non_exported_macro_attrs {
|
||||
collector.visit_attribute(rustc_hir::CRATE_HIR_ID, attr);
|
||||
}
|
||||
|
||||
intravisit::walk_crate(&mut collector, krate);
|
||||
collector.lib_features
|
||||
}
|
||||
|
|
|
@ -263,6 +263,7 @@ impl<'tcx> ReachableContext<'tcx> {
|
|||
| hir::ItemKind::Use(..)
|
||||
| hir::ItemKind::OpaqueTy(..)
|
||||
| hir::ItemKind::TyAlias(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::Mod(..)
|
||||
| hir::ItemKind::ForeignMod { .. }
|
||||
| hir::ItemKind::Impl { .. }
|
||||
|
|
|
@ -538,19 +538,6 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> {
|
|||
);
|
||||
}
|
||||
|
||||
fn visit_macro_def(&mut self, md: &'tcx hir::MacroDef<'tcx>) {
|
||||
self.annotate(
|
||||
md.def_id,
|
||||
md.span,
|
||||
None,
|
||||
AnnotationKind::Required,
|
||||
InheritDeprecation::Yes,
|
||||
InheritConstStability::No,
|
||||
InheritStability::No,
|
||||
|_| {},
|
||||
);
|
||||
}
|
||||
|
||||
fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
|
||||
let kind = match &p.kind {
|
||||
// Allow stability attributes on default generic arguments.
|
||||
|
@ -662,11 +649,6 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
|
|||
self.check_missing_stability(i.def_id, i.span);
|
||||
intravisit::walk_foreign_item(self, i);
|
||||
}
|
||||
|
||||
fn visit_macro_def(&mut self, md: &'tcx hir::MacroDef<'tcx>) {
|
||||
self.check_missing_stability(md.def_id, md.span);
|
||||
}
|
||||
|
||||
// Note that we don't need to `check_missing_stability` for default generic parameters,
|
||||
// as we assume that any default generic parameters without attributes are automatically
|
||||
// stable (assuming they have not inherited instability from their parent).
|
||||
|
|
|
@ -5,6 +5,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
rustc_middle = { path = "../rustc_middle" }
|
||||
rustc_ast = { path = "../rustc_ast" }
|
||||
rustc_attr = { path = "../rustc_attr" }
|
||||
rustc_errors = { path = "../rustc_errors" }
|
||||
rustc_hir = { path = "../rustc_hir" }
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use rustc_ast::MacroDef;
|
||||
use rustc_attr as attr;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_errors::struct_span_err;
|
||||
|
@ -26,7 +27,7 @@ use rustc_middle::ty::subst::{InternalSubsts, Subst};
|
|||
use rustc_middle::ty::{self, Const, GenericParamDefKind, TraitRef, Ty, TyCtxt, TypeFoldable};
|
||||
use rustc_session::lint;
|
||||
use rustc_span::hygiene::Transparency;
|
||||
use rustc_span::symbol::{kw, Ident};
|
||||
use rustc_span::symbol::{kw, sym, Ident};
|
||||
use rustc_span::Span;
|
||||
use rustc_trait_selection::traits::const_evaluatable::{self, AbstractConst};
|
||||
|
||||
|
@ -462,6 +463,43 @@ impl EmbargoVisitor<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
// We have to make sure that the items that macros might reference
|
||||
// are reachable, since they might be exported transitively.
|
||||
fn update_reachability_from_macro(&mut self, local_def_id: LocalDefId, md: &MacroDef) {
|
||||
// Non-opaque macros cannot make other items more accessible than they already are.
|
||||
|
||||
let hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id);
|
||||
let attrs = self.tcx.hir().attrs(hir_id);
|
||||
if attr::find_transparency(&attrs, md.macro_rules).0 != Transparency::Opaque {
|
||||
return;
|
||||
}
|
||||
|
||||
let item_def_id = local_def_id.to_def_id();
|
||||
let macro_module_def_id =
|
||||
ty::DefIdTree::parent(self.tcx, item_def_id).unwrap().expect_local();
|
||||
if self.tcx.hir().opt_def_kind(macro_module_def_id) != Some(DefKind::Mod) {
|
||||
// The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252).
|
||||
return;
|
||||
}
|
||||
|
||||
if self.get(local_def_id).is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we are starting from an externally visible module,
|
||||
// all the parents in the loop below are also guaranteed to be modules.
|
||||
let mut module_def_id = macro_module_def_id;
|
||||
loop {
|
||||
let changed_reachability =
|
||||
self.update_macro_reachable(module_def_id, macro_module_def_id);
|
||||
if changed_reachability || module_def_id == CRATE_DEF_ID {
|
||||
break;
|
||||
}
|
||||
module_def_id =
|
||||
ty::DefIdTree::parent(self.tcx, module_def_id.to_def_id()).unwrap().expect_local();
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the item as being reachable through a macro defined in the given
|
||||
/// module. Returns `true` if the level has changed.
|
||||
fn update_macro_reachable(
|
||||
|
@ -511,16 +549,26 @@ impl EmbargoVisitor<'tcx> {
|
|||
}
|
||||
match def_kind {
|
||||
// No type privacy, so can be directly marked as reachable.
|
||||
DefKind::Const
|
||||
| DefKind::Macro(_)
|
||||
| DefKind::Static
|
||||
| DefKind::TraitAlias
|
||||
| DefKind::TyAlias => {
|
||||
DefKind::Const | DefKind::Static | DefKind::TraitAlias | DefKind::TyAlias => {
|
||||
if vis.is_accessible_from(module.to_def_id(), self.tcx) {
|
||||
self.update(def_id, level);
|
||||
}
|
||||
}
|
||||
|
||||
// Hygine isn't really implemented for `macro_rules!` macros at the
|
||||
// moment. Accordingly, marking them as reachable is unwise. `macro` macros
|
||||
// have normal hygine, so we can treat them like other items without type
|
||||
// privacy and mark them reachable.
|
||||
DefKind::Macro(_) => {
|
||||
let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id);
|
||||
let item = self.tcx.hir().expect_item(hir_id);
|
||||
if let hir::ItemKind::Macro(MacroDef { macro_rules: false, .. }) = item.kind {
|
||||
if vis.is_accessible_from(module.to_def_id(), self.tcx) {
|
||||
self.update(def_id, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can't use a module name as the final segment of a path, except
|
||||
// in use statements. Since re-export checking doesn't consider
|
||||
// hygiene these don't need to be marked reachable. The contents of
|
||||
|
@ -644,6 +692,12 @@ impl Visitor<'tcx> for EmbargoVisitor<'tcx> {
|
|||
hir::ItemKind::Impl { .. } => {
|
||||
Option::<AccessLevel>::of_impl(item.def_id, self.tcx, &self.access_levels)
|
||||
}
|
||||
// Only exported `macro_rules!` items are public, but they always are.
|
||||
hir::ItemKind::Macro(MacroDef { macro_rules: true, .. }) => {
|
||||
let def_id = item.def_id.to_def_id();
|
||||
let is_macro_export = self.tcx.has_attr(def_id, sym::macro_export);
|
||||
if is_macro_export { Some(AccessLevel::Public) } else { None }
|
||||
}
|
||||
// Foreign modules inherit level from parents.
|
||||
hir::ItemKind::ForeignMod { .. } => self.prev_level,
|
||||
// Other `pub` items inherit levels from parents.
|
||||
|
@ -652,6 +706,7 @@ impl Visitor<'tcx> for EmbargoVisitor<'tcx> {
|
|||
| hir::ItemKind::ExternCrate(..)
|
||||
| hir::ItemKind::GlobalAsm(..)
|
||||
| hir::ItemKind::Fn(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::Mod(..)
|
||||
| hir::ItemKind::Static(..)
|
||||
| hir::ItemKind::Struct(..)
|
||||
|
@ -708,6 +763,9 @@ impl Visitor<'tcx> for EmbargoVisitor<'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
hir::ItemKind::Macro(ref macro_def) => {
|
||||
self.update_reachability_from_macro(item.def_id, macro_def);
|
||||
}
|
||||
hir::ItemKind::ForeignMod { items, .. } => {
|
||||
for foreign_item in items {
|
||||
if foreign_item.vis.node.is_pub() {
|
||||
|
@ -715,6 +773,7 @@ impl Visitor<'tcx> for EmbargoVisitor<'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
hir::ItemKind::OpaqueTy(..)
|
||||
| hir::ItemKind::Use(..)
|
||||
| hir::ItemKind::Static(..)
|
||||
|
@ -730,7 +789,7 @@ impl Visitor<'tcx> for EmbargoVisitor<'tcx> {
|
|||
// Mark all items in interfaces of reachable items as reachable.
|
||||
match item.kind {
|
||||
// The interface is empty.
|
||||
hir::ItemKind::ExternCrate(..) => {}
|
||||
hir::ItemKind::Macro(..) | hir::ItemKind::ExternCrate(..) => {}
|
||||
// All nested items are checked by `visit_item`.
|
||||
hir::ItemKind::Mod(..) => {}
|
||||
// Re-exports are handled in `visit_mod`. However, in order to avoid looping over
|
||||
|
@ -885,45 +944,6 @@ impl Visitor<'tcx> for EmbargoVisitor<'tcx> {
|
|||
|
||||
intravisit::walk_mod(self, m, id);
|
||||
}
|
||||
|
||||
fn visit_macro_def(&mut self, md: &'tcx hir::MacroDef<'tcx>) {
|
||||
// Non-opaque macros cannot make other items more accessible than they already are.
|
||||
let attrs = self.tcx.hir().attrs(md.hir_id());
|
||||
if attr::find_transparency(&attrs, md.ast.macro_rules).0 != Transparency::Opaque {
|
||||
// `#[macro_export]`-ed `macro_rules!` are `Public` since they
|
||||
// ignore their containing path to always appear at the crate root.
|
||||
if md.ast.macro_rules {
|
||||
self.update(md.def_id, Some(AccessLevel::Public));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let macro_module_def_id =
|
||||
ty::DefIdTree::parent(self.tcx, md.def_id.to_def_id()).unwrap().expect_local();
|
||||
if self.tcx.hir().opt_def_kind(macro_module_def_id) != Some(DefKind::Mod) {
|
||||
// The macro's parent doesn't correspond to a `mod`, return early (#63164, #65252).
|
||||
return;
|
||||
}
|
||||
|
||||
let level = if md.vis.node.is_pub() { self.get(macro_module_def_id) } else { None };
|
||||
let new_level = self.update(md.def_id, level);
|
||||
if new_level.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we are starting from an externally visible module,
|
||||
// all the parents in the loop below are also guaranteed to be modules.
|
||||
let mut module_def_id = macro_module_def_id;
|
||||
loop {
|
||||
let changed_reachability =
|
||||
self.update_macro_reachable(module_def_id, macro_module_def_id);
|
||||
if changed_reachability || module_def_id == CRATE_DEF_ID {
|
||||
break;
|
||||
}
|
||||
module_def_id =
|
||||
ty::DefIdTree::parent(self.tcx, module_def_id.to_def_id()).unwrap().expect_local();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ReachEverythingInTheInterfaceVisitor<'_, 'tcx> {
|
||||
|
@ -1981,7 +2001,7 @@ impl<'tcx> Visitor<'tcx> for PrivateItemsInPublicInterfacesVisitor<'tcx> {
|
|||
// Checked in resolve.
|
||||
hir::ItemKind::Use(..) => {}
|
||||
// No subitems.
|
||||
hir::ItemKind::GlobalAsm(..) => {}
|
||||
hir::ItemKind::Macro(..) | hir::ItemKind::GlobalAsm(..) => {}
|
||||
// Subitems of these items have inherited publicity.
|
||||
hir::ItemKind::Const(..)
|
||||
| hir::ItemKind::Static(..)
|
||||
|
|
|
@ -740,6 +740,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
|
|||
|
||||
hir::ItemKind::ExternCrate(_)
|
||||
| hir::ItemKind::Use(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::Mod(..)
|
||||
| hir::ItemKind::ForeignMod { .. }
|
||||
| hir::ItemKind::GlobalAsm(..) => {
|
||||
|
|
|
@ -416,6 +416,14 @@ impl<'hir> Sig for hir::Item<'hir> {
|
|||
|
||||
Ok(sig)
|
||||
}
|
||||
hir::ItemKind::Macro(_) => {
|
||||
let mut text = "macro".to_owned();
|
||||
let name = self.ident.to_string();
|
||||
text.push_str(&name);
|
||||
text.push_str(&"! {}");
|
||||
|
||||
Ok(text_sig(text))
|
||||
}
|
||||
hir::ItemKind::Mod(ref _mod) => {
|
||||
let mut text = "mod ".to_owned();
|
||||
let name = self.ident.to_string();
|
||||
|
|
|
@ -746,6 +746,7 @@ fn convert_item(tcx: TyCtxt<'_>, item_id: hir::ItemId) {
|
|||
// These don't define types.
|
||||
hir::ItemKind::ExternCrate(_)
|
||||
| hir::ItemKind::Use(..)
|
||||
| hir::ItemKind::Macro(_)
|
||||
| hir::ItemKind::Mod(_)
|
||||
| hir::ItemKind::GlobalAsm(_) => {}
|
||||
hir::ItemKind::ForeignMod { items, .. } => {
|
||||
|
|
|
@ -427,6 +427,7 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: DefId) -> Ty<'_> {
|
|||
}
|
||||
ItemKind::Trait(..)
|
||||
| ItemKind::TraitAlias(..)
|
||||
| ItemKind::Macro(..)
|
||||
| ItemKind::Mod(..)
|
||||
| ItemKind::ForeignMod { .. }
|
||||
| ItemKind::GlobalAsm(..)
|
||||
|
|
|
@ -123,6 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
|
|||
hir::ItemKind::Const(..)
|
||||
| hir::ItemKind::Enum(..)
|
||||
| hir::ItemKind::Mod(..)
|
||||
| hir::ItemKind::Macro(..)
|
||||
| hir::ItemKind::Static(..)
|
||||
| hir::ItemKind::Struct(..)
|
||||
| hir::ItemKind::Trait(..)
|
||||
|
|
Loading…
Add table
Reference in a new issue