Skip to content

Commit 0b3b95a

Browse files
committed
Rust: macro expansion
1 parent cad2b74 commit 0b3b95a

File tree

17 files changed

+427
-31
lines changed

17 files changed

+427
-31
lines changed

rust/extractor/src/generated/.generated.list

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/extractor/src/generated/top.rs

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/extractor/src/main.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Context;
2-
use ra_ap_ide_db::line_index::LineIndex;
2+
use ra_ap_ide_db::line_index::{LineCol, LineIndex};
33
mod archive;
44
mod config;
55
pub mod generated;
@@ -17,10 +17,21 @@ fn extract(
1717
let display_path = file.to_string_lossy();
1818
let mut trap = traps.create("source", &file);
1919
let label = trap.emit_file(&file);
20-
let mut translator = translate::Translator::new(trap, label, line_index, semi);
20+
let mut translator =
21+
translate::Translator::new(trap, display_path.as_ref(), label, line_index, semi);
2122

2223
for err in parse_errors {
23-
translator.emit_parse_error(display_path.as_ref(), err);
24+
translator.emit_parse_error(&err);
25+
}
26+
let no_location = (LineCol { line: 0, col: 0 }, LineCol { line: 0, col: 0 });
27+
if translator.semi.is_none() {
28+
translator.emit_diagnostic(
29+
trap::DiagnosticSeverity::Warning,
30+
"semantics".to_owned(),
31+
"semantic analyzer unavailable".to_owned(),
32+
"semantic analyzer unavailable: macro expansion, call graph, and type inference will be skipped.".to_owned(),
33+
no_location,
34+
);
2435
}
2536
translator.emit_source_file(ast);
2637
translator.trap.commit()?;

rust/extractor/src/rust_analyzer.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,21 @@ impl RustAnalyzer {
8585
}
8686
}
8787
}
88-
let input = std::fs::read(&path).unwrap();
88+
let mut errors = Vec::new();
89+
let input = match std::fs::read(path) {
90+
Ok(data) => data,
91+
Err(e) => {
92+
errors.push(SyntaxError::new(
93+
format!("Could not read {}: {}", path.to_string_lossy(), e),
94+
TextRange::empty(TextSize::default()),
95+
));
96+
vec![]
97+
}
98+
};
8999
let (input, err) = from_utf8_lossy(&input);
90100
let parse = ra_ap_syntax::ast::SourceFile::parse(&input, Edition::CURRENT);
91-
let mut errors = parse.errors();
92-
errors.extend(err.into_iter());
101+
errors.extend(parse.errors());
102+
errors.extend(err);
93103
(parse.tree(), input.as_ref().into(), errors, None)
94104
}
95105
}

rust/extractor/src/translate/base.rs

Lines changed: 145 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
use crate::generated::{self, AstNode};
1+
use crate::generated::MacroCall;
2+
use crate::generated::{self};
23
use crate::trap::{DiagnosticSeverity, TrapFile, TrapId};
34
use crate::trap::{Label, TrapClass};
45
use codeql_extractor::trap::{self};
6+
use log::Level;
7+
use ra_ap_hir::db::ExpandDatabase;
58
use ra_ap_hir::Semantics;
69
use ra_ap_ide_db::line_index::{LineCol, LineIndex};
710
use ra_ap_ide_db::RootDatabase;
811
use ra_ap_parser::SyntaxKind;
912
use ra_ap_syntax::ast::RangeItem;
10-
use ra_ap_syntax::{ast, NodeOrToken, SyntaxElementChildren, SyntaxError, SyntaxToken, TextRange};
13+
use ra_ap_syntax::{
14+
ast, AstNode, NodeOrToken, SyntaxElementChildren, SyntaxError, SyntaxToken, TextRange,
15+
};
1116
pub trait TextValue {
1217
fn try_get_text(&self) -> Option<String>;
1318
}
@@ -60,32 +65,35 @@ impl TextValue for ast::RangePat {
6065
}
6166
pub struct Translator<'a> {
6267
pub trap: TrapFile,
68+
path: &'a str,
6369
label: trap::Label,
6470
line_index: LineIndex,
65-
semi: Option<Semantics<'a, RootDatabase>>,
71+
pub semi: Option<Semantics<'a, RootDatabase>>,
6672
}
6773

68-
impl Translator<'_> {
74+
impl<'a> Translator<'a> {
6975
pub fn new(
7076
trap: TrapFile,
77+
path: &'a str,
7178
label: trap::Label,
7279
line_index: LineIndex,
73-
semi: Option<Semantics<'_, RootDatabase>>,
74-
) -> Translator {
80+
semi: Option<Semantics<'a, RootDatabase>>,
81+
) -> Translator<'a> {
7582
Translator {
7683
trap,
84+
path,
7785
label,
7886
line_index,
7987
semi,
8088
}
8189
}
82-
pub fn location(&self, range: TextRange) -> (LineCol, LineCol) {
90+
fn location(&self, range: TextRange) -> (LineCol, LineCol) {
8391
let start = self.line_index.line_col(range.start());
8492
let range_end = range.end();
8593
// QL end positions are inclusive, while TextRange offsets are exclusive and point at the position
8694
// right after the last character of the range. We need to shift the end offset one character to the left to
8795
// get the right inclusive QL position. Unfortunately, simply subtracting `1` from the end-offset may cause
88-
// the offset to point in the middle of a mult-byte character, resulting in a `panic`. Therefore we use `try_line_col`
96+
// the offset to point in the middle of a multi-byte character, resulting in a `panic`. Therefore we use `try_line_col`
8997
// with decreasing offsets to find the start of the last character included in the range.
9098
for i in 1..4 {
9199
if let Some(end) = range_end
@@ -98,28 +106,72 @@ impl Translator<'_> {
98106
let end = self.line_index.line_col(range_end);
99107
(start, end)
100108
}
109+
110+
pub fn text_range_for_node(&mut self, node: &impl ast::AstNode) -> TextRange {
111+
if let Some(semi) = self.semi.as_ref() {
112+
let file_range = semi.original_range(node.syntax());
113+
file_range.range
114+
} else {
115+
node.syntax().text_range()
116+
}
117+
}
101118
pub fn emit_location<T: TrapClass>(&mut self, label: Label<T>, node: &impl ast::AstNode) {
102-
let (start, end) = self.location(node.syntax().text_range());
119+
let range = self.text_range_for_node(node);
120+
let (start, end) = self.location(range);
103121
self.trap.emit_location(self.label, label, start, end)
104122
}
105123
pub fn emit_location_token(&mut self, label: Label<generated::Token>, token: &SyntaxToken) {
106124
let (start, end) = self.location(token.text_range());
107125
self.trap.emit_location(self.label, label, start, end)
108126
}
109-
pub fn emit_parse_error(&mut self, path: &str, err: SyntaxError) {
110-
let (start, end) = self.location(err.range());
111-
log::warn!("{}:{}:{}: {}", path, start.line + 1, start.col + 1, err);
112-
let message = err.to_string();
127+
pub fn emit_diagnostic(
128+
&mut self,
129+
severity: DiagnosticSeverity,
130+
error_tag: String,
131+
error_message: String,
132+
full_error_message: String,
133+
location: (LineCol, LineCol),
134+
) {
135+
let (start, end) = location;
136+
let level = match severity {
137+
DiagnosticSeverity::Debug => Level::Debug,
138+
DiagnosticSeverity::Info => Level::Info,
139+
DiagnosticSeverity::Warning => Level::Warn,
140+
DiagnosticSeverity::Error => Level::Error,
141+
};
142+
log::log!(
143+
level,
144+
"{}:{}:{}: {}",
145+
self.path,
146+
start.line + 1,
147+
start.col + 1,
148+
&error_message
149+
);
113150
let location = self.trap.emit_location_label(self.label, start, end);
114151
self.trap.emit_diagnostic(
152+
severity,
153+
error_tag,
154+
error_message,
155+
full_error_message,
156+
location,
157+
);
158+
}
159+
pub fn emit_parse_error(&mut self, err: &SyntaxError) {
160+
let location = self.location(err.range());
161+
let message = err.to_string();
162+
self.emit_diagnostic(
115163
DiagnosticSeverity::Warning,
116164
"parse_error".to_owned(),
117165
message.clone(),
118166
message,
119167
location,
120168
);
121169
}
122-
pub fn emit_tokens(&mut self, parent: Label<AstNode>, children: SyntaxElementChildren) {
170+
pub fn emit_tokens(
171+
&mut self,
172+
parent: Label<generated::AstNode>,
173+
children: SyntaxElementChildren,
174+
) {
123175
for child in children {
124176
if let NodeOrToken::Token(token) = child {
125177
if token.kind() == SyntaxKind::COMMENT {
@@ -133,4 +185,83 @@ impl Translator<'_> {
133185
}
134186
}
135187
}
188+
pub(crate) fn extract_macro_call_expanded(
189+
&mut self,
190+
mcall: &ast::MacroCall,
191+
label: Label<generated::MacroCall>,
192+
) {
193+
if let Some(semi) = &self.semi {
194+
if let Some(expanded) = semi.expand(mcall) {
195+
if let Some(value) =
196+
semi.hir_file_for(&expanded)
197+
.macro_file()
198+
.and_then(|macro_file| {
199+
semi.db
200+
.parse_macro_expansion_error(macro_file.macro_call_id)
201+
})
202+
{
203+
if let Some(err) = &value.err {
204+
let (message, _error) = err.render_to_string(semi.db);
205+
206+
if err.span().anchor.file_id == semi.hir_file_for(mcall.syntax()) {
207+
let location = err.span().range
208+
+ semi
209+
.db
210+
.ast_id_map(err.span().anchor.file_id.into())
211+
.get_erased(err.span().anchor.ast_id)
212+
.text_range()
213+
.start();
214+
self.emit_parse_error(&SyntaxError::new(message, location));
215+
};
216+
}
217+
for err in value.value.iter() {
218+
self.emit_parse_error(err);
219+
}
220+
}
221+
let expand_to = ra_ap_hir_expand::ExpandTo::from_call_site(mcall);
222+
let kind = expanded.kind();
223+
let value: Option<Label<crate::generated::AstNode>> = match expand_to {
224+
ra_ap_hir_expand::ExpandTo::Statements => {
225+
ast::MacroStmts::cast(expanded).map(|x| self.emit_macro_stmts(x).into())
226+
}
227+
ra_ap_hir_expand::ExpandTo::Items => {
228+
ast::MacroItems::cast(expanded).map(|x| self.emit_macro_items(x).into())
229+
}
230+
231+
ra_ap_hir_expand::ExpandTo::Pattern => {
232+
ast::Pat::cast(expanded).map(|x| self.emit_pat(x).into())
233+
}
234+
ra_ap_hir_expand::ExpandTo::Type => {
235+
ast::Type::cast(expanded).map(|x| self.emit_type(x).into())
236+
}
237+
ra_ap_hir_expand::ExpandTo::Expr => {
238+
ast::Expr::cast(expanded).map(|x| self.emit_expr(x).into())
239+
}
240+
};
241+
if let Some(value) = value {
242+
MacroCall::emit_expanded(label, value, &mut self.trap.writer);
243+
} else {
244+
let range = self.text_range_for_node(mcall);
245+
self.emit_parse_error(&SyntaxError::new(
246+
format!(
247+
"macro expansion failed: the macro '{}' expands to {:?} but a {:?} was expected",
248+
mcall.path().map(|p| p.to_string()).unwrap_or_default(),
249+
kind, expand_to
250+
),
251+
range,
252+
));
253+
}
254+
} else {
255+
let range = self.text_range_for_node(mcall);
256+
257+
self.emit_parse_error(&SyntaxError::new(
258+
format!(
259+
"macro expansion failed: could not resolve macro '{}'",
260+
mcall.path().map(|p| p.to_string()).unwrap_or_default()
261+
),
262+
range,
263+
));
264+
}
265+
}
266+
}
136267
}

rust/extractor/src/translate/generated.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/extractor/src/trap.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ impl<T: TrapClass> From<Label<T>> for trap::Arg {
124124

125125
pub struct TrapFile {
126126
path: PathBuf,
127-
writer: Writer,
127+
pub writer: Writer,
128128
compression: Compression,
129129
}
130130

0 commit comments

Comments
 (0)