Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/hir/src/code_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,7 @@ impl Function {
}

pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
hir_ty::diagnostics::validate_module_item(db, self.id.into(), sink);
hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
}
Expand Down
63 changes: 21 additions & 42 deletions crates/hir_def/src/body.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Defines `Body`: a lowered representation of bodies of functions, statics and
//! consts.
mod lower;
mod diagnostics;
#[cfg(test)]
mod tests;
pub mod scope;

use std::{mem, ops::Index, sync::Arc};
Expand All @@ -10,7 +13,10 @@ use base_db::CrateId;
use cfg::CfgOptions;
use drop_bomb::DropBomb;
use either::Either;
use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId};
use hir_expand::{
ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, HirFileId, InFile,
MacroDefId,
};
use rustc_hash::FxHashMap;
use syntax::{ast, AstNode, AstPtr};
use test_utils::mark;
Expand Down Expand Up @@ -150,8 +156,12 @@ impl Expander {
InFile { file_id: self.current_file_id, value }
}

pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool {
self.cfg_expander.is_cfg_enabled(owner)
pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs {
self.cfg_expander.parse_attrs(owner)
}

pub(crate) fn cfg_options(&self) -> &CfgOptions {
&self.cfg_expander.cfg_options
}

fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
Expand Down Expand Up @@ -219,6 +229,10 @@ pub struct BodySourceMap {
pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>,
field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>,
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,

/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
/// the source map (since they're just as volatile).
diagnostics: Vec<diagnostics::BodyDiagnostic>,
}

#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
Expand Down Expand Up @@ -318,45 +332,10 @@ impl BodySourceMap {
pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> {
self.field_map[&(expr, field)].clone()
}
}

#[cfg(test)]
mod tests {
use base_db::{fixture::WithFixture, SourceDatabase};
use test_utils::mark;

use crate::ModuleDefId;

use super::*;

fn lower(ra_fixture: &str) -> Arc<Body> {
let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture);

let krate = db.crate_graph().iter().next().unwrap();
let def_map = db.crate_def_map(krate);
let module = def_map.modules_for_file(file_id).next().unwrap();
let module = &def_map[module];
let fn_def = match module.scope.declarations().next().unwrap() {
ModuleDefId::FunctionId(it) => it,
_ => panic!(),
};

db.body(fn_def.into())
}

#[test]
fn your_stack_belongs_to_me() {
mark::check!(your_stack_belongs_to_me);
lower(
"
macro_rules! n_nuple {
($e:tt) => ();
($($rest:tt)*) => {{
(n_nuple!($($rest)*)None,)
}};
}
fn main() { n_nuple!(1,2,3); }
",
);
pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
for diag in &self.diagnostics {
diag.add_to(sink);
}
}
}
20 changes: 20 additions & 0 deletions crates/hir_def/src/body/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Diagnostics emitted during body lowering.

use hir_expand::diagnostics::DiagnosticSink;

use crate::diagnostics::InactiveCode;

#[derive(Debug, Eq, PartialEq)]
pub enum BodyDiagnostic {
InactiveCode(InactiveCode),
}

impl BodyDiagnostic {
pub fn add_to(&self, sink: &mut DiagnosticSink<'_>) {
match self {
BodyDiagnostic::InactiveCode(diag) => {
sink.push(diag.clone());
}
}
}
}
60 changes: 46 additions & 14 deletions crates/hir_def/src/body/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use syntax::{
self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner,
SlicePatComponents,
},
AstNode, AstPtr,
AstNode, AstPtr, SyntaxNodePtr,
};
use test_utils::mark;

Expand All @@ -25,6 +25,7 @@ use crate::{
body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax},
builtin_type::{BuiltinFloat, BuiltinInt},
db::DefDatabase,
diagnostics::InactiveCode,
expr::{
dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal,
LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
Expand All @@ -37,7 +38,7 @@ use crate::{
StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
};

use super::{ExprSource, PatSource};
use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};

pub(crate) struct LowerCtx {
hygiene: Hygiene,
Expand Down Expand Up @@ -176,7 +177,7 @@ impl ExprCollector<'_> {

fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
let syntax_ptr = AstPtr::new(&expr);
if !self.expander.is_cfg_enabled(&expr) {
if self.check_cfg(&expr).is_none() {
return self.missing_expr();
}

Expand Down Expand Up @@ -354,13 +355,15 @@ impl ExprCollector<'_> {
let arms = if let Some(match_arm_list) = e.match_arm_list() {
match_arm_list
.arms()
.map(|arm| MatchArm {
pat: self.collect_pat_opt(arm.pat()),
expr: self.collect_expr_opt(arm.expr()),
guard: arm
.guard()
.and_then(|guard| guard.expr())
.map(|e| self.collect_expr(e)),
.filter_map(|arm| {
self.check_cfg(&arm).map(|()| MatchArm {
pat: self.collect_pat_opt(arm.pat()),
expr: self.collect_expr_opt(arm.expr()),
guard: arm
.guard()
.and_then(|guard| guard.expr())
.map(|e| self.collect_expr(e)),
})
})
.collect()
} else {
Expand Down Expand Up @@ -406,9 +409,8 @@ impl ExprCollector<'_> {
.fields()
.inspect(|field| field_ptrs.push(AstPtr::new(field)))
.filter_map(|field| {
if !self.expander.is_cfg_enabled(&field) {
return None;
}
self.check_cfg(&field)?;

let name = field.field_name()?.as_name();

Some(RecordLitField {
Expand Down Expand Up @@ -620,15 +622,23 @@ impl ExprCollector<'_> {
.filter_map(|s| {
let stmt = match s {
ast::Stmt::LetStmt(stmt) => {
self.check_cfg(&stmt)?;

let pat = self.collect_pat_opt(stmt.pat());
let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
let initializer = stmt.initializer().map(|e| self.collect_expr(e));
Statement::Let { pat, type_ref, initializer }
}
ast::Stmt::ExprStmt(stmt) => {
self.check_cfg(&stmt)?;

Statement::Expr(self.collect_expr_opt(stmt.expr()))
}
ast::Stmt::Item(_) => return None,
ast::Stmt::Item(item) => {
self.check_cfg(&item)?;

return None;
}
};
Some(stmt)
})
Expand Down Expand Up @@ -872,6 +882,28 @@ impl ExprCollector<'_> {

(args, ellipsis)
}

/// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when
/// not.
fn check_cfg(&mut self, owner: &dyn ast::AttrsOwner) -> Option<()> {
match self.expander.parse_attrs(owner).cfg() {
Some(cfg) => {
if self.expander.cfg_options().check(&cfg) != Some(false) {
return Some(());
}

self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode {
file: self.expander.current_file_id,
node: SyntaxNodePtr::new(owner.syntax()),
cfg,
opts: self.expander.cfg_options().clone(),
}));

None
}
None => Some(()),
}
}
}

impl From<ast::BinOp> for BinaryOp {
Expand Down
75 changes: 75 additions & 0 deletions crates/hir_def/src/body/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use base_db::{fixture::WithFixture, SourceDatabase};
use test_utils::mark;

use crate::{test_db::TestDB, ModuleDefId};

use super::*;

fn lower(ra_fixture: &str) -> Arc<Body> {
let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture);

let krate = db.crate_graph().iter().next().unwrap();
let def_map = db.crate_def_map(krate);
let module = def_map.modules_for_file(file_id).next().unwrap();
let module = &def_map[module];
let fn_def = match module.scope.declarations().next().unwrap() {
ModuleDefId::FunctionId(it) => it,
_ => panic!(),
};

db.body(fn_def.into())
}

fn check_diagnostics(ra_fixture: &str) {
let db: TestDB = TestDB::with_files(ra_fixture);
db.check_diagnostics();
}

#[test]
fn your_stack_belongs_to_me() {
mark::check!(your_stack_belongs_to_me);
lower(
"
macro_rules! n_nuple {
($e:tt) => ();
($($rest:tt)*) => {{
(n_nuple!($($rest)*)None,)
}};
}
fn main() { n_nuple!(1,2,3); }
",
);
}

#[test]
fn cfg_diagnostics() {
check_diagnostics(
r"
fn f() {
// The three g̶e̶n̶d̶e̶r̶s̶ statements:

#[cfg(a)] fn f() {} // Item statement
//^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
#[cfg(a)] {} // Expression statement
//^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
#[cfg(a)] let x = 0; // let statement
//^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled

abc(#[cfg(a)] 0);
//^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
let x = Struct {
#[cfg(a)] f: 0,
//^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
};
match () {
() => (),
#[cfg(a)] () => (),
//^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
}

#[cfg(a)] 0 // Trailing expression of block
//^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
}
",
);
}
13 changes: 10 additions & 3 deletions crates/hir_def/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ use std::any::Any;
use stdx::format_to;

use cfg::{CfgExpr, CfgOptions, DnfExpr};
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode};
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
use hir_expand::{HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxNodePtr};

use crate::{db::DefDatabase, DefWithBodyId};

pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
let source_map = db.body_with_source_map(owner).1;
source_map.add_diagnostics(db, sink);
}

// Diagnostic: unresolved-module
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
Expand Down Expand Up @@ -88,10 +95,10 @@ impl Diagnostic for UnresolvedImport {
}
}

// Diagnostic: unconfigured-code
// Diagnostic: inactive-code
//
// This diagnostic is shown for code with inactive `#[cfg]` attributes.
#[derive(Debug)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InactiveCode {
pub file: HirFileId,
pub node: SyntaxNodePtr,
Expand Down
Loading