mirror of
https://github.com/mtshiba/pylyzer.git
synced 2025-07-07 17:45:00 +00:00
209 lines
7.5 KiB
Rust
209 lines
7.5 KiB
Rust
use std::fs::{create_dir_all, File};
|
|
use std::io::{BufWriter, Write};
|
|
use std::path::Path;
|
|
|
|
use erg_common::pathutil::{mod_name, NormalizedPathBuf};
|
|
use erg_common::set::Set;
|
|
use erg_common::traits::LimitedDisplay;
|
|
use erg_common::{log, Str};
|
|
use erg_compiler::build_package::{CheckStatus, PylyzerStatus};
|
|
use erg_compiler::hir::{ClassDef, Expr, HIR};
|
|
use erg_compiler::module::SharedModuleCache;
|
|
use erg_compiler::ty::value::{GenTypeObj, TypeObj};
|
|
use erg_compiler::ty::{HasType, Type};
|
|
|
|
pub struct DeclFile {
|
|
pub filename: String,
|
|
pub code: String,
|
|
}
|
|
pub struct DeclFileGenerator {
|
|
filename: String,
|
|
namespace: String,
|
|
imported: Set<Str>,
|
|
code: String,
|
|
}
|
|
|
|
impl DeclFileGenerator {
|
|
pub fn new(path: &NormalizedPathBuf, status: CheckStatus) -> Self {
|
|
let (timestamp, hash) = {
|
|
let metadata = std::fs::metadata(path).unwrap();
|
|
let dummy_hash = metadata.len();
|
|
(metadata.modified().unwrap(), dummy_hash)
|
|
};
|
|
let status = PylyzerStatus {
|
|
status,
|
|
file: path.to_path_buf(),
|
|
timestamp,
|
|
hash,
|
|
};
|
|
let code = format!("{status}\n");
|
|
Self {
|
|
filename: path
|
|
.file_name()
|
|
.unwrap()
|
|
.to_string_lossy()
|
|
.replace(".py", ".d.er"),
|
|
namespace: "".to_string(),
|
|
imported: Set::new(),
|
|
code,
|
|
}
|
|
}
|
|
|
|
pub fn gen_decl_er(mut self, hir: &HIR) -> DeclFile {
|
|
for chunk in hir.module.iter() {
|
|
self.gen_chunk_decl(chunk);
|
|
}
|
|
log!("code:\n{}", self.code);
|
|
DeclFile {
|
|
filename: self.filename,
|
|
code: self.code,
|
|
}
|
|
}
|
|
|
|
fn escape_type(&self, typ: String) -> String {
|
|
typ.replace('%', "Type_")
|
|
.replace("<module>", "")
|
|
.trim_start_matches(self.filename.trim_end_matches(".d.er"))
|
|
.trim_start_matches(&self.namespace)
|
|
.to_string()
|
|
}
|
|
|
|
// e.g. `x: foo.Bar` => `foo = pyimport "foo"; x: foo.Bar`
|
|
fn prepare_using_type(&mut self, typ: &Type) {
|
|
let namespace = Str::rc(typ.namespace().split('.').next().unwrap());
|
|
if namespace != self.namespace
|
|
&& !namespace.is_empty()
|
|
&& self.imported.insert(namespace.clone())
|
|
{
|
|
self.code += &format!("{namespace} = pyimport \"{namespace}\"\n");
|
|
}
|
|
}
|
|
|
|
fn gen_chunk_decl(&mut self, chunk: &Expr) {
|
|
match chunk {
|
|
Expr::Def(def) => {
|
|
let mut name = def
|
|
.sig
|
|
.ident()
|
|
.inspect()
|
|
.replace('\0', "")
|
|
.replace(['%', '*'], "___");
|
|
let ref_t = def.sig.ident().ref_t();
|
|
self.prepare_using_type(ref_t);
|
|
let typ = self.escape_type(ref_t.replace_failure().to_string_unabbreviated());
|
|
// Erg can automatically import nested modules
|
|
// `import http.client` => `http = pyimport "http"`
|
|
let decl = if ref_t.is_py_module() {
|
|
name = name.split('.').next().unwrap().to_string();
|
|
let full_path_str = ref_t.typarams()[0].to_string_unabbreviated();
|
|
let mod_name = mod_name(Path::new(full_path_str.trim_matches('"')));
|
|
let imported = if self.imported.insert(mod_name.clone()) {
|
|
format!("{}.{mod_name} = pyimport \"{mod_name}\"", self.namespace)
|
|
} else {
|
|
"".to_string()
|
|
};
|
|
if self.imported.insert(name.clone().into()) {
|
|
format!(
|
|
"{}.{name} = pyimport \"{mod_name}\"\n{imported}",
|
|
self.namespace,
|
|
)
|
|
} else {
|
|
imported
|
|
}
|
|
} else {
|
|
format!("{}.{name}: {typ}", self.namespace)
|
|
};
|
|
self.code += &decl;
|
|
}
|
|
Expr::ClassDef(def) => {
|
|
let class_name = def
|
|
.sig
|
|
.ident()
|
|
.inspect()
|
|
.replace('\0', "")
|
|
.replace(['%', '*'], "___");
|
|
let src = format!("{}.{class_name}", self.namespace);
|
|
let stash = std::mem::replace(&mut self.namespace, src);
|
|
let decl = format!(".{class_name}: ClassType");
|
|
self.code += &decl;
|
|
self.code.push('\n');
|
|
if let GenTypeObj::Subclass(class) = def.obj.as_ref() {
|
|
let sup = class
|
|
.sup
|
|
.as_ref()
|
|
.typ()
|
|
.replace_failure()
|
|
.to_string_unabbreviated();
|
|
self.prepare_using_type(class.sup.typ());
|
|
let sup = self.escape_type(sup);
|
|
let decl = format!(".{class_name} <: {sup}\n");
|
|
self.code += &decl;
|
|
}
|
|
if let Some(TypeObj::Builtin {
|
|
t: Type::Record(rec),
|
|
..
|
|
}) = def.obj.base_or_sup()
|
|
{
|
|
for (attr, t) in rec.iter() {
|
|
self.prepare_using_type(t);
|
|
let typ = self.escape_type(t.replace_failure().to_string_unabbreviated());
|
|
let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
|
|
self.code += &decl;
|
|
}
|
|
}
|
|
if let Some(TypeObj::Builtin {
|
|
t: Type::Record(rec),
|
|
..
|
|
}) = def.obj.additional()
|
|
{
|
|
for (attr, t) in rec.iter() {
|
|
self.prepare_using_type(t);
|
|
let typ = self.escape_type(t.replace_failure().to_string_unabbreviated());
|
|
let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
|
|
self.code += &decl;
|
|
}
|
|
}
|
|
for attr in ClassDef::get_all_methods(&def.methods_list) {
|
|
self.gen_chunk_decl(attr);
|
|
}
|
|
self.namespace = stash;
|
|
}
|
|
Expr::Dummy(dummy) => {
|
|
for chunk in dummy.iter() {
|
|
self.gen_chunk_decl(chunk);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
self.code.push('\n');
|
|
}
|
|
}
|
|
|
|
fn dump_decl_er(path: &NormalizedPathBuf, hir: &HIR, status: CheckStatus) {
|
|
let decl_gen = DeclFileGenerator::new(path, status);
|
|
let file = decl_gen.gen_decl_er(hir);
|
|
let Some(dir) = path.parent().and_then(|p| p.canonicalize().ok()) else {
|
|
return;
|
|
};
|
|
let cache_dir = dir.join("__pycache__");
|
|
if !cache_dir.exists() {
|
|
let _ = create_dir_all(&cache_dir);
|
|
}
|
|
let path = cache_dir.join(file.filename);
|
|
if !path.exists() {
|
|
let _f = File::create(&path);
|
|
}
|
|
let Ok(f) = File::options().write(true).open(path) else {
|
|
return;
|
|
};
|
|
let mut f = BufWriter::new(f);
|
|
let _ = f.write_all(file.code.as_bytes());
|
|
}
|
|
|
|
pub fn dump_decl_package(modules: &SharedModuleCache) {
|
|
for (path, module) in modules.raw_iter() {
|
|
if let Some(hir) = module.hir.as_ref() {
|
|
dump_decl_er(path, hir, module.status);
|
|
}
|
|
}
|
|
}
|