2020-04-17 11:06:02 +00:00
|
|
|
//! Collects diagnostics & fixits for a single file.
|
|
|
|
//!
|
|
|
|
//! The tricky bit here is that diagnostics are produced by hir in terms of
|
|
|
|
//! macro-expanded files, but we need to present them to the users in terms of
|
|
|
|
//! original files. So we need to map the ranges.
|
2019-09-30 08:58:53 +00:00
|
|
|
|
2020-08-18 14:22:01 +00:00
|
|
|
mod fixes;
|
2020-10-15 15:07:53 +00:00
|
|
|
mod field_shorthand;
|
2021-03-15 00:39:23 +00:00
|
|
|
mod unlinked_file;
|
2020-08-18 14:03:15 +00:00
|
|
|
|
|
|
|
use std::cell::RefCell;
|
2019-03-24 07:21:36 +00:00
|
|
|
|
2020-10-20 15:49:21 +00:00
|
|
|
use hir::{
|
2021-02-28 11:12:11 +00:00
|
|
|
db::AstDatabase,
|
2020-11-17 15:14:45 +00:00
|
|
|
diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
|
2021-02-28 11:12:11 +00:00
|
|
|
InFile, Semantics,
|
2020-10-20 15:49:21 +00:00
|
|
|
};
|
2021-05-03 14:08:09 +00:00
|
|
|
use ide_assists::AssistResolveStrategy;
|
2021-01-14 21:43:36 +00:00
|
|
|
use ide_db::{base_db::SourceDatabase, RootDatabase};
|
2019-03-21 16:05:15 +00:00
|
|
|
use itertools::Itertools;
|
2020-08-18 14:03:15 +00:00
|
|
|
use rustc_hash::FxHashSet;
|
2020-08-12 16:26:51 +00:00
|
|
|
use syntax::{
|
2020-08-10 21:55:57 +00:00
|
|
|
ast::{self, AstNode},
|
2021-04-09 12:22:38 +00:00
|
|
|
SyntaxNode, SyntaxNodePtr, TextRange, TextSize,
|
2019-03-21 16:05:15 +00:00
|
|
|
};
|
2020-08-12 15:03:06 +00:00
|
|
|
use text_edit::TextEdit;
|
2021-03-15 00:39:23 +00:00
|
|
|
use unlinked_file::UnlinkedFile;
|
2019-03-21 16:05:15 +00:00
|
|
|
|
2021-04-12 14:58:01 +00:00
|
|
|
use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
|
2019-01-08 19:33:36 +00:00
|
|
|
|
2021-05-17 23:11:07 +00:00
|
|
|
use self::fixes::DiagnosticWithFixes;
|
2020-08-18 14:22:01 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Diagnostic {
|
2020-08-18 16:39:43 +00:00
|
|
|
// pub name: Option<String>,
|
2020-08-18 14:22:01 +00:00
|
|
|
pub message: String,
|
|
|
|
pub range: TextRange,
|
|
|
|
pub severity: Severity,
|
2021-05-17 23:11:07 +00:00
|
|
|
pub fixes: Option<Vec<Assist>>,
|
2020-10-20 15:48:43 +00:00
|
|
|
pub unused: bool,
|
2020-11-17 15:14:45 +00:00
|
|
|
pub code: Option<DiagnosticCode>,
|
2020-10-20 15:48:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic {
|
|
|
|
fn error(range: TextRange, message: String) -> Self {
|
2021-05-17 23:11:07 +00:00
|
|
|
Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None }
|
2020-10-20 15:48:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn hint(range: TextRange, message: String) -> Self {
|
2020-11-17 15:14:45 +00:00
|
|
|
Self {
|
|
|
|
message,
|
|
|
|
range,
|
|
|
|
severity: Severity::WeakWarning,
|
2021-05-17 23:11:07 +00:00
|
|
|
fixes: None,
|
2020-11-17 15:14:45 +00:00
|
|
|
unused: false,
|
|
|
|
code: None,
|
|
|
|
}
|
2020-10-20 15:48:43 +00:00
|
|
|
}
|
|
|
|
|
2021-05-17 23:11:07 +00:00
|
|
|
fn with_fixes(self, fixes: Option<Vec<Assist>>) -> Self {
|
|
|
|
Self { fixes, ..self }
|
2020-10-20 15:48:43 +00:00
|
|
|
}
|
2020-10-20 15:49:21 +00:00
|
|
|
|
|
|
|
fn with_unused(self, unused: bool) -> Self {
|
|
|
|
Self { unused, ..self }
|
|
|
|
}
|
2020-11-17 15:14:45 +00:00
|
|
|
|
|
|
|
fn with_code(self, code: Option<DiagnosticCode>) -> Self {
|
|
|
|
Self { code, ..self }
|
|
|
|
}
|
2020-08-18 14:22:01 +00:00
|
|
|
}
|
|
|
|
|
2019-03-23 16:34:49 +00:00
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
pub enum Severity {
|
|
|
|
Error,
|
|
|
|
WeakWarning,
|
|
|
|
}
|
|
|
|
|
2020-08-18 14:03:15 +00:00
|
|
|
#[derive(Default, Debug, Clone)]
|
|
|
|
pub struct DiagnosticsConfig {
|
|
|
|
pub disable_experimental: bool,
|
|
|
|
pub disabled: FxHashSet<String>,
|
|
|
|
}
|
|
|
|
|
2020-07-24 15:39:44 +00:00
|
|
|
pub(crate) fn diagnostics(
|
|
|
|
db: &RootDatabase,
|
2020-08-18 14:03:15 +00:00
|
|
|
config: &DiagnosticsConfig,
|
2021-05-03 15:03:28 +00:00
|
|
|
resolve: &AssistResolveStrategy,
|
2020-07-24 15:39:44 +00:00
|
|
|
file_id: FileId,
|
|
|
|
) -> Vec<Diagnostic> {
|
2020-08-12 14:32:36 +00:00
|
|
|
let _p = profile::span("diagnostics");
|
2020-02-18 17:35:10 +00:00
|
|
|
let sema = Semantics::new(db);
|
2019-05-28 15:46:11 +00:00
|
|
|
let parse = db.parse(file_id);
|
2019-03-21 16:21:00 +00:00
|
|
|
let mut res = Vec::new();
|
|
|
|
|
2020-07-15 14:05:45 +00:00
|
|
|
// [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
|
2020-10-20 15:48:43 +00:00
|
|
|
res.extend(
|
|
|
|
parse
|
|
|
|
.errors()
|
|
|
|
.iter()
|
|
|
|
.take(128)
|
|
|
|
.map(|err| Diagnostic::error(err.range(), format!("Syntax Error: {}", err))),
|
|
|
|
);
|
2019-05-28 15:46:11 +00:00
|
|
|
|
2019-07-12 16:41:13 +00:00
|
|
|
for node in parse.tree().syntax().descendants() {
|
2019-07-19 09:56:47 +00:00
|
|
|
check_unnecessary_braces_in_use_statement(&mut res, file_id, &node);
|
2020-10-15 15:07:53 +00:00
|
|
|
field_shorthand::check(&mut res, file_id, &node);
|
2019-03-21 16:21:00 +00:00
|
|
|
}
|
2019-03-24 07:21:36 +00:00
|
|
|
let res = RefCell::new(res);
|
2020-08-18 14:03:15 +00:00
|
|
|
let sink_builder = DiagnosticSinkBuilder::new()
|
2020-07-24 14:30:12 +00:00
|
|
|
.on::<hir::diagnostics::UnresolvedModule, _>(|d| {
|
2021-04-13 08:48:12 +00:00
|
|
|
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
2019-03-24 07:21:36 +00:00
|
|
|
})
|
2020-07-24 14:30:12 +00:00
|
|
|
.on::<hir::diagnostics::MissingFields, _>(|d| {
|
2021-04-13 08:48:12 +00:00
|
|
|
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
2019-04-10 21:00:56 +00:00
|
|
|
})
|
2020-12-30 17:23:00 +00:00
|
|
|
.on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
|
2021-04-13 08:48:12 +00:00
|
|
|
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
2019-08-10 15:40:48 +00:00
|
|
|
})
|
2020-07-24 14:30:12 +00:00
|
|
|
.on::<hir::diagnostics::NoSuchField, _>(|d| {
|
2021-04-13 08:48:12 +00:00
|
|
|
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
2020-06-09 21:11:16 +00:00
|
|
|
})
|
2020-12-08 18:47:20 +00:00
|
|
|
.on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
|
2021-04-13 08:48:12 +00:00
|
|
|
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
|
2020-12-08 18:47:20 +00:00
|
|
|
})
|
2020-10-03 14:34:52 +00:00
|
|
|
.on::<hir::diagnostics::IncorrectCase, _>(|d| {
|
2021-04-13 08:48:12 +00:00
|
|
|
res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
|
2020-10-03 14:34:52 +00:00
|
|
|
})
|
2020-12-28 13:41:15 +00:00
|
|
|
.on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
|
2021-04-13 08:48:12 +00:00
|
|
|
res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
|
2020-12-28 13:41:15 +00:00
|
|
|
})
|
2020-10-20 16:29:47 +00:00
|
|
|
.on::<hir::diagnostics::InactiveCode, _>(|d| {
|
2020-12-03 19:46:16 +00:00
|
|
|
// If there's inactive code somewhere in a macro, don't propagate to the call-site.
|
|
|
|
if d.display_source().file_id.expansion_info(db).is_some() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-20 15:49:21 +00:00
|
|
|
// Override severity and mark as unused.
|
|
|
|
res.borrow_mut().push(
|
2021-02-28 11:12:11 +00:00
|
|
|
Diagnostic::hint(
|
|
|
|
sema.diagnostics_display_range(d.display_source()).range,
|
|
|
|
d.message(),
|
|
|
|
)
|
|
|
|
.with_unused(true)
|
|
|
|
.with_code(Some(d.code())),
|
2020-10-20 15:49:21 +00:00
|
|
|
);
|
|
|
|
})
|
2021-03-15 00:39:23 +00:00
|
|
|
.on::<UnlinkedFile, _>(|d| {
|
2021-04-09 12:22:38 +00:00
|
|
|
// Limit diagnostic to the first few characters in the file. This matches how VS Code
|
|
|
|
// renders it with the full span, but on other editors, and is less invasive.
|
|
|
|
let range = sema.diagnostics_display_range(d.display_source()).range;
|
|
|
|
let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
|
|
|
|
|
2021-03-15 00:39:23 +00:00
|
|
|
// Override severity and mark as unused.
|
|
|
|
res.borrow_mut().push(
|
2021-04-09 12:22:38 +00:00
|
|
|
Diagnostic::hint(range, d.message())
|
2021-05-17 23:11:07 +00:00
|
|
|
.with_fixes(d.fixes(&sema, resolve))
|
2021-04-09 12:22:38 +00:00
|
|
|
.with_code(Some(d.code())),
|
2021-03-15 00:39:23 +00:00
|
|
|
);
|
|
|
|
})
|
2020-11-26 19:09:54 +00:00
|
|
|
.on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
|
2020-11-27 15:29:40 +00:00
|
|
|
// Use more accurate position if available.
|
2021-02-28 11:12:11 +00:00
|
|
|
let display_range = d
|
|
|
|
.precise_location
|
|
|
|
.unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range);
|
2020-11-27 15:29:40 +00:00
|
|
|
|
2020-11-26 19:09:54 +00:00
|
|
|
// FIXME: it would be nice to tell the user whether proc macros are currently disabled
|
2020-11-27 15:29:40 +00:00
|
|
|
res.borrow_mut()
|
|
|
|
.push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
|
2020-11-26 19:09:54 +00:00
|
|
|
})
|
2021-02-28 11:12:11 +00:00
|
|
|
.on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| {
|
|
|
|
let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| {
|
|
|
|
d.node
|
|
|
|
.to_node(&root)
|
|
|
|
.path()
|
|
|
|
.and_then(|it| it.segment())
|
|
|
|
.and_then(|it| it.name_ref())
|
|
|
|
.map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax())))
|
|
|
|
});
|
|
|
|
let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source());
|
|
|
|
let display_range = sema.diagnostics_display_range(diagnostics).range;
|
|
|
|
res.borrow_mut()
|
|
|
|
.push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code())));
|
|
|
|
})
|
2021-05-30 02:19:47 +00:00
|
|
|
.on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
|
|
|
|
let display_range = sema.diagnostics_display_range(d.display_source()).range;
|
|
|
|
res.borrow_mut()
|
|
|
|
.push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
|
|
|
|
})
|
2020-07-24 15:39:44 +00:00
|
|
|
// Only collect experimental diagnostics when they're enabled.
|
2020-08-18 14:03:15 +00:00
|
|
|
.filter(|diag| !(diag.is_experimental() && config.disable_experimental))
|
2020-08-18 16:39:43 +00:00
|
|
|
.filter(|diag| !config.disabled.contains(diag.code().as_str()));
|
2020-08-07 11:25:55 +00:00
|
|
|
|
|
|
|
// Finalize the `DiagnosticSink` building process.
|
|
|
|
let mut sink = sink_builder
|
2020-07-24 15:39:44 +00:00
|
|
|
// Diagnostics not handled above get no fix and default treatment.
|
2020-07-24 14:30:12 +00:00
|
|
|
.build(|d| {
|
2020-11-17 15:14:45 +00:00
|
|
|
res.borrow_mut().push(
|
2021-02-28 11:12:11 +00:00
|
|
|
Diagnostic::error(
|
|
|
|
sema.diagnostics_display_range(d.display_source()).range,
|
|
|
|
d.message(),
|
|
|
|
)
|
|
|
|
.with_code(Some(d.code())),
|
2020-11-17 15:14:45 +00:00
|
|
|
);
|
2020-07-24 14:30:12 +00:00
|
|
|
});
|
2020-06-09 21:11:16 +00:00
|
|
|
|
2021-03-10 19:30:20 +00:00
|
|
|
match sema.to_module_def(file_id) {
|
|
|
|
Some(m) => m.diagnostics(db, &mut sink),
|
|
|
|
None => {
|
2021-03-15 00:39:23 +00:00
|
|
|
sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(&parse.tree().syntax()) });
|
2021-03-10 19:30:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-24 07:21:36 +00:00
|
|
|
drop(sink);
|
|
|
|
res.into_inner()
|
2019-01-08 19:33:36 +00:00
|
|
|
}
|
2019-03-21 16:05:15 +00:00
|
|
|
|
2021-05-17 23:11:07 +00:00
|
|
|
fn diagnostic_with_fix<D: DiagnosticWithFixes>(
|
2021-04-13 08:48:12 +00:00
|
|
|
d: &D,
|
|
|
|
sema: &Semantics<RootDatabase>,
|
2021-05-03 15:03:28 +00:00
|
|
|
resolve: &AssistResolveStrategy,
|
2021-04-13 08:48:12 +00:00
|
|
|
) -> Diagnostic {
|
2021-02-28 11:12:11 +00:00
|
|
|
Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message())
|
2021-05-17 23:11:07 +00:00
|
|
|
.with_fixes(d.fixes(&sema, resolve))
|
2020-11-17 15:14:45 +00:00
|
|
|
.with_code(Some(d.code()))
|
2020-06-09 21:11:16 +00:00
|
|
|
}
|
|
|
|
|
2021-05-17 23:11:07 +00:00
|
|
|
fn warning_with_fix<D: DiagnosticWithFixes>(
|
2021-04-13 08:48:12 +00:00
|
|
|
d: &D,
|
|
|
|
sema: &Semantics<RootDatabase>,
|
2021-05-03 15:03:28 +00:00
|
|
|
resolve: &AssistResolveStrategy,
|
2021-04-13 08:48:12 +00:00
|
|
|
) -> Diagnostic {
|
2021-02-28 11:12:11 +00:00
|
|
|
Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message())
|
2021-05-17 23:11:07 +00:00
|
|
|
.with_fixes(d.fixes(&sema, resolve))
|
2020-11-17 15:14:45 +00:00
|
|
|
.with_code(Some(d.code()))
|
2020-10-03 14:34:52 +00:00
|
|
|
}
|
|
|
|
|
2019-03-21 16:05:15 +00:00
|
|
|
fn check_unnecessary_braces_in_use_statement(
|
|
|
|
acc: &mut Vec<Diagnostic>,
|
2019-03-21 16:21:00 +00:00
|
|
|
file_id: FileId,
|
2019-03-21 16:05:15 +00:00
|
|
|
node: &SyntaxNode,
|
|
|
|
) -> Option<()> {
|
2019-07-19 09:56:47 +00:00
|
|
|
let use_tree_list = ast::UseTreeList::cast(node.clone())?;
|
2019-03-21 16:05:15 +00:00
|
|
|
if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() {
|
2020-12-30 05:46:34 +00:00
|
|
|
// If there is a comment inside the bracketed `use`,
|
|
|
|
// assume it is a commented out module path and don't show diagnostic.
|
|
|
|
if use_tree_list.has_inner_comment() {
|
|
|
|
return Some(());
|
|
|
|
}
|
|
|
|
|
2020-07-27 12:53:57 +00:00
|
|
|
let use_range = use_tree_list.syntax().text_range();
|
2019-03-21 16:05:15 +00:00
|
|
|
let edit =
|
2019-07-19 09:56:47 +00:00
|
|
|
text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree)
|
2019-03-21 16:05:15 +00:00
|
|
|
.unwrap_or_else(|| {
|
|
|
|
let to_replace = single_use_tree.syntax().text().to_string();
|
2020-08-12 14:58:56 +00:00
|
|
|
let mut edit_builder = TextEdit::builder();
|
2020-07-27 12:53:57 +00:00
|
|
|
edit_builder.delete(use_range);
|
|
|
|
edit_builder.insert(use_range.start(), to_replace);
|
2019-03-21 16:05:15 +00:00
|
|
|
edit_builder.finish()
|
|
|
|
});
|
|
|
|
|
2020-10-20 15:48:43 +00:00
|
|
|
acc.push(
|
|
|
|
Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
|
2021-05-17 23:11:07 +00:00
|
|
|
.with_fixes(Some(vec![fix(
|
2021-04-12 14:58:01 +00:00
|
|
|
"remove_braces",
|
2020-10-20 15:48:43 +00:00
|
|
|
"Remove unnecessary braces",
|
2021-01-14 21:43:36 +00:00
|
|
|
SourceChange::from_text_edit(file_id, edit),
|
2020-10-20 15:48:43 +00:00
|
|
|
use_range,
|
2021-05-17 23:11:07 +00:00
|
|
|
)])),
|
2020-10-20 15:48:43 +00:00
|
|
|
);
|
2019-03-21 16:05:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Some(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
|
|
|
|
single_use_tree: &ast::UseTree,
|
|
|
|
) -> Option<TextEdit> {
|
|
|
|
let use_tree_list_node = single_use_tree.syntax().parent()?;
|
2021-01-15 17:57:32 +00:00
|
|
|
if single_use_tree.path()?.segment()?.self_token().is_some() {
|
2019-07-20 09:58:27 +00:00
|
|
|
let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
|
|
|
|
let end = use_tree_list_node.text_range().end();
|
2020-07-27 12:53:57 +00:00
|
|
|
return Some(TextEdit::delete(TextRange::new(start, end)));
|
2019-03-21 16:05:15 +00:00
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-04-12 14:58:01 +00:00
|
|
|
fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
|
2021-04-13 08:48:12 +00:00
|
|
|
let mut res = unresolved_fix(id, label, target);
|
|
|
|
res.source_change = Some(source_change);
|
|
|
|
res
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
|
2021-04-12 14:58:01 +00:00
|
|
|
assert!(!id.contains(' '));
|
|
|
|
Assist {
|
|
|
|
id: AssistId(id, AssistKind::QuickFix),
|
|
|
|
label: Label::new(label),
|
|
|
|
group: None,
|
|
|
|
target,
|
2021-04-13 08:48:12 +00:00
|
|
|
source_change: None,
|
2021-04-12 14:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-21 16:05:15 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
use expect_test::Expect;
|
2021-06-12 14:17:23 +00:00
|
|
|
use hir::diagnostics::DiagnosticCode;
|
2021-05-03 14:08:09 +00:00
|
|
|
use ide_assists::AssistResolveStrategy;
|
2020-06-23 20:27:24 +00:00
|
|
|
use stdx::trim_indent;
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
use test_utils::{assert_eq_text, extract_annotations};
|
2019-03-25 11:28:04 +00:00
|
|
|
|
2020-10-02 15:34:31 +00:00
|
|
|
use crate::{fixture, DiagnosticsConfig};
|
2019-03-21 16:05:15 +00:00
|
|
|
|
2019-08-17 06:12:50 +00:00
|
|
|
/// Takes a multi-file input fixture with annotated cursor positions,
|
|
|
|
/// and checks that:
|
|
|
|
/// * a diagnostic is produced
|
2021-05-17 23:11:07 +00:00
|
|
|
/// * the first diagnostic fix trigger range touches the input cursor position
|
2019-08-17 06:12:50 +00:00
|
|
|
/// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied
|
2021-05-25 23:01:58 +00:00
|
|
|
#[track_caller]
|
2020-12-24 14:54:44 +00:00
|
|
|
pub(crate) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
|
2021-05-17 23:11:07 +00:00
|
|
|
check_nth_fix(0, ra_fixture_before, ra_fixture_after);
|
|
|
|
}
|
|
|
|
/// Takes a multi-file input fixture with annotated cursor positions,
|
|
|
|
/// and checks that:
|
|
|
|
/// * a diagnostic is produced
|
|
|
|
/// * every diagnostic fixes trigger range touches the input cursor position
|
|
|
|
/// * that the contents of the file containing the cursor match `after` after each diagnostic fix is applied
|
|
|
|
pub(crate) fn check_fixes(ra_fixture_before: &str, ra_fixtures_after: Vec<&str>) {
|
|
|
|
for (i, ra_fixture_after) in ra_fixtures_after.iter().enumerate() {
|
|
|
|
check_nth_fix(i, ra_fixture_before, ra_fixture_after)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-25 23:01:58 +00:00
|
|
|
#[track_caller]
|
2021-05-17 23:11:07 +00:00
|
|
|
fn check_nth_fix(nth: usize, ra_fixture_before: &str, ra_fixture_after: &str) {
|
2020-10-03 14:34:52 +00:00
|
|
|
let after = trim_indent(ra_fixture_after);
|
|
|
|
|
|
|
|
let (analysis, file_position) = fixture::position(ra_fixture_before);
|
|
|
|
let diagnostic = analysis
|
2021-05-03 14:08:09 +00:00
|
|
|
.diagnostics(
|
|
|
|
&DiagnosticsConfig::default(),
|
|
|
|
AssistResolveStrategy::All,
|
|
|
|
file_position.file_id,
|
|
|
|
)
|
2020-10-03 14:34:52 +00:00
|
|
|
.unwrap()
|
|
|
|
.pop()
|
|
|
|
.unwrap();
|
2021-05-17 23:11:07 +00:00
|
|
|
let fix = &diagnostic.fixes.unwrap()[nth];
|
2020-10-03 14:34:52 +00:00
|
|
|
let actual = {
|
2021-05-17 23:11:07 +00:00
|
|
|
let source_change = fix.source_change.as_ref().unwrap();
|
2021-04-12 14:58:01 +00:00
|
|
|
let file_id = *source_change.source_file_edits.keys().next().unwrap();
|
2020-12-24 16:36:13 +00:00
|
|
|
let mut actual = analysis.file_text(file_id).unwrap().to_string();
|
|
|
|
|
2021-04-12 14:58:01 +00:00
|
|
|
for edit in source_change.source_file_edits.values() {
|
2021-01-14 17:35:22 +00:00
|
|
|
edit.apply(&mut actual);
|
2020-10-03 14:34:52 +00:00
|
|
|
}
|
|
|
|
actual
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq_text!(&after, &actual);
|
|
|
|
assert!(
|
2021-04-12 14:58:01 +00:00
|
|
|
fix.target.contains_inclusive(file_position.offset),
|
2020-10-03 14:34:52 +00:00
|
|
|
"diagnostic fix range {:?} does not touch cursor position {:?}",
|
2021-04-12 14:58:01 +00:00
|
|
|
fix.target,
|
2020-10-03 14:34:52 +00:00
|
|
|
file_position.offset
|
|
|
|
);
|
|
|
|
}
|
2021-03-15 00:39:23 +00:00
|
|
|
/// Checks that there's a diagnostic *without* fix at `$0`.
|
|
|
|
fn check_no_fix(ra_fixture: &str) {
|
|
|
|
let (analysis, file_position) = fixture::position(ra_fixture);
|
|
|
|
let diagnostic = analysis
|
2021-05-03 14:08:09 +00:00
|
|
|
.diagnostics(
|
|
|
|
&DiagnosticsConfig::default(),
|
|
|
|
AssistResolveStrategy::All,
|
|
|
|
file_position.file_id,
|
|
|
|
)
|
2021-03-15 00:39:23 +00:00
|
|
|
.unwrap()
|
|
|
|
.pop()
|
|
|
|
.unwrap();
|
2021-05-17 23:11:07 +00:00
|
|
|
assert!(diagnostic.fixes.is_none(), "got a fix when none was expected: {:?}", diagnostic);
|
2021-03-15 00:39:23 +00:00
|
|
|
}
|
|
|
|
|
2019-08-17 06:12:50 +00:00
|
|
|
/// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
|
|
|
|
/// apply to the file containing the cursor.
|
2020-10-15 15:07:53 +00:00
|
|
|
pub(crate) fn check_no_diagnostics(ra_fixture: &str) {
|
2020-10-02 15:34:31 +00:00
|
|
|
let (analysis, files) = fixture::files(ra_fixture);
|
2020-07-09 12:33:03 +00:00
|
|
|
let diagnostics = files
|
|
|
|
.into_iter()
|
2020-08-18 14:03:15 +00:00
|
|
|
.flat_map(|file_id| {
|
2021-05-03 14:08:09 +00:00
|
|
|
analysis
|
|
|
|
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
|
|
|
|
.unwrap()
|
2020-08-18 14:03:15 +00:00
|
|
|
})
|
2020-07-09 12:33:03 +00:00
|
|
|
.collect::<Vec<_>>();
|
|
|
|
assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
|
|
|
|
}
|
|
|
|
|
2021-05-17 09:04:17 +00:00
|
|
|
pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
|
2020-10-02 15:34:31 +00:00
|
|
|
let (analysis, file_id) = fixture::file(ra_fixture);
|
2021-05-03 14:08:09 +00:00
|
|
|
let diagnostics = analysis
|
|
|
|
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
|
|
|
|
.unwrap();
|
2020-07-09 12:33:03 +00:00
|
|
|
expect.assert_debug_eq(&diagnostics)
|
2019-04-10 21:00:56 +00:00
|
|
|
}
|
|
|
|
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
pub(crate) fn check_diagnostics(ra_fixture: &str) {
|
|
|
|
let (analysis, file_id) = fixture::file(ra_fixture);
|
|
|
|
let diagnostics = analysis
|
|
|
|
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
|
2021-06-12 14:17:23 +00:00
|
|
|
let mut actual = diagnostics
|
|
|
|
.into_iter()
|
|
|
|
.filter(|d| d.code != Some(DiagnosticCode("inactive-code")))
|
|
|
|
.map(|d| (d.range, d.message))
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
actual.sort_by_key(|(range, _)| range.start());
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
assert_eq!(expected, actual);
|
|
|
|
}
|
|
|
|
|
2021-02-28 11:12:11 +00:00
|
|
|
#[test]
|
|
|
|
fn test_unresolved_macro_range() {
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
foo::bar!(92);
|
|
|
|
//^^^ unresolved macro `foo::bar!`
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unresolved_import_in_use_tree() {
|
|
|
|
// Only the relevant part of a nested `use` item should be highlighted.
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
use does_exist::{Exists, DoesntExist};
|
|
|
|
//^^^^^^^^^^^ unresolved import
|
|
|
|
|
|
|
|
use {does_not_exist::*, does_exist};
|
|
|
|
//^^^^^^^^^^^^^^^^^ unresolved import
|
|
|
|
|
|
|
|
use does_not_exist::{
|
|
|
|
a,
|
|
|
|
//^ unresolved import
|
|
|
|
b,
|
|
|
|
//^ unresolved import
|
|
|
|
c,
|
|
|
|
//^ unresolved import
|
|
|
|
};
|
|
|
|
|
|
|
|
mod does_exist {
|
|
|
|
pub struct Exists;
|
|
|
|
}
|
|
|
|
"#,
|
2020-07-09 12:33:03 +00:00
|
|
|
);
|
2019-03-25 11:28:04 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 11:06:02 +00:00
|
|
|
#[test]
|
|
|
|
fn range_mapping_out_of_macros() {
|
2020-07-09 12:33:03 +00:00
|
|
|
// FIXME: this is very wrong, but somewhat tricky to fix.
|
|
|
|
check_fix(
|
|
|
|
r#"
|
|
|
|
fn some() {}
|
|
|
|
fn items() {}
|
|
|
|
fn here() {}
|
2020-04-17 11:06:02 +00:00
|
|
|
|
2020-07-09 12:33:03 +00:00
|
|
|
macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
|
2020-04-17 11:06:02 +00:00
|
|
|
|
2020-07-09 12:33:03 +00:00
|
|
|
fn main() {
|
2021-01-06 20:15:48 +00:00
|
|
|
let _x = id![Foo { a: $042 }];
|
2020-07-09 12:33:03 +00:00
|
|
|
}
|
2020-04-17 11:06:02 +00:00
|
|
|
|
2020-07-09 12:33:03 +00:00
|
|
|
pub struct Foo { pub a: i32, pub b: i32 }
|
|
|
|
"#,
|
|
|
|
r#"
|
2021-05-16 15:10:56 +00:00
|
|
|
fn some(, b: () ) {}
|
2020-07-09 12:33:03 +00:00
|
|
|
fn items() {}
|
|
|
|
fn here() {}
|
|
|
|
|
|
|
|
macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let _x = id![Foo { a: 42 }];
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Foo { pub a: i32, pub b: i32 }
|
|
|
|
"#,
|
2020-04-17 11:06:02 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-03-21 16:05:15 +00:00
|
|
|
#[test]
|
|
|
|
fn test_check_unnecessary_braces_in_use_statement() {
|
2020-07-09 12:33:03 +00:00
|
|
|
check_no_diagnostics(
|
|
|
|
r#"
|
|
|
|
use a;
|
|
|
|
use a::{c, d::e};
|
2020-09-16 15:24:34 +00:00
|
|
|
|
2020-12-30 05:52:47 +00:00
|
|
|
mod a {
|
|
|
|
mod c {}
|
|
|
|
mod d {
|
|
|
|
mod e {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
check_no_diagnostics(
|
|
|
|
r#"
|
|
|
|
use a;
|
|
|
|
use a::{
|
|
|
|
c,
|
|
|
|
// d::e
|
|
|
|
};
|
|
|
|
|
2020-09-16 15:24:34 +00:00
|
|
|
mod a {
|
|
|
|
mod c {}
|
|
|
|
mod d {
|
|
|
|
mod e {}
|
|
|
|
}
|
|
|
|
}
|
2020-07-09 12:33:03 +00:00
|
|
|
"#,
|
2019-03-21 16:05:15 +00:00
|
|
|
);
|
2020-09-16 15:24:34 +00:00
|
|
|
check_fix(
|
|
|
|
r"
|
|
|
|
mod b {}
|
2021-01-06 20:15:48 +00:00
|
|
|
use {$0b};
|
2020-09-16 15:24:34 +00:00
|
|
|
",
|
|
|
|
r"
|
|
|
|
mod b {}
|
|
|
|
use b;
|
|
|
|
",
|
|
|
|
);
|
|
|
|
check_fix(
|
|
|
|
r"
|
|
|
|
mod b {}
|
2021-01-06 20:15:48 +00:00
|
|
|
use {b$0};
|
2020-09-16 15:24:34 +00:00
|
|
|
",
|
|
|
|
r"
|
|
|
|
mod b {}
|
|
|
|
use b;
|
|
|
|
",
|
|
|
|
);
|
|
|
|
check_fix(
|
|
|
|
r"
|
|
|
|
mod a { mod c {} }
|
2021-01-06 20:15:48 +00:00
|
|
|
use a::{c$0};
|
2020-09-16 15:24:34 +00:00
|
|
|
",
|
|
|
|
r"
|
|
|
|
mod a { mod c {} }
|
|
|
|
use a::c;
|
|
|
|
",
|
|
|
|
);
|
|
|
|
check_fix(
|
|
|
|
r"
|
|
|
|
mod a {}
|
2021-01-06 20:15:48 +00:00
|
|
|
use a::{self$0};
|
2020-09-16 15:24:34 +00:00
|
|
|
",
|
|
|
|
r"
|
|
|
|
mod a {}
|
|
|
|
use a;
|
|
|
|
",
|
|
|
|
);
|
|
|
|
check_fix(
|
|
|
|
r"
|
|
|
|
mod a { mod c {} mod d { mod e {} } }
|
2021-01-06 20:15:48 +00:00
|
|
|
use a::{c, d::{e$0}};
|
2020-09-16 15:24:34 +00:00
|
|
|
",
|
|
|
|
r"
|
|
|
|
mod a { mod c {} mod d { mod e {} } }
|
|
|
|
use a::{c, d::e};
|
|
|
|
",
|
|
|
|
);
|
2019-03-21 16:05:15 +00:00
|
|
|
}
|
|
|
|
|
2020-08-07 11:26:36 +00:00
|
|
|
#[test]
|
|
|
|
fn test_disabled_diagnostics() {
|
2020-08-18 16:39:43 +00:00
|
|
|
let mut config = DiagnosticsConfig::default();
|
|
|
|
config.disabled.insert("unresolved-module".into());
|
|
|
|
|
2020-10-02 15:34:31 +00:00
|
|
|
let (analysis, file_id) = fixture::file(r#"mod foo;"#);
|
2020-08-18 16:39:43 +00:00
|
|
|
|
2021-05-03 14:08:09 +00:00
|
|
|
let diagnostics =
|
|
|
|
analysis.diagnostics(&config, AssistResolveStrategy::All, file_id).unwrap();
|
2020-08-18 16:39:43 +00:00
|
|
|
assert!(diagnostics.is_empty());
|
|
|
|
|
2021-05-03 14:08:09 +00:00
|
|
|
let diagnostics = analysis
|
|
|
|
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
|
|
|
|
.unwrap();
|
2020-08-18 16:39:43 +00:00
|
|
|
assert!(!diagnostics.is_empty());
|
2020-08-07 11:26:36 +00:00
|
|
|
}
|
2020-10-03 14:34:52 +00:00
|
|
|
|
2021-03-15 00:39:23 +00:00
|
|
|
#[test]
|
|
|
|
fn unlinked_file_prepend_first_item() {
|
|
|
|
cov_mark::check!(unlinked_file_prepend_before_first_item);
|
2021-05-17 23:11:07 +00:00
|
|
|
// Only tests the first one for `pub mod` since the rest are the same
|
|
|
|
check_fixes(
|
2021-03-15 00:39:23 +00:00
|
|
|
r#"
|
|
|
|
//- /main.rs
|
|
|
|
fn f() {}
|
|
|
|
//- /foo.rs
|
|
|
|
$0
|
|
|
|
"#,
|
2021-05-17 23:11:07 +00:00
|
|
|
vec![
|
|
|
|
r#"
|
2021-03-15 00:39:23 +00:00
|
|
|
mod foo;
|
|
|
|
|
|
|
|
fn f() {}
|
|
|
|
"#,
|
2021-05-17 23:11:07 +00:00
|
|
|
r#"
|
|
|
|
pub mod foo;
|
|
|
|
|
|
|
|
fn f() {}
|
|
|
|
"#,
|
|
|
|
],
|
2021-03-15 00:39:23 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlinked_file_append_mod() {
|
|
|
|
cov_mark::check!(unlinked_file_append_to_existing_mods);
|
|
|
|
check_fix(
|
|
|
|
r#"
|
|
|
|
//- /main.rs
|
|
|
|
//! Comment on top
|
|
|
|
|
|
|
|
mod preexisting;
|
|
|
|
|
|
|
|
mod preexisting2;
|
|
|
|
|
|
|
|
struct S;
|
|
|
|
|
|
|
|
mod preexisting_bottom;)
|
|
|
|
//- /foo.rs
|
|
|
|
$0
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
//! Comment on top
|
|
|
|
|
|
|
|
mod preexisting;
|
|
|
|
|
|
|
|
mod preexisting2;
|
|
|
|
mod foo;
|
|
|
|
|
|
|
|
struct S;
|
|
|
|
|
|
|
|
mod preexisting_bottom;)
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlinked_file_insert_in_empty_file() {
|
|
|
|
cov_mark::check!(unlinked_file_empty_file);
|
|
|
|
check_fix(
|
|
|
|
r#"
|
|
|
|
//- /main.rs
|
|
|
|
//- /foo.rs
|
|
|
|
$0
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
mod foo;
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlinked_file_old_style_modrs() {
|
|
|
|
check_fix(
|
|
|
|
r#"
|
|
|
|
//- /main.rs
|
|
|
|
mod submod;
|
|
|
|
//- /submod/mod.rs
|
|
|
|
// in mod.rs
|
|
|
|
//- /submod/foo.rs
|
|
|
|
$0
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
// in mod.rs
|
|
|
|
mod foo;
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlinked_file_new_style_mod() {
|
|
|
|
check_fix(
|
|
|
|
r#"
|
|
|
|
//- /main.rs
|
|
|
|
mod submod;
|
|
|
|
//- /submod.rs
|
|
|
|
//- /submod/foo.rs
|
|
|
|
$0
|
|
|
|
"#,
|
|
|
|
r#"
|
|
|
|
mod foo;
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlinked_file_with_cfg_off() {
|
|
|
|
cov_mark::check!(unlinked_file_skip_fix_when_mod_already_exists);
|
|
|
|
check_no_fix(
|
|
|
|
r#"
|
|
|
|
//- /main.rs
|
|
|
|
#[cfg(never)]
|
|
|
|
mod foo;
|
|
|
|
|
|
|
|
//- /foo.rs
|
|
|
|
$0
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlinked_file_with_cfg_on() {
|
|
|
|
check_no_diagnostics(
|
|
|
|
r#"
|
|
|
|
//- /main.rs
|
|
|
|
#[cfg(not(never))]
|
|
|
|
mod foo;
|
|
|
|
|
|
|
|
//- /foo.rs
|
2021-06-12 14:17:23 +00:00
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn break_outside_of_loop() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
fn foo() { break; }
|
|
|
|
//^^^^^ break outside of loop
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn no_such_field_diagnostics() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
struct S { foo: i32, bar: () }
|
|
|
|
impl S {
|
|
|
|
fn new() -> S {
|
|
|
|
S {
|
|
|
|
//^ Missing structure fields:
|
|
|
|
//| - bar
|
|
|
|
foo: 92,
|
|
|
|
baz: 62,
|
|
|
|
//^^^^^^^ no such field
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
#[test]
|
|
|
|
fn no_such_field_with_feature_flag_diagnostics() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs crate:foo cfg:feature=foo
|
|
|
|
struct MyStruct {
|
|
|
|
my_val: usize,
|
|
|
|
#[cfg(feature = "foo")]
|
|
|
|
bar: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MyStruct {
|
|
|
|
#[cfg(feature = "foo")]
|
|
|
|
pub(crate) fn new(my_val: usize, bar: bool) -> Self {
|
|
|
|
Self { my_val, bar }
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "foo"))]
|
|
|
|
pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
|
|
|
|
Self { my_val }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn no_such_field_enum_with_feature_flag_diagnostics() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs crate:foo cfg:feature=foo
|
|
|
|
enum Foo {
|
|
|
|
#[cfg(not(feature = "foo"))]
|
|
|
|
Buz,
|
|
|
|
#[cfg(feature = "foo")]
|
|
|
|
Bar,
|
|
|
|
Baz
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_fn(f: Foo) {
|
|
|
|
match f {
|
|
|
|
Foo::Bar => {},
|
|
|
|
Foo::Baz => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs crate:foo cfg:feature=foo
|
|
|
|
struct S {
|
|
|
|
#[cfg(feature = "foo")]
|
|
|
|
foo: u32,
|
|
|
|
#[cfg(not(feature = "foo"))]
|
|
|
|
bar: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl S {
|
|
|
|
#[cfg(feature = "foo")]
|
|
|
|
fn new(foo: u32) -> Self {
|
|
|
|
Self { foo }
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "foo"))]
|
|
|
|
fn new(bar: u32) -> Self {
|
|
|
|
Self { bar }
|
|
|
|
}
|
|
|
|
fn new2(bar: u32) -> Self {
|
|
|
|
#[cfg(feature = "foo")]
|
|
|
|
{ Self { foo: bar } }
|
|
|
|
#[cfg(not(feature = "foo"))]
|
|
|
|
{ Self { bar } }
|
|
|
|
}
|
|
|
|
fn new2(val: u32) -> Self {
|
|
|
|
Self {
|
|
|
|
#[cfg(feature = "foo")]
|
|
|
|
foo: val,
|
|
|
|
#[cfg(not(feature = "foo"))]
|
|
|
|
bar: val,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn no_such_field_with_type_macro() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
macro_rules! Type { () => { u32 }; }
|
|
|
|
struct Foo { bar: Type![] }
|
|
|
|
|
|
|
|
impl Foo {
|
|
|
|
fn new() -> Self {
|
|
|
|
Foo { bar: 0 }
|
|
|
|
}
|
|
|
|
}
|
2021-06-12 14:39:46 +00:00
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn missing_unsafe_diagnostic_with_raw_ptr() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
fn main() {
|
|
|
|
let x = &5 as *const usize;
|
|
|
|
unsafe { let y = *x; }
|
|
|
|
let z = *x;
|
|
|
|
} //^^ This operation is unsafe and requires an unsafe function or block
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn missing_unsafe_diagnostic_with_unsafe_call() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
struct HasUnsafe;
|
|
|
|
|
|
|
|
impl HasUnsafe {
|
|
|
|
unsafe fn unsafe_fn(&self) {
|
|
|
|
let x = &5 as *const usize;
|
|
|
|
let y = *x;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn unsafe_fn() {
|
|
|
|
let x = &5 as *const usize;
|
|
|
|
let y = *x;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
unsafe_fn();
|
|
|
|
//^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
|
|
|
HasUnsafe.unsafe_fn();
|
|
|
|
//^^^^^^^^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
|
|
|
unsafe {
|
|
|
|
unsafe_fn();
|
|
|
|
HasUnsafe.unsafe_fn();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn missing_unsafe_diagnostic_with_static_mut() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
struct Ty {
|
|
|
|
a: u8,
|
|
|
|
}
|
|
|
|
|
|
|
|
static mut STATIC_MUT: Ty = Ty { a: 0 };
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let x = STATIC_MUT.a;
|
|
|
|
//^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
|
|
|
unsafe {
|
|
|
|
let x = STATIC_MUT.a;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn no_missing_unsafe_diagnostic_with_safe_intrinsic() {
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
extern "rust-intrinsic" {
|
|
|
|
pub fn bitreverse(x: u32) -> u32; // Safe intrinsic
|
|
|
|
pub fn floorf32(x: f32) -> f32; // Unsafe intrinsic
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let _ = bitreverse(12);
|
|
|
|
let _ = floorf32(12.0);
|
|
|
|
//^^^^^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
|
|
|
|
}
|
2021-03-15 00:39:23 +00:00
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
2019-03-21 16:05:15 +00:00
|
|
|
}
|