Skip to content

Commit 3488b9f

Browse files
authored
Merge pull request #17920 from github/redsun82/rust-files
Rust: exclude uncompiled files from semantics and surface semantic-less reason
2 parents 07bb60d + 64d522e commit 3488b9f

File tree

3 files changed

+146
-104
lines changed

3 files changed

+146
-104
lines changed

rust/extractor/src/main.rs

Lines changed: 86 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
use crate::rust_analyzer::path_to_file_id;
12
use anyhow::Context;
23
use archive::Archiver;
34
use log::info;
45
use ra_ap_hir::Semantics;
56
use ra_ap_ide_db::line_index::{LineCol, LineIndex};
7+
use ra_ap_ide_db::RootDatabase;
68
use ra_ap_project_model::ProjectManifest;
9+
use ra_ap_vfs::Vfs;
710
use rust_analyzer::{ParseResult, RustAnalyzer};
811
use std::{
912
collections::HashMap,
1013
path::{Path, PathBuf},
1114
};
15+
1216
mod archive;
1317
mod config;
1418
pub mod generated;
@@ -17,54 +21,71 @@ mod rust_analyzer;
1721
mod translate;
1822
pub mod trap;
1923

20-
fn extract(
21-
rust_analyzer: &rust_analyzer::RustAnalyzer,
22-
archiver: &Archiver,
23-
traps: &trap::TrapFileProvider,
24-
file: &std::path::Path,
25-
) {
26-
archiver.archive(file);
24+
struct Extractor<'a> {
25+
archiver: &'a Archiver,
26+
traps: &'a trap::TrapFileProvider,
27+
}
2728

28-
let ParseResult {
29-
ast,
30-
text,
31-
errors,
32-
file_id,
33-
} = rust_analyzer.parse(file);
34-
let line_index = LineIndex::new(text.as_ref());
35-
let display_path = file.to_string_lossy();
36-
let mut trap = traps.create("source", file);
37-
let label = trap.emit_file(file);
38-
let mut translator = translate::Translator::new(
39-
trap,
40-
display_path.as_ref(),
41-
label,
42-
line_index,
43-
file_id,
44-
file_id.and(rust_analyzer.semantics()),
45-
);
29+
impl Extractor<'_> {
30+
fn extract(&self, rust_analyzer: &rust_analyzer::RustAnalyzer, file: &std::path::Path) {
31+
self.archiver.archive(file);
4632

47-
for err in errors {
48-
translator.emit_parse_error(&ast, &err);
49-
}
50-
let no_location = (LineCol { line: 0, col: 0 }, LineCol { line: 0, col: 0 });
51-
if translator.semantics.is_none() {
52-
translator.emit_diagnostic(
53-
trap::DiagnosticSeverity::Warning,
54-
"semantics".to_owned(),
55-
"semantic analyzer unavailable".to_owned(),
56-
"semantic analyzer unavailable: macro expansion, call graph, and type inference will be skipped.".to_owned(),
57-
no_location,
33+
let ParseResult {
34+
ast,
35+
text,
36+
errors,
37+
semantics_info,
38+
} = rust_analyzer.parse(file);
39+
let line_index = LineIndex::new(text.as_ref());
40+
let display_path = file.to_string_lossy();
41+
let mut trap = self.traps.create("source", file);
42+
let label = trap.emit_file(file);
43+
let mut translator = translate::Translator::new(
44+
trap,
45+
display_path.as_ref(),
46+
label,
47+
line_index,
48+
semantics_info.as_ref().ok(),
5849
);
50+
51+
for err in errors {
52+
translator.emit_parse_error(&ast, &err);
53+
}
54+
let no_location = (LineCol { line: 0, col: 0 }, LineCol { line: 0, col: 0 });
55+
if let Err(reason) = semantics_info {
56+
let message = format!("semantic analyzer unavailable ({reason})");
57+
let full_message = format!(
58+
"{message}: macro expansion, call graph, and type inference will be skipped."
59+
);
60+
translator.emit_diagnostic(
61+
trap::DiagnosticSeverity::Warning,
62+
"semantics".to_owned(),
63+
message,
64+
full_message,
65+
no_location,
66+
);
67+
}
68+
translator.emit_source_file(ast);
69+
translator.trap.commit().unwrap_or_else(|err| {
70+
log::error!(
71+
"Failed to write trap file for: {}: {}",
72+
display_path,
73+
err.to_string()
74+
)
75+
});
76+
}
77+
78+
pub fn extract_with_semantics(
79+
&self,
80+
file: &Path,
81+
semantics: &Semantics<'_, RootDatabase>,
82+
vfs: &Vfs,
83+
) {
84+
self.extract(&RustAnalyzer::new(vfs, semantics), file);
85+
}
86+
pub fn extract_without_semantics(&self, file: &Path, reason: &str) {
87+
self.extract(&RustAnalyzer::WithoutSemantics { reason }, file);
5988
}
60-
translator.emit_source_file(ast);
61-
translator.trap.commit().unwrap_or_else(|err| {
62-
log::error!(
63-
"Failed to write trap file for: {}: {}",
64-
display_path,
65-
err.to_string()
66-
)
67-
});
6889
}
6990

7091
fn main() -> anyhow::Result<()> {
@@ -82,6 +103,10 @@ fn main() -> anyhow::Result<()> {
82103
let archiver = archive::Archiver {
83104
root: cfg.source_archive_dir.clone(),
84105
};
106+
let extractor = Extractor {
107+
archiver: &archiver,
108+
traps: &traps,
109+
};
85110
let files: Vec<PathBuf> = cfg
86111
.inputs
87112
.iter()
@@ -95,38 +120,39 @@ fn main() -> anyhow::Result<()> {
95120
.iter()
96121
.map(|x| (x.manifest_path().parent().as_ref(), (x, Vec::new())))
97122
.collect();
98-
let mut other_files = Vec::new();
99123

100124
'outer: for file in &files {
101-
let mut p = file.as_path();
102-
while let Some(parent) = p.parent() {
103-
p = parent;
104-
if let Some((_, files)) = map.get_mut(parent) {
125+
for ancestor in file.as_path().ancestors() {
126+
if let Some((_, files)) = map.get_mut(ancestor) {
105127
files.push(file);
106128
continue 'outer;
107129
}
108130
}
109-
other_files.push(file);
131+
extractor.extract_without_semantics(file, "no manifest found");
110132
}
111-
for (manifest, files) in map.values() {
112-
if files.is_empty() {
113-
break;
114-
}
133+
for (manifest, files) in map.values().filter(|(_, files)| !files.is_empty()) {
115134
if let Some((ref db, ref vfs)) = RustAnalyzer::load_workspace(manifest, &cfg.scratch_dir) {
116135
let semantics = Semantics::new(db);
117-
let rust_analyzer = RustAnalyzer::new(vfs, semantics);
118136
for file in files {
119-
extract(&rust_analyzer, &archiver, &traps, file);
137+
let Some(id) = path_to_file_id(file, vfs) else {
138+
extractor.extract_without_semantics(
139+
file,
140+
"not included in files loaded from manifest",
141+
);
142+
continue;
143+
};
144+
if semantics.file_to_module_def(id).is_none() {
145+
extractor.extract_without_semantics(file, "not included as a module");
146+
continue;
147+
}
148+
extractor.extract_with_semantics(file, &semantics, vfs);
120149
}
121150
} else {
122151
for file in files {
123-
extract(&RustAnalyzer::WithoutSemantics, &archiver, &traps, file);
152+
extractor.extract_without_semantics(file, "unable to load manifest");
124153
}
125154
}
126155
}
127-
for file in other_files {
128-
extract(&RustAnalyzer::WithoutSemantics, &archiver, &traps, file);
129-
}
130156

131157
Ok(())
132158
}

rust/extractor/src/rust_analyzer.rs

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,34 @@ use ra_ap_span::TextRange;
1414
use ra_ap_span::TextSize;
1515
use ra_ap_syntax::SourceFile;
1616
use ra_ap_syntax::SyntaxError;
17-
use ra_ap_vfs::AbsPathBuf;
1817
use ra_ap_vfs::Vfs;
1918
use ra_ap_vfs::VfsPath;
19+
use ra_ap_vfs::{AbsPathBuf, FileId};
2020
use std::borrow::Cow;
2121
use std::path::{Path, PathBuf};
2222
use triomphe::Arc;
2323
pub enum RustAnalyzer<'a> {
2424
WithSemantics {
2525
vfs: &'a Vfs,
26-
semantics: Semantics<'a, RootDatabase>,
26+
semantics: &'a Semantics<'a, RootDatabase>,
2727
},
28-
WithoutSemantics,
28+
WithoutSemantics {
29+
reason: &'a str,
30+
},
31+
}
32+
33+
pub struct FileSemanticInformation<'a> {
34+
pub file_id: EditionedFileId,
35+
pub semantics: &'a Semantics<'a, RootDatabase>,
2936
}
30-
pub struct ParseResult {
37+
38+
pub struct ParseResult<'a> {
3139
pub ast: SourceFile,
3240
pub text: Arc<str>,
3341
pub errors: Vec<SyntaxError>,
34-
pub file_id: Option<EditionedFileId>,
42+
pub semantics_info: Result<FileSemanticInformation<'a>, &'a str>,
3543
}
44+
3645
impl<'a> RustAnalyzer<'a> {
3746
pub fn load_workspace(
3847
project: &ProjectManifest,
@@ -61,47 +70,45 @@ impl<'a> RustAnalyzer<'a> {
6170
}
6271
}
6372
}
64-
pub fn new(vfs: &'a Vfs, semantics: Semantics<'a, RootDatabase>) -> Self {
73+
pub fn new(vfs: &'a Vfs, semantics: &'a Semantics<'a, RootDatabase>) -> Self {
6574
RustAnalyzer::WithSemantics { vfs, semantics }
6675
}
67-
pub fn semantics(&'a self) -> Option<&'a Semantics<'a, RootDatabase>> {
68-
match self {
69-
RustAnalyzer::WithSemantics { vfs: _, semantics } => Some(semantics),
70-
RustAnalyzer::WithoutSemantics => None,
71-
}
72-
}
7376
pub fn parse(&self, path: &Path) -> ParseResult {
74-
if let RustAnalyzer::WithSemantics { vfs, semantics } = self {
75-
if let Some(file_id) = Utf8PathBuf::from_path_buf(path.to_path_buf())
76-
.ok()
77-
.and_then(|x| AbsPathBuf::try_from(x).ok())
78-
.map(VfsPath::from)
79-
.and_then(|x| vfs.file_id(&x))
80-
{
81-
if let Ok(input) = std::panic::catch_unwind(|| semantics.db.file_text(file_id)) {
82-
let file_id = EditionedFileId::current_edition(file_id);
83-
let source_file = semantics.parse(file_id);
84-
let errors = semantics
85-
.db
86-
.parse_errors(file_id)
87-
.into_iter()
88-
.flat_map(|x| x.to_vec())
89-
.collect();
77+
let no_semantics_reason;
78+
match self {
79+
RustAnalyzer::WithSemantics { vfs, semantics } => {
80+
if let Some(file_id) = path_to_file_id(path, vfs) {
81+
if let Ok(input) = std::panic::catch_unwind(|| semantics.db.file_text(file_id))
82+
{
83+
let file_id = EditionedFileId::current_edition(file_id);
84+
let source_file = semantics.parse(file_id);
85+
let errors = semantics
86+
.db
87+
.parse_errors(file_id)
88+
.into_iter()
89+
.flat_map(|x| x.to_vec())
90+
.collect();
9091

91-
return ParseResult {
92-
ast: source_file,
93-
text: input,
94-
errors,
95-
file_id: Some(file_id),
96-
};
97-
} else {
98-
log::debug!(
92+
return ParseResult {
93+
ast: source_file,
94+
text: input,
95+
errors,
96+
semantics_info: Ok(FileSemanticInformation { file_id, semantics }),
97+
};
98+
}
99+
debug!(
99100
"No text available for file_id '{:?}', falling back to loading file '{}' from disk.",
100101
file_id,
101102
path.to_string_lossy()
102-
)
103+
);
104+
no_semantics_reason = "no text available for the file in the project";
105+
} else {
106+
no_semantics_reason = "file not found in project";
103107
}
104108
}
109+
RustAnalyzer::WithoutSemantics { reason } => {
110+
no_semantics_reason = reason;
111+
}
105112
}
106113
let mut errors = Vec::new();
107114
let input = match std::fs::read(path) {
@@ -123,7 +130,7 @@ impl<'a> RustAnalyzer<'a> {
123130
ast: parse.tree(),
124131
text: input.as_ref().into(),
125132
errors,
126-
file_id: None,
133+
semantics_info: Err(no_semantics_reason),
127134
}
128135
}
129136
}
@@ -187,3 +194,11 @@ fn from_utf8_lossy(v: &[u8]) -> (Cow<'_, str>, Option<SyntaxError>) {
187194

188195
(Cow::Owned(res), Some(error))
189196
}
197+
198+
pub(crate) fn path_to_file_id(path: &Path, vfs: &Vfs) -> Option<FileId> {
199+
Utf8PathBuf::from_path_buf(path.to_path_buf())
200+
.ok()
201+
.and_then(|x| AbsPathBuf::try_from(x).ok())
202+
.map(VfsPath::from)
203+
.and_then(|x| vfs.file_id(&x))
204+
}

rust/extractor/src/translate/base.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::mappings::{AddressableAst, AddressableHir};
22
use crate::generated::MacroCall;
33
use crate::generated::{self};
4+
use crate::rust_analyzer::FileSemanticInformation;
45
use crate::trap::{DiagnosticSeverity, TrapFile, TrapId};
56
use crate::trap::{Label, TrapClass};
67
use codeql_extractor::trap::{self};
@@ -64,16 +65,15 @@ impl<'a> Translator<'a> {
6465
path: &'a str,
6566
label: trap::Label,
6667
line_index: LineIndex,
67-
file_id: Option<EditionedFileId>,
68-
semantics: Option<&'a Semantics<'a, RootDatabase>>,
68+
semantic_info: Option<&FileSemanticInformation<'a>>,
6969
) -> Translator<'a> {
7070
Translator {
7171
trap,
7272
path,
7373
label,
7474
line_index,
75-
file_id,
76-
semantics,
75+
file_id: semantic_info.map(|i| i.file_id),
76+
semantics: semantic_info.map(|i| i.semantics),
7777
}
7878
}
7979
fn location(&self, range: TextRange) -> (LineCol, LineCol) {
@@ -160,7 +160,7 @@ impl<'a> Translator<'a> {
160160
self.path,
161161
start.line + 1,
162162
start.col + 1,
163-
&message
163+
&full_message
164164
);
165165
if severity > DiagnosticSeverity::Debug {
166166
let location = self.trap.emit_location_label(self.label, start, end);
@@ -284,7 +284,8 @@ impl<'a> Translator<'a> {
284284
range.unwrap_or_else(|| TextRange::empty(TextSize::from(0))),
285285
));
286286
}
287-
} else {
287+
} else if self.semantics.is_some() {
288+
// let's not spam warnings if we don't have semantics, we already emitted one
288289
let range = self.text_range_for_node(mcall);
289290
self.emit_parse_error(
290291
mcall,

0 commit comments

Comments
 (0)