impl Drop for CargoProject {
fn drop(&mut self) {
if let Some(temp_dir) = &self.temp_dir {
let _ = std::fs::remove_dir_all(temp_dir);
}
}
}
impl Default for RustCompiler {
fn default() -> Self {
Self::new()
}
}
impl CompilerInterface for RustCompiler {
fn compile(&self, source: &str, options: &CompileOptions) -> CITLResult<CompilationResult> {
match &self.mode {
CompilationMode::Standalone => self.compile_standalone(source, options),
CompilationMode::Cargo { manifest_path }
| CompilationMode::CargoCheck { manifest_path } => {
self.compile_cargo_check(source, manifest_path, options)
}
}
}
fn parse_diagnostic(&self, raw: &str) -> Option<CompilerDiagnostic> {
self.parse_single_json_diagnostic(raw)
}
fn version(&self) -> CITLResult<CompilerVersion> {
let output = Command::new(&self.rustc_path).arg("--version").output()?;
let version_str = String::from_utf8_lossy(&output.stdout);
let parts: Vec<&str> = version_str.split_whitespace().collect();
if parts.len() >= 2 {
if let Some(mut version) = CompilerVersion::parse(parts[1]) {
version.full = version_str.trim().to_string();
if parts.len() >= 3 {
version.commit =
Some(parts[2].trim_matches(|c| c == '(' || c == ')').to_string());
}
return Ok(version);
}
}
Err(CITLError::ParseError {
raw: version_str.to_string(),
details: "Could not parse rustc version".to_string(),
})
}
fn name(&self) -> &'static str {
"rustc"
}
fn is_available(&self) -> bool {
Command::new(&self.rustc_path)
.arg("--version")
.output()
.is_ok()
}
}
fn check_env_binary(env_var: &str) -> Option<PathBuf> {
std::env::var(env_var)
.ok()
.map(PathBuf::from)
.filter(|p| p.exists())
}
fn check_cargo_bin(binary_name: &str) -> Option<PathBuf> {
std::env::var("HOME")
.ok()
.map(|h| PathBuf::from(h).join(".cargo/bin").join(binary_name))
.filter(|p| p.exists())
}
fn check_system_paths(paths: &[&str]) -> Option<PathBuf> {
paths.iter().map(PathBuf::from).find(|p| p.exists())
}
fn which_rustc() -> PathBuf {
check_env_binary("RUSTC")
.or_else(|| check_cargo_bin("rustc"))
.or_else(|| check_system_paths(&["/usr/bin/rustc", "/usr/local/bin/rustc"]))
.unwrap_or_else(|| PathBuf::from("rustc"))
}
fn which_cargo() -> PathBuf {
check_env_binary("CARGO")
.or_else(|| check_cargo_bin("cargo"))
.or_else(|| check_system_paths(&["/usr/bin/cargo", "/usr/local/bin/cargo"]))
.unwrap_or_else(|| PathBuf::from("cargo"))
}
fn lookup_error_code(code: &str) -> ErrorCode {
let codes = super::rust_error_codes();
codes
.get(code)
.cloned()
.unwrap_or_else(|| ErrorCode::from_code(code))
}
fn extract_value_from_segment(segment: &str, pattern: &str) -> Option<String> {
let start = segment.find(pattern)? + pattern.len();
let rest = &segment[start..];
let end = rest.find('"')?;
Some(rest[..end].to_string())
}
fn find_children_array_end(json: &str, children_start: usize) -> usize {
let offset = children_start + 12; let mut depth = 0;
for (i, c) in json[offset..].char_indices() {
match c {
'[' => depth += 1,
']' => {
if depth == 0 {
return offset + i + 1;
}
depth -= 1;
}
_ => {}
}
}
json.len()
}
fn extract_top_level_json_value(
json: &str,
pattern: &str,
children_start: usize,
) -> Option<String> {
let before = &json[..children_start];
if let Some(value) = extract_value_from_segment(before, pattern) {
return Some(value);
}
let children_end = find_children_array_end(json, children_start);
let after = &json[children_end..];
extract_value_from_segment(after, pattern)
}
fn extract_json_string(json: &str, key: &str) -> Option<String> {
let pattern = format!("\"{key}\":\"");
if key == "level" || key == "message" {
if let Some(children_start) = json.find("\"children\":[") {
return extract_top_level_json_value(json, &pattern, children_start);
}
}
extract_value_from_segment(json, &pattern)
}
fn extract_nested_json_string(json: &str, outer_key: &str, inner_key: &str) -> Option<String> {
let outer_pattern = format!("\"{outer_key}\":{{");
let start = json.find(&outer_pattern)?;
let rest = &json[start..];
let end = rest.find('}')?;
let inner_json = &rest[..=end];
extract_json_string(inner_json, inner_key)
}
#[allow(clippy::disallowed_methods, clippy::unwrap_or_default)]
fn extract_span_from_json(json: &str) -> SourceSpan {
let file = extract_json_string(json, "file_name").unwrap_or_else(String::new);
let line_start: usize = extract_json_string(json, "line_start")
.and_then(|s| s.parse().ok())
.unwrap_or(1);
let line_end: usize = extract_json_string(json, "line_end")
.and_then(|s| s.parse().ok())
.unwrap_or(line_start);
let column_start: usize = extract_json_string(json, "column_start")
.and_then(|s| s.parse().ok())
.unwrap_or(1);
let column_end: usize = extract_json_string(json, "column_end")
.and_then(|s| s.parse().ok())
.unwrap_or(column_start);
SourceSpan::new(&file, line_start, line_end, column_start, column_end)
}
fn extract_suggestions_from_json(_json: &str) -> Option<Vec<CompilerSuggestion>> {
None
}
#[cfg(test)]
mod tests;