Move around the code responsible for decorating builtin diagnostics
This commit is contained in:
parent
d59f06fc64
commit
429287243b
@ -18,34 +18,32 @@ use self::TargetLint::*;
|
||||
|
||||
use crate::levels::LintLevelsBuilder;
|
||||
use crate::passes::{EarlyLintPassObject, LateLintPassObject};
|
||||
use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::sync;
|
||||
use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage};
|
||||
use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle};
|
||||
use rustc_errors::{DecorateLint, DiagnosticBuilder, DiagnosticMessage, MultiSpan};
|
||||
use rustc_feature::Features;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::def_id::{CrateNum, DefId};
|
||||
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
|
||||
use rustc_middle::middle::privacy::EffectiveVisibilities;
|
||||
use rustc_middle::middle::stability;
|
||||
use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
|
||||
use rustc_middle::ty::print::{with_no_trimmed_paths, PrintError};
|
||||
use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt};
|
||||
use rustc_session::config::ExpectedValues;
|
||||
use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
|
||||
use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
|
||||
use rustc_session::{LintStoreMarker, Session};
|
||||
use rustc_span::edit_distance::find_best_match_for_name;
|
||||
use rustc_span::symbol::{sym, Ident, Symbol};
|
||||
use rustc_span::{BytePos, Span};
|
||||
use rustc_span::Span;
|
||||
use rustc_target::abi;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::iter;
|
||||
use std::slice;
|
||||
|
||||
mod diagnostics;
|
||||
|
||||
type EarlyLintPassFactory = dyn Fn() -> EarlyLintPassObject + sync::DynSend + sync::DynSync;
|
||||
type LateLintPassFactory =
|
||||
dyn for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + sync::DynSend + sync::DynSync;
|
||||
@ -531,445 +529,9 @@ pub trait LintContext {
|
||||
diagnostic: BuiltinLintDiagnostics,
|
||||
) {
|
||||
// We first generate a blank diagnostic.
|
||||
self.lookup(lint, span, msg,|db| {
|
||||
self.lookup(lint, span, msg, |db| {
|
||||
// Now, set up surrounding context.
|
||||
let sess = self.sess();
|
||||
match diagnostic {
|
||||
BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => {
|
||||
let spans: Vec<_> = content
|
||||
.char_indices()
|
||||
.filter_map(|(i, c)| {
|
||||
TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| {
|
||||
let lo = span.lo() + BytePos(2 + i as u32);
|
||||
(c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let (an, s) = match spans.len() {
|
||||
1 => ("an ", ""),
|
||||
_ => ("", "s"),
|
||||
};
|
||||
db.span_label(span, format!(
|
||||
"this comment contains {an}invisible unicode text flow control codepoint{s}",
|
||||
));
|
||||
for (c, span) in &spans {
|
||||
db.span_label(*span, format!("{c:?}"));
|
||||
}
|
||||
db.note(
|
||||
"these kind of unicode codepoints change the way text flows on \
|
||||
applications that support them, but can cause confusion because they \
|
||||
change the order of characters on the screen",
|
||||
);
|
||||
if !spans.is_empty() {
|
||||
db.multipart_suggestion_with_style(
|
||||
"if their presence wasn't intentional, you can remove them",
|
||||
spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(),
|
||||
Applicability::MachineApplicable,
|
||||
SuggestionStyle::HideCodeAlways,
|
||||
);
|
||||
}
|
||||
},
|
||||
BuiltinLintDiagnostics::Normal => (),
|
||||
BuiltinLintDiagnostics::AbsPathWithModule(span) => {
|
||||
let (sugg, app) = match sess.source_map().span_to_snippet(span) {
|
||||
Ok(ref s) => {
|
||||
// FIXME(Manishearth) ideally the emitting code
|
||||
// can tell us whether or not this is global
|
||||
let opt_colon =
|
||||
if s.trim_start().starts_with("::") { "" } else { "::" };
|
||||
|
||||
(format!("crate{opt_colon}{s}"), Applicability::MachineApplicable)
|
||||
}
|
||||
Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
|
||||
};
|
||||
db.span_suggestion(span, "use `crate`", sugg, app);
|
||||
}
|
||||
BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => {
|
||||
db.span_label(
|
||||
span,
|
||||
"names from parent modules are not accessible without an explicit import",
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths(
|
||||
span_def,
|
||||
) => {
|
||||
db.span_note(span_def, "the macro is defined here");
|
||||
}
|
||||
BuiltinLintDiagnostics::ElidedLifetimesInPaths(
|
||||
n,
|
||||
path_span,
|
||||
incl_angl_brckt,
|
||||
insertion_span,
|
||||
) => {
|
||||
add_elided_lifetime_in_path_suggestion(
|
||||
sess.source_map(),
|
||||
db,
|
||||
n,
|
||||
path_span,
|
||||
incl_angl_brckt,
|
||||
insertion_span,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => {
|
||||
db.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect);
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => {
|
||||
if !replaces.is_empty() {
|
||||
db.tool_only_multipart_suggestion(
|
||||
message,
|
||||
replaces,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(span) = in_test_module {
|
||||
db.span_help(
|
||||
self.sess().source_map().guess_head_span(span),
|
||||
"consider adding a `#[cfg(test)]` to the containing module",
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::RedundantImport(spans, ident) => {
|
||||
for (span, is_imported) in spans {
|
||||
let introduced = if is_imported { "imported" } else { "defined" };
|
||||
db.span_label(
|
||||
span,
|
||||
format!("the item `{ident}` is already {introduced} here"),
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
|
||||
stability::deprecation_suggestion(db, "macro", suggestion, span)
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedDocComment(span) => {
|
||||
db.span_label(span, "rustdoc does not generate documentation for macro invocations");
|
||||
db.help("to document an item produced by a macro, \
|
||||
the macro must produce the documentation as part of its expansion");
|
||||
}
|
||||
BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => {
|
||||
db.span_suggestion(span, "remove `mut` from the parameter", ident, Applicability::MachineApplicable);
|
||||
}
|
||||
BuiltinLintDiagnostics::MissingAbi(span, default_abi) => {
|
||||
db.span_label(span, "ABI should be specified here");
|
||||
db.help(format!("the default ABI is {}", default_abi.name()));
|
||||
}
|
||||
BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
|
||||
db.span_label(span, "the attribute is introduced here");
|
||||
}
|
||||
BuiltinLintDiagnostics::ProcMacroBackCompat(note) => {
|
||||
db.note(note);
|
||||
}
|
||||
BuiltinLintDiagnostics::OrPatternsBackCompat(span,suggestion) => {
|
||||
db.span_suggestion(span, "use pat_param to preserve semantics", suggestion, Applicability::MachineApplicable);
|
||||
}
|
||||
BuiltinLintDiagnostics::ReservedPrefix(span) => {
|
||||
db.span_label(span, "unknown prefix");
|
||||
db.span_suggestion_verbose(
|
||||
span.shrink_to_hi(),
|
||||
"insert whitespace here to avoid this being parsed as a prefix in Rust 2021",
|
||||
" ",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedBuiltinAttribute {
|
||||
attr_name,
|
||||
macro_name,
|
||||
invoc_span
|
||||
} => {
|
||||
db.span_note(
|
||||
invoc_span,
|
||||
format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`")
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => {
|
||||
if is_trailing {
|
||||
db.note("macro invocations at the end of a block are treated as expressions");
|
||||
db.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`"));
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => {
|
||||
db.multipart_suggestion(
|
||||
"wrap this expression in parentheses",
|
||||
vec![(span.shrink_to_lo(), "(".to_string()),
|
||||
(span.shrink_to_hi(), ")".to_string())],
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::NamedAsmLabel(help) => {
|
||||
db.help(help);
|
||||
db.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information");
|
||||
},
|
||||
BuiltinLintDiagnostics::UnexpectedCfgName((name, name_span), value) => {
|
||||
let possibilities: Vec<Symbol> = sess.parse_sess.check_config.expecteds.keys().copied().collect();
|
||||
let is_from_cargo = std::env::var_os("CARGO").is_some();
|
||||
let mut is_feature_cfg = name == sym::feature;
|
||||
|
||||
if is_feature_cfg && is_from_cargo {
|
||||
db.help("consider defining some features in `Cargo.toml`");
|
||||
// Suggest the most probable if we found one
|
||||
} else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
|
||||
if let Some(ExpectedValues::Some(best_match_values)) =
|
||||
sess.parse_sess.check_config.expecteds.get(&best_match) {
|
||||
let mut possibilities = best_match_values.iter()
|
||||
.flatten()
|
||||
.map(Symbol::as_str)
|
||||
.collect::<Vec<_>>();
|
||||
possibilities.sort();
|
||||
|
||||
let mut should_print_possibilities = true;
|
||||
if let Some((value, value_span)) = value {
|
||||
if best_match_values.contains(&Some(value)) {
|
||||
db.span_suggestion(name_span, "there is a config with a similar name and value", best_match, Applicability::MaybeIncorrect);
|
||||
should_print_possibilities = false;
|
||||
} else if best_match_values.contains(&None) {
|
||||
db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and no value", best_match, Applicability::MaybeIncorrect);
|
||||
should_print_possibilities = false;
|
||||
} else if let Some(first_value) = possibilities.first() {
|
||||
db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", format!("{best_match} = \"{first_value}\""), Applicability::MaybeIncorrect);
|
||||
} else {
|
||||
db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", best_match, Applicability::MaybeIncorrect);
|
||||
};
|
||||
} else {
|
||||
db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect);
|
||||
}
|
||||
|
||||
if !possibilities.is_empty() && should_print_possibilities {
|
||||
let possibilities = possibilities.join("`, `");
|
||||
db.help(format!("expected values for `{best_match}` are: `{possibilities}`"));
|
||||
}
|
||||
} else {
|
||||
db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect);
|
||||
}
|
||||
|
||||
is_feature_cfg |= best_match == sym::feature;
|
||||
} else if !possibilities.is_empty() {
|
||||
let mut possibilities = possibilities.iter()
|
||||
.map(Symbol::as_str)
|
||||
.collect::<Vec<_>>();
|
||||
possibilities.sort();
|
||||
let possibilities = possibilities.join("`, `");
|
||||
|
||||
// The list of expected names can be long (even by default) and
|
||||
// so the diagnostic produced can take a lot of space. To avoid
|
||||
// cloging the user output we only want to print that diagnostic
|
||||
// once.
|
||||
db.help_once(format!("expected names are: `{possibilities}`"));
|
||||
}
|
||||
|
||||
let inst = if let Some((value, _value_span)) = value {
|
||||
let pre = if is_from_cargo { "\\" } else { "" };
|
||||
format!("cfg({name}, values({pre}\"{value}{pre}\"))")
|
||||
} else {
|
||||
format!("cfg({name})")
|
||||
};
|
||||
|
||||
if is_from_cargo {
|
||||
if !is_feature_cfg {
|
||||
db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
|
||||
}
|
||||
db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
|
||||
} else {
|
||||
db.help(format!("to expect this configuration use `--check-cfg={inst}`"));
|
||||
db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
|
||||
}
|
||||
},
|
||||
BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => {
|
||||
let Some(ExpectedValues::Some(values)) = &sess.parse_sess.check_config.expecteds.get(&name) else {
|
||||
bug!("it shouldn't be possible to have a diagnostic on a value whose name is not in values");
|
||||
};
|
||||
let mut have_none_possibility = false;
|
||||
let possibilities: Vec<Symbol> = values.iter()
|
||||
.inspect(|a| have_none_possibility |= a.is_none())
|
||||
.copied()
|
||||
.flatten()
|
||||
.collect();
|
||||
let is_from_cargo = std::env::var_os("CARGO").is_some();
|
||||
|
||||
// Show the full list if all possible values for a given name, but don't do it
|
||||
// for names as the possibilities could be very long
|
||||
if !possibilities.is_empty() {
|
||||
{
|
||||
let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
|
||||
possibilities.sort();
|
||||
|
||||
let possibilities = possibilities.join("`, `");
|
||||
let none = if have_none_possibility { "(none), " } else { "" };
|
||||
|
||||
db.note(format!("expected values for `{name}` are: {none}`{possibilities}`"));
|
||||
}
|
||||
|
||||
if let Some((value, value_span)) = value {
|
||||
// Suggest the most probable if we found one
|
||||
if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
|
||||
db.span_suggestion(value_span, "there is a expected value with a similar name", format!("\"{best_match}\""), Applicability::MaybeIncorrect);
|
||||
|
||||
}
|
||||
} else if let &[first_possibility] = &possibilities[..] {
|
||||
db.span_suggestion(name_span.shrink_to_hi(), "specify a config value", format!(" = \"{first_possibility}\""), Applicability::MaybeIncorrect);
|
||||
}
|
||||
} else if have_none_possibility {
|
||||
db.note(format!("no expected value for `{name}`"));
|
||||
if let Some((_value, value_span)) = value {
|
||||
db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect);
|
||||
}
|
||||
}
|
||||
|
||||
let inst = if let Some((value, _value_span)) = value {
|
||||
let pre = if is_from_cargo { "\\" } else { "" };
|
||||
format!("cfg({name}, values({pre}\"{value}{pre}\"))")
|
||||
} else {
|
||||
format!("cfg({name})")
|
||||
};
|
||||
|
||||
if is_from_cargo {
|
||||
if name == sym::feature {
|
||||
if let Some((value, _value_span)) = value {
|
||||
db.help(format!("consider adding `{value}` as a feature in `Cargo.toml`"));
|
||||
}
|
||||
} else {
|
||||
db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
|
||||
}
|
||||
db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
|
||||
} else {
|
||||
db.help(format!("to expect this configuration use `--check-cfg={inst}`"));
|
||||
db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
|
||||
}
|
||||
},
|
||||
BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => {
|
||||
db.multipart_suggestion(
|
||||
"move it to the end of the type declaration",
|
||||
vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
db.note(
|
||||
"see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information",
|
||||
);
|
||||
},
|
||||
BuiltinLintDiagnostics::SingleUseLifetime {
|
||||
param_span,
|
||||
use_span: Some((use_span, elide)),
|
||||
deletion_span,
|
||||
} => {
|
||||
debug!(?param_span, ?use_span, ?deletion_span);
|
||||
db.span_label(param_span, "this lifetime...");
|
||||
db.span_label(use_span, "...is used only here");
|
||||
if let Some(deletion_span) = deletion_span {
|
||||
let msg = "elide the single-use lifetime";
|
||||
let (use_span, replace_lt) = if elide {
|
||||
let use_span = sess.source_map().span_extend_while(
|
||||
use_span,
|
||||
char::is_whitespace,
|
||||
).unwrap_or(use_span);
|
||||
(use_span, String::new())
|
||||
} else {
|
||||
(use_span, "'_".to_owned())
|
||||
};
|
||||
debug!(?deletion_span, ?use_span);
|
||||
|
||||
// issue 107998 for the case such as a wrong function pointer type
|
||||
// `deletion_span` is empty and there is no need to report lifetime uses here
|
||||
let suggestions = if deletion_span.is_empty() {
|
||||
vec![(use_span, replace_lt)]
|
||||
} else {
|
||||
vec![(deletion_span, String::new()), (use_span, replace_lt)]
|
||||
};
|
||||
db.multipart_suggestion(
|
||||
msg,
|
||||
suggestions,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
},
|
||||
BuiltinLintDiagnostics::SingleUseLifetime {
|
||||
param_span: _,
|
||||
use_span: None,
|
||||
deletion_span,
|
||||
} => {
|
||||
debug!(?deletion_span);
|
||||
if let Some(deletion_span) = deletion_span {
|
||||
db.span_suggestion(
|
||||
deletion_span,
|
||||
"elide the unused lifetime",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
},
|
||||
BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => {
|
||||
db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string");
|
||||
if let Some(positional_arg_for_msg) = position_sp_for_msg {
|
||||
let msg = format!("this formatting argument uses named argument `{named_arg_name}` by position");
|
||||
db.span_label(positional_arg_for_msg, msg);
|
||||
}
|
||||
|
||||
if let Some(positional_arg_to_replace) = position_sp_to_replace {
|
||||
let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name };
|
||||
let span_to_replace = if let Ok(positional_arg_content) =
|
||||
self.sess().source_map().span_to_snippet(positional_arg_to_replace) && positional_arg_content.starts_with(':') {
|
||||
positional_arg_to_replace.shrink_to_lo()
|
||||
} else {
|
||||
positional_arg_to_replace
|
||||
};
|
||||
db.span_suggestion_verbose(
|
||||
span_to_replace,
|
||||
"use the named argument by name to avoid ambiguity",
|
||||
name,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive => {
|
||||
db.help("consider implementing the trait by hand, or remove the `packed` attribute");
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedExternCrate { removal_span }=> {
|
||||
db.span_suggestion(
|
||||
removal_span,
|
||||
"remove it",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::ExternCrateNotIdiomatic { vis_span, ident_span }=> {
|
||||
let suggestion_span = vis_span.between(ident_span);
|
||||
db.span_suggestion_verbose(
|
||||
suggestion_span,
|
||||
"convert it to a `use`",
|
||||
if vis_span.is_empty() { "use " } else { " use " },
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => {
|
||||
rustc_errors::report_ambiguity_error(db, diag);
|
||||
}
|
||||
BuiltinLintDiagnostics::AmbiguousGlobReexports { name, namespace, first_reexport_span, duplicate_reexport_span } => {
|
||||
db.span_label(first_reexport_span, format!("the name `{name}` in the {namespace} namespace is first re-exported here"));
|
||||
db.span_label(duplicate_reexport_span, format!("but the name `{name}` in the {namespace} namespace is also re-exported here"));
|
||||
}
|
||||
BuiltinLintDiagnostics::HiddenGlobReexports { name, namespace, glob_reexport_span, private_item_span } => {
|
||||
db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here"));
|
||||
db.span_note(private_item_span, "but the private item here shadows it".to_owned());
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedQualifications { removal_span } => {
|
||||
db.span_suggestion_verbose(
|
||||
removal_span,
|
||||
"remove the unnecessary path segments",
|
||||
"",
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::AssociatedConstElidedLifetime { elided, span } => {
|
||||
db.span_suggestion_verbose(
|
||||
if elided { span.shrink_to_hi() } else { span },
|
||||
"use the `'static` lifetime",
|
||||
if elided { "'static " } else { "'static" },
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
},
|
||||
BuiltinLintDiagnostics::RedundantImportVisibility { max_vis, span } => {
|
||||
db.span_note(span, format!("the most public imported item is `{max_vis}`"));
|
||||
db.help("reduce the glob import's visibility or increase visibility of imported items");
|
||||
}
|
||||
}
|
||||
diagnostics::builtin(self.sess(), diagnostic, db);
|
||||
// Rewrap `db`, and pass control to the user.
|
||||
decorate(db)
|
||||
});
|
||||
|
532
compiler/rustc_lint/src/context/diagnostics.rs
Normal file
532
compiler/rustc_lint/src/context/diagnostics.rs
Normal file
@ -0,0 +1,532 @@
|
||||
use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
|
||||
use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder};
|
||||
use rustc_errors::{Applicability, SuggestionStyle};
|
||||
use rustc_middle::middle::stability;
|
||||
use rustc_session::config::ExpectedValues;
|
||||
use rustc_session::lint::BuiltinLintDiagnostics;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::edit_distance::find_best_match_for_name;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use rustc_span::BytePos;
|
||||
|
||||
pub(super) fn builtin(
|
||||
sess: &Session,
|
||||
diagnostic: BuiltinLintDiagnostics,
|
||||
db: &mut DiagnosticBuilder<'_, ()>,
|
||||
) {
|
||||
match diagnostic {
|
||||
BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => {
|
||||
let spans: Vec<_> = content
|
||||
.char_indices()
|
||||
.filter_map(|(i, c)| {
|
||||
TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| {
|
||||
let lo = span.lo() + BytePos(2 + i as u32);
|
||||
(c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let (an, s) = match spans.len() {
|
||||
1 => ("an ", ""),
|
||||
_ => ("", "s"),
|
||||
};
|
||||
db.span_label(
|
||||
span,
|
||||
format!(
|
||||
"this comment contains {an}invisible unicode text flow control codepoint{s}",
|
||||
),
|
||||
);
|
||||
for (c, span) in &spans {
|
||||
db.span_label(*span, format!("{c:?}"));
|
||||
}
|
||||
db.note(
|
||||
"these kind of unicode codepoints change the way text flows on \
|
||||
applications that support them, but can cause confusion because they \
|
||||
change the order of characters on the screen",
|
||||
);
|
||||
if !spans.is_empty() {
|
||||
db.multipart_suggestion_with_style(
|
||||
"if their presence wasn't intentional, you can remove them",
|
||||
spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(),
|
||||
Applicability::MachineApplicable,
|
||||
SuggestionStyle::HideCodeAlways,
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::Normal => (),
|
||||
BuiltinLintDiagnostics::AbsPathWithModule(span) => {
|
||||
let (sugg, app) = match sess.source_map().span_to_snippet(span) {
|
||||
Ok(ref s) => {
|
||||
// FIXME(Manishearth) ideally the emitting code
|
||||
// can tell us whether or not this is global
|
||||
let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" };
|
||||
|
||||
(format!("crate{opt_colon}{s}"), Applicability::MachineApplicable)
|
||||
}
|
||||
Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
|
||||
};
|
||||
db.span_suggestion(span, "use `crate`", sugg, app);
|
||||
}
|
||||
BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => {
|
||||
db.span_label(
|
||||
span,
|
||||
"names from parent modules are not accessible without an explicit import",
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => {
|
||||
db.span_note(span_def, "the macro is defined here");
|
||||
}
|
||||
BuiltinLintDiagnostics::ElidedLifetimesInPaths(
|
||||
n,
|
||||
path_span,
|
||||
incl_angl_brckt,
|
||||
insertion_span,
|
||||
) => {
|
||||
add_elided_lifetime_in_path_suggestion(
|
||||
sess.source_map(),
|
||||
db,
|
||||
n,
|
||||
path_span,
|
||||
incl_angl_brckt,
|
||||
insertion_span,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => {
|
||||
db.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect);
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => {
|
||||
if !replaces.is_empty() {
|
||||
db.tool_only_multipart_suggestion(
|
||||
message,
|
||||
replaces,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(span) = in_test_module {
|
||||
db.span_help(
|
||||
sess.source_map().guess_head_span(span),
|
||||
"consider adding a `#[cfg(test)]` to the containing module",
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::RedundantImport(spans, ident) => {
|
||||
for (span, is_imported) in spans {
|
||||
let introduced = if is_imported { "imported" } else { "defined" };
|
||||
db.span_label(span, format!("the item `{ident}` is already {introduced} here"));
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
|
||||
stability::deprecation_suggestion(db, "macro", suggestion, span)
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedDocComment(span) => {
|
||||
db.span_label(span, "rustdoc does not generate documentation for macro invocations");
|
||||
db.help("to document an item produced by a macro, \
|
||||
the macro must produce the documentation as part of its expansion");
|
||||
}
|
||||
BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => {
|
||||
db.span_suggestion(
|
||||
span,
|
||||
"remove `mut` from the parameter",
|
||||
ident,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::MissingAbi(span, default_abi) => {
|
||||
db.span_label(span, "ABI should be specified here");
|
||||
db.help(format!("the default ABI is {}", default_abi.name()));
|
||||
}
|
||||
BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
|
||||
db.span_label(span, "the attribute is introduced here");
|
||||
}
|
||||
BuiltinLintDiagnostics::ProcMacroBackCompat(note) => {
|
||||
db.note(note);
|
||||
}
|
||||
BuiltinLintDiagnostics::OrPatternsBackCompat(span, suggestion) => {
|
||||
db.span_suggestion(
|
||||
span,
|
||||
"use pat_param to preserve semantics",
|
||||
suggestion,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::ReservedPrefix(span) => {
|
||||
db.span_label(span, "unknown prefix");
|
||||
db.span_suggestion_verbose(
|
||||
span.shrink_to_hi(),
|
||||
"insert whitespace here to avoid this being parsed as a prefix in Rust 2021",
|
||||
" ",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => {
|
||||
db.span_note(
|
||||
invoc_span,
|
||||
format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`")
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => {
|
||||
if is_trailing {
|
||||
db.note("macro invocations at the end of a block are treated as expressions");
|
||||
db.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`"));
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => {
|
||||
db.multipart_suggestion(
|
||||
"wrap this expression in parentheses",
|
||||
vec![
|
||||
(span.shrink_to_lo(), "(".to_string()),
|
||||
(span.shrink_to_hi(), ")".to_string()),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::NamedAsmLabel(help) => {
|
||||
db.help(help);
|
||||
db.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information");
|
||||
}
|
||||
BuiltinLintDiagnostics::UnexpectedCfgName((name, name_span), value) => {
|
||||
let possibilities: Vec<Symbol> =
|
||||
sess.parse_sess.check_config.expecteds.keys().copied().collect();
|
||||
let is_from_cargo = std::env::var_os("CARGO").is_some();
|
||||
let mut is_feature_cfg = name == sym::feature;
|
||||
|
||||
if is_feature_cfg && is_from_cargo {
|
||||
db.help("consider defining some features in `Cargo.toml`");
|
||||
// Suggest the most probable if we found one
|
||||
} else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
|
||||
if let Some(ExpectedValues::Some(best_match_values)) =
|
||||
sess.parse_sess.check_config.expecteds.get(&best_match)
|
||||
{
|
||||
let mut possibilities =
|
||||
best_match_values.iter().flatten().map(Symbol::as_str).collect::<Vec<_>>();
|
||||
possibilities.sort();
|
||||
|
||||
let mut should_print_possibilities = true;
|
||||
if let Some((value, value_span)) = value {
|
||||
if best_match_values.contains(&Some(value)) {
|
||||
db.span_suggestion(
|
||||
name_span,
|
||||
"there is a config with a similar name and value",
|
||||
best_match,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
should_print_possibilities = false;
|
||||
} else if best_match_values.contains(&None) {
|
||||
db.span_suggestion(
|
||||
name_span.to(value_span),
|
||||
"there is a config with a similar name and no value",
|
||||
best_match,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
should_print_possibilities = false;
|
||||
} else if let Some(first_value) = possibilities.first() {
|
||||
db.span_suggestion(
|
||||
name_span.to(value_span),
|
||||
"there is a config with a similar name and different values",
|
||||
format!("{best_match} = \"{first_value}\""),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
} else {
|
||||
db.span_suggestion(
|
||||
name_span.to(value_span),
|
||||
"there is a config with a similar name and different values",
|
||||
best_match,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
};
|
||||
} else {
|
||||
db.span_suggestion(
|
||||
name_span,
|
||||
"there is a config with a similar name",
|
||||
best_match,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
if !possibilities.is_empty() && should_print_possibilities {
|
||||
let possibilities = possibilities.join("`, `");
|
||||
db.help(format!(
|
||||
"expected values for `{best_match}` are: `{possibilities}`"
|
||||
));
|
||||
}
|
||||
} else {
|
||||
db.span_suggestion(
|
||||
name_span,
|
||||
"there is a config with a similar name",
|
||||
best_match,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
|
||||
is_feature_cfg |= best_match == sym::feature;
|
||||
} else if !possibilities.is_empty() {
|
||||
let mut possibilities =
|
||||
possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
|
||||
possibilities.sort();
|
||||
let possibilities = possibilities.join("`, `");
|
||||
|
||||
// The list of expected names can be long (even by default) and
|
||||
// so the diagnostic produced can take a lot of space. To avoid
|
||||
// cloging the user output we only want to print that diagnostic
|
||||
// once.
|
||||
db.help_once(format!("expected names are: `{possibilities}`"));
|
||||
}
|
||||
|
||||
let inst = if let Some((value, _value_span)) = value {
|
||||
let pre = if is_from_cargo { "\\" } else { "" };
|
||||
format!("cfg({name}, values({pre}\"{value}{pre}\"))")
|
||||
} else {
|
||||
format!("cfg({name})")
|
||||
};
|
||||
|
||||
if is_from_cargo {
|
||||
if !is_feature_cfg {
|
||||
db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
|
||||
}
|
||||
db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
|
||||
} else {
|
||||
db.help(format!("to expect this configuration use `--check-cfg={inst}`"));
|
||||
db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => {
|
||||
let Some(ExpectedValues::Some(values)) =
|
||||
&sess.parse_sess.check_config.expecteds.get(&name)
|
||||
else {
|
||||
bug!(
|
||||
"it shouldn't be possible to have a diagnostic on a value whose name is not in values"
|
||||
);
|
||||
};
|
||||
let mut have_none_possibility = false;
|
||||
let possibilities: Vec<Symbol> = values
|
||||
.iter()
|
||||
.inspect(|a| have_none_possibility |= a.is_none())
|
||||
.copied()
|
||||
.flatten()
|
||||
.collect();
|
||||
let is_from_cargo = std::env::var_os("CARGO").is_some();
|
||||
|
||||
// Show the full list if all possible values for a given name, but don't do it
|
||||
// for names as the possibilities could be very long
|
||||
if !possibilities.is_empty() {
|
||||
{
|
||||
let mut possibilities =
|
||||
possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
|
||||
possibilities.sort();
|
||||
|
||||
let possibilities = possibilities.join("`, `");
|
||||
let none = if have_none_possibility { "(none), " } else { "" };
|
||||
|
||||
db.note(format!("expected values for `{name}` are: {none}`{possibilities}`"));
|
||||
}
|
||||
|
||||
if let Some((value, value_span)) = value {
|
||||
// Suggest the most probable if we found one
|
||||
if let Some(best_match) = find_best_match_for_name(&possibilities, value, None)
|
||||
{
|
||||
db.span_suggestion(
|
||||
value_span,
|
||||
"there is a expected value with a similar name",
|
||||
format!("\"{best_match}\""),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
} else if let &[first_possibility] = &possibilities[..] {
|
||||
db.span_suggestion(
|
||||
name_span.shrink_to_hi(),
|
||||
"specify a config value",
|
||||
format!(" = \"{first_possibility}\""),
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
} else if have_none_possibility {
|
||||
db.note(format!("no expected value for `{name}`"));
|
||||
if let Some((_value, value_span)) = value {
|
||||
db.span_suggestion(
|
||||
name_span.shrink_to_hi().to(value_span),
|
||||
"remove the value",
|
||||
"",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let inst = if let Some((value, _value_span)) = value {
|
||||
let pre = if is_from_cargo { "\\" } else { "" };
|
||||
format!("cfg({name}, values({pre}\"{value}{pre}\"))")
|
||||
} else {
|
||||
format!("cfg({name})")
|
||||
};
|
||||
|
||||
if is_from_cargo {
|
||||
if name == sym::feature {
|
||||
if let Some((value, _value_span)) = value {
|
||||
db.help(format!("consider adding `{value}` as a feature in `Cargo.toml`"));
|
||||
}
|
||||
} else {
|
||||
db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`"));
|
||||
}
|
||||
db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration");
|
||||
} else {
|
||||
db.help(format!("to expect this configuration use `--check-cfg={inst}`"));
|
||||
db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration");
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => {
|
||||
db.multipart_suggestion(
|
||||
"move it to the end of the type declaration",
|
||||
vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
db.note(
|
||||
"see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information",
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::SingleUseLifetime {
|
||||
param_span,
|
||||
use_span: Some((use_span, elide)),
|
||||
deletion_span,
|
||||
} => {
|
||||
debug!(?param_span, ?use_span, ?deletion_span);
|
||||
db.span_label(param_span, "this lifetime...");
|
||||
db.span_label(use_span, "...is used only here");
|
||||
if let Some(deletion_span) = deletion_span {
|
||||
let msg = "elide the single-use lifetime";
|
||||
let (use_span, replace_lt) = if elide {
|
||||
let use_span = sess
|
||||
.source_map()
|
||||
.span_extend_while(use_span, char::is_whitespace)
|
||||
.unwrap_or(use_span);
|
||||
(use_span, String::new())
|
||||
} else {
|
||||
(use_span, "'_".to_owned())
|
||||
};
|
||||
debug!(?deletion_span, ?use_span);
|
||||
|
||||
// issue 107998 for the case such as a wrong function pointer type
|
||||
// `deletion_span` is empty and there is no need to report lifetime uses here
|
||||
let suggestions = if deletion_span.is_empty() {
|
||||
vec![(use_span, replace_lt)]
|
||||
} else {
|
||||
vec![(deletion_span, String::new()), (use_span, replace_lt)]
|
||||
};
|
||||
db.multipart_suggestion(msg, suggestions, Applicability::MachineApplicable);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::SingleUseLifetime {
|
||||
param_span: _,
|
||||
use_span: None,
|
||||
deletion_span,
|
||||
} => {
|
||||
debug!(?deletion_span);
|
||||
if let Some(deletion_span) = deletion_span {
|
||||
db.span_suggestion(
|
||||
deletion_span,
|
||||
"elide the unused lifetime",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::NamedArgumentUsedPositionally {
|
||||
position_sp_to_replace,
|
||||
position_sp_for_msg,
|
||||
named_arg_sp,
|
||||
named_arg_name,
|
||||
is_formatting_arg,
|
||||
} => {
|
||||
db.span_label(
|
||||
named_arg_sp,
|
||||
"this named argument is referred to by position in formatting string",
|
||||
);
|
||||
if let Some(positional_arg_for_msg) = position_sp_for_msg {
|
||||
let msg = format!(
|
||||
"this formatting argument uses named argument `{named_arg_name}` by position"
|
||||
);
|
||||
db.span_label(positional_arg_for_msg, msg);
|
||||
}
|
||||
|
||||
if let Some(positional_arg_to_replace) = position_sp_to_replace {
|
||||
let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name };
|
||||
let span_to_replace = if let Ok(positional_arg_content) =
|
||||
sess.source_map().span_to_snippet(positional_arg_to_replace)
|
||||
&& positional_arg_content.starts_with(':')
|
||||
{
|
||||
positional_arg_to_replace.shrink_to_lo()
|
||||
} else {
|
||||
positional_arg_to_replace
|
||||
};
|
||||
db.span_suggestion_verbose(
|
||||
span_to_replace,
|
||||
"use the named argument by name to avoid ambiguity",
|
||||
name,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive => {
|
||||
db.help("consider implementing the trait by hand, or remove the `packed` attribute");
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedExternCrate { removal_span } => {
|
||||
db.span_suggestion(removal_span, "remove it", "", Applicability::MachineApplicable);
|
||||
}
|
||||
BuiltinLintDiagnostics::ExternCrateNotIdiomatic { vis_span, ident_span } => {
|
||||
let suggestion_span = vis_span.between(ident_span);
|
||||
db.span_suggestion_verbose(
|
||||
suggestion_span,
|
||||
"convert it to a `use`",
|
||||
if vis_span.is_empty() { "use " } else { " use " },
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => {
|
||||
rustc_errors::report_ambiguity_error(db, diag);
|
||||
}
|
||||
BuiltinLintDiagnostics::AmbiguousGlobReexports {
|
||||
name,
|
||||
namespace,
|
||||
first_reexport_span,
|
||||
duplicate_reexport_span,
|
||||
} => {
|
||||
db.span_label(
|
||||
first_reexport_span,
|
||||
format!("the name `{name}` in the {namespace} namespace is first re-exported here"),
|
||||
);
|
||||
db.span_label(
|
||||
duplicate_reexport_span,
|
||||
format!(
|
||||
"but the name `{name}` in the {namespace} namespace is also re-exported here"
|
||||
),
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::HiddenGlobReexports {
|
||||
name,
|
||||
namespace,
|
||||
glob_reexport_span,
|
||||
private_item_span,
|
||||
} => {
|
||||
db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here"));
|
||||
db.span_note(private_item_span, "but the private item here shadows it".to_owned());
|
||||
}
|
||||
BuiltinLintDiagnostics::UnusedQualifications { removal_span } => {
|
||||
db.span_suggestion_verbose(
|
||||
removal_span,
|
||||
"remove the unnecessary path segments",
|
||||
"",
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::AssociatedConstElidedLifetime { elided, span } => {
|
||||
db.span_suggestion_verbose(
|
||||
if elided { span.shrink_to_hi() } else { span },
|
||||
"use the `'static` lifetime",
|
||||
if elided { "'static " } else { "'static" },
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
BuiltinLintDiagnostics::RedundantImportVisibility { max_vis, span } => {
|
||||
db.span_note(span, format!("the most public imported item is `{max_vis}`"));
|
||||
db.help("reduce the glob import's visibility or increase visibility of imported items");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user