1use std::fs::{create_dir_all, File};
2use std::io::{BufWriter, Write};
3use std::path::Path;
4
5use erg_common::pathutil::{mod_name, NormalizedPathBuf};
6use erg_common::set::Set;
7use erg_common::traits::LimitedDisplay;
8use erg_common::{log, Str};
9use erg_compiler::build_package::{CheckStatus, PylyzerStatus};
10use erg_compiler::hir::{ClassDef, Expr, HIR};
11use erg_compiler::module::SharedModuleCache;
12use erg_compiler::ty::value::{GenTypeObj, TypeObj};
13use erg_compiler::ty::{HasType, Type};
14
15pub struct DeclFile {
16 pub filename: String,
17 pub code: String,
18}
19pub struct DeclFileGenerator {
20 filename: String,
21 namespace: String,
22 imported: Set<Str>,
23 code: String,
24}
25
26impl DeclFileGenerator {
27 pub fn new(path: &NormalizedPathBuf, status: CheckStatus) -> std::io::Result<Self> {
28 let (timestamp, hash) = {
29 let metadata = std::fs::metadata(path)?;
30 let dummy_hash = metadata.len();
31 (metadata.modified()?, dummy_hash)
32 };
33 let status = PylyzerStatus {
34 status,
35 file: path.to_path_buf(),
36 timestamp,
37 hash,
38 };
39 let code = format!("{status}\n");
40 Ok(Self {
41 filename: path
42 .file_name()
43 .unwrap_or_default()
44 .to_string_lossy()
45 .replace(".py", ".d.er"),
46 namespace: "".to_string(),
47 imported: Set::new(),
48 code,
49 })
50 }
51
52 pub fn gen_decl_er(mut self, hir: &HIR) -> DeclFile {
53 for chunk in hir.module.iter() {
54 self.gen_chunk_decl(chunk);
55 }
56 log!("code:\n{}", self.code);
57 DeclFile {
58 filename: self.filename,
59 code: self.code,
60 }
61 }
62
63 fn escape_type(&self, typ: String) -> String {
64 typ.replace('%', "Type_")
65 .replace("<module>", "")
66 .replace('/', ".")
67 .trim_start_matches(self.filename.trim_end_matches(".d.er"))
68 .trim_start_matches(&self.namespace)
69 .to_string()
70 }
71
72 fn prepare_using_type(&mut self, typ: &Type) {
74 let namespace = Str::rc(
75 typ.namespace()
76 .split('/')
77 .next()
78 .unwrap()
79 .split('.')
80 .next()
81 .unwrap(),
82 );
83 if namespace != self.namespace
84 && !namespace.is_empty()
85 && self.imported.insert(namespace.clone())
86 {
87 self.code += &format!("{namespace} = pyimport \"{namespace}\"\n");
88 }
89 }
90
91 fn gen_chunk_decl(&mut self, chunk: &Expr) {
92 match chunk {
93 Expr::Def(def) => {
94 let mut name = def
95 .sig
96 .ident()
97 .inspect()
98 .replace('\0', "")
99 .replace(['%', '*'], "___");
100 let ref_t = def.sig.ident().ref_t();
101 self.prepare_using_type(ref_t);
102 let typ = self.escape_type(ref_t.replace_failure().to_string_unabbreviated());
103 let decl = if ref_t.is_py_module() && ref_t.typarams()[0].is_str_value() {
106 name = name.split('.').next().unwrap().to_string();
107 let full_path_str = ref_t.typarams()[0].to_string_unabbreviated();
108 let mod_name = mod_name(Path::new(full_path_str.trim_matches('"')));
109 let imported = if self.imported.insert(mod_name.clone()) {
110 format!("{}.{mod_name} = pyimport \"{mod_name}\"", self.namespace)
111 } else {
112 "".to_string()
113 };
114 if self.imported.insert(name.clone().into()) {
115 format!(
116 "{}.{name} = pyimport \"{mod_name}\"\n{imported}",
117 self.namespace,
118 )
119 } else {
120 imported
121 }
122 } else {
123 format!("{}.{name}: {typ}", self.namespace)
124 };
125 self.code += &decl;
126 }
127 Expr::ClassDef(def) => {
128 let class_name = def
129 .sig
130 .ident()
131 .inspect()
132 .replace('\0', "")
133 .replace(['%', '*'], "___");
134 let src = format!("{}.{class_name}", self.namespace);
135 let stash = std::mem::replace(&mut self.namespace, src);
136 let decl = format!(".{class_name}: ClassType");
137 self.code += &decl;
138 self.code.push('\n');
139 if let GenTypeObj::Subclass(class) = def.obj.as_ref() {
140 let sup = class
141 .sup
142 .as_ref()
143 .typ()
144 .replace_failure()
145 .to_string_unabbreviated();
146 self.prepare_using_type(class.sup.typ());
147 let sup = self.escape_type(sup);
148 let decl = format!(".{class_name} <: {sup}\n");
149 self.code += &decl;
150 }
151 if let Some(TypeObj::Builtin {
152 t: Type::Record(rec),
153 ..
154 }) = def.obj.base_or_sup()
155 {
156 for (attr, t) in rec.iter() {
157 self.prepare_using_type(t);
158 let typ = self.escape_type(t.replace_failure().to_string_unabbreviated());
159 let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
160 self.code += &decl;
161 }
162 }
163 if let Some(TypeObj::Builtin {
164 t: Type::Record(rec),
165 ..
166 }) = def.obj.additional()
167 {
168 for (attr, t) in rec.iter() {
169 self.prepare_using_type(t);
170 let typ = self.escape_type(t.replace_failure().to_string_unabbreviated());
171 let decl = format!("{}.{}: {typ}\n", self.namespace, attr.symbol);
172 self.code += &decl;
173 }
174 }
175 for attr in ClassDef::get_all_methods(&def.methods_list) {
176 self.gen_chunk_decl(attr);
177 }
178 self.namespace = stash;
179 }
180 Expr::Dummy(dummy) => {
181 for chunk in dummy.iter() {
182 self.gen_chunk_decl(chunk);
183 }
184 }
185 Expr::Compound(compound) => {
186 for chunk in compound.iter() {
187 self.gen_chunk_decl(chunk);
188 }
189 }
190 Expr::Call(call) if call.control_kind().is_some() => {
191 for arg in call.args.iter() {
192 self.gen_chunk_decl(arg);
193 }
194 }
195 Expr::Lambda(lambda) => {
196 for arg in lambda.body.iter() {
197 self.gen_chunk_decl(arg);
198 }
199 }
200 _ => {}
201 }
202 self.code.push('\n');
203 }
204}
205
206fn dump_decl_er(path: &NormalizedPathBuf, hir: &HIR, status: CheckStatus) -> std::io::Result<()> {
207 let decl_gen = DeclFileGenerator::new(path, status)?;
208 let file = decl_gen.gen_decl_er(hir);
209 let Some(dir) = path.parent().and_then(|p| p.canonicalize().ok()) else {
210 return Ok(());
211 };
212 let cache_dir = dir.join("__pycache__");
213 if !cache_dir.exists() {
214 let _ = create_dir_all(&cache_dir);
215 }
216 let path = cache_dir.join(file.filename);
217 if !path.exists() {
218 File::create(&path)?;
219 }
220 let f = File::options().write(true).open(path)?;
221 let mut f = BufWriter::new(f);
222 f.write_all(file.code.as_bytes())
223}
224
225pub fn dump_decl_package(modules: &SharedModuleCache) {
226 for (path, module) in modules.raw_iter() {
227 if let Some(hir) = module.hir.as_ref() {
228 let _ = dump_decl_er(path, hir, module.status);
229 }
230 }
231}