Add ErrorGuaranteed
to ast::ExprKind::Err
This commit is contained in:
parent
a3fce72a27
commit
c440a5b814
37 changed files with 660 additions and 602 deletions
|
@ -532,7 +532,7 @@ impl MacResult for MacEager {
|
|||
/// after hitting errors.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DummyResult {
|
||||
is_error: bool,
|
||||
guar: Option<ErrorGuaranteed>,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
|
@ -541,20 +541,24 @@ impl DummyResult {
|
|||
///
|
||||
/// Use this as a return value after hitting any errors and
|
||||
/// calling `span_err`.
|
||||
pub fn any(span: Span) -> Box<dyn MacResult + 'static> {
|
||||
Box::new(DummyResult { is_error: true, span })
|
||||
pub fn any(span: Span, guar: ErrorGuaranteed) -> Box<dyn MacResult + 'static> {
|
||||
Box::new(DummyResult { guar: Some(guar), span })
|
||||
}
|
||||
|
||||
/// Same as `any`, but must be a valid fragment, not error.
|
||||
pub fn any_valid(span: Span) -> Box<dyn MacResult + 'static> {
|
||||
Box::new(DummyResult { is_error: false, span })
|
||||
Box::new(DummyResult { guar: None, span })
|
||||
}
|
||||
|
||||
/// A plain dummy expression.
|
||||
pub fn raw_expr(sp: Span, is_error: bool) -> P<ast::Expr> {
|
||||
pub fn raw_expr(sp: Span, guar: Option<ErrorGuaranteed>) -> P<ast::Expr> {
|
||||
P(ast::Expr {
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
kind: if is_error { ast::ExprKind::Err } else { ast::ExprKind::Tup(ThinVec::new()) },
|
||||
kind: if let Some(guar) = guar {
|
||||
ast::ExprKind::Err(guar)
|
||||
} else {
|
||||
ast::ExprKind::Tup(ThinVec::new())
|
||||
},
|
||||
span: sp,
|
||||
attrs: ast::AttrVec::new(),
|
||||
tokens: None,
|
||||
|
@ -582,7 +586,7 @@ impl DummyResult {
|
|||
|
||||
impl MacResult for DummyResult {
|
||||
fn make_expr(self: Box<DummyResult>) -> Option<P<ast::Expr>> {
|
||||
Some(DummyResult::raw_expr(self.span, self.is_error))
|
||||
Some(DummyResult::raw_expr(self.span, self.guar))
|
||||
}
|
||||
|
||||
fn make_pat(self: Box<DummyResult>) -> Option<P<ast::Pat>> {
|
||||
|
@ -608,7 +612,7 @@ impl MacResult for DummyResult {
|
|||
fn make_stmts(self: Box<DummyResult>) -> Option<SmallVec<[ast::Stmt; 1]>> {
|
||||
Some(smallvec![ast::Stmt {
|
||||
id: ast::DUMMY_NODE_ID,
|
||||
kind: ast::StmtKind::Expr(DummyResult::raw_expr(self.span, self.is_error)),
|
||||
kind: ast::StmtKind::Expr(DummyResult::raw_expr(self.span, self.guar)),
|
||||
span: self.span,
|
||||
}])
|
||||
}
|
||||
|
@ -884,17 +888,19 @@ impl SyntaxExtension {
|
|||
}
|
||||
}
|
||||
|
||||
/// A dummy bang macro `foo!()`.
|
||||
pub fn dummy_bang(edition: Edition) -> SyntaxExtension {
|
||||
fn expander<'cx>(
|
||||
_: &'cx mut ExtCtxt<'_>,
|
||||
cx: &'cx mut ExtCtxt<'_>,
|
||||
span: Span,
|
||||
_: TokenStream,
|
||||
) -> Box<dyn MacResult + 'cx> {
|
||||
DummyResult::any(span)
|
||||
DummyResult::any(span, cx.dcx().span_delayed_bug(span, "expanded a dummy bang macro"))
|
||||
}
|
||||
SyntaxExtension::default(SyntaxExtensionKind::LegacyBang(Box::new(expander)), edition)
|
||||
}
|
||||
|
||||
/// A dummy derive macro `#[derive(Foo)]`.
|
||||
pub fn dummy_derive(edition: Edition) -> SyntaxExtension {
|
||||
fn expander(
|
||||
_: &mut ExtCtxt<'_>,
|
||||
|
@ -1066,7 +1072,7 @@ pub struct ExtCtxt<'a> {
|
|||
pub sess: &'a Session,
|
||||
pub ecfg: expand::ExpansionConfig<'a>,
|
||||
pub num_standard_library_imports: usize,
|
||||
pub reduced_recursion_limit: Option<Limit>,
|
||||
pub reduced_recursion_limit: Option<(Limit, ErrorGuaranteed)>,
|
||||
pub root_path: PathBuf,
|
||||
pub resolver: &'a mut dyn ResolverExpand,
|
||||
pub current_expansion: ExpansionData,
|
||||
|
@ -1244,7 +1250,7 @@ pub fn resolve_path(
|
|||
/// Extracts a string literal from the macro expanded version of `expr`,
|
||||
/// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
|
||||
/// The returned bool indicates whether an applicable suggestion has already been
|
||||
/// added to the diagnostic to avoid emitting multiple suggestions. `Err(None)`
|
||||
/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))`
|
||||
/// indicates that an ast error was encountered.
|
||||
// FIXME(Nilstrieb) Make this function setup translatable
|
||||
#[allow(rustc::untranslatable_diagnostic)]
|
||||
|
@ -1252,7 +1258,10 @@ pub fn expr_to_spanned_string<'a>(
|
|||
cx: &'a mut ExtCtxt<'_>,
|
||||
expr: P<ast::Expr>,
|
||||
err_msg: &'static str,
|
||||
) -> Result<(Symbol, ast::StrStyle, Span), Option<(DiagnosticBuilder<'a>, bool)>> {
|
||||
) -> Result<
|
||||
(Symbol, ast::StrStyle, Span),
|
||||
Result<(DiagnosticBuilder<'a>, bool /* has_suggestions */), ErrorGuaranteed>,
|
||||
> {
|
||||
// Perform eager expansion on the expression.
|
||||
// We want to be able to handle e.g., `concat!("foo", "bar")`.
|
||||
let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
|
||||
|
@ -1269,38 +1278,33 @@ pub fn expr_to_spanned_string<'a>(
|
|||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
Some((err, true))
|
||||
Ok((err, true))
|
||||
}
|
||||
Ok(ast::LitKind::Err(_)) => None,
|
||||
Err(err) => {
|
||||
report_lit_error(&cx.sess.parse_sess, err, token_lit, expr.span);
|
||||
None
|
||||
}
|
||||
_ => Some((cx.dcx().struct_span_err(expr.span, err_msg), false)),
|
||||
Ok(ast::LitKind::Err(guar)) => Err(guar),
|
||||
Err(err) => Err(report_lit_error(&cx.sess.parse_sess, err, token_lit, expr.span)),
|
||||
_ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
|
||||
},
|
||||
ast::ExprKind::Err => None,
|
||||
ast::ExprKind::Err(guar) => Err(guar),
|
||||
ast::ExprKind::Dummy => {
|
||||
cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`")
|
||||
}
|
||||
_ => Some((cx.dcx().struct_span_err(expr.span, err_msg), false)),
|
||||
_ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Extracts a string literal from the macro expanded version of `expr`,
|
||||
/// emitting `err_msg` if `expr` is not a string literal. This does not stop
|
||||
/// compilation on error, merely emits a non-fatal error and returns `None`.
|
||||
/// compilation on error, merely emits a non-fatal error and returns `Err`.
|
||||
pub fn expr_to_string(
|
||||
cx: &mut ExtCtxt<'_>,
|
||||
expr: P<ast::Expr>,
|
||||
err_msg: &'static str,
|
||||
) -> Option<(Symbol, ast::StrStyle)> {
|
||||
) -> Result<(Symbol, ast::StrStyle), ErrorGuaranteed> {
|
||||
expr_to_spanned_string(cx, expr, err_msg)
|
||||
.map_err(|err| {
|
||||
err.map(|(err, _)| {
|
||||
err.emit();
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
Ok((err, _)) => err.emit(),
|
||||
Err(guar) => guar,
|
||||
})
|
||||
.ok()
|
||||
.map(|(symbol, style, _)| (symbol, style))
|
||||
}
|
||||
|
||||
|
@ -1314,32 +1318,30 @@ pub fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse an expression. On error, emit it, advancing to `Eof`, and return `None`.
|
||||
pub fn parse_expr(p: &mut parser::Parser<'_>) -> Option<P<ast::Expr>> {
|
||||
match p.parse_expr() {
|
||||
Ok(e) => return Some(e),
|
||||
Err(err) => {
|
||||
err.emit();
|
||||
}
|
||||
}
|
||||
/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`.
|
||||
pub fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> {
|
||||
let guar = match p.parse_expr() {
|
||||
Ok(expr) => return Ok(expr),
|
||||
Err(err) => err.emit(),
|
||||
};
|
||||
while p.token != token::Eof {
|
||||
p.bump();
|
||||
}
|
||||
None
|
||||
Err(guar)
|
||||
}
|
||||
|
||||
/// Interpreting `tts` as a comma-separated sequence of expressions,
|
||||
/// expect exactly one string literal, or emit an error and return `None`.
|
||||
/// expect exactly one string literal, or emit an error and return `Err`.
|
||||
pub fn get_single_str_from_tts(
|
||||
cx: &mut ExtCtxt<'_>,
|
||||
span: Span,
|
||||
tts: TokenStream,
|
||||
name: &str,
|
||||
) -> Option<Symbol> {
|
||||
) -> Result<Symbol, ErrorGuaranteed> {
|
||||
let mut p = cx.new_parser_from_tts(tts);
|
||||
if p.token == token::Eof {
|
||||
cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
|
||||
return None;
|
||||
let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
|
||||
return Err(guar);
|
||||
}
|
||||
let ret = parse_expr(&mut p)?;
|
||||
let _ = p.eat(&token::Comma);
|
||||
|
@ -1351,8 +1353,11 @@ pub fn get_single_str_from_tts(
|
|||
}
|
||||
|
||||
/// Extracts comma-separated expressions from `tts`.
|
||||
/// On error, emit it, and return `None`.
|
||||
pub fn get_exprs_from_tts(cx: &mut ExtCtxt<'_>, tts: TokenStream) -> Option<Vec<P<ast::Expr>>> {
|
||||
/// On error, emit it, and return `Err`.
|
||||
pub fn get_exprs_from_tts(
|
||||
cx: &mut ExtCtxt<'_>,
|
||||
tts: TokenStream,
|
||||
) -> Result<Vec<P<ast::Expr>>, ErrorGuaranteed> {
|
||||
let mut p = cx.new_parser_from_tts(tts);
|
||||
let mut es = Vec::new();
|
||||
while p.token != token::Eof {
|
||||
|
@ -1367,11 +1372,11 @@ pub fn get_exprs_from_tts(cx: &mut ExtCtxt<'_>, tts: TokenStream) -> Option<Vec<
|
|||
continue;
|
||||
}
|
||||
if p.token != token::Eof {
|
||||
cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
|
||||
return None;
|
||||
let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
|
||||
return Err(guar);
|
||||
}
|
||||
}
|
||||
Some(es)
|
||||
Ok(es)
|
||||
}
|
||||
|
||||
pub fn parse_macro_name_and_helper_attrs(
|
||||
|
|
|
@ -34,7 +34,7 @@ use rustc_session::lint::BuiltinLintDiagnostics;
|
|||
use rustc_session::parse::feature_err;
|
||||
use rustc_session::{Limit, Session};
|
||||
use rustc_span::symbol::{sym, Ident};
|
||||
use rustc_span::{FileName, LocalExpnId, Span};
|
||||
use rustc_span::{ErrorGuaranteed, FileName, LocalExpnId, Span};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use std::ops::Deref;
|
||||
|
@ -232,8 +232,8 @@ pub enum SupportsMacroExpansion {
|
|||
}
|
||||
|
||||
impl AstFragmentKind {
|
||||
pub(crate) fn dummy(self, span: Span) -> AstFragment {
|
||||
self.make_from(DummyResult::any(span)).expect("couldn't create a dummy AST fragment")
|
||||
pub(crate) fn dummy(self, span: Span, guar: ErrorGuaranteed) -> AstFragment {
|
||||
self.make_from(DummyResult::any(span, guar)).expect("couldn't create a dummy AST fragment")
|
||||
}
|
||||
|
||||
pub fn supports_macro_expansion(self) -> SupportsMacroExpansion {
|
||||
|
@ -604,14 +604,14 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
|
|||
(fragment, invocations)
|
||||
}
|
||||
|
||||
fn error_recursion_limit_reached(&mut self) {
|
||||
fn error_recursion_limit_reached(&mut self) -> ErrorGuaranteed {
|
||||
let expn_data = self.cx.current_expansion.id.expn_data();
|
||||
let suggested_limit = match self.cx.ecfg.recursion_limit {
|
||||
Limit(0) => Limit(2),
|
||||
limit => limit * 2,
|
||||
};
|
||||
|
||||
self.cx.dcx().emit_err(RecursionLimitReached {
|
||||
let guar = self.cx.dcx().emit_err(RecursionLimitReached {
|
||||
span: expn_data.call_site,
|
||||
descr: expn_data.kind.descr(),
|
||||
suggested_limit,
|
||||
|
@ -619,14 +619,21 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
|
|||
});
|
||||
|
||||
self.cx.trace_macros_diag();
|
||||
guar
|
||||
}
|
||||
|
||||
/// A macro's expansion does not fit in this fragment kind.
|
||||
/// For example, a non-type macro in a type position.
|
||||
fn error_wrong_fragment_kind(&mut self, kind: AstFragmentKind, mac: &ast::MacCall, span: Span) {
|
||||
self.cx.dcx().emit_err(WrongFragmentKind { span, kind: kind.name(), name: &mac.path });
|
||||
|
||||
fn error_wrong_fragment_kind(
|
||||
&mut self,
|
||||
kind: AstFragmentKind,
|
||||
mac: &ast::MacCall,
|
||||
span: Span,
|
||||
) -> ErrorGuaranteed {
|
||||
let guar =
|
||||
self.cx.dcx().emit_err(WrongFragmentKind { span, kind: kind.name(), name: &mac.path });
|
||||
self.cx.trace_macros_diag();
|
||||
guar
|
||||
}
|
||||
|
||||
fn expand_invoc(
|
||||
|
@ -634,36 +641,41 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
|
|||
invoc: Invocation,
|
||||
ext: &SyntaxExtensionKind,
|
||||
) -> ExpandResult<AstFragment, Invocation> {
|
||||
let recursion_limit =
|
||||
self.cx.reduced_recursion_limit.unwrap_or(self.cx.ecfg.recursion_limit);
|
||||
let recursion_limit = match self.cx.reduced_recursion_limit {
|
||||
Some((limit, _)) => limit,
|
||||
None => self.cx.ecfg.recursion_limit,
|
||||
};
|
||||
|
||||
if !recursion_limit.value_within_limit(self.cx.current_expansion.depth) {
|
||||
if self.cx.reduced_recursion_limit.is_none() {
|
||||
self.error_recursion_limit_reached();
|
||||
}
|
||||
let guar = match self.cx.reduced_recursion_limit {
|
||||
Some((_, guar)) => guar,
|
||||
None => self.error_recursion_limit_reached(),
|
||||
};
|
||||
|
||||
// Reduce the recursion limit by half each time it triggers.
|
||||
self.cx.reduced_recursion_limit = Some(recursion_limit / 2);
|
||||
self.cx.reduced_recursion_limit = Some((recursion_limit / 2, guar));
|
||||
|
||||
return ExpandResult::Ready(invoc.fragment_kind.dummy(invoc.span()));
|
||||
return ExpandResult::Ready(invoc.fragment_kind.dummy(invoc.span(), guar));
|
||||
}
|
||||
|
||||
let (fragment_kind, span) = (invoc.fragment_kind, invoc.span());
|
||||
ExpandResult::Ready(match invoc.kind {
|
||||
InvocationKind::Bang { mac, .. } => match ext {
|
||||
SyntaxExtensionKind::Bang(expander) => {
|
||||
let Ok(tok_result) = expander.expand(self.cx, span, mac.args.tokens.clone())
|
||||
else {
|
||||
return ExpandResult::Ready(fragment_kind.dummy(span));
|
||||
};
|
||||
self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span)
|
||||
match expander.expand(self.cx, span, mac.args.tokens.clone()) {
|
||||
Ok(tok_result) => {
|
||||
self.parse_ast_fragment(tok_result, fragment_kind, &mac.path, span)
|
||||
}
|
||||
Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
|
||||
}
|
||||
}
|
||||
SyntaxExtensionKind::LegacyBang(expander) => {
|
||||
let tok_result = expander.expand(self.cx, span, mac.args.tokens.clone());
|
||||
let result = if let Some(result) = fragment_kind.make_from(tok_result) {
|
||||
result
|
||||
} else {
|
||||
self.error_wrong_fragment_kind(fragment_kind, &mac, span);
|
||||
fragment_kind.dummy(span)
|
||||
let guar = self.error_wrong_fragment_kind(fragment_kind, &mac, span);
|
||||
fragment_kind.dummy(span, guar)
|
||||
};
|
||||
result
|
||||
}
|
||||
|
@ -705,11 +717,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
|
|||
self.cx.dcx().emit_err(UnsupportedKeyValue { span });
|
||||
}
|
||||
let inner_tokens = attr_item.args.inner_tokens();
|
||||
let Ok(tok_result) = expander.expand(self.cx, span, inner_tokens, tokens)
|
||||
else {
|
||||
return ExpandResult::Ready(fragment_kind.dummy(span));
|
||||
};
|
||||
self.parse_ast_fragment(tok_result, fragment_kind, &attr_item.path, span)
|
||||
match expander.expand(self.cx, span, inner_tokens, tokens) {
|
||||
Ok(tok_result) => self.parse_ast_fragment(
|
||||
tok_result,
|
||||
fragment_kind,
|
||||
&attr_item.path,
|
||||
span,
|
||||
),
|
||||
Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)),
|
||||
}
|
||||
}
|
||||
SyntaxExtensionKind::LegacyAttr(expander) => {
|
||||
match validate_attr::parse_meta(&self.cx.sess.parse_sess, &attr) {
|
||||
|
@ -729,15 +745,15 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
|
|||
AstFragmentKind::Expr | AstFragmentKind::MethodReceiverExpr
|
||||
) && items.is_empty()
|
||||
{
|
||||
self.cx.dcx().emit_err(RemoveExprNotSupported { span });
|
||||
fragment_kind.dummy(span)
|
||||
let guar = self.cx.dcx().emit_err(RemoveExprNotSupported { span });
|
||||
fragment_kind.dummy(span, guar)
|
||||
} else {
|
||||
fragment_kind.expect_from_annotatables(items)
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
err.emit();
|
||||
fragment_kind.dummy(span)
|
||||
let guar = err.emit();
|
||||
fragment_kind.dummy(span, guar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -857,9 +873,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
|
|||
err.span(span);
|
||||
}
|
||||
annotate_err_with_kind(&mut err, kind, span);
|
||||
err.emit();
|
||||
let guar = err.emit();
|
||||
self.cx.trace_macros_diag();
|
||||
kind.dummy(span)
|
||||
kind.dummy(span, guar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use rustc_errors::{Applicability, DiagCtxt, DiagnosticBuilder, DiagnosticMessage
|
|||
use rustc_parse::parser::{Parser, Recovery};
|
||||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{ErrorGuaranteed, Span};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::macro_rules::{parser_from_cx, NoopTracker};
|
||||
|
@ -47,7 +47,7 @@ pub(super) fn failed_to_match_macro<'cx>(
|
|||
|
||||
let Some(BestFailure { token, msg: label, remaining_matcher, .. }) = tracker.best_failure
|
||||
else {
|
||||
return DummyResult::any(sp);
|
||||
return DummyResult::any(sp, cx.dcx().span_delayed_bug(sp, "failed to match a macro"));
|
||||
};
|
||||
|
||||
let span = token.span.substitute_dummy(sp);
|
||||
|
@ -106,9 +106,9 @@ pub(super) fn failed_to_match_macro<'cx>(
|
|||
}
|
||||
}
|
||||
}
|
||||
err.emit();
|
||||
let guar = err.emit();
|
||||
cx.trace_macros_diag();
|
||||
DummyResult::any(sp)
|
||||
DummyResult::any(sp, guar)
|
||||
}
|
||||
|
||||
/// The tracker used for the slow error path that collects useful info for diagnostics.
|
||||
|
@ -180,10 +180,10 @@ impl<'a, 'cx, 'matcher> Tracker<'matcher> for CollectTrackerAndEmitter<'a, 'cx,
|
|||
}
|
||||
Error(err_sp, msg) => {
|
||||
let span = err_sp.substitute_dummy(self.root_span);
|
||||
self.cx.dcx().span_err(span, msg.clone());
|
||||
self.result = Some(DummyResult::any(span));
|
||||
let guar = self.cx.dcx().span_err(span, msg.clone());
|
||||
self.result = Some(DummyResult::any(span, guar));
|
||||
}
|
||||
ErrorReported(_) => self.result = Some(DummyResult::any(self.root_span)),
|
||||
ErrorReported(guar) => self.result = Some(DummyResult::any(self.root_span, *guar)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +224,7 @@ pub(super) fn emit_frag_parse_err(
|
|||
site_span: Span,
|
||||
arm_span: Span,
|
||||
kind: AstFragmentKind,
|
||||
) {
|
||||
) -> ErrorGuaranteed {
|
||||
// FIXME(davidtwco): avoid depending on the error message text
|
||||
if parser.token == token::Eof
|
||||
&& let DiagnosticMessage::Str(message) = &e.messages[0].0
|
||||
|
@ -282,7 +282,7 @@ pub(super) fn emit_frag_parse_err(
|
|||
},
|
||||
_ => annotate_err_with_kind(&mut e, kind, site_span),
|
||||
};
|
||||
e.emit();
|
||||
e.emit()
|
||||
}
|
||||
|
||||
pub(crate) fn annotate_err_with_kind(
|
||||
|
|
|
@ -114,7 +114,7 @@ use rustc_errors::{DiagnosticMessage, MultiSpan};
|
|||
use rustc_session::lint::builtin::{META_VARIABLE_MISUSE, MISSING_FRAGMENT_SPECIFIER};
|
||||
use rustc_session::parse::ParseSess;
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{symbol::MacroRulesNormalizedIdent, Span};
|
||||
use rustc_span::{symbol::MacroRulesNormalizedIdent, ErrorGuaranteed, Span};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -203,17 +203,17 @@ pub(super) fn check_meta_variables(
|
|||
span: Span,
|
||||
lhses: &[TokenTree],
|
||||
rhses: &[TokenTree],
|
||||
) -> bool {
|
||||
) -> Result<(), ErrorGuaranteed> {
|
||||
if lhses.len() != rhses.len() {
|
||||
sess.dcx.span_bug(span, "length mismatch between LHSes and RHSes")
|
||||
}
|
||||
let mut valid = true;
|
||||
let mut guar = None;
|
||||
for (lhs, rhs) in iter::zip(lhses, rhses) {
|
||||
let mut binders = Binders::default();
|
||||
check_binders(sess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut valid);
|
||||
check_occurrences(sess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut valid);
|
||||
check_binders(sess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar);
|
||||
check_occurrences(sess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar);
|
||||
}
|
||||
valid
|
||||
guar.map_or(Ok(()), Err)
|
||||
}
|
||||
|
||||
/// Checks `lhs` as part of the LHS of a macro definition, extends `binders` with new binders, and
|
||||
|
@ -226,7 +226,7 @@ pub(super) fn check_meta_variables(
|
|||
/// - `macros` is the stack of possible outer macros
|
||||
/// - `binders` contains the binders of the LHS
|
||||
/// - `ops` is the stack of Kleene operators from the LHS
|
||||
/// - `valid` is set in case of errors
|
||||
/// - `guar` is set in case of errors
|
||||
fn check_binders(
|
||||
sess: &ParseSess,
|
||||
node_id: NodeId,
|
||||
|
@ -234,7 +234,7 @@ fn check_binders(
|
|||
macros: &Stack<'_, MacroState<'_>>,
|
||||
binders: &mut Binders,
|
||||
ops: &Stack<'_, KleeneToken>,
|
||||
valid: &mut bool,
|
||||
guar: &mut Option<ErrorGuaranteed>,
|
||||
) {
|
||||
match *lhs {
|
||||
TokenTree::Token(..) => {}
|
||||
|
@ -258,7 +258,7 @@ fn check_binders(
|
|||
binders.insert(name, BinderInfo { span, ops: ops.into() });
|
||||
} else {
|
||||
// 3. The meta-variable is bound: This is an occurrence.
|
||||
check_occurrences(sess, node_id, lhs, macros, binders, ops, valid);
|
||||
check_occurrences(sess, node_id, lhs, macros, binders, ops, guar);
|
||||
}
|
||||
}
|
||||
// Similarly, this can only happen when checking a toplevel macro.
|
||||
|
@ -281,8 +281,10 @@ fn check_binders(
|
|||
if let Some(prev_info) = get_binder_info(macros, binders, name) {
|
||||
// Duplicate binders at the top-level macro definition are errors. The lint is only
|
||||
// for nested macro definitions.
|
||||
sess.dcx.emit_err(errors::DuplicateMatcherBinding { span, prev: prev_info.span });
|
||||
*valid = false;
|
||||
*guar = Some(
|
||||
sess.dcx
|
||||
.emit_err(errors::DuplicateMatcherBinding { span, prev: prev_info.span }),
|
||||
);
|
||||
} else {
|
||||
binders.insert(name, BinderInfo { span, ops: ops.into() });
|
||||
}
|
||||
|
@ -291,13 +293,13 @@ fn check_binders(
|
|||
TokenTree::MetaVarExpr(..) => {}
|
||||
TokenTree::Delimited(.., ref del) => {
|
||||
for tt in &del.tts {
|
||||
check_binders(sess, node_id, tt, macros, binders, ops, valid);
|
||||
check_binders(sess, node_id, tt, macros, binders, ops, guar);
|
||||
}
|
||||
}
|
||||
TokenTree::Sequence(_, ref seq) => {
|
||||
let ops = ops.push(seq.kleene);
|
||||
for tt in &seq.tts {
|
||||
check_binders(sess, node_id, tt, macros, binders, &ops, valid);
|
||||
check_binders(sess, node_id, tt, macros, binders, &ops, guar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,7 +329,7 @@ fn get_binder_info<'a>(
|
|||
/// - `macros` is the stack of possible outer macros
|
||||
/// - `binders` contains the binders of the associated LHS
|
||||
/// - `ops` is the stack of Kleene operators from the RHS
|
||||
/// - `valid` is set in case of errors
|
||||
/// - `guar` is set in case of errors
|
||||
fn check_occurrences(
|
||||
sess: &ParseSess,
|
||||
node_id: NodeId,
|
||||
|
@ -335,7 +337,7 @@ fn check_occurrences(
|
|||
macros: &Stack<'_, MacroState<'_>>,
|
||||
binders: &Binders,
|
||||
ops: &Stack<'_, KleeneToken>,
|
||||
valid: &mut bool,
|
||||
guar: &mut Option<ErrorGuaranteed>,
|
||||
) {
|
||||
match *rhs {
|
||||
TokenTree::Token(..) => {}
|
||||
|
@ -353,11 +355,11 @@ fn check_occurrences(
|
|||
check_ops_is_prefix(sess, node_id, macros, binders, ops, dl.entire(), name);
|
||||
}
|
||||
TokenTree::Delimited(.., ref del) => {
|
||||
check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, valid);
|
||||
check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, guar);
|
||||
}
|
||||
TokenTree::Sequence(_, ref seq) => {
|
||||
let ops = ops.push(seq.kleene);
|
||||
check_nested_occurrences(sess, node_id, &seq.tts, macros, binders, &ops, valid);
|
||||
check_nested_occurrences(sess, node_id, &seq.tts, macros, binders, &ops, guar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -392,7 +394,7 @@ enum NestedMacroState {
|
|||
/// - `macros` is the stack of possible outer macros
|
||||
/// - `binders` contains the binders of the associated LHS
|
||||
/// - `ops` is the stack of Kleene operators from the RHS
|
||||
/// - `valid` is set in case of errors
|
||||
/// - `guar` is set in case of errors
|
||||
fn check_nested_occurrences(
|
||||
sess: &ParseSess,
|
||||
node_id: NodeId,
|
||||
|
@ -400,7 +402,7 @@ fn check_nested_occurrences(
|
|||
macros: &Stack<'_, MacroState<'_>>,
|
||||
binders: &Binders,
|
||||
ops: &Stack<'_, KleeneToken>,
|
||||
valid: &mut bool,
|
||||
guar: &mut Option<ErrorGuaranteed>,
|
||||
) {
|
||||
let mut state = NestedMacroState::Empty;
|
||||
let nested_macros = macros.push(MacroState { binders, ops: ops.into() });
|
||||
|
@ -432,7 +434,7 @@ fn check_nested_occurrences(
|
|||
(NestedMacroState::MacroRulesNot, &TokenTree::MetaVar(..)) => {
|
||||
state = NestedMacroState::MacroRulesNotName;
|
||||
// We check that the meta-variable is correctly used.
|
||||
check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
|
||||
check_occurrences(sess, node_id, tt, macros, binders, ops, guar);
|
||||
}
|
||||
(NestedMacroState::MacroRulesNotName, TokenTree::Delimited(.., del))
|
||||
| (NestedMacroState::MacroName, TokenTree::Delimited(.., del))
|
||||
|
@ -441,7 +443,7 @@ fn check_nested_occurrences(
|
|||
let macro_rules = state == NestedMacroState::MacroRulesNotName;
|
||||
state = NestedMacroState::Empty;
|
||||
let rest =
|
||||
check_nested_macro(sess, node_id, macro_rules, &del.tts, &nested_macros, valid);
|
||||
check_nested_macro(sess, node_id, macro_rules, &del.tts, &nested_macros, guar);
|
||||
// If we did not check the whole macro definition, then check the rest as if outside
|
||||
// the macro definition.
|
||||
check_nested_occurrences(
|
||||
|
@ -451,7 +453,7 @@ fn check_nested_occurrences(
|
|||
macros,
|
||||
binders,
|
||||
ops,
|
||||
valid,
|
||||
guar,
|
||||
);
|
||||
}
|
||||
(
|
||||
|
@ -463,7 +465,7 @@ fn check_nested_occurrences(
|
|||
(NestedMacroState::Macro, &TokenTree::MetaVar(..)) => {
|
||||
state = NestedMacroState::MacroName;
|
||||
// We check that the meta-variable is correctly used.
|
||||
check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
|
||||
check_occurrences(sess, node_id, tt, macros, binders, ops, guar);
|
||||
}
|
||||
(NestedMacroState::MacroName, TokenTree::Delimited(.., del))
|
||||
if del.delim == Delimiter::Parenthesis =>
|
||||
|
@ -477,7 +479,7 @@ fn check_nested_occurrences(
|
|||
&nested_macros,
|
||||
&mut nested_binders,
|
||||
&Stack::Empty,
|
||||
valid,
|
||||
guar,
|
||||
);
|
||||
}
|
||||
(NestedMacroState::MacroNameParen, TokenTree::Delimited(.., del))
|
||||
|
@ -491,12 +493,12 @@ fn check_nested_occurrences(
|
|||
&nested_macros,
|
||||
&nested_binders,
|
||||
&Stack::Empty,
|
||||
valid,
|
||||
guar,
|
||||
);
|
||||
}
|
||||
(_, tt) => {
|
||||
state = NestedMacroState::Empty;
|
||||
check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
|
||||
check_occurrences(sess, node_id, tt, macros, binders, ops, guar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -515,14 +517,14 @@ fn check_nested_occurrences(
|
|||
/// - `macro_rules` specifies whether the macro is `macro_rules`
|
||||
/// - `tts` is checked as a list of (LHS) => {RHS}
|
||||
/// - `macros` is the stack of outer macros
|
||||
/// - `valid` is set in case of errors
|
||||
/// - `guar` is set in case of errors
|
||||
fn check_nested_macro(
|
||||
sess: &ParseSess,
|
||||
node_id: NodeId,
|
||||
macro_rules: bool,
|
||||
tts: &[TokenTree],
|
||||
macros: &Stack<'_, MacroState<'_>>,
|
||||
valid: &mut bool,
|
||||
guar: &mut Option<ErrorGuaranteed>,
|
||||
) -> usize {
|
||||
let n = tts.len();
|
||||
let mut i = 0;
|
||||
|
@ -539,8 +541,8 @@ fn check_nested_macro(
|
|||
let lhs = &tts[i];
|
||||
let rhs = &tts[i + 2];
|
||||
let mut binders = Binders::default();
|
||||
check_binders(sess, node_id, lhs, macros, &mut binders, &Stack::Empty, valid);
|
||||
check_occurrences(sess, node_id, rhs, macros, &binders, &Stack::Empty, valid);
|
||||
check_binders(sess, node_id, lhs, macros, &mut binders, &Stack::Empty, guar);
|
||||
check_occurrences(sess, node_id, rhs, macros, &binders, &Stack::Empty, guar);
|
||||
// Since the last semicolon is optional for `macro_rules` macros and decl_macro are not terminated,
|
||||
// we increment our checked position by how many token trees we already checked (the 3
|
||||
// above) before checking for the separator.
|
||||
|
|
|
@ -66,8 +66,10 @@ impl<'a> ParserAnyMacro<'a> {
|
|||
let fragment = match parse_ast_fragment(parser, kind) {
|
||||
Ok(f) => f,
|
||||
Err(err) => {
|
||||
diagnostics::emit_frag_parse_err(err, parser, snapshot, site_span, arm_span, kind);
|
||||
return kind.dummy(site_span);
|
||||
let guar = diagnostics::emit_frag_parse_err(
|
||||
err, parser, snapshot, site_span, arm_span, kind,
|
||||
);
|
||||
return kind.dummy(site_span, guar);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -101,7 +103,6 @@ struct MacroRulesMacroExpander {
|
|||
transparency: Transparency,
|
||||
lhses: Vec<Vec<MatcherLoc>>,
|
||||
rhses: Vec<mbe::TokenTree>,
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
impl TTMacroExpander for MacroRulesMacroExpander {
|
||||
|
@ -111,9 +112,6 @@ impl TTMacroExpander for MacroRulesMacroExpander {
|
|||
sp: Span,
|
||||
input: TokenStream,
|
||||
) -> Box<dyn MacResult + 'cx> {
|
||||
if !self.valid {
|
||||
return DummyResult::any(sp);
|
||||
}
|
||||
expand_macro(
|
||||
cx,
|
||||
sp,
|
||||
|
@ -128,12 +126,17 @@ impl TTMacroExpander for MacroRulesMacroExpander {
|
|||
}
|
||||
}
|
||||
|
||||
fn macro_rules_dummy_expander<'cx>(
|
||||
_: &'cx mut ExtCtxt<'_>,
|
||||
span: Span,
|
||||
_: TokenStream,
|
||||
) -> Box<dyn MacResult + 'cx> {
|
||||
DummyResult::any(span)
|
||||
struct DummyExpander(ErrorGuaranteed);
|
||||
|
||||
impl TTMacroExpander for DummyExpander {
|
||||
fn expand<'cx>(
|
||||
&self,
|
||||
_: &'cx mut ExtCtxt<'_>,
|
||||
span: Span,
|
||||
_: TokenStream,
|
||||
) -> Box<dyn MacResult + 'cx> {
|
||||
DummyResult::any(span, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn trace_macros_note(cx_expansions: &mut FxIndexMap<Span, Vec<String>>, sp: Span, message: String) {
|
||||
|
@ -217,8 +220,8 @@ fn expand_macro<'cx>(
|
|||
let tts = match transcribe(cx, &named_matches, rhs, rhs_span, transparency) {
|
||||
Ok(tts) => tts,
|
||||
Err(err) => {
|
||||
err.emit();
|
||||
return DummyResult::any(arm_span);
|
||||
let guar = err.emit();
|
||||
return DummyResult::any(arm_span, guar);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -249,9 +252,9 @@ fn expand_macro<'cx>(
|
|||
is_local,
|
||||
})
|
||||
}
|
||||
Err(CanRetry::No(_)) => {
|
||||
Err(CanRetry::No(guar)) => {
|
||||
debug!("Will not retry matching as an error was emitted already");
|
||||
DummyResult::any(sp)
|
||||
DummyResult::any(sp, guar)
|
||||
}
|
||||
Err(CanRetry::Yes) => {
|
||||
// Retry and emit a better error.
|
||||
|
@ -371,7 +374,7 @@ pub fn compile_declarative_macro(
|
|||
def.id != DUMMY_NODE_ID,
|
||||
)
|
||||
};
|
||||
let dummy_syn_ext = || (mk_syn_ext(Box::new(macro_rules_dummy_expander)), Vec::new());
|
||||
let dummy_syn_ext = |guar| (mk_syn_ext(Box::new(DummyExpander(guar))), Vec::new());
|
||||
|
||||
let dcx = &sess.parse_sess.dcx;
|
||||
let lhs_nm = Ident::new(sym::lhs, def.span);
|
||||
|
@ -456,19 +459,20 @@ pub fn compile_declarative_macro(
|
|||
let mut err = sess.dcx().struct_span_err(sp, s);
|
||||
err.span_label(sp, msg);
|
||||
annotate_doc_comment(sess.dcx(), &mut err, sess.source_map(), sp);
|
||||
err.emit();
|
||||
return dummy_syn_ext();
|
||||
let guar = err.emit();
|
||||
return dummy_syn_ext(guar);
|
||||
}
|
||||
Error(sp, msg) => {
|
||||
sess.dcx().span_err(sp.substitute_dummy(def.span), msg);
|
||||
return dummy_syn_ext();
|
||||
let guar = sess.dcx().span_err(sp.substitute_dummy(def.span), msg);
|
||||
return dummy_syn_ext(guar);
|
||||
}
|
||||
ErrorReported(_) => {
|
||||
return dummy_syn_ext();
|
||||
ErrorReported(guar) => {
|
||||
return dummy_syn_ext(guar);
|
||||
}
|
||||
};
|
||||
|
||||
let mut valid = true;
|
||||
let mut guar = None;
|
||||
let mut check_emission = |ret: Result<(), ErrorGuaranteed>| guar = guar.or(ret.err());
|
||||
|
||||
// Extract the arguments:
|
||||
let lhses = match &argument_map[&MacroRulesNormalizedIdent::new(lhs_nm)] {
|
||||
|
@ -488,7 +492,7 @@ pub fn compile_declarative_macro(
|
|||
.unwrap();
|
||||
// We don't handle errors here, the driver will abort
|
||||
// after parsing/expansion. we can report every error in every macro this way.
|
||||
valid &= check_lhs_nt_follows(sess, def, &tt).is_ok();
|
||||
check_emission(check_lhs_nt_follows(sess, def, &tt));
|
||||
return tt;
|
||||
}
|
||||
sess.dcx().span_bug(def.span, "wrong-structured lhs")
|
||||
|
@ -520,15 +524,21 @@ pub fn compile_declarative_macro(
|
|||
};
|
||||
|
||||
for rhs in &rhses {
|
||||
valid &= check_rhs(sess, rhs);
|
||||
check_emission(check_rhs(sess, rhs));
|
||||
}
|
||||
|
||||
// don't abort iteration early, so that errors for multiple lhses can be reported
|
||||
for lhs in &lhses {
|
||||
valid &= check_lhs_no_empty_seq(sess, slice::from_ref(lhs));
|
||||
check_emission(check_lhs_no_empty_seq(sess, slice::from_ref(lhs)));
|
||||
}
|
||||
|
||||
valid &= macro_check::check_meta_variables(&sess.parse_sess, def.id, def.span, &lhses, &rhses);
|
||||
check_emission(macro_check::check_meta_variables(
|
||||
&sess.parse_sess,
|
||||
def.id,
|
||||
def.span,
|
||||
&lhses,
|
||||
&rhses,
|
||||
));
|
||||
|
||||
let (transparency, transparency_error) = attr::find_transparency(&def.attrs, macro_rules);
|
||||
match transparency_error {
|
||||
|
@ -541,11 +551,15 @@ pub fn compile_declarative_macro(
|
|||
None => {}
|
||||
}
|
||||
|
||||
if let Some(guar) = guar {
|
||||
// To avoid warning noise, only consider the rules of this
|
||||
// macro for the lint, if all rules are valid.
|
||||
return dummy_syn_ext(guar);
|
||||
}
|
||||
|
||||
// Compute the spans of the macro rules for unused rule linting.
|
||||
// To avoid warning noise, only consider the rules of this
|
||||
// macro for the lint, if all rules are valid.
|
||||
// Also, we are only interested in non-foreign macros.
|
||||
let rule_spans = if valid && def.id != DUMMY_NODE_ID {
|
||||
let rule_spans = if def.id != DUMMY_NODE_ID {
|
||||
lhses
|
||||
.iter()
|
||||
.zip(rhses.iter())
|
||||
|
@ -562,23 +576,19 @@ pub fn compile_declarative_macro(
|
|||
};
|
||||
|
||||
// Convert the lhses into `MatcherLoc` form, which is better for doing the
|
||||
// actual matching. Unless the matcher is invalid.
|
||||
let lhses = if valid {
|
||||
lhses
|
||||
.iter()
|
||||
.map(|lhs| {
|
||||
// Ignore the delimiters around the matcher.
|
||||
match lhs {
|
||||
mbe::TokenTree::Delimited(.., delimited) => {
|
||||
mbe::macro_parser::compute_locs(&delimited.tts)
|
||||
}
|
||||
_ => sess.dcx().span_bug(def.span, "malformed macro lhs"),
|
||||
// actual matching.
|
||||
let lhses = lhses
|
||||
.iter()
|
||||
.map(|lhs| {
|
||||
// Ignore the delimiters around the matcher.
|
||||
match lhs {
|
||||
mbe::TokenTree::Delimited(.., delimited) => {
|
||||
mbe::macro_parser::compute_locs(&delimited.tts)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
_ => sess.dcx().span_bug(def.span, "malformed macro lhs"),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expander = Box::new(MacroRulesMacroExpander {
|
||||
name: def.ident,
|
||||
|
@ -587,7 +597,6 @@ pub fn compile_declarative_macro(
|
|||
transparency,
|
||||
lhses,
|
||||
rhses,
|
||||
valid,
|
||||
});
|
||||
(mk_syn_ext(expander), rule_spans)
|
||||
}
|
||||
|
@ -640,7 +649,7 @@ fn is_empty_token_tree(sess: &Session, seq: &mbe::SequenceRepetition) -> bool {
|
|||
|
||||
/// Checks that the lhs contains no repetition which could match an empty token
|
||||
/// tree, because then the matcher would hang indefinitely.
|
||||
fn check_lhs_no_empty_seq(sess: &Session, tts: &[mbe::TokenTree]) -> bool {
|
||||
fn check_lhs_no_empty_seq(sess: &Session, tts: &[mbe::TokenTree]) -> Result<(), ErrorGuaranteed> {
|
||||
use mbe::TokenTree;
|
||||
for tt in tts {
|
||||
match tt {
|
||||
|
@ -648,35 +657,26 @@ fn check_lhs_no_empty_seq(sess: &Session, tts: &[mbe::TokenTree]) -> bool {
|
|||
| TokenTree::MetaVar(..)
|
||||
| TokenTree::MetaVarDecl(..)
|
||||
| TokenTree::MetaVarExpr(..) => (),
|
||||
TokenTree::Delimited(.., del) => {
|
||||
if !check_lhs_no_empty_seq(sess, &del.tts) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
TokenTree::Delimited(.., del) => check_lhs_no_empty_seq(sess, &del.tts)?,
|
||||
TokenTree::Sequence(span, seq) => {
|
||||
if is_empty_token_tree(sess, seq) {
|
||||
let sp = span.entire();
|
||||
sess.dcx().span_err(sp, "repetition matches empty token tree");
|
||||
return false;
|
||||
}
|
||||
if !check_lhs_no_empty_seq(sess, &seq.tts) {
|
||||
return false;
|
||||
let guar = sess.dcx().span_err(sp, "repetition matches empty token tree");
|
||||
return Err(guar);
|
||||
}
|
||||
check_lhs_no_empty_seq(sess, &seq.tts)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_rhs(sess: &Session, rhs: &mbe::TokenTree) -> bool {
|
||||
fn check_rhs(sess: &Session, rhs: &mbe::TokenTree) -> Result<(), ErrorGuaranteed> {
|
||||
match *rhs {
|
||||
mbe::TokenTree::Delimited(..) => return true,
|
||||
_ => {
|
||||
sess.dcx().span_err(rhs.span(), "macro rhs must be delimited");
|
||||
}
|
||||
mbe::TokenTree::Delimited(..) => Ok(()),
|
||||
_ => Err(sess.dcx().span_err(rhs.span(), "macro rhs must be delimited")),
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn check_matcher(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue