Skip to content

Commit 50b7185

Browse files
committed
Auto merge of rust-lang#18099 - ChayimFriedman2:diag-only-necessary, r=Veykril
Use more correct handling of lint attributes The previous analysis was top-down, and worked on a single file (expanding macros). The new analysis is bottom-up, starting from the diagnostics and climbing up the syntax and module tree. While this is more efficient (and in fact, efficiency was the motivating reason to work on this), unfortunately the code was already fast enough. But luckily, it also fixes a correctness problem: outline parent modules' attributes were not respected for the previous analysis. Case lints specifically did their own analysis to accommodate that, but it was limited to only them. The new analysis works on all kinds of lints, present and future. It was basically impossible to fix the old analysis without rewriting it because navigating the module hierarchy must come bottom-up, and if we already have a bottom-up analysis (including syntax analysis because modules can be nested in other syntax elements, including macros), it makes sense to use only this kind of analysis. Few other bugs (not fundamental to the previous analysis) are also fixed, e.g. overwriting of lint levels (i.e. `#[allow(lint)] mod foo { #[warn(lint)] mod bar; }`. After this PR is merged I intend to work on an editor command that does workspace-wide diagnostics analysis (that is, `rust-analyzer diagnostics` but from your editor and without having to spawn a new process, which will have to analyze the workspace from scratch). This can be useful to users who do not want to enable check on save because of its overhead, but want to see workspace wide diagnostics from r-a (or to maintainers of rust-analyzer). Closes rust-lang#18086. Closes rust-lang#18081. Fixes rust-lang#18056.
2 parents 3831b72 + a933329 commit 50b7185

File tree

6 files changed

+403
-279
lines changed

6 files changed

+403
-279
lines changed

src/tools/rust-analyzer/crates/hir-def/src/nameres.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ use la_arena::Arena;
6969
use rustc_hash::{FxHashMap, FxHashSet};
7070
use span::{Edition, EditionedFileId, FileAstId, FileId, ROOT_ERASED_FILE_AST_ID};
7171
use stdx::format_to;
72-
use syntax::{ast, SmolStr};
72+
use syntax::{ast, AstNode, SmolStr, SyntaxNode};
7373
use triomphe::Arc;
7474
use tt::TextRange;
7575

@@ -291,7 +291,7 @@ impl ModuleOrigin {
291291

292292
/// Returns a node which defines this module.
293293
/// That is, a file or a `mod foo {}` with items.
294-
fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> {
294+
pub fn definition_source(&self, db: &dyn DefDatabase) -> InFile<ModuleSource> {
295295
match self {
296296
&ModuleOrigin::File { definition, .. } | &ModuleOrigin::CrateRoot { definition } => {
297297
let sf = db.parse(definition).tree();
@@ -728,6 +728,16 @@ pub enum ModuleSource {
728728
BlockExpr(ast::BlockExpr),
729729
}
730730

731+
impl ModuleSource {
732+
pub fn node(&self) -> SyntaxNode {
733+
match self {
734+
ModuleSource::SourceFile(it) => it.syntax().clone(),
735+
ModuleSource::Module(it) => it.syntax().clone(),
736+
ModuleSource::BlockExpr(it) => it.syntax().clone(),
737+
}
738+
}
739+
}
740+
731741
/// See `sub_namespace_match()`.
732742
#[derive(Clone, Copy, PartialEq, Eq)]
733743
pub enum MacroSubNs {

src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs

+11-141
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ mod case_conv;
1616
use std::fmt;
1717

1818
use hir_def::{
19-
data::adt::VariantData, db::DefDatabase, hir::Pat, src::HasSource, AdtId, AttrDefId, ConstId,
20-
EnumId, EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId,
21-
StaticId, StructId, TraitId, TypeAliasId,
19+
data::adt::VariantData, db::DefDatabase, hir::Pat, src::HasSource, AdtId, ConstId, EnumId,
20+
EnumVariantId, FunctionId, HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, StaticId,
21+
StructId, TraitId, TypeAliasId,
2222
};
2323
use hir_expand::{
2424
name::{AsName, Name},
25-
HirFileId, HirFileIdExt, MacroFileIdExt,
25+
HirFileId, HirFileIdExt,
2626
};
27-
use intern::sym;
2827
use stdx::{always, never};
2928
use syntax::{
3029
ast::{self, HasName},
@@ -36,14 +35,6 @@ use crate::db::HirDatabase;
3635

3736
use self::case_conv::{to_camel_case, to_lower_snake_case, to_upper_snake_case};
3837

39-
mod allow {
40-
pub(super) const BAD_STYLE: &str = "bad_style";
41-
pub(super) const NONSTANDARD_STYLE: &str = "nonstandard_style";
42-
pub(super) const NON_SNAKE_CASE: &str = "non_snake_case";
43-
pub(super) const NON_UPPER_CASE_GLOBAL: &str = "non_upper_case_globals";
44-
pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types";
45-
}
46-
4738
pub fn incorrect_case(db: &dyn HirDatabase, owner: ModuleDefId) -> Vec<IncorrectCase> {
4839
let _p = tracing::info_span!("incorrect_case").entered();
4940
let mut validator = DeclValidator::new(db);
@@ -160,92 +151,7 @@ impl<'a> DeclValidator<'a> {
160151
}
161152
}
162153

163-
/// Checks whether not following the convention is allowed for this item.
164-
fn allowed(&self, id: AttrDefId, allow_name: &str, recursing: bool) -> bool {
165-
let is_allowed = |def_id| {
166-
let attrs = self.db.attrs(def_id);
167-
// don't bug the user about directly no_mangle annotated stuff, they can't do anything about it
168-
(!recursing && attrs.by_key(&sym::no_mangle).exists())
169-
|| attrs.by_key(&sym::allow).tt_values().any(|tt| {
170-
let allows = tt.to_string();
171-
allows.contains(allow_name)
172-
|| allows.contains(allow::BAD_STYLE)
173-
|| allows.contains(allow::NONSTANDARD_STYLE)
174-
})
175-
};
176-
let db = self.db.upcast();
177-
let file_id_is_derive = || {
178-
match id {
179-
AttrDefId::ModuleId(m) => {
180-
m.def_map(db)[m.local_id].origin.file_id().map(Into::into)
181-
}
182-
AttrDefId::FunctionId(f) => Some(f.lookup(db).id.file_id()),
183-
AttrDefId::StaticId(sid) => Some(sid.lookup(db).id.file_id()),
184-
AttrDefId::ConstId(cid) => Some(cid.lookup(db).id.file_id()),
185-
AttrDefId::TraitId(tid) => Some(tid.lookup(db).id.file_id()),
186-
AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).id.file_id()),
187-
AttrDefId::ImplId(iid) => Some(iid.lookup(db).id.file_id()),
188-
AttrDefId::ExternBlockId(id) => Some(id.lookup(db).id.file_id()),
189-
AttrDefId::ExternCrateId(id) => Some(id.lookup(db).id.file_id()),
190-
AttrDefId::UseId(id) => Some(id.lookup(db).id.file_id()),
191-
// These warnings should not explore macro definitions at all
192-
AttrDefId::MacroId(_) => None,
193-
AttrDefId::AdtId(aid) => match aid {
194-
AdtId::StructId(sid) => Some(sid.lookup(db).id.file_id()),
195-
AdtId::EnumId(eid) => Some(eid.lookup(db).id.file_id()),
196-
// Unions aren't yet supported
197-
AdtId::UnionId(_) => None,
198-
},
199-
AttrDefId::FieldId(_) => None,
200-
AttrDefId::EnumVariantId(_) => None,
201-
AttrDefId::TypeAliasId(_) => None,
202-
AttrDefId::GenericParamId(_) => None,
203-
}
204-
.map_or(false, |file_id| {
205-
matches!(file_id.macro_file(), Some(file_id) if file_id.is_custom_derive(db.upcast()) || file_id.is_builtin_derive(db.upcast()))
206-
})
207-
};
208-
209-
let parent = || {
210-
match id {
211-
AttrDefId::ModuleId(m) => m.containing_module(db).map(|v| v.into()),
212-
AttrDefId::FunctionId(f) => Some(f.lookup(db).container.into()),
213-
AttrDefId::StaticId(sid) => Some(sid.lookup(db).container.into()),
214-
AttrDefId::ConstId(cid) => Some(cid.lookup(db).container.into()),
215-
AttrDefId::TraitId(tid) => Some(tid.lookup(db).container.into()),
216-
AttrDefId::TraitAliasId(taid) => Some(taid.lookup(db).container.into()),
217-
AttrDefId::ImplId(iid) => Some(iid.lookup(db).container.into()),
218-
AttrDefId::ExternBlockId(id) => Some(id.lookup(db).container.into()),
219-
AttrDefId::ExternCrateId(id) => Some(id.lookup(db).container.into()),
220-
AttrDefId::UseId(id) => Some(id.lookup(db).container.into()),
221-
// These warnings should not explore macro definitions at all
222-
AttrDefId::MacroId(_) => None,
223-
AttrDefId::AdtId(aid) => match aid {
224-
AdtId::StructId(sid) => Some(sid.lookup(db).container.into()),
225-
AdtId::EnumId(eid) => Some(eid.lookup(db).container.into()),
226-
// Unions aren't yet supported
227-
AdtId::UnionId(_) => None,
228-
},
229-
AttrDefId::FieldId(_) => None,
230-
AttrDefId::EnumVariantId(_) => None,
231-
AttrDefId::TypeAliasId(_) => None,
232-
AttrDefId::GenericParamId(_) => None,
233-
}
234-
.is_some_and(|mid| self.allowed(mid, allow_name, true))
235-
};
236-
is_allowed(id)
237-
// FIXME: this is a hack to avoid false positives in derive macros currently
238-
|| file_id_is_derive()
239-
// go upwards one step or give up
240-
|| parent()
241-
}
242-
243154
fn validate_module(&mut self, module_id: ModuleId) {
244-
// Check whether non-snake case identifiers are allowed for this module.
245-
if self.allowed(module_id.into(), allow::NON_SNAKE_CASE, false) {
246-
return;
247-
}
248-
249155
// Check the module name.
250156
let Some(module_name) = module_id.name(self.db.upcast()) else { return };
251157
let Some(module_name_replacement) =
@@ -270,11 +176,6 @@ impl<'a> DeclValidator<'a> {
270176
}
271177

272178
fn validate_trait(&mut self, trait_id: TraitId) {
273-
// Check whether non-snake case identifiers are allowed for this trait.
274-
if self.allowed(trait_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
275-
return;
276-
}
277-
278179
// Check the trait name.
279180
let data = self.db.trait_data(trait_id);
280181
self.create_incorrect_case_diagnostic_for_item_name(
@@ -292,11 +193,6 @@ impl<'a> DeclValidator<'a> {
292193
return;
293194
}
294195

295-
// Check whether non-snake case identifiers are allowed for this function.
296-
if self.allowed(func.into(), allow::NON_SNAKE_CASE, false) {
297-
return;
298-
}
299-
300196
// Check the function name.
301197
// Skipped if function is an associated item of a trait implementation.
302198
if !self.is_trait_impl_container(container) {
@@ -389,28 +285,20 @@ impl<'a> DeclValidator<'a> {
389285

390286
fn validate_struct(&mut self, struct_id: StructId) {
391287
// Check the structure name.
392-
let non_camel_case_allowed =
393-
self.allowed(struct_id.into(), allow::NON_CAMEL_CASE_TYPES, false);
394-
if !non_camel_case_allowed {
395-
let data = self.db.struct_data(struct_id);
396-
self.create_incorrect_case_diagnostic_for_item_name(
397-
struct_id,
398-
&data.name,
399-
CaseType::UpperCamelCase,
400-
IdentType::Structure,
401-
);
402-
}
288+
let data = self.db.struct_data(struct_id);
289+
self.create_incorrect_case_diagnostic_for_item_name(
290+
struct_id,
291+
&data.name,
292+
CaseType::UpperCamelCase,
293+
IdentType::Structure,
294+
);
403295

404296
// Check the field names.
405297
self.validate_struct_fields(struct_id);
406298
}
407299

408300
/// Check incorrect names for struct fields.
409301
fn validate_struct_fields(&mut self, struct_id: StructId) {
410-
if self.allowed(struct_id.into(), allow::NON_SNAKE_CASE, false) {
411-
return;
412-
}
413-
414302
let data = self.db.struct_data(struct_id);
415303
let VariantData::Record(fields) = data.variant_data.as_ref() else {
416304
return;
@@ -484,11 +372,6 @@ impl<'a> DeclValidator<'a> {
484372
fn validate_enum(&mut self, enum_id: EnumId) {
485373
let data = self.db.enum_data(enum_id);
486374

487-
// Check whether non-camel case names are allowed for this enum.
488-
if self.allowed(enum_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
489-
return;
490-
}
491-
492375
// Check the enum name.
493376
self.create_incorrect_case_diagnostic_for_item_name(
494377
enum_id,
@@ -653,10 +536,6 @@ impl<'a> DeclValidator<'a> {
653536
return;
654537
}
655538

656-
if self.allowed(const_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
657-
return;
658-
}
659-
660539
let data = self.db.const_data(const_id);
661540
let Some(name) = &data.name else {
662541
return;
@@ -676,10 +555,6 @@ impl<'a> DeclValidator<'a> {
676555
return;
677556
}
678557

679-
if self.allowed(static_id.into(), allow::NON_UPPER_CASE_GLOBAL, false) {
680-
return;
681-
}
682-
683558
self.create_incorrect_case_diagnostic_for_item_name(
684559
static_id,
685560
&data.name,
@@ -695,11 +570,6 @@ impl<'a> DeclValidator<'a> {
695570
return;
696571
}
697572

698-
// Check whether non-snake case identifiers are allowed for this type alias.
699-
if self.allowed(type_alias_id.into(), allow::NON_CAMEL_CASE_TYPES, false) {
700-
return;
701-
}
702-
703573
// Check the type alias name.
704574
let data = self.db.type_alias_data(type_alias_id);
705575
self.create_incorrect_case_diagnostic_for_item_name(

src/tools/rust-analyzer/crates/hir/src/semantics.rs

+43-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use either::Either;
1313
use hir_def::{
1414
hir::Expr,
1515
lower::LowerCtx,
16-
nameres::MacroSubNs,
16+
nameres::{MacroSubNs, ModuleOrigin},
1717
path::ModPath,
1818
resolver::{self, HasResolver, Resolver, TypeNs},
1919
type_ref::Mutability,
@@ -32,7 +32,7 @@ use intern::Symbol;
3232
use itertools::Itertools;
3333
use rustc_hash::{FxHashMap, FxHashSet};
3434
use smallvec::{smallvec, SmallVec};
35-
use span::{EditionedFileId, FileId};
35+
use span::{EditionedFileId, FileId, HirFileIdRepr};
3636
use stdx::TupleExt;
3737
use syntax::{
3838
algo::skip_trivia_token,
@@ -323,6 +323,47 @@ impl<'db> SemanticsImpl<'db> {
323323
tree
324324
}
325325

326+
pub fn find_parent_file(&self, file_id: HirFileId) -> Option<InFile<SyntaxNode>> {
327+
match file_id.repr() {
328+
HirFileIdRepr::FileId(file_id) => {
329+
let module = self.file_to_module_defs(file_id.file_id()).next()?;
330+
let def_map = self.db.crate_def_map(module.krate().id);
331+
match def_map[module.id.local_id].origin {
332+
ModuleOrigin::CrateRoot { .. } => None,
333+
ModuleOrigin::File { declaration, declaration_tree_id, .. } => {
334+
let file_id = declaration_tree_id.file_id();
335+
let in_file = InFile::new(file_id, declaration);
336+
let node = in_file.to_node(self.db.upcast());
337+
let root = find_root(node.syntax());
338+
self.cache(root, file_id);
339+
Some(in_file.with_value(node.syntax().clone()))
340+
}
341+
_ => unreachable!("FileId can only belong to a file module"),
342+
}
343+
}
344+
HirFileIdRepr::MacroFile(macro_file) => {
345+
let node = self
346+
.db
347+
.lookup_intern_macro_call(macro_file.macro_call_id)
348+
.to_node(self.db.upcast());
349+
let root = find_root(&node.value);
350+
self.cache(root, node.file_id);
351+
Some(node)
352+
}
353+
}
354+
}
355+
356+
/// Returns the `SyntaxNode` of the module. If this is a file module, returns
357+
/// the `SyntaxNode` of the *definition* file, not of the *declaration*.
358+
pub fn module_definition_node(&self, module: Module) -> InFile<SyntaxNode> {
359+
let def_map = module.id.def_map(self.db.upcast());
360+
let definition = def_map[module.id.local_id].origin.definition_source(self.db.upcast());
361+
let definition = definition.map(|it| it.node());
362+
let root_node = find_root(&definition.value);
363+
self.cache(root_node, definition.file_id);
364+
definition
365+
}
366+
326367
pub fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode {
327368
let node = self.db.parse_or_expand(file_id);
328369
self.cache(node.clone(), file_id);

0 commit comments

Comments
 (0)