Auto merge of #87042 - petrochenkov:cleanquotspan, r=Aaron1011
Cleanup span quoting I finally got to reviewing https://github.com/rust-lang/rust/pull/84278. See the individual commit messages. r? `@Aaron1011`
This commit is contained in:
commit
4581c4ef6f
23 changed files with 89 additions and 203 deletions
|
@ -19,7 +19,6 @@ use crate::deriving::*;
|
||||||
|
|
||||||
use rustc_expand::base::{MacroExpanderFn, ResolverExpand, SyntaxExtensionKind};
|
use rustc_expand::base::{MacroExpanderFn, ResolverExpand, SyntaxExtensionKind};
|
||||||
use rustc_expand::proc_macro::BangProcMacro;
|
use rustc_expand::proc_macro::BangProcMacro;
|
||||||
use rustc_span::def_id::LOCAL_CRATE;
|
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
|
|
||||||
mod asm;
|
mod asm;
|
||||||
|
@ -113,8 +112,5 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
|
let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
|
||||||
register(
|
register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })));
|
||||||
sym::quote,
|
|
||||||
SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client, krate: LOCAL_CRATE })),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,9 +309,7 @@ pub trait Emitter {
|
||||||
// are some which do actually involve macros.
|
// are some which do actually involve macros.
|
||||||
ExpnKind::Inlined | ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
|
ExpnKind::Inlined | ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
|
||||||
|
|
||||||
ExpnKind::Macro { kind: macro_kind, name, proc_macro: _ } => {
|
ExpnKind::Macro(macro_kind, name) => Some((macro_kind, name)),
|
||||||
Some((macro_kind, name))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -372,19 +370,10 @@ pub trait Emitter {
|
||||||
new_labels
|
new_labels
|
||||||
.push((trace.call_site, "in the inlined copy of this code".to_string()));
|
.push((trace.call_site, "in the inlined copy of this code".to_string()));
|
||||||
} else if always_backtrace {
|
} else if always_backtrace {
|
||||||
let proc_macro = if let ExpnKind::Macro { kind: _, name: _, proc_macro: true } =
|
|
||||||
trace.kind
|
|
||||||
{
|
|
||||||
"procedural macro "
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
new_labels.push((
|
new_labels.push((
|
||||||
trace.def_site,
|
trace.def_site,
|
||||||
format!(
|
format!(
|
||||||
"in this expansion of {}`{}`{}",
|
"in this expansion of `{}`{}",
|
||||||
proc_macro,
|
|
||||||
trace.kind.descr(),
|
trace.kind.descr(),
|
||||||
if macro_backtrace.len() > 1 {
|
if macro_backtrace.len() > 1 {
|
||||||
// if macro_backtrace.len() == 1 it'll be
|
// if macro_backtrace.len() == 1 it'll be
|
||||||
|
@ -410,11 +399,7 @@ pub trait Emitter {
|
||||||
// and it needs an "in this macro invocation" label to match that.
|
// and it needs an "in this macro invocation" label to match that.
|
||||||
let redundant_span = trace.call_site.contains(sp);
|
let redundant_span = trace.call_site.contains(sp);
|
||||||
|
|
||||||
if !redundant_span
|
if !redundant_span && matches!(trace.kind, ExpnKind::Macro(MacroKind::Bang, _))
|
||||||
&& matches!(
|
|
||||||
trace.kind,
|
|
||||||
ExpnKind::Macro { kind: MacroKind::Bang, name: _, proc_macro: _ }
|
|
||||||
)
|
|
||||||
|| always_backtrace
|
|| always_backtrace
|
||||||
{
|
{
|
||||||
new_labels.push((
|
new_labels.push((
|
||||||
|
|
|
@ -811,16 +811,8 @@ impl SyntaxExtension {
|
||||||
macro_def_id: Option<DefId>,
|
macro_def_id: Option<DefId>,
|
||||||
parent_module: Option<DefId>,
|
parent_module: Option<DefId>,
|
||||||
) -> ExpnData {
|
) -> ExpnData {
|
||||||
use SyntaxExtensionKind::*;
|
|
||||||
let proc_macro = match self.kind {
|
|
||||||
// User-defined proc macro
|
|
||||||
Bang(..) | Attr(..) | Derive(..) => true,
|
|
||||||
// Consider everthing else to be not a proc
|
|
||||||
// macro for diagnostic purposes
|
|
||||||
LegacyBang(..) | LegacyAttr(..) | NonMacroAttr { .. } | LegacyDerive(..) => false,
|
|
||||||
};
|
|
||||||
ExpnData::new(
|
ExpnData::new(
|
||||||
ExpnKind::Macro { kind: self.macro_kind(), name: descr, proc_macro },
|
ExpnKind::Macro(self.macro_kind(), descr),
|
||||||
parent,
|
parent,
|
||||||
call_site,
|
call_site,
|
||||||
self.span,
|
self.span,
|
||||||
|
|
|
@ -9,14 +9,12 @@ use rustc_data_structures::sync::Lrc;
|
||||||
use rustc_errors::ErrorReported;
|
use rustc_errors::ErrorReported;
|
||||||
use rustc_parse::nt_to_tokenstream;
|
use rustc_parse::nt_to_tokenstream;
|
||||||
use rustc_parse::parser::ForceCollect;
|
use rustc_parse::parser::ForceCollect;
|
||||||
use rustc_span::def_id::CrateNum;
|
|
||||||
use rustc_span::{Span, DUMMY_SP};
|
use rustc_span::{Span, DUMMY_SP};
|
||||||
|
|
||||||
const EXEC_STRATEGY: pm::bridge::server::SameThread = pm::bridge::server::SameThread;
|
const EXEC_STRATEGY: pm::bridge::server::SameThread = pm::bridge::server::SameThread;
|
||||||
|
|
||||||
pub struct BangProcMacro {
|
pub struct BangProcMacro {
|
||||||
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
|
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
|
||||||
pub krate: CrateNum,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl base::ProcMacro for BangProcMacro {
|
impl base::ProcMacro for BangProcMacro {
|
||||||
|
@ -26,7 +24,7 @@ impl base::ProcMacro for BangProcMacro {
|
||||||
span: Span,
|
span: Span,
|
||||||
input: TokenStream,
|
input: TokenStream,
|
||||||
) -> Result<TokenStream, ErrorReported> {
|
) -> Result<TokenStream, ErrorReported> {
|
||||||
let server = proc_macro_server::Rustc::new(ecx, self.krate);
|
let server = proc_macro_server::Rustc::new(ecx);
|
||||||
self.client.run(&EXEC_STRATEGY, server, input, ecx.ecfg.proc_macro_backtrace).map_err(|e| {
|
self.client.run(&EXEC_STRATEGY, server, input, ecx.ecfg.proc_macro_backtrace).map_err(|e| {
|
||||||
let mut err = ecx.struct_span_err(span, "proc macro panicked");
|
let mut err = ecx.struct_span_err(span, "proc macro panicked");
|
||||||
if let Some(s) = e.as_str() {
|
if let Some(s) = e.as_str() {
|
||||||
|
@ -40,7 +38,6 @@ impl base::ProcMacro for BangProcMacro {
|
||||||
|
|
||||||
pub struct AttrProcMacro {
|
pub struct AttrProcMacro {
|
||||||
pub client: pm::bridge::client::Client<fn(pm::TokenStream, pm::TokenStream) -> pm::TokenStream>,
|
pub client: pm::bridge::client::Client<fn(pm::TokenStream, pm::TokenStream) -> pm::TokenStream>,
|
||||||
pub krate: CrateNum,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl base::AttrProcMacro for AttrProcMacro {
|
impl base::AttrProcMacro for AttrProcMacro {
|
||||||
|
@ -51,7 +48,7 @@ impl base::AttrProcMacro for AttrProcMacro {
|
||||||
annotation: TokenStream,
|
annotation: TokenStream,
|
||||||
annotated: TokenStream,
|
annotated: TokenStream,
|
||||||
) -> Result<TokenStream, ErrorReported> {
|
) -> Result<TokenStream, ErrorReported> {
|
||||||
let server = proc_macro_server::Rustc::new(ecx, self.krate);
|
let server = proc_macro_server::Rustc::new(ecx);
|
||||||
self.client
|
self.client
|
||||||
.run(&EXEC_STRATEGY, server, annotation, annotated, ecx.ecfg.proc_macro_backtrace)
|
.run(&EXEC_STRATEGY, server, annotation, annotated, ecx.ecfg.proc_macro_backtrace)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -67,7 +64,6 @@ impl base::AttrProcMacro for AttrProcMacro {
|
||||||
|
|
||||||
pub struct ProcMacroDerive {
|
pub struct ProcMacroDerive {
|
||||||
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
|
pub client: pm::bridge::client::Client<fn(pm::TokenStream) -> pm::TokenStream>,
|
||||||
pub krate: CrateNum,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MultiItemModifier for ProcMacroDerive {
|
impl MultiItemModifier for ProcMacroDerive {
|
||||||
|
@ -101,7 +97,7 @@ impl MultiItemModifier for ProcMacroDerive {
|
||||||
nt_to_tokenstream(&item, &ecx.sess.parse_sess, CanSynthesizeMissingTokens::No)
|
nt_to_tokenstream(&item, &ecx.sess.parse_sess, CanSynthesizeMissingTokens::No)
|
||||||
};
|
};
|
||||||
|
|
||||||
let server = proc_macro_server::Rustc::new(ecx, self.krate);
|
let server = proc_macro_server::Rustc::new(ecx);
|
||||||
let stream =
|
let stream =
|
||||||
match self.client.run(&EXEC_STRATEGY, server, input, ecx.ecfg.proc_macro_backtrace) {
|
match self.client.run(&EXEC_STRATEGY, server, input, ecx.ecfg.proc_macro_backtrace) {
|
||||||
Ok(stream) => stream,
|
Ok(stream) => stream,
|
||||||
|
|
|
@ -14,7 +14,6 @@ use rustc_parse::lexer::nfc_normalize;
|
||||||
use rustc_parse::{nt_to_tokenstream, parse_stream_from_source_str};
|
use rustc_parse::{nt_to_tokenstream, parse_stream_from_source_str};
|
||||||
use rustc_session::parse::ParseSess;
|
use rustc_session::parse::ParseSess;
|
||||||
use rustc_span::def_id::CrateNum;
|
use rustc_span::def_id::CrateNum;
|
||||||
use rustc_span::hygiene::ExpnId;
|
|
||||||
use rustc_span::hygiene::ExpnKind;
|
use rustc_span::hygiene::ExpnKind;
|
||||||
use rustc_span::symbol::{self, kw, sym, Symbol};
|
use rustc_span::symbol::{self, kw, sym, Symbol};
|
||||||
use rustc_span::{BytePos, FileName, MultiSpan, Pos, RealFileName, SourceFile, Span};
|
use rustc_span::{BytePos, FileName, MultiSpan, Pos, RealFileName, SourceFile, Span};
|
||||||
|
@ -363,26 +362,20 @@ pub(crate) struct Rustc<'a> {
|
||||||
mixed_site: Span,
|
mixed_site: Span,
|
||||||
span_debug: bool,
|
span_debug: bool,
|
||||||
krate: CrateNum,
|
krate: CrateNum,
|
||||||
expn_id: ExpnId,
|
|
||||||
rebased_spans: FxHashMap<usize, Span>,
|
rebased_spans: FxHashMap<usize, Span>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Rustc<'a> {
|
impl<'a> Rustc<'a> {
|
||||||
pub fn new(cx: &'a ExtCtxt<'_>, krate: CrateNum) -> Self {
|
pub fn new(cx: &'a ExtCtxt<'_>) -> Self {
|
||||||
let expn_data = cx.current_expansion.id.expn_data();
|
let expn_data = cx.current_expansion.id.expn_data();
|
||||||
let def_site = cx.with_def_site_ctxt(expn_data.def_site);
|
|
||||||
let call_site = cx.with_call_site_ctxt(expn_data.call_site);
|
|
||||||
let mixed_site = cx.with_mixed_site_ctxt(expn_data.call_site);
|
|
||||||
let sess = cx.parse_sess();
|
|
||||||
Rustc {
|
Rustc {
|
||||||
resolver: cx.resolver,
|
resolver: cx.resolver,
|
||||||
sess,
|
sess: cx.parse_sess(),
|
||||||
def_site,
|
def_site: cx.with_def_site_ctxt(expn_data.def_site),
|
||||||
call_site,
|
call_site: cx.with_call_site_ctxt(expn_data.call_site),
|
||||||
mixed_site,
|
mixed_site: cx.with_mixed_site_ctxt(expn_data.call_site),
|
||||||
span_debug: cx.ecfg.span_debug,
|
span_debug: cx.ecfg.span_debug,
|
||||||
krate,
|
krate: expn_data.macro_def_id.unwrap().krate,
|
||||||
expn_id: cx.current_expansion.id,
|
|
||||||
rebased_spans: FxHashMap::default(),
|
rebased_spans: FxHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,25 +775,15 @@ impl server::Span for Rustc<'_> {
|
||||||
/// span from the metadata of `my_proc_macro` (which we have access to,
|
/// span from the metadata of `my_proc_macro` (which we have access to,
|
||||||
/// since we've loaded `my_proc_macro` from disk in order to execute it).
|
/// since we've loaded `my_proc_macro` from disk in order to execute it).
|
||||||
/// In this way, we have obtained a span pointing into `my_proc_macro`
|
/// In this way, we have obtained a span pointing into `my_proc_macro`
|
||||||
fn save_span(&mut self, mut span: Self::Span) -> usize {
|
fn save_span(&mut self, span: Self::Span) -> usize {
|
||||||
// Throw away the `SyntaxContext`, since we currently
|
|
||||||
// skip serializing `SyntaxContext`s for proc-macro crates
|
|
||||||
span = span.with_ctxt(rustc_span::SyntaxContext::root());
|
|
||||||
self.sess.save_proc_macro_span(span)
|
self.sess.save_proc_macro_span(span)
|
||||||
}
|
}
|
||||||
fn recover_proc_macro_span(&mut self, id: usize) -> Self::Span {
|
fn recover_proc_macro_span(&mut self, id: usize) -> Self::Span {
|
||||||
let resolver = self.resolver;
|
let (resolver, krate, def_site) = (self.resolver, self.krate, self.def_site);
|
||||||
let krate = self.krate;
|
|
||||||
let expn_id = self.expn_id;
|
|
||||||
*self.rebased_spans.entry(id).or_insert_with(|| {
|
*self.rebased_spans.entry(id).or_insert_with(|| {
|
||||||
let raw_span = resolver.get_proc_macro_quoted_span(krate, id);
|
// FIXME: `SyntaxContext` for spans from proc macro crates is lost during encoding,
|
||||||
// Ignore the deserialized `SyntaxContext` entirely.
|
// replace it with a def-site context until we are encoding it properly.
|
||||||
// FIXME: Preserve the macro backtrace from the serialized span
|
resolver.get_proc_macro_quoted_span(krate, id).with_ctxt(def_site.ctxt())
|
||||||
// For example, if a proc-macro crate has code like
|
|
||||||
// `macro_one!() -> macro_two!() -> quote!()`, we might
|
|
||||||
// want to 'concatenate' this backtrace with the backtrace from
|
|
||||||
// our current call site.
|
|
||||||
raw_span.with_def_site_ctxt(expn_id)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -812,7 +795,7 @@ fn ident_name_compatibility_hack(
|
||||||
rustc: &mut Rustc<'_>,
|
rustc: &mut Rustc<'_>,
|
||||||
) -> Option<(rustc_span::symbol::Ident, bool)> {
|
) -> Option<(rustc_span::symbol::Ident, bool)> {
|
||||||
if let NtIdent(ident, is_raw) = nt {
|
if let NtIdent(ident, is_raw) = nt {
|
||||||
if let ExpnKind::Macro { name: macro_name, .. } = orig_span.ctxt().outer_expn_data().kind {
|
if let ExpnKind::Macro(_, macro_name) = orig_span.ctxt().outer_expn_data().kind {
|
||||||
let source_map = rustc.sess.source_map();
|
let source_map = rustc.sess.source_map();
|
||||||
let filename = source_map.span_to_filename(orig_span);
|
let filename = source_map.span_to_filename(orig_span);
|
||||||
if let FileName::Real(RealFileName::LocalPath(path)) = filename {
|
if let FileName::Real(RealFileName::LocalPath(path)) = filename {
|
||||||
|
|
|
@ -248,21 +248,10 @@ impl EarlyLintPass for LintPassImpl {
|
||||||
if last.ident.name == sym::LintPass {
|
if last.ident.name == sym::LintPass {
|
||||||
let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
|
let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
|
||||||
let call_site = expn_data.call_site;
|
let call_site = expn_data.call_site;
|
||||||
if !matches!(
|
if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
|
||||||
expn_data.kind,
|
&& call_site.ctxt().outer_expn_data().kind
|
||||||
ExpnKind::Macro {
|
!= ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
|
||||||
kind: MacroKind::Bang,
|
{
|
||||||
name: sym::impl_lint_pass,
|
|
||||||
proc_macro: _
|
|
||||||
}
|
|
||||||
) && !matches!(
|
|
||||||
call_site.ctxt().outer_expn_data().kind,
|
|
||||||
ExpnKind::Macro {
|
|
||||||
kind: MacroKind::Bang,
|
|
||||||
name: sym::declare_lint_pass,
|
|
||||||
proc_macro: _
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
cx.struct_span_lint(
|
cx.struct_span_lint(
|
||||||
LINT_PASS_IMPL_WITHOUT_MACRO,
|
LINT_PASS_IMPL_WITHOUT_MACRO,
|
||||||
lint_pass.path.span,
|
lint_pass.path.span,
|
||||||
|
|
|
@ -256,10 +256,6 @@ fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
let macro_symbol =
|
let macro_symbol =
|
||||||
if let hygiene::ExpnKind::Macro { kind: _, name: symbol, proc_macro: _ } = expn.kind {
|
if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic };
|
||||||
symbol
|
|
||||||
} else {
|
|
||||||
Symbol::intern("panic")
|
|
||||||
};
|
|
||||||
(expn.call_site, panic_macro, macro_symbol.as_str())
|
(expn.call_site, panic_macro, macro_symbol.as_str())
|
||||||
}
|
}
|
||||||
|
|
|
@ -725,37 +725,30 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
|
||||||
.decode((self, sess))
|
.decode((self, sess))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_proc_macro(&self, def_id: DefId, sess: &Session) -> SyntaxExtension {
|
fn load_proc_macro(&self, id: DefIndex, sess: &Session) -> SyntaxExtension {
|
||||||
let (name, kind, helper_attrs) = match *self.raw_proc_macro(def_id.index) {
|
let (name, kind, helper_attrs) = match *self.raw_proc_macro(id) {
|
||||||
ProcMacro::CustomDerive { trait_name, attributes, client } => {
|
ProcMacro::CustomDerive { trait_name, attributes, client } => {
|
||||||
let helper_attrs =
|
let helper_attrs =
|
||||||
attributes.iter().cloned().map(Symbol::intern).collect::<Vec<_>>();
|
attributes.iter().cloned().map(Symbol::intern).collect::<Vec<_>>();
|
||||||
(
|
(
|
||||||
trait_name,
|
trait_name,
|
||||||
SyntaxExtensionKind::Derive(Box::new(ProcMacroDerive {
|
SyntaxExtensionKind::Derive(Box::new(ProcMacroDerive { client })),
|
||||||
client,
|
|
||||||
krate: def_id.krate,
|
|
||||||
})),
|
|
||||||
helper_attrs,
|
helper_attrs,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ProcMacro::Attr { name, client } => (
|
ProcMacro::Attr { name, client } => {
|
||||||
name,
|
(name, SyntaxExtensionKind::Attr(Box::new(AttrProcMacro { client })), Vec::new())
|
||||||
SyntaxExtensionKind::Attr(Box::new(AttrProcMacro { client, krate: def_id.krate })),
|
}
|
||||||
Vec::new(),
|
ProcMacro::Bang { name, client } => {
|
||||||
),
|
(name, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })), Vec::new())
|
||||||
ProcMacro::Bang { name, client } => (
|
}
|
||||||
name,
|
|
||||||
SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client, krate: def_id.krate })),
|
|
||||||
Vec::new(),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let attrs: Vec<_> = self.get_item_attrs(def_id.index, sess).collect();
|
let attrs: Vec<_> = self.get_item_attrs(id, sess).collect();
|
||||||
SyntaxExtension::new(
|
SyntaxExtension::new(
|
||||||
sess,
|
sess,
|
||||||
kind,
|
kind,
|
||||||
self.get_span(def_id.index, sess),
|
self.get_span(id, sess),
|
||||||
helper_attrs,
|
helper_attrs,
|
||||||
self.root.edition,
|
self.root.edition,
|
||||||
Symbol::intern(name),
|
Symbol::intern(name),
|
||||||
|
|
|
@ -411,7 +411,7 @@ impl CStore {
|
||||||
|
|
||||||
let data = self.get_crate_data(id.krate);
|
let data = self.get_crate_data(id.krate);
|
||||||
if data.root.is_proc_macro_crate() {
|
if data.root.is_proc_macro_crate() {
|
||||||
return LoadedMacro::ProcMacro(data.load_proc_macro(id, sess));
|
return LoadedMacro::ProcMacro(data.load_proc_macro(id.index, sess));
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = data.get_span(id.index, sess);
|
let span = data.get_span(id.index, sess);
|
||||||
|
|
|
@ -387,7 +387,7 @@ pub fn in_external_macro(sess: &Session, span: Span) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
|
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
|
||||||
ExpnKind::Macro { kind: MacroKind::Bang, name: _, proc_macro: _ } => {
|
ExpnKind::Macro(MacroKind::Bang, _) => {
|
||||||
// Dummy span for the `def_site` means it's an external macro.
|
// Dummy span for the `def_site` means it's an external macro.
|
||||||
expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site)
|
expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site)
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,11 +184,8 @@ impl CoverageSpan {
|
||||||
self.current_macro_or_none
|
self.current_macro_or_none
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.get_or_insert_with(|| {
|
.get_or_insert_with(|| {
|
||||||
if let ExpnKind::Macro {
|
if let ExpnKind::Macro(MacroKind::Bang, current_macro) =
|
||||||
kind: MacroKind::Bang,
|
self.expn_span.ctxt().outer_expn_data().kind
|
||||||
name: current_macro,
|
|
||||||
proc_macro: _,
|
|
||||||
} = self.expn_span.ctxt().outer_expn_data().kind
|
|
||||||
{
|
{
|
||||||
return Some(current_macro);
|
return Some(current_macro);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1775,11 +1775,9 @@ impl<'a> Resolver<'a> {
|
||||||
let expn_data = expn_id.expn_data();
|
let expn_data = expn_id.expn_data();
|
||||||
match expn_data.kind {
|
match expn_data.kind {
|
||||||
ExpnKind::Root
|
ExpnKind::Root
|
||||||
| ExpnKind::Macro {
|
| ExpnKind::Macro(MacroKind::Bang | MacroKind::Derive, _) => {
|
||||||
kind: MacroKind::Bang | MacroKind::Derive,
|
Scope::DeriveHelpersCompat
|
||||||
name: _,
|
}
|
||||||
proc_macro: _,
|
|
||||||
} => Scope::DeriveHelpersCompat,
|
|
||||||
_ => Scope::DeriveHelpers(expn_data.parent),
|
_ => Scope::DeriveHelpers(expn_data.parent),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -319,11 +319,7 @@ impl<'a> ResolverExpand for Resolver<'a> {
|
||||||
let expn_data = expn_id.expn_data();
|
let expn_data = expn_id.expn_data();
|
||||||
match expn_data.kind {
|
match expn_data.kind {
|
||||||
ExpnKind::Root
|
ExpnKind::Root
|
||||||
| ExpnKind::Macro {
|
| ExpnKind::Macro(MacroKind::Bang | MacroKind::Derive, _) => {
|
||||||
name: _,
|
|
||||||
kind: MacroKind::Bang | MacroKind::Derive,
|
|
||||||
proc_macro: _,
|
|
||||||
} => {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => expn_id = expn_data.parent,
|
_ => expn_id = expn_data.parent,
|
||||||
|
|
|
@ -788,7 +788,7 @@ impl<'tcx> SaveContext<'tcx> {
|
||||||
let callee = span.source_callee()?;
|
let callee = span.source_callee()?;
|
||||||
|
|
||||||
let mac_name = match callee.kind {
|
let mac_name = match callee.kind {
|
||||||
ExpnKind::Macro { kind, name, proc_macro: _ } => match kind {
|
ExpnKind::Macro(kind, name) => match kind {
|
||||||
MacroKind::Bang => name,
|
MacroKind::Bang => name,
|
||||||
|
|
||||||
// Ignore attribute macros, their spans are usually mangled
|
// Ignore attribute macros, their spans are usually mangled
|
||||||
|
|
|
@ -144,10 +144,7 @@ impl ExpnId {
|
||||||
let expn_data = self.expn_data();
|
let expn_data = self.expn_data();
|
||||||
// Stop going up the backtrace once include! is encountered
|
// Stop going up the backtrace once include! is encountered
|
||||||
if expn_data.is_root()
|
if expn_data.is_root()
|
||||||
|| matches!(
|
|| expn_data.kind == ExpnKind::Macro(MacroKind::Bang, sym::include)
|
||||||
expn_data.kind,
|
|
||||||
ExpnKind::Macro { kind: MacroKind::Bang, name: sym::include, proc_macro: _ }
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -712,6 +709,31 @@ pub struct ExpnData {
|
||||||
/// call_site span would have its own ExpnData, with the call_site
|
/// call_site span would have its own ExpnData, with the call_site
|
||||||
/// pointing to the `foo!` invocation.
|
/// pointing to the `foo!` invocation.
|
||||||
pub call_site: Span,
|
pub call_site: Span,
|
||||||
|
/// The crate that originally created this `ExpnData`. During
|
||||||
|
/// metadata serialization, we only encode `ExpnData`s that were
|
||||||
|
/// created locally - when our serialized metadata is decoded,
|
||||||
|
/// foreign `ExpnId`s will have their `ExpnData` looked up
|
||||||
|
/// from the crate specified by `Crate
|
||||||
|
krate: CrateNum,
|
||||||
|
/// The raw that this `ExpnData` had in its original crate.
|
||||||
|
/// An `ExpnData` can be created before being assigned an `ExpnId`,
|
||||||
|
/// so this might be `None` until `set_expn_data` is called
|
||||||
|
// This is used only for serialization/deserialization purposes:
|
||||||
|
// two `ExpnData`s that differ only in their `orig_id` should
|
||||||
|
// be considered equivalent.
|
||||||
|
#[stable_hasher(ignore)]
|
||||||
|
orig_id: Option<u32>,
|
||||||
|
/// Used to force two `ExpnData`s to have different `Fingerprint`s.
|
||||||
|
/// Due to macro expansion, it's possible to end up with two `ExpnId`s
|
||||||
|
/// that have identical `ExpnData`s. This violates the contract of `HashStable`
|
||||||
|
/// - the two `ExpnId`s are not equal, but their `Fingerprint`s are equal
|
||||||
|
/// (since the numerical `ExpnId` value is not considered by the `HashStable`
|
||||||
|
/// implementation).
|
||||||
|
///
|
||||||
|
/// The `disambiguator` field is set by `update_disambiguator` when two distinct
|
||||||
|
/// `ExpnId`s would end up with the same `Fingerprint`. Since `ExpnData` includes
|
||||||
|
/// a `krate` field, this value only needs to be unique within a single crate.
|
||||||
|
disambiguator: u32,
|
||||||
|
|
||||||
// --- The part specific to the macro/desugaring definition.
|
// --- The part specific to the macro/desugaring definition.
|
||||||
// --- It may be reasonable to share this part between expansions with the same definition,
|
// --- It may be reasonable to share this part between expansions with the same definition,
|
||||||
|
@ -737,32 +759,6 @@ pub struct ExpnData {
|
||||||
pub macro_def_id: Option<DefId>,
|
pub macro_def_id: Option<DefId>,
|
||||||
/// The normal module (`mod`) in which the expanded macro was defined.
|
/// The normal module (`mod`) in which the expanded macro was defined.
|
||||||
pub parent_module: Option<DefId>,
|
pub parent_module: Option<DefId>,
|
||||||
/// The crate that originally created this `ExpnData`. During
|
|
||||||
/// metadata serialization, we only encode `ExpnData`s that were
|
|
||||||
/// created locally - when our serialized metadata is decoded,
|
|
||||||
/// foreign `ExpnId`s will have their `ExpnData` looked up
|
|
||||||
/// from the crate specified by `Crate
|
|
||||||
krate: CrateNum,
|
|
||||||
/// The raw that this `ExpnData` had in its original crate.
|
|
||||||
/// An `ExpnData` can be created before being assigned an `ExpnId`,
|
|
||||||
/// so this might be `None` until `set_expn_data` is called
|
|
||||||
// This is used only for serialization/deserialization purposes:
|
|
||||||
// two `ExpnData`s that differ only in their `orig_id` should
|
|
||||||
// be considered equivalent.
|
|
||||||
#[stable_hasher(ignore)]
|
|
||||||
orig_id: Option<u32>,
|
|
||||||
|
|
||||||
/// Used to force two `ExpnData`s to have different `Fingerprint`s.
|
|
||||||
/// Due to macro expansion, it's possible to end up with two `ExpnId`s
|
|
||||||
/// that have identical `ExpnData`s. This violates the contract of `HashStable`
|
|
||||||
/// - the two `ExpnId`s are not equal, but their `Fingerprint`s are equal
|
|
||||||
/// (since the numerical `ExpnId` value is not considered by the `HashStable`
|
|
||||||
/// implementation).
|
|
||||||
///
|
|
||||||
/// The `disambiguator` field is set by `update_disambiguator` when two distinct
|
|
||||||
/// `ExpnId`s would end up with the same `Fingerprint`. Since `ExpnData` includes
|
|
||||||
/// a `krate` field, this value only needs to be unique within a single crate.
|
|
||||||
disambiguator: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These would require special handling of `orig_id`.
|
// These would require special handling of `orig_id`.
|
||||||
|
@ -850,13 +846,7 @@ pub enum ExpnKind {
|
||||||
/// No expansion, aka root expansion. Only `ExpnId::root()` has this kind.
|
/// No expansion, aka root expansion. Only `ExpnId::root()` has this kind.
|
||||||
Root,
|
Root,
|
||||||
/// Expansion produced by a macro.
|
/// Expansion produced by a macro.
|
||||||
Macro {
|
Macro(MacroKind, Symbol),
|
||||||
kind: MacroKind,
|
|
||||||
name: Symbol,
|
|
||||||
/// If `true`, this macro is a procedural macro. This
|
|
||||||
/// flag is only used for diagnostic purposes
|
|
||||||
proc_macro: bool,
|
|
||||||
},
|
|
||||||
/// Transform done by the compiler on the AST.
|
/// Transform done by the compiler on the AST.
|
||||||
AstPass(AstPass),
|
AstPass(AstPass),
|
||||||
/// Desugaring done by the compiler during HIR lowering.
|
/// Desugaring done by the compiler during HIR lowering.
|
||||||
|
@ -869,7 +859,7 @@ impl ExpnKind {
|
||||||
pub fn descr(&self) -> String {
|
pub fn descr(&self) -> String {
|
||||||
match *self {
|
match *self {
|
||||||
ExpnKind::Root => kw::PathRoot.to_string(),
|
ExpnKind::Root => kw::PathRoot.to_string(),
|
||||||
ExpnKind::Macro { kind, name, proc_macro: _ } => match kind {
|
ExpnKind::Macro(macro_kind, name) => match macro_kind {
|
||||||
MacroKind::Bang => format!("{}!", name),
|
MacroKind::Bang => format!("{}!", name),
|
||||||
MacroKind::Attr => format!("#[{}]", name),
|
MacroKind::Attr => format!("#[{}]", name),
|
||||||
MacroKind::Derive => format!("#[derive({})]", name),
|
MacroKind::Derive => format!("#[derive({})]", name),
|
||||||
|
|
|
@ -520,10 +520,7 @@ impl Span {
|
||||||
|
|
||||||
/// Returns `true` if `span` originates in a derive-macro's expansion.
|
/// Returns `true` if `span` originates in a derive-macro's expansion.
|
||||||
pub fn in_derive_expansion(self) -> bool {
|
pub fn in_derive_expansion(self) -> bool {
|
||||||
matches!(
|
matches!(self.ctxt().outer_expn_data().kind, ExpnKind::Macro(MacroKind::Derive, _))
|
||||||
self.ctxt().outer_expn_data().kind,
|
|
||||||
ExpnKind::Macro { kind: MacroKind::Derive, name: _, proc_macro: _ }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -20,7 +20,7 @@ fn y /* 0#0 */() { }
|
||||||
/*
|
/*
|
||||||
Expansions:
|
Expansions:
|
||||||
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
||||||
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "foo", proc_macro: false }
|
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "foo")
|
||||||
|
|
||||||
SyntaxContexts:
|
SyntaxContexts:
|
||||||
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
||||||
|
|
|
@ -45,10 +45,10 @@ fn main /* 0#0 */() { ; }
|
||||||
Expansions:
|
Expansions:
|
||||||
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
||||||
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||||
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "produce_it", proc_macro: false }
|
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "produce_it")
|
||||||
3: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
3: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||||
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "meta_macro::print_def_site", proc_macro: true }
|
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #0, kind: Macro(Bang, "meta_macro::print_def_site")
|
||||||
5: parent: ExpnId(4), call_site_ctxt: #5, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "$crate::dummy", proc_macro: true }
|
5: parent: ExpnId(4), call_site_ctxt: #5, def_site_ctxt: #0, kind: Macro(Bang, "$crate::dummy")
|
||||||
|
|
||||||
SyntaxContexts:
|
SyntaxContexts:
|
||||||
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
||||||
|
|
|
@ -69,10 +69,10 @@ fn main /* 0#0 */() { }
|
||||||
Expansions:
|
Expansions:
|
||||||
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
0: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Root
|
||||||
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
1: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||||
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "outer", proc_macro: false }
|
2: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: Macro(Bang, "outer")
|
||||||
3: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
3: parent: ExpnId(0), call_site_ctxt: #0, def_site_ctxt: #0, kind: AstPass(StdImports)
|
||||||
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #4, kind: Macro { kind: Bang, name: "inner", proc_macro: false }
|
4: parent: ExpnId(2), call_site_ctxt: #4, def_site_ctxt: #4, kind: Macro(Bang, "inner")
|
||||||
5: parent: ExpnId(4), call_site_ctxt: #6, def_site_ctxt: #0, kind: Macro { kind: Bang, name: "print_bang", proc_macro: true }
|
5: parent: ExpnId(4), call_site_ctxt: #6, def_site_ctxt: #0, kind: Macro(Bang, "print_bang")
|
||||||
|
|
||||||
SyntaxContexts:
|
SyntaxContexts:
|
||||||
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
#0: parent: #0, outer_mark: (ExpnId(0), Opaque)
|
||||||
|
|
|
@ -2,7 +2,7 @@ error[E0412]: cannot find type `MissingType` in this scope
|
||||||
--> $DIR/auxiliary/span-from-proc-macro.rs:37:20
|
--> $DIR/auxiliary/span-from-proc-macro.rs:37:20
|
||||||
|
|
|
|
||||||
LL | pub fn error_from_attribute(_args: TokenStream, _input: TokenStream) -> TokenStream {
|
LL | pub fn error_from_attribute(_args: TokenStream, _input: TokenStream) -> TokenStream {
|
||||||
| ----------------------------------------------------------------------------------- in this expansion of procedural macro `#[error_from_attribute]`
|
| ----------------------------------------------------------------------------------- in this expansion of `#[error_from_attribute]`
|
||||||
...
|
...
|
||||||
LL | field: MissingType
|
LL | field: MissingType
|
||||||
| ^^^^^^^^^^^ not found in this scope
|
| ^^^^^^^^^^^ not found in this scope
|
||||||
|
@ -16,7 +16,7 @@ error[E0412]: cannot find type `OtherMissingType` in this scope
|
||||||
--> $DIR/auxiliary/span-from-proc-macro.rs:46:21
|
--> $DIR/auxiliary/span-from-proc-macro.rs:46:21
|
||||||
|
|
|
|
||||||
LL | pub fn error_from_derive(_input: TokenStream) -> TokenStream {
|
LL | pub fn error_from_derive(_input: TokenStream) -> TokenStream {
|
||||||
| ------------------------------------------------------------ in this expansion of procedural macro `#[derive(ErrorFromDerive)]`
|
| ------------------------------------------------------------ in this expansion of `#[derive(ErrorFromDerive)]`
|
||||||
...
|
...
|
||||||
LL | Variant(OtherMissingType)
|
LL | Variant(OtherMissingType)
|
||||||
| ^^^^^^^^^^^^^^^^ not found in this scope
|
| ^^^^^^^^^^^^^^^^ not found in this scope
|
||||||
|
@ -30,7 +30,7 @@ error[E0425]: cannot find value `my_ident` in this scope
|
||||||
--> $DIR/auxiliary/span-from-proc-macro.rs:29:9
|
--> $DIR/auxiliary/span-from-proc-macro.rs:29:9
|
||||||
|
|
|
|
||||||
LL | pub fn other_error_from_bang(_input: TokenStream) -> TokenStream {
|
LL | pub fn other_error_from_bang(_input: TokenStream) -> TokenStream {
|
||||||
| ---------------------------------------------------------------- in this expansion of procedural macro `other_error_from_bang!`
|
| ---------------------------------------------------------------- in this expansion of `other_error_from_bang!`
|
||||||
LL | custom_quote::custom_quote! {
|
LL | custom_quote::custom_quote! {
|
||||||
LL | my_ident
|
LL | my_ident
|
||||||
| ^^^^^^^^ not found in this scope
|
| ^^^^^^^^ not found in this scope
|
||||||
|
@ -49,7 +49,7 @@ LL | let bang_error: bool = 25;
|
||||||
| expected due to this
|
| expected due to this
|
||||||
...
|
...
|
||||||
LL | pub fn error_from_bang(_input: TokenStream) -> TokenStream {
|
LL | pub fn error_from_bang(_input: TokenStream) -> TokenStream {
|
||||||
| ---------------------------------------------------------- in this expansion of procedural macro `error_from_bang!`
|
| ---------------------------------------------------------- in this expansion of `error_from_bang!`
|
||||||
|
|
|
|
||||||
::: $DIR/span-from-proc-macro.rs:15:5
|
::: $DIR/span-from-proc-macro.rs:15:5
|
||||||
|
|
|
|
||||||
|
|
|
@ -662,14 +662,7 @@ fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
|
||||||
use rustc_span::hygiene::MacroKind;
|
use rustc_span::hygiene::MacroKind;
|
||||||
if expr.span.from_expansion() {
|
if expr.span.from_expansion() {
|
||||||
let data = expr.span.ctxt().outer_expn_data();
|
let data = expr.span.ctxt().outer_expn_data();
|
||||||
matches!(
|
matches!(data.kind, ExpnKind::Macro(MacroKind::Attr, _))
|
||||||
data.kind,
|
|
||||||
ExpnKind::Macro {
|
|
||||||
kind: MacroKind::Attr,
|
|
||||||
name: _,
|
|
||||||
proc_macro: _
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,7 @@ use super::UNIT_CMP;
|
||||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||||
if expr.span.from_expansion() {
|
if expr.span.from_expansion() {
|
||||||
if let Some(callee) = expr.span.source_callee() {
|
if let Some(callee) = expr.span.source_callee() {
|
||||||
if let ExpnKind::Macro {
|
if let ExpnKind::Macro(MacroKind::Bang, symbol) = callee.kind {
|
||||||
kind: MacroKind::Bang,
|
|
||||||
name: symbol,
|
|
||||||
proc_macro: _,
|
|
||||||
} = callee.kind
|
|
||||||
{
|
|
||||||
if let ExprKind::Binary(ref cmp, left, _) = expr.kind {
|
if let ExprKind::Binary(ref cmp, left, _) = expr.kind {
|
||||||
let op = cmp.node;
|
let op = cmp.node;
|
||||||
if op.is_comparison() && cx.typeck_results().expr_ty(left).is_unit() {
|
if op.is_comparison() && cx.typeck_results().expr_ty(left).is_unit() {
|
||||||
|
|
|
@ -953,12 +953,7 @@ pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
|
||||||
let data = span.ctxt().outer_expn_data();
|
let data = span.ctxt().outer_expn_data();
|
||||||
let new_span = data.call_site;
|
let new_span = data.call_site;
|
||||||
|
|
||||||
if let ExpnKind::Macro {
|
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
|
||||||
kind: MacroKind::Bang,
|
|
||||||
name: mac_name,
|
|
||||||
proc_macro: _,
|
|
||||||
} = data.kind
|
|
||||||
{
|
|
||||||
if mac_name.as_str() == name {
|
if mac_name.as_str() == name {
|
||||||
return Some(new_span);
|
return Some(new_span);
|
||||||
}
|
}
|
||||||
|
@ -986,12 +981,7 @@ pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
|
||||||
let data = span.ctxt().outer_expn_data();
|
let data = span.ctxt().outer_expn_data();
|
||||||
let new_span = data.call_site;
|
let new_span = data.call_site;
|
||||||
|
|
||||||
if let ExpnKind::Macro {
|
if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind {
|
||||||
kind: MacroKind::Bang,
|
|
||||||
name: mac_name,
|
|
||||||
proc_macro: _,
|
|
||||||
} = data.kind
|
|
||||||
{
|
|
||||||
if mac_name.as_str() == name {
|
if mac_name.as_str() == name {
|
||||||
return Some(new_span);
|
return Some(new_span);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue