Skip to content

Commit 42ef1d1

Browse files
alanzmeta-codesync[bot]
authored andcommitted
3/n: includes: convert HIR diagnostics into ide Diagnostic W0057
Summary: Create a new diagnostic W0057 (hir_unresolved_macro) to report macros not resolved during HIR lowering. For a properly configured buck2 project this simply shadows the existing Erlang service `E1507` / `E1508` diagnostics, but for an incorrectly configured one they show up alone. The next diff filters them out if they are already reported. It also ensures it does not report on usages of `?FUNCTION_NAME` or ?FUNCTION_ARITY` in the replacement part of a `-define()` attribute. Reviewed By: TD5 Differential Revision: D86963336 fbshipit-source-id: 08f8d1b6c58dde6ee0a106f9151b237709dc56e0
1 parent 6fe7f10 commit 42ef1d1

File tree

10 files changed

+299
-11
lines changed

10 files changed

+299
-11
lines changed

crates/elp/src/resources/test/buck_tests_2/resolves_generated_includes.stdout

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ Diagnostics reported in 5 modules:
99
0:8-0:18::[WeakWarning] [W0046] The module is not documented.
1010
eqwalizer: 1
1111
1:8-1:17::[WeakWarning] [W0046] The module is not documented.
12-
top_includer: 5
13-
9:0-9:3::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented.
12+
top_includer: 3
13+
12:4-12:10::[Warning] [W0057] undefined macro 'THIRD/2'
1414
0:8-0:20::[WeakWarning] [W0046] The module is not documented.
1515
12:4-12:10::[Error] [E1508] undefined macro 'THIRD/2'
16-
4:9-4:14::[Error] [L1227] function foo/0 undefined
17-
8:6-8:9::[Error] [L1308] spec for undefined function foo/0
1816
wa_buck2_module_search: 6
1917
19:0-19:18::[WeakWarning] [W0040] The function is non-trivial, exported, but not documented.
2018
2:8-2:30::[WeakWarning] [W0046] The module is not documented.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
module specified: main_app
22
Diagnostics reported in 1 modules:
3-
main_app: 5
3+
main_app: 6
44
4:13-4:55::[Error] [E1516] can't find include lib "external_app/include/external_header.hrl"
55
11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl
66
14:9-14:21::[Hint] [W0037] Unspecific include.
77
16:9-16:24::[Error] [L1227] function test_function/0 undefined
8+
23:4-23:19::[Warning] [W0057] undefined macro 'EXTERNAL_MACRO'
89
23:4-23:19::[Error] [E1507] undefined macro 'EXTERNAL_MACRO'
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module specified: main_app
22
Diagnostics reported in 1 modules:
3-
main_app: 4
3+
main_app: 5
44
11:0-11:42::[Warning] [W0020] Unused file: stdlib/include/assert.hrl
55
14:9-14:21::[Error] [E1516] can't find include file "assert.hrl"
66
16:9-16:24::[Error] [L1227] function test_function/0 undefined
7+
20:4-20:25::[Warning] [W0057] undefined macro 'normalDepAssertEqual/2'
78
20:4-20:25::[Error] [E1508] undefined macro 'normalDepAssertEqual/2'

crates/hir/src/body/lower.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ pub struct Ctx<'a> {
118118
// This means we need to lower a Var specially when processing a SSR
119119
// template, where if it has a prefix of `_@` it is a placeholder.
120120
in_ssr: bool,
121+
// Track when we're lowering the RHS of a define preprocessor directive
122+
in_macro_rhs: bool,
121123
// Diagnostics collected during body lowering
122124
diagnostics: Vec<BodyDiagnostic>,
123125
}
@@ -156,6 +158,7 @@ impl<'a> Ctx<'a> {
156158
starting_stack_size: 1,
157159
macro_source_map: FxHashMap::default(),
158160
in_ssr: false,
161+
in_macro_rhs: false,
159162
diagnostics: Vec::new(),
160163
}
161164
}
@@ -407,7 +410,9 @@ impl<'a> Ctx<'a> {
407410
let replacement = define.replacement();
408411
match replacement {
409412
Some(MacroDefReplacement::Expr(expr)) => {
413+
self.in_macro_rhs = true;
410414
let expr = self.lower_expr(&expr);
415+
self.in_macro_rhs = false;
411416
let (body, source_map) = self.finish();
412417
(DefineBody { body, expr }, source_map)
413418
}
@@ -3023,6 +3028,17 @@ impl<'a> Ctx<'a> {
30233028
}
30243029

30253030
fn record_unresolved_macro(&mut self, call: &ast::MacroCallExpr) {
3031+
// If we're in a macro RHS and this is ?FUNCTION_NAME or ?FUNCTION_ARITY, skip the diagnostic
3032+
if self.in_macro_rhs {
3033+
let macro_name = self.macro_call_name(call.name());
3034+
if let MacroCallName::Var(var) = macro_name {
3035+
let name_str = var.as_string(self.db.upcast());
3036+
if name_str == "FUNCTION_NAME" || name_str == "FUNCTION_ARITY" {
3037+
return;
3038+
}
3039+
}
3040+
}
3041+
30263042
let source = InFileAstPtr::new(self.curr_file_id(), AstPtr::new(call).cast().unwrap());
30273043
self.diagnostics.push(BodyDiagnostic {
30283044
source,

crates/ide/src/diagnostics.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ use elp_text_edit::TextEdit;
6767
use elp_types_db::TypedSemantic;
6868
use fxhash::FxHashMap;
6969
use fxhash::FxHashSet;
70+
use hir::FormIdx;
7071
use hir::InFile;
72+
use hir::PPDirective;
7173
use hir::Semantic;
7274
use hir::db::DefDatabase;
7375
use hir::fold::MacroStrategy;
@@ -1400,6 +1402,7 @@ pub fn native_diagnostics(
14001402
}
14011403

14021404
res.append(&mut form_missing_separator_diagnostics(&parse));
1405+
res.extend(get_hir_diagnostics(db, file_id));
14031406

14041407
adhoc_semantic_diagnostics
14051408
.iter()
@@ -1743,6 +1746,143 @@ pub fn filter_diagnostics(diagnostics: Vec<Diagnostic>, code: DiagnosticCode) ->
17431746
diagnostics.into_iter().filter(|d| d.code == code).collect()
17441747
}
17451748

1749+
/// Retrieve all BodyDiagnostic values from the BodySourceMaps from lowering
1750+
/// all the bodies for a given FileId.
1751+
///
1752+
/// This function iterates through all forms in the file and collects diagnostics
1753+
/// from the BodySourceMaps associated with each form's body.
1754+
pub fn collect_body_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<hir::BodyDiagnostic> {
1755+
let sema = Semantic::new(db);
1756+
let form_list = sema.form_list(file_id);
1757+
let mut diagnostics = Vec::new();
1758+
1759+
for form_idx in form_list.forms().iter() {
1760+
let body_map = match form_idx {
1761+
FormIdx::FunctionClause(function_clause_id) => {
1762+
let (_, body_map) = sema
1763+
.db
1764+
.function_clause_body_with_source(InFile::new(file_id, *function_clause_id));
1765+
Some(body_map)
1766+
}
1767+
FormIdx::TypeAlias(type_alias_id) => {
1768+
let (_, body_map) = sema
1769+
.db
1770+
.type_body_with_source(InFile::new(file_id, *type_alias_id));
1771+
Some(body_map)
1772+
}
1773+
FormIdx::Spec(spec_id) => {
1774+
let (_, body_map) = sema
1775+
.db
1776+
.spec_body_with_source(InFile::new(file_id, *spec_id));
1777+
Some(body_map)
1778+
}
1779+
FormIdx::Callback(callback_id) => {
1780+
let (_, body_map) = sema
1781+
.db
1782+
.callback_body_with_source(InFile::new(file_id, *callback_id));
1783+
Some(body_map)
1784+
}
1785+
FormIdx::Record(record_id) => {
1786+
let (_, body_map) = sema
1787+
.db
1788+
.record_body_with_source(InFile::new(file_id, *record_id));
1789+
Some(body_map)
1790+
}
1791+
FormIdx::Attribute(attribute_id) => {
1792+
let (_, body_map) = sema
1793+
.db
1794+
.attribute_body_with_source(InFile::new(file_id, *attribute_id));
1795+
Some(body_map)
1796+
}
1797+
FormIdx::CompileOption(compile_option_id) => {
1798+
let (_, body_map) = sema
1799+
.db
1800+
.compile_body_with_source(InFile::new(file_id, *compile_option_id));
1801+
Some(body_map)
1802+
}
1803+
FormIdx::PPDirective(idx) => {
1804+
if let PPDirective::Define(define_id) = &form_list[*idx] {
1805+
let (_, body_map) = sema
1806+
.db
1807+
.define_body_with_source(InFile::new(file_id, *define_id));
1808+
Some(body_map)
1809+
} else {
1810+
None
1811+
}
1812+
}
1813+
_ => None,
1814+
};
1815+
1816+
if let Some(body_map) = body_map {
1817+
diagnostics.extend_from_slice(body_map.diagnostics());
1818+
}
1819+
}
1820+
1821+
diagnostics
1822+
}
1823+
1824+
/// Convert HIR body diagnostics to IDE Diagnostics.
1825+
/// This function takes the diagnostics collected during HIR lowering and converts
1826+
/// them to the IDE Diagnostic format for display to users.
1827+
pub fn get_hir_diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> {
1828+
let body_diagnostics = collect_body_diagnostics(db, file_id);
1829+
1830+
body_diagnostics
1831+
.into_iter()
1832+
.filter_map(|body_diag| {
1833+
let full_range = body_diag.source.range();
1834+
1835+
// Only include diagnostics for the requested file
1836+
if full_range.file_id != file_id {
1837+
return None;
1838+
}
1839+
1840+
let (code, message, range) = match body_diag.kind {
1841+
hir::BodyDiagnosticKind::UnresolvedMacro => {
1842+
// Get the macro call AST node to extract name and arity
1843+
let macro_call = body_diag.source.to_ast(db);
1844+
let macro_name = macro_call
1845+
.name()
1846+
.map(|name| name.to_string())
1847+
.unwrap_or_else(|| "?".to_string());
1848+
1849+
let message = match macro_call.arity() {
1850+
Some(arity) => format!("undefined macro '{}/{}'", macro_name, arity),
1851+
None => format!("undefined macro '{}'", macro_name),
1852+
};
1853+
1854+
// For macros with arguments, only highlight the name part, not the full call
1855+
let range = macro_call
1856+
.name()
1857+
.map(|name| {
1858+
// Get the syntax range of just the macro name
1859+
let name_range = name.syntax().text_range();
1860+
// Include the '?' prefix by extending one character to the left
1861+
if name_range.start() > 0.into() {
1862+
TextRange::new(
1863+
name_range.start() - TextSize::from(1),
1864+
name_range.end(),
1865+
)
1866+
} else {
1867+
name_range
1868+
}
1869+
})
1870+
.unwrap_or(full_range.range);
1871+
1872+
(DiagnosticCode::HirUnresolvedMacro, message, range)
1873+
}
1874+
};
1875+
1876+
Some(
1877+
Diagnostic::new(code, message, range)
1878+
// We set the severity to Warning for now, until we have cleaned
1879+
// up the code base from this diagnostic
1880+
.with_severity(Severity::Warning),
1881+
)
1882+
})
1883+
.collect()
1884+
}
1885+
17461886
fn no_module_definition_diagnostic(
17471887
diagnostics: &mut Vec<Diagnostic>,
17481888
parse: &Parse<ast::SourceFile>,
@@ -3750,4 +3890,22 @@ baz(1)->4.
37503890
"#,
37513891
);
37523892
}
3893+
3894+
#[test]
3895+
fn no_unused_macro_in_macro_rhs_for_function_name() {
3896+
let config = DiagnosticsConfig::default()
3897+
.set_experimental(true)
3898+
.disable(DiagnosticCode::UnspecificInclude)
3899+
.disable(DiagnosticCode::BinaryStringToSigil);
3900+
check_diagnostics_with_config(
3901+
config,
3902+
r#"
3903+
//- /my_app/src/a_file.erl
3904+
-module(a_file).
3905+
-define(A_MACRO, ?FUNCTION_NAME).
3906+
foo() -> ?A_MACRO.
3907+
3908+
"#,
3909+
);
3910+
}
37533911
}

crates/ide/src/diagnostics/unused_macro.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ mod tests {
123123

124124
#[track_caller]
125125
pub(crate) fn check_diagnostics(fixture: &str) {
126-
let config = DiagnosticsConfig::default().disable(DiagnosticCode::UndefinedFunction);
126+
let config = DiagnosticsConfig::default()
127+
.disable(DiagnosticCode::UndefinedFunction)
128+
.disable(DiagnosticCode::HirUnresolvedMacro);
127129
check_diagnostics_with_config(config, fixture)
128130
}
129131

crates/ide/src/tests.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,8 @@ pub(crate) fn check_diagnostics(fixture: &str) {
376376
let config = DiagnosticsConfig::default()
377377
.set_experimental(true)
378378
.disable(DiagnosticCode::UnspecificInclude)
379-
.disable(DiagnosticCode::BinaryStringToSigil);
379+
.disable(DiagnosticCode::BinaryStringToSigil)
380+
.disable(DiagnosticCode::HirUnresolvedMacro);
380381
check_diagnostics_with_config(config, fixture)
381382
}
382383

@@ -524,6 +525,7 @@ pub fn check_no_parse_errors(analysis: &Analysis, file_id: FileId) {
524525
let config = DiagnosticsConfig::default()
525526
.disable(DiagnosticCode::UnspecificInclude)
526527
.disable(DiagnosticCode::UndefinedFunction)
528+
.disable(DiagnosticCode::HirUnresolvedMacro)
527529
.disable(DiagnosticCode::NoCatch);
528530
check_no_parse_errors_with_config(analysis, file_id, &config, &vec![]);
529531
}

crates/ide_db/src/diagnostic_code.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pub enum DiagnosticCode {
9090
NoNoWarnSuppressions,
9191
CouldBeAStringLiteral,
9292
ListsReverseAppend,
93+
HirUnresolvedMacro,
9394

9495
// Wrapper for erlang service diagnostic codes
9596
ErlangService(String),
@@ -250,6 +251,7 @@ impl DiagnosticCode {
250251
DiagnosticCode::NoNoWarnSuppressions => "W0054".to_string(),
251252
DiagnosticCode::CouldBeAStringLiteral => "W0055".to_string(),
252253
DiagnosticCode::ListsReverseAppend => "W0056".to_string(),
254+
DiagnosticCode::HirUnresolvedMacro => "W0057".to_string(),
253255
DiagnosticCode::ErlangService(c) => c.to_string(),
254256
DiagnosticCode::Eqwalizer(c) => format!("eqwalizer: {c}"),
255257
DiagnosticCode::AdHoc(c) => format!("ad-hoc: {c}"),
@@ -347,6 +349,7 @@ impl DiagnosticCode {
347349
DiagnosticCode::NoNoWarnSuppressions => "no_nowarn_suppressions".to_string(),
348350
DiagnosticCode::CouldBeAStringLiteral => "could_be_a_binary_string_literal".to_string(),
349351
DiagnosticCode::ListsReverseAppend => "lists_reverse_append".to_string(),
352+
DiagnosticCode::HirUnresolvedMacro => "hir_unresolved_macro".to_string(),
350353

351354
DiagnosticCode::ErlangService(c) => c.to_string(),
352355
DiagnosticCode::Eqwalizer(c) => c.to_string(),
@@ -524,6 +527,7 @@ impl DiagnosticCode {
524527
DiagnosticCode::NoErrorLogger => false,
525528
DiagnosticCode::NoNoWarnSuppressions => false,
526529
DiagnosticCode::ListsReverseAppend => false,
530+
DiagnosticCode::HirUnresolvedMacro => false,
527531

528532
DiagnosticCode::BinaryStringToSigil => false,
529533
DiagnosticCode::ErlangService(_) => false,

test_projects/buck_tests_2/check_include/src/top_includer.erl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
-compile(warn_missing_spec_all).
44

5-
-export([foo/0]).
6-
75
-include_lib("check_include_separate_1/include/top_includer.hrl").
86

9-
-spec foo() -> ok.
7+
-define(A_MACRO, ?FUNCTION_NAME).
8+
109
foo() ->
1110
?FIRST,
11+
?A_MACRO
1212
?SECOND,
1313
?THIRD(41,34),
1414
?FUNCTION_NAME.

0 commit comments

Comments
 (0)