use super::ast::GoFile;
use super::converter::TypeConverter;
use crate::core::{PackageInfo, SharedUniverse, SymbolId};
use dashmap::DashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tracing::{info, warn};
#[derive(Debug, Clone)]
pub struct ImportResult {
pub package_path: String,
pub types_imported: usize,
pub functions_imported: usize,
pub variables_imported: usize,
pub errors: Vec<ImportError>,
}
#[derive(Debug, Clone)]
pub struct ImportError {
pub kind: ImportErrorKind,
pub message: String,
pub file: Option<String>,
}
#[derive(Debug, Clone, Copy)]
pub enum ImportErrorKind {
NotFound,
ParseError,
ConversionError,
CircularImport,
}
pub struct PackageImporter {
universe: SharedUniverse,
converter: TypeConverter,
import_cache: DashMap<String, Arc<ImportedPackage>>,
gopath: PathBuf,
module_cache: PathBuf,
}
#[derive(Debug, Clone)]
pub struct ImportedPackage {
pub info: PackageInfo,
pub files: Vec<GoFile>,
}
impl PackageImporter {
pub fn new(universe: SharedUniverse) -> Self {
Self {
converter: TypeConverter::new(universe.clone()),
universe,
import_cache: DashMap::new(),
gopath: Self::detect_gopath(),
module_cache: Self::detect_module_cache(),
}
}
fn detect_gopath() -> PathBuf {
std::env::var("GOPATH")
.map(PathBuf::from)
.unwrap_or_else(|_| {
let home = dirs::home_dir().unwrap_or_default();
home.join("go")
})
}
fn detect_module_cache() -> PathBuf {
std::env::var("GOMODCACHE")
.map(PathBuf::from)
.unwrap_or_else(|_| Self::detect_gopath().join("pkg/mod"))
}
pub async fn import(&self, package_path: &str) -> Result<ImportResult, ImportError> {
if let Some(cached) = self.import_cache.get(package_path) {
return Ok(ImportResult {
package_path: package_path.to_string(),
types_imported: cached.info.exports.len(),
functions_imported: 0,
variables_imported: 0,
errors: vec![],
});
}
info!("Importing package: {}", package_path);
let pkg_dir = self.find_package_dir(package_path).await?;
let files = self.parse_package(&pkg_dir).await?;
let mut types_imported = 0;
let functions_imported = 0;
let mut errors = Vec::new();
for file in &files {
for decl in &file.decls {
match self.converter.convert_decl(decl).await {
Ok(Some(_type_id)) => {
types_imported += 1;
}
Ok(None) => {}
Err(e) => {
errors.push(ImportError {
kind: ImportErrorKind::ConversionError,
message: e.to_string(),
file: None,
});
}
}
}
}
let exports: Vec<SymbolId> = files
.iter()
.flat_map(|f| f.decls.iter())
.filter_map(|d| self.extract_exported_symbol(d))
.collect();
let info = PackageInfo {
path: package_path.into(),
name: files
.first()
.map(|f| f.package.clone().into())
.unwrap_or_else(|| package_path.into()),
exports,
imports: files
.iter()
.flat_map(|f| f.imports.iter())
.map(|i| i.path.clone().into())
.collect(),
};
self.universe.register_package(info.clone());
let imported = ImportedPackage { info, files };
self.import_cache
.insert(package_path.to_string(), Arc::new(imported));
info!("Imported {} types from {}", types_imported, package_path);
Ok(ImportResult {
package_path: package_path.to_string(),
types_imported,
functions_imported,
variables_imported: 0,
errors,
})
}
async fn find_package_dir(&self, package_path: &str) -> Result<PathBuf, ImportError> {
let module_path = self.module_cache.join(package_path);
if module_path.exists() {
return Ok(module_path);
}
let gopath_path = self.gopath.join("src").join(package_path);
if gopath_path.exists() {
return Ok(gopath_path);
}
let goroot = std::env::var("GOROOT").unwrap_or_default();
if !goroot.is_empty() {
let stdlib_path = PathBuf::from(goroot).join("src").join(package_path);
if stdlib_path.exists() {
return Ok(stdlib_path);
}
}
Err(ImportError {
kind: ImportErrorKind::NotFound,
message: format!("Package not found: {}", package_path),
file: None,
})
}
async fn parse_package(&self, dir: &Path) -> Result<Vec<GoFile>, ImportError> {
let mut files = Vec::new();
let mut entries = tokio::fs::read_dir(dir).await.map_err(|e| ImportError {
kind: ImportErrorKind::NotFound,
message: e.to_string(),
file: Some(dir.to_string_lossy().to_string()),
})?;
while let Some(entry) = entries.next_entry().await.map_err(|e| ImportError {
kind: ImportErrorKind::ParseError,
message: e.to_string(),
file: None,
})? {
let path = entry.path();
if path.extension() == Some(std::ffi::OsStr::new("go")) {
if let Some(stem) = path.file_stem() {
let stem = stem.to_string_lossy();
if stem.ends_with("_test") {
continue;
}
}
match self.parse_file(&path).await {
Ok(file) => files.push(file),
Err(e) => {
warn!("Failed to parse {:?}: {}", path, e.message);
}
}
}
}
if files.is_empty() {
return Err(ImportError {
kind: ImportErrorKind::NotFound,
message: "No Go files found in package".to_string(),
file: Some(dir.to_string_lossy().to_string()),
});
}
Ok(files)
}
async fn parse_file(&self, path: &Path) -> Result<GoFile, ImportError> {
let source = tokio::fs::read_to_string(path)
.await
.map_err(|e| ImportError {
kind: ImportErrorKind::ParseError,
message: e.to_string(),
file: Some(path.to_string_lossy().to_string()),
})?;
self.parse_source(&source, path)
}
fn parse_source(&self, _source: &str, _path: &Path) -> Result<GoFile, ImportError> {
Ok(GoFile {
package: "main".to_string(),
imports: vec![],
decls: vec![],
})
}
fn extract_exported_symbol(&self, decl: &super::ast::Decl) -> Option<SymbolId> {
use super::ast::Decl;
match decl {
Decl::Type(spec) if Self::is_exported(&spec.name) => {
Some(self.universe.symbols().intern(&spec.name))
}
Decl::Func(func) if Self::is_exported(&func.name) => {
Some(self.universe.symbols().intern(&func.name))
}
_ => None,
}
}
fn is_exported(name: &str) -> bool {
name.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
}
pub fn get_cached(&self, package_path: &str) -> Option<Arc<ImportedPackage>> {
self.import_cache.get(package_path).map(|p| p.clone())
}
pub fn clear_cache(&self) {
self.import_cache.clear();
}
pub async fn preload_stdlib(&self) -> Vec<ImportResult> {
let stdlib_packages = vec![
"fmt", "os", "io", "strings", "bytes", "time", "sync", "context", "errors", "sort",
];
let mut results = Vec::new();
for pkg in stdlib_packages {
match self.import(pkg).await {
Ok(result) => results.push(result),
Err(e) => {
warn!("Failed to preload {}: {:?}", pkg, e);
}
}
}
results
}
}
#[derive(Debug)]
pub enum ImporterError {
Import(ImportError),
Io(std::io::Error),
}
impl std::fmt::Display for ImporterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Import(e) => write!(f, "Import error: {:?}", e),
Self::Io(e) => write!(f, "IO error: {}", e),
}
}
}
impl std::error::Error for ImporterError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::TypeUniverse;
#[tokio::test]
async fn test_importer_creation() {
let universe = Arc::new(TypeUniverse::new());
let importer = PackageImporter::new(universe);
assert!(!importer.gopath.as_os_str().is_empty());
}
#[test]
fn test_is_exported() {
assert!(PackageImporter::is_exported("Exported"));
assert!(!PackageImporter::is_exported("unexported"));
assert!(!PackageImporter::is_exported("_private"));
}
}