2021-04-09 12:39:07 +00:00
|
|
|
//! Type inference-based diagnostics.
|
2020-07-14 08:52:18 +00:00
|
|
|
mod expr;
|
2020-07-14 08:18:08 +00:00
|
|
|
mod match_check;
|
2020-07-14 08:52:18 +00:00
|
|
|
mod unsafe_check;
|
2020-10-03 09:48:02 +00:00
|
|
|
mod decl_check;
|
2019-11-27 14:46:02 +00:00
|
|
|
|
2020-10-03 09:48:02 +00:00
|
|
|
use std::{any::Any, fmt};
|
2019-11-27 14:46:02 +00:00
|
|
|
|
2020-12-17 00:19:56 +00:00
|
|
|
use base_db::CrateId;
|
2021-06-12 16:28:19 +00:00
|
|
|
use hir_def::ModuleDefId;
|
|
|
|
use hir_expand::{HirFileId, InFile};
|
2020-08-12 16:26:51 +00:00
|
|
|
use syntax::{ast, AstPtr, SyntaxNodePtr};
|
2019-11-27 14:46:02 +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
|
|
|
use crate::{
|
|
|
|
db::HirDatabase,
|
|
|
|
diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
|
|
|
|
};
|
2020-07-14 08:28:55 +00:00
|
|
|
|
2021-06-12 14:39:46 +00:00
|
|
|
pub use crate::diagnostics::{
|
2021-06-12 16:28:19 +00:00
|
|
|
expr::{
|
|
|
|
record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
|
|
|
|
},
|
2021-06-12 14:39:46 +00:00
|
|
|
unsafe_check::missing_unsafe,
|
|
|
|
};
|
2020-07-14 08:52:18 +00:00
|
|
|
|
2020-10-03 09:48:02 +00:00
|
|
|
pub fn validate_module_item(
|
|
|
|
db: &dyn HirDatabase,
|
2020-12-17 00:19:56 +00:00
|
|
|
krate: CrateId,
|
2020-10-03 09:48:02 +00:00
|
|
|
owner: ModuleDefId,
|
|
|
|
sink: &mut DiagnosticSink<'_>,
|
|
|
|
) {
|
2020-10-04 06:26:39 +00:00
|
|
|
let _p = profile::span("validate_module_item");
|
2020-12-17 00:19:56 +00:00
|
|
|
let mut validator = decl_check::DeclValidator::new(db, krate, sink);
|
|
|
|
validator.validate_item(owner);
|
2020-10-03 09:48:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum CaseType {
|
|
|
|
// `some_var`
|
|
|
|
LowerSnakeCase,
|
|
|
|
// `SOME_CONST`
|
|
|
|
UpperSnakeCase,
|
|
|
|
// `SomeStruct`
|
|
|
|
UpperCamelCase,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for CaseType {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let repr = match self {
|
|
|
|
CaseType::LowerSnakeCase => "snake_case",
|
|
|
|
CaseType::UpperSnakeCase => "UPPER_SNAKE_CASE",
|
2020-10-03 11:47:46 +00:00
|
|
|
CaseType::UpperCamelCase => "CamelCase",
|
2020-10-03 09:48:02 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
write!(f, "{}", repr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-05 15:09:45 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum IdentType {
|
|
|
|
Constant,
|
|
|
|
Enum,
|
|
|
|
Field,
|
|
|
|
Function,
|
2021-05-31 16:09:44 +00:00
|
|
|
Parameter,
|
2021-02-05 15:09:45 +00:00
|
|
|
StaticVariable,
|
|
|
|
Structure,
|
|
|
|
Variable,
|
|
|
|
Variant,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for IdentType {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let repr = match self {
|
|
|
|
IdentType::Constant => "Constant",
|
|
|
|
IdentType::Enum => "Enum",
|
|
|
|
IdentType::Field => "Field",
|
|
|
|
IdentType::Function => "Function",
|
2021-05-31 16:09:44 +00:00
|
|
|
IdentType::Parameter => "Parameter",
|
2021-02-05 15:09:45 +00:00
|
|
|
IdentType::StaticVariable => "Static variable",
|
|
|
|
IdentType::Structure => "Structure",
|
|
|
|
IdentType::Variable => "Variable",
|
|
|
|
IdentType::Variant => "Variant",
|
|
|
|
};
|
|
|
|
|
|
|
|
write!(f, "{}", repr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-17 14:29:06 +00:00
|
|
|
// Diagnostic: incorrect-ident-case
|
|
|
|
//
|
2020-12-28 13:41:15 +00:00
|
|
|
// This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention].
|
2020-10-03 09:48:02 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct IncorrectCase {
|
|
|
|
pub file: HirFileId,
|
2020-10-08 06:27:38 +00:00
|
|
|
pub ident: AstPtr<ast::Name>,
|
2020-10-03 09:48:02 +00:00
|
|
|
pub expected_case: CaseType,
|
2021-02-05 15:09:45 +00:00
|
|
|
pub ident_type: IdentType,
|
2020-10-03 09:48:02 +00:00
|
|
|
pub ident_text: String,
|
|
|
|
pub suggested_text: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Diagnostic for IncorrectCase {
|
|
|
|
fn code(&self) -> DiagnosticCode {
|
|
|
|
DiagnosticCode("incorrect-ident-case")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn message(&self) -> String {
|
|
|
|
format!(
|
2020-10-04 04:39:35 +00:00
|
|
|
"{} `{}` should have {} name, e.g. `{}`",
|
2020-10-03 10:39:10 +00:00
|
|
|
self.ident_type,
|
2020-10-03 09:48:02 +00:00
|
|
|
self.ident_text,
|
|
|
|
self.expected_case.to_string(),
|
|
|
|
self.suggested_text
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn display_source(&self) -> InFile<SyntaxNodePtr> {
|
2020-10-08 06:27:38 +00:00
|
|
|
InFile::new(self.file, self.ident.clone().into())
|
2020-10-03 09:48:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn as_any(&self) -> &(dyn Any + Send + 'static) {
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_experimental(&self) -> bool {
|
2020-10-03 15:01:25 +00:00
|
|
|
true
|
2020-10-03 09:48:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-14 10:05:50 +00:00
|
|
|
#[cfg(test)]
|
2020-07-14 14:43:39 +00:00
|
|
|
mod tests {
|
2020-08-13 14:25:38 +00:00
|
|
|
use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
|
2020-07-14 14:43:39 +00:00
|
|
|
use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
|
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 hir_expand::db::AstDatabase;
|
2020-07-14 10:05:50 +00:00
|
|
|
use rustc_hash::FxHashMap;
|
2020-08-12 16:26:51 +00:00
|
|
|
use syntax::{TextRange, TextSize};
|
2020-07-14 10:05:50 +00:00
|
|
|
|
2020-10-03 09:48:02 +00:00
|
|
|
use crate::{
|
2021-06-12 16:28:19 +00:00
|
|
|
diagnostics::validate_module_item,
|
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
|
|
|
diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder},
|
2020-10-03 09:48:02 +00:00
|
|
|
test_db::TestDB,
|
|
|
|
};
|
2020-07-14 14:43:39 +00:00
|
|
|
|
|
|
|
impl TestDB {
|
|
|
|
fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
|
|
|
|
let crate_graph = self.crate_graph();
|
|
|
|
for krate in crate_graph.iter() {
|
|
|
|
let crate_def_map = self.crate_def_map(krate);
|
|
|
|
|
|
|
|
let mut fns = Vec::new();
|
2021-01-20 14:41:18 +00:00
|
|
|
for (module_id, _) in crate_def_map.modules() {
|
2020-07-14 14:43:39 +00:00
|
|
|
for decl in crate_def_map[module_id].scope.declarations() {
|
2020-10-03 09:48:02 +00:00
|
|
|
let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
|
2020-12-17 00:19:56 +00:00
|
|
|
validate_module_item(self, krate, decl, &mut sink);
|
2020-10-03 09:48:02 +00:00
|
|
|
|
2020-07-14 14:43:39 +00:00
|
|
|
if let ModuleDefId::FunctionId(f) = decl {
|
|
|
|
fns.push(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for impl_id in crate_def_map[module_id].scope.impls() {
|
|
|
|
let impl_data = self.impl_data(impl_id);
|
|
|
|
for item in impl_data.items.iter() {
|
|
|
|
if let AssocItemId::FunctionId(f) = item {
|
2020-10-04 04:39:35 +00:00
|
|
|
let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
|
2020-12-17 00:19:56 +00:00
|
|
|
validate_module_item(
|
|
|
|
self,
|
|
|
|
krate,
|
|
|
|
ModuleDefId::FunctionId(*f),
|
|
|
|
&mut sink,
|
|
|
|
);
|
2020-07-14 14:43:39 +00:00
|
|
|
fns.push(*f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn check_diagnostics(ra_fixture: &str) {
|
|
|
|
let db = TestDB::with_files(ra_fixture);
|
|
|
|
let annotations = db.extract_annotations();
|
|
|
|
|
|
|
|
let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
|
|
|
|
db.diagnostics(|d| {
|
2020-08-11 14:15:11 +00:00
|
|
|
let src = d.display_source();
|
|
|
|
let root = db.parse_or_expand(src.file_id).unwrap();
|
2020-07-27 20:56:57 +00:00
|
|
|
// FIXME: macros...
|
2020-08-11 14:15:11 +00:00
|
|
|
let file_id = src.file_id.original_file(&db);
|
|
|
|
let range = src.value.to_node(&root).text_range();
|
2021-02-05 15:57:26 +00:00
|
|
|
let message = d.message();
|
2020-07-14 14:43:39 +00:00
|
|
|
actual.entry(file_id).or_default().push((range, message));
|
|
|
|
});
|
|
|
|
|
|
|
|
for (file_id, diags) in actual.iter_mut() {
|
|
|
|
diags.sort_by_key(|it| it.0.start());
|
|
|
|
let text = db.file_text(*file_id);
|
|
|
|
// For multiline spans, place them on line start
|
|
|
|
for (range, content) in diags {
|
|
|
|
if text[*range].contains('\n') {
|
|
|
|
*range = TextRange::new(range.start(), range.start() + TextSize::from(1));
|
|
|
|
*content = format!("... {}", content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(annotations, actual);
|
|
|
|
}
|
|
|
|
|
2021-03-10 15:33:18 +00:00
|
|
|
#[test]
|
|
|
|
fn import_extern_crate_clash_with_inner_item() {
|
|
|
|
// This is more of a resolver test, but doesn't really work with the hir_def testsuite.
|
|
|
|
|
|
|
|
check_diagnostics(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs crate:lib deps:jwt
|
|
|
|
mod permissions;
|
|
|
|
|
|
|
|
use permissions::jwt;
|
|
|
|
|
|
|
|
fn f() {
|
|
|
|
fn inner() {}
|
|
|
|
jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic
|
|
|
|
}
|
|
|
|
|
|
|
|
//- /permissions.rs
|
|
|
|
pub mod jwt {
|
|
|
|
pub struct Claims {}
|
|
|
|
}
|
|
|
|
|
|
|
|
//- /jwt/lib.rs crate:jwt
|
|
|
|
pub struct Claims {
|
|
|
|
field: u8,
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
}
|
2020-07-14 10:05:50 +00:00
|
|
|
}
|