Skip to main content

libperl_macrogen/
infer_api.rs

1//! マクロ型推論の高レベル API
2//!
3//! build.rs や外部ツールから型推論を実行するための API を提供する。
4
5use std::collections::{HashMap, HashSet};
6use std::ops::ControlFlow;
7use std::path::{Path, PathBuf};
8
9use crate::apidoc::{ApidocCollector, ApidocDict, ApidocResolveError};
10use crate::ast::{DerivedDecl, ExternalDecl, TypeSpec};
11use crate::c_fn_decl::{CFnDecl, CFnDeclDict, CParam};
12use crate::enum_dict::EnumDict;
13use crate::error::EnrichedCompileError;
14use crate::fields_dict::FieldsDict;
15use crate::inline_fn::InlineFnDict;
16use crate::intern::InternedStr;
17use crate::macro_infer::{ExplicitExpandSymbols, MacroInferContext, NoExpandSymbols};
18use crate::parser::Parser;
19use crate::perl_config::PerlConfigError;
20use crate::preprocessor::{MacroCallWatcher, MacroDefCallback, Preprocessor};
21use crate::rust_decl::RustDeclDict;
22
23/// typedef 辞書の型エイリアス
24pub type TypedefDict = HashSet<InternedStr>;
25
26/// 型推論エラー
27#[derive(Debug)]
28pub enum InferError {
29    /// Perl 設定取得エラー
30    PerlConfig(PerlConfigError),
31    /// apidoc 解決エラー
32    ApidocResolve(ApidocResolveError),
33    /// プリプロセッサ/パースエラー(ファイルパスと該当行で強化済み)
34    Compile(EnrichedCompileError),
35    /// ファイル I/O エラー
36    Io(std::io::Error),
37}
38
39impl std::fmt::Display for InferError {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            InferError::PerlConfig(e) => write!(f, "Perl config error: {}", e),
43            InferError::ApidocResolve(e) => write!(f, "Apidoc resolve error: {}", e),
44            InferError::Compile(e) => write!(f, "Compile error: {}", e),
45            InferError::Io(e) => write!(f, "I/O error: {}", e),
46        }
47    }
48}
49
50impl std::error::Error for InferError {
51    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
52        match self {
53            InferError::PerlConfig(e) => Some(e),
54            InferError::ApidocResolve(e) => Some(e),
55            InferError::Compile(e) => Some(e),
56            InferError::Io(e) => Some(e),
57        }
58    }
59}
60
61impl From<PerlConfigError> for InferError {
62    fn from(e: PerlConfigError) -> Self {
63        InferError::PerlConfig(e)
64    }
65}
66
67impl From<ApidocResolveError> for InferError {
68    fn from(e: ApidocResolveError) -> Self {
69        InferError::ApidocResolve(e)
70    }
71}
72
73impl From<EnrichedCompileError> for InferError {
74    fn from(e: EnrichedCompileError) -> Self {
75        InferError::Compile(e)
76    }
77}
78
79impl From<std::io::Error> for InferError {
80    fn from(e: std::io::Error) -> Self {
81        InferError::Io(e)
82    }
83}
84
85/// 型推論の設定
86#[derive(Debug, Clone)]
87pub struct InferConfig {
88    /// 入力ファイル(wrapper.h など)
89    pub input_file: PathBuf,
90    /// apidoc ファイルのパス(省略時は自動検索)
91    pub apidoc_path: Option<PathBuf>,
92    /// Rust バインディングファイルのパス
93    pub bindings_path: Option<PathBuf>,
94    /// apidoc ディレクトリの検索パス(省略時は自動検索)
95    pub apidoc_dir: Option<PathBuf>,
96    /// デバッグ出力
97    pub debug: bool,
98}
99
100impl InferConfig {
101    /// 入力ファイルのみを指定した最小構成
102    pub fn new(input_file: PathBuf) -> Self {
103        Self {
104            input_file,
105            apidoc_path: None,
106            bindings_path: None,
107            apidoc_dir: None,
108            debug: false,
109        }
110    }
111
112    /// apidoc パスを設定
113    pub fn with_apidoc(mut self, path: PathBuf) -> Self {
114        self.apidoc_path = Some(path);
115        self
116    }
117
118    /// bindings パスを設定
119    pub fn with_bindings(mut self, path: PathBuf) -> Self {
120        self.bindings_path = Some(path);
121        self
122    }
123
124    /// apidoc ディレクトリを設定
125    pub fn with_apidoc_dir(mut self, path: PathBuf) -> Self {
126        self.apidoc_dir = Some(path);
127        self
128    }
129
130    /// デバッグモードを設定
131    pub fn with_debug(mut self, debug: bool) -> Self {
132        self.debug = debug;
133        self
134    }
135}
136
137/// デバッグ出力オプション
138///
139/// パイプラインの特定の段階でデータ構造をダンプして早期終了するためのオプション。
140#[derive(Debug, Clone, Default)]
141pub struct DebugOptions {
142    /// apidoc マージ後にダンプして終了
143    /// Some(filter) でフィルタ指定(正規表現)、Some("") で全件出力
144    pub dump_apidoc_after_merge: Option<String>,
145    /// 型推論デバッグ対象のマクロ名リスト
146    pub debug_type_inference: Vec<String>,
147}
148
149impl DebugOptions {
150    pub fn new() -> Self {
151        Self::default()
152    }
153
154    /// apidoc マージ後にダンプして終了するオプションを設定
155    pub fn dump_apidoc(mut self, filter: impl Into<String>) -> Self {
156        self.dump_apidoc_after_merge = Some(filter.into());
157        self
158    }
159}
160
161/// 共通フィールドマクロ (`_XPV_HEAD` / `_XPVCV_COMMON` 等) の本体を
162/// `#define` の時点で捕獲する `MacroDefCallback`。
163///
164/// これらのマクロは perl のヘッダ(perl.h, sv.h)で `#undef` されてから
165/// 利用が終わるため、最終的な MacroTable には残らない。`on_macro_defined`
166/// で本体を保存しておくことで Phase 2 後にもアクセスできる。
167struct CommonMacroBodyCollector {
168    targets: HashSet<InternedStr>,
169    bodies: HashMap<InternedStr, Vec<crate::token::Token>>,
170}
171
172impl CommonMacroBodyCollector {
173    fn new(targets: HashSet<InternedStr>) -> Self {
174        Self { targets, bodies: HashMap::new() }
175    }
176}
177
178impl MacroDefCallback for CommonMacroBodyCollector {
179    fn on_macro_defined(&mut self, def: &crate::macro_def::MacroDef) {
180        if self.targets.contains(&def.name) {
181            self.bodies.insert(def.name, def.body.clone());
182        }
183    }
184    fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
185}
186
187/// 統計情報
188#[derive(Debug, Clone, Default)]
189pub struct InferStats {
190    /// コメントから収集した apidoc 数
191    pub apidoc_from_comments: usize,
192    /// THX 依存マクロ数
193    pub thx_dependent_count: usize,
194    /// 収集された C 関数宣言数
195    pub c_fn_decl_count: usize,
196    /// THX 依存の C 関数数
197    pub c_fn_thx_count: usize,
198}
199
200/// 型推論の結果
201pub struct InferResult {
202    /// マクロ推論コンテキスト(全マクロの解析結果)
203    pub infer_ctx: MacroInferContext,
204    /// フィールド辞書
205    pub fields_dict: FieldsDict,
206    /// Enum 辞書
207    pub enum_dict: EnumDict,
208    /// インライン関数辞書
209    pub inline_fn_dict: InlineFnDict,
210    /// Apidoc 辞書
211    pub apidoc: ApidocDict,
212    /// Rust 宣言辞書
213    pub rust_decl_dict: Option<RustDeclDict>,
214    /// C 関数宣言辞書
215    pub c_fn_decl_dict: CFnDeclDict,
216    /// typedef 辞書
217    pub typedefs: TypedefDict,
218    /// global static const 配列宣言辞書(bodies_by_type 等)
219    pub global_const_dict: crate::global_const_dict::GlobalConstDict,
220    /// apidoc patches(perl C ヘッダ既知バグの訂正データ)
221    pub apidoc_patches: crate::apidoc_patches::ApidocPatchSet,
222    /// 対象 perl の build mode(threaded / non-threaded)
223    pub perl_build_mode: crate::perl_config::PerlBuildMode,
224    /// PERLVAR 観測辞書 (Phase 1 で収集、Phase 3 で `PL_xxx!()` 出力に使う)。
225    /// Pipeline で `with_perlvar_collection(false)` した場合は空。
226    pub perlvar_dict: crate::perlvar_dict::PerlvarDict,
227    /// プリプロセッサ(マクロテーブル、StringInterner、FileRegistry へのアクセス用)
228    pub preprocessor: Preprocessor,
229    /// 統計情報
230    pub stats: InferStats,
231}
232
233/// 既存の Preprocessor を使ってマクロ型推論を実行
234///
235/// Preprocessor が既に初期化されている場合に使用。
236/// 主に Pipeline の内部実装から呼び出される。
237///
238/// **Note**: 新しいコードでは `Pipeline` API の使用を推奨します。
239/// この関数は Pipeline の内部実装で使用されており、直接呼び出す必要は
240/// 通常ありません。
241///
242/// `debug_opts` が指定され、デバッグダンプで早期終了した場合は `Ok(None)` を返す。
243pub fn run_inference_with_preprocessor(
244    mut pp: Preprocessor,
245    apidoc_path: Option<&Path>,
246    bindings_path: Option<&Path>,
247    debug_opts: Option<&DebugOptions>,
248    skip_codegen_lists: &[PathBuf],
249    perl_build_mode_override: Option<crate::perl_config::PerlBuildMode>,
250) -> Result<Option<InferResult>, InferError> {
251    // Perl build mode を確定(明示指定があれば優先、なければ auto-detect)
252    let perl_build_mode = match perl_build_mode_override {
253        Some(m) => m,
254        None => crate::perl_config::PerlBuildMode::detect_from_perl_config()
255            .unwrap_or(crate::perl_config::PerlBuildMode::Threaded),
256    };
257    eprintln!("[perl-mode] {:?}", perl_build_mode);
258    // RustDeclDict をロード(パーサー作成前に行い、展開抑制を設定)
259    let rust_decl_dict = if let Some(path) = bindings_path {
260        Some(RustDeclDict::parse_file(path)?)
261    } else {
262        None
263    };
264
265    // bindings.rs の定数名を展開抑制に登録
266    if let Some(ref dict) = rust_decl_dict {
267        for name in dict.consts.keys() {
268            let interned = pp.interner_mut().intern(name);
269            pp.add_skip_expand_macro(interned);
270        }
271        // bindgen 生成の anonymous union 型名 (`pmop__bindgen_ty_2` 等) を
272        // intern しておくことで、後段の型推論で
273        // `from_apidoc_string` 経由の `TypedefName` 解決が可能になる。
274        dict.intern_names(pp.interner_mut());
275    }
276
277    // 明示展開マクロを Preprocessor に登録(wrapped_macros の引数展開用)
278    // SvANY, SvFLAGS など、preserve_function_macros モードでも展開するマクロ
279    {
280        let explicit_expand = ExplicitExpandSymbols::new(pp.interner_mut());
281        pp.add_explicit_expand_macros(explicit_expand.iter());
282    }
283
284    // フィールド辞書を作成(パースしながら収集)
285    let mut fields_dict = FieldsDict::new();
286    let mut global_const_dict = crate::global_const_dict::GlobalConstDict::new();
287
288    // Enum 辞書を作成(パースしながら収集)
289    let mut enum_dict = EnumDict::new();
290
291    // ApidocCollector を Preprocessor に設定
292    pp.set_comment_callback(Box::new(ApidocCollector::new()));
293
294    // _SV_HEAD マクロ呼び出しを監視。
295    // perl 5.44 で予約識別子の整理により `_SV_HEAD` → `SV_HEAD_` に改名された
296    // (sv.h)。新旧両方を監視する (存在しない名前の watcher は発火しないだけで
297    // 無害なため、バージョン分岐は不要)。
298    const SV_HEAD_MACROS: &[&str] = &["_SV_HEAD", "SV_HEAD_"];
299    let sv_head_ids: Vec<InternedStr> = SV_HEAD_MACROS
300        .iter()
301        .map(|name| {
302            let id = pp.interner_mut().intern(name);
303            pp.set_macro_called_callback(id, Box::new(MacroCallWatcher::new()));
304            id
305        })
306        .collect();
307
308    // 共通フィールド宣言マクロ(perl5 専用ハードコード)。
309    // `_SV_HEAD` と同様、struct 通過時に Watcher を見て使用関係を fields_dict
310    // に記録する。新規追加はこのリストに 1 行足すだけで良い。
311    // `XPV_HEAD_` / `XPVCV_COMMON_` は 5.44 での改名後の名前 (同上)。
312    //
313    // また、これらのマクロは perl.h / sv.h で `#undef` されるため、最終的な
314    // MacroTable には残らない。本体(フィールド宣言列)は
315    // `CommonMacroBodyCollector` が `#define` 時点で捕獲する。
316    const COMMON_FIELD_MACROS: &[&str] =
317        &["_XPV_HEAD", "_XPVCV_COMMON", "XPV_HEAD_", "XPVCV_COMMON_"];
318    let common_field_macro_ids: Vec<InternedStr> = COMMON_FIELD_MACROS
319        .iter()
320        .map(|name| {
321            let id = pp.interner_mut().intern(name);
322            pp.set_macro_called_callback(id, Box::new(MacroCallWatcher::new()));
323            id
324        })
325        .collect();
326    pp.set_macro_def_callback(Box::new(CommonMacroBodyCollector::new(
327        common_field_macro_ids.iter().copied().collect(),
328    )));
329
330    // pTHX_ と pTHX マクロ呼び出しを監視(関数宣言の THX 依存検出用)
331    //
332    // 非 threaded perl では `pTHX_` / `pTHX` は空マクロに展開される。
333    // しかし `set_macro_called_callback` はマクロが呼び出された事実を
334    // 記録するため、展開結果が空でも is_thx=true が立ってしまう。
335    // それをそのまま codegen に渡すと「存在しない my_perl 引数」を
336    // 注入する破綻を起こすので、threaded mode のときだけ callback を
337    // 登録する(non-threaded では誰も THX 依存にならない)。
338    let pthx_id = pp.interner_mut().intern("pTHX_");
339    let pthx_no_comma_id = pp.interner_mut().intern("pTHX");
340    if perl_build_mode.is_threaded() {
341        pp.set_macro_called_callback(pthx_id, Box::new(MacroCallWatcher::new()));
342        pp.set_macro_called_callback(pthx_no_comma_id, Box::new(MacroCallWatcher::new()));
343    }
344
345    // C 関数宣言辞書を作成
346    let mut c_fn_decl_dict = CFnDeclDict::new();
347
348    // パーサー作成
349    let mut parser = match Parser::new(&mut pp) {
350        Ok(p) => p,
351        Err(e) => return Err(InferError::Compile(e.with_files(pp.files()))),
352    };
353
354    // inline 関数辞書を作成
355    let mut inline_fn_dict = InlineFnDict::new();
356
357    // parse_each_with_pp でフィールド辞書と inline 関数を収集
358    // 同時に _SV_HEAD マクロ呼び出しを検出して SV ファミリーを動的に構築
359    // また、関数宣言を収集して THX 依存性を検出
360    let parse_result = parser.parse_each_with_pp(|decl, loc, path, pp| {
361        let interner = pp.interner();
362        fields_dict.collect_from_external_decl(decl, decl.is_target(), interner);
363
364        // global static const declarations を捕捉
365        // 例: `static const struct body_details bodies_by_type[] = {...}`
366        global_const_dict.try_collect(decl, decl.is_target(), interner);
367
368        // enum 情報を収集
369        enum_dict.collect_from_external_decl(decl, decl.is_target(), interner);
370
371        // inline 関数を収集
372        if decl.is_target() {
373            if let ExternalDecl::FunctionDef(func_def) = decl {
374                inline_fn_dict.collect_from_function_def(func_def, interner);
375            }
376        }
377
378        // 関数宣言を収集(THX 依存性検出用)
379        if let ExternalDecl::Declaration(declaration) = decl {
380            // pTHX_ または pTHX が呼ばれたかチェック
381            let is_thx = check_macro_called(pp, pthx_id) || check_macro_called(pp, pthx_no_comma_id);
382
383            // 関数宣言を収集
384            collect_function_declarations(
385                declaration,
386                &mut c_fn_decl_dict,
387                is_thx,
388                loc,
389                path,
390                interner,
391            );
392
393            // フラグをリセット(次の宣言のために)
394            reset_macro_called(pp, pthx_id);
395            reset_macro_called(pp, pthx_no_comma_id);
396        }
397
398        // 構造体定義の場合、_SV_HEAD と共通フィールドマクロのフラグをチェック
399        if decl.is_target() {
400            if let Some(struct_names) = extract_struct_names(decl) {
401                // _SV_HEAD / SV_HEAD_ (5.44+) が呼ばれていたら SV ファミリーに追加
402                for &sv_head_id in &sv_head_ids {
403                    if let Some(cb) = pp.get_macro_called_callback(sv_head_id) {
404                        if let Some(watcher) = cb.as_any().downcast_ref::<MacroCallWatcher>() {
405                            if watcher.take_called() {
406                                // _SV_HEAD(typeName) の引数を取得
407                                let type_name = watcher.last_args()
408                                    .and_then(|args| args.first().cloned())
409                                    .unwrap_or_default();
410
411                                for name in &struct_names {
412                                    // typeName → 構造体名マッピングも同時に登録
413                                    fields_dict.add_sv_family_member_with_type(*name, &type_name);
414                                }
415                            }
416                        }
417                    }
418                }
419
420                // 共通フィールドマクロ (_XPV_HEAD, _XPVCV_COMMON 等) の使用を記録
421                for &macro_id in &common_field_macro_ids {
422                    if let Some(cb) = pp.get_macro_called_callback(macro_id) {
423                        if let Some(watcher) = cb.as_any().downcast_ref::<MacroCallWatcher>() {
424                            if watcher.take_called() {
425                                for name in &struct_names {
426                                    fields_dict.add_struct_uses_common_macro(*name, macro_id);
427                                }
428                            }
429                        }
430                    }
431                }
432            }
433        }
434        ControlFlow::Continue(())
435    });
436    if let Err(e) = parse_result {
437        // parser を drop して pp の借用を解放してから enrich する
438        drop(parser);
439        return Err(InferError::Compile(e.with_files(pp.files())));
440    }
441
442    // パーサーから typedef 辞書を取得
443    let typedefs = parser.typedefs().clone();
444
445    // コールバックを取り出してダウンキャスト
446    let callback = pp.take_comment_callback().expect("callback should exist");
447    let apidoc_collector = callback
448        .into_any()
449        .downcast::<ApidocCollector>()
450        .expect("callback type mismatch");
451
452    // token 型マクロを intern して InternedStr のベクターに変換
453    // (apidoc から検出されたトークン合成マクロ、例: XopENTRYCUSTOM)
454    let token_type_macros: Vec<InternedStr> = apidoc_collector
455        .token_type_macros()
456        .iter()
457        .map(|name| pp.interner_mut().intern(name))
458        .collect();
459
460    // 一致型キャッシュを構築(全フィールドについて型の一貫性を事前計算)
461    fields_dict.build_consistent_type_cache(pp.interner());
462
463    // 共通フィールドマクロの canonical field set を構築(B-2)。
464    // B-1 で観測対象として登録した各マクロの本体を struct member 列として
465    // パースし、フィールド名と関数ポインタ判定を `FieldsDict.common_macros`
466    // と `field_to_defining_macro` に格納する。
467    {
468        let collector = pp
469            .take_macro_def_callback()
470            .and_then(|cb| cb.into_any().downcast::<CommonMacroBodyCollector>().ok());
471        let mut macro_bodies: Vec<(InternedStr, Vec<crate::token::Token>)> = collector
472            .map(|c| c.bodies.into_iter().collect())
473            .unwrap_or_default();
474        // perl 共通マクロ本体内に現れる `pTHX_` / `pTHX` トークンは関数ポインタ
475        // シグネチャ内のスレッド対応マクロ。本体パース時は意味的に空に展開
476        // されるべきなのでトークンレベルで除去する。
477        let pthx_id = pp.interner_mut().intern("pTHX_");
478        let pthx_no_comma_id = pp.interner_mut().intern("pTHX");
479        for (_id, body) in macro_bodies.iter_mut() {
480            body.retain(|t| !matches!(&t.kind,
481                crate::token::TokenKind::Ident(id)
482                    if *id == pthx_id || *id == pthx_no_comma_id));
483        }
484        let interner = pp.interner();
485        let files = pp.files().clone();
486        let typedefs_ref = typedefs.clone();
487        fields_dict.build_common_macro_fields(&macro_bodies, |body| {
488            crate::parser::parse_struct_members_from_tokens_ref(
489                body, interner, &files, &typedefs_ref,
490            ).map_err(crate::error::CompileError::from)
491        });
492    }
493
494    // 共通フィールドマクロ宣言フィールド名 → bindings.rs 由来の Rust 型
495    // のマッピングを構築。`CvXSUB` の戻り値型のような無名 union メンバ
496    // への型解決に使う。
497    if let Some(ref dict) = rust_decl_dict {
498        fields_dict.build_common_field_rust_types(dict, pp.interner_mut());
499    }
500
501    // 共通フィールドマクロ → 一意な SV ファミリー typedef の事前マッピング
502    // を構築。`CvHASGV(cv)` のように `_XPVCV_COMMON` 由来フィールドへの
503    // アクセス経路から `cv: *const CV` を逆推論するために使う。
504    fields_dict.build_common_macro_sv_family(pp.interner());
505
506    // sv_u フィールド型は parse_each で動的に収集済み
507    // (SV ファミリー構造体の sv_u union から自動検出)
508
509    // Apidoc をロード(ファイルから + コメントから)
510    let mut apidoc = if let Some(path) = apidoc_path {
511        ApidocDict::load_auto(path)?
512    } else {
513        ApidocDict::new()
514    };
515    let apidoc_from_comments = apidoc_collector.len();
516    apidoc_collector.merge_into(&mut apidoc);
517
518    // apidoc patches を適用(merge 後 / type macro 展開前)
519    // perl の C ヘッダや apidoc に含まれる既知の誤りを訂正する。
520    // 2 段マージ:
521    //   1. <apidoc dir>/common.patches.json     ← 全バージョン共通
522    //   2. <apidoc dir>/v$major.$minor.patches.json ← 当該バージョン固有
523    // 両方とも存在しなければ no-op。
524    let mut apidoc_patches = if let Some(path) = apidoc_path {
525        crate::apidoc_patches::ApidocPatchSet::load_for_apidoc_path(path)?
526    } else {
527        crate::apidoc_patches::ApidocPatchSet::empty()
528    };
529    // テキスト形式の skip-list ファイルをマージ(同名は patches 側を優先)
530    for list_path in skip_codegen_lists {
531        let added = apidoc_patches.merge_skip_list(list_path)?;
532        eprintln!(
533            "[apidoc-patches] merged {} skip entry(ies) from {}",
534            added, list_path.display()
535        );
536    }
537    if !apidoc_patches.is_empty() {
538        let applied = apidoc_patches.apply_to_apidoc(&mut apidoc);
539        if !apidoc_patches.source_paths.is_empty() {
540            let paths_str = apidoc_patches.source_paths.iter()
541                .map(|p| p.display().to_string())
542                .collect::<Vec<_>>()
543                .join(", ");
544            eprintln!(
545                "[apidoc-patches] loaded {} patch(es) from [{}] ({} override/add_decl applied, {} skip-codegen registered)",
546                apidoc_patches.count(),
547                paths_str,
548                applied.len(),
549                apidoc_patches.skip_codegen.len(),
550            );
551        }
552    }
553
554    // apidoc 内の型マクロを展開 (Off_t → off_t, Size_t → size_t など)
555    apidoc.expand_type_macros(pp.macros(), pp.interner());
556
557    // デバッグ: apidoc マージ後にダンプして早期終了
558    if let Some(opts) = debug_opts {
559        if let Some(filter) = &opts.dump_apidoc_after_merge {
560            apidoc.dump_filtered(filter);
561            return Ok(None);
562        }
563    }
564
565    // MacroInferContext を作成して解析
566    let mut infer_ctx = MacroInferContext::new();
567
568    // デバッグ対象マクロを設定
569    if let Some(opts) = debug_opts {
570        if !opts.debug_type_inference.is_empty() {
571            infer_ctx.set_debug_macros(opts.debug_type_inference.iter().cloned());
572        }
573    }
574
575    // THX シンボルを事前に intern
576    let sym_athx = pp.interner_mut().intern("aTHX");
577    let sym_tthx = pp.interner_mut().intern("tTHX");
578    let sym_my_perl = pp.interner_mut().intern("my_perl");
579    let thx_symbols = (sym_athx, sym_tthx, sym_my_perl);
580
581    // 展開を抑制するマクロシンボルを作成(assert など特殊処理用)
582    let no_expand = NoExpandSymbols::new(pp.interner_mut());
583
584    // 明示的に展開するマクロを Preprocessor に登録
585    // (SvANY, SvFLAGS など + apidoc から検出した token 型マクロ)
586    {
587        let explicit_expand = ExplicitExpandSymbols::new(pp.interner_mut());
588        pp.add_explicit_expand_macros(explicit_expand.iter());
589    }
590    pp.add_explicit_expand_macros(token_type_macros.iter().copied());
591
592    // パッチ適用済みの apidoc 辞書からも token 型引数を持つマクロを登録する。
593    // collector 由来の token_type_macros はヘッダの `=for apidoc` スクレイプ
594    // のみが源で、パッチ適用前に確定している。旧 perl のヘッダは token 注釈を
595    // 欠く (例: <=5.36 の XopENTRYCUSTOM は型無し `which` — 5.38 で上流修正)
596    // ため、arg_type_override で補った token 注釈はここで拾う必要がある。
597    {
598        let dict_token_macros: Vec<InternedStr> = apidoc
599            .iter()
600            .filter(|(_, entry)| entry.has_token_arg())
601            .map(|(name, _)| pp.interner_mut().intern(name))
602            .collect();
603        pp.add_explicit_expand_macros(dict_token_macros);
604    }
605
606    infer_ctx.analyze_all_macros(
607        &mut pp,
608        Some(&apidoc),
609        Some(&apidoc_patches),
610        Some(&fields_dict),
611        rust_decl_dict.as_ref(),
612        Some(&mut inline_fn_dict),
613        Some(&c_fn_decl_dict),
614        &typedefs,
615        thx_symbols,
616        no_expand,
617        perl_build_mode,
618    );
619
620    // THX 依存マクロ数をカウント
621    let thx_dependent_count = infer_ctx.macros.values()
622        .filter(|info| info.is_target && info.is_thx_dependent)
623        .count();
624
625    // C 関数宣言の統計
626    let c_fn_decl_count = c_fn_decl_dict.len();
627    let c_fn_thx_count = c_fn_decl_dict.thx_count();
628
629    let stats = InferStats {
630        apidoc_from_comments,
631        thx_dependent_count,
632        c_fn_decl_count,
633        c_fn_thx_count,
634    };
635
636    // Phase 2 最終パス: パラメータ/戻り値型の確定(const/mut, bool)
637    infer_ctx.resolve_param_and_return_types(
638        pp.interner_mut(),
639        rust_decl_dict.as_ref(),
640        &inline_fn_dict,
641    );
642
643    Ok(Some(InferResult {
644        infer_ctx,
645        fields_dict,
646        enum_dict,
647        inline_fn_dict,
648        apidoc,
649        rust_decl_dict,
650        c_fn_decl_dict,
651        typedefs,
652        global_const_dict,
653        apidoc_patches,
654        perl_build_mode,
655        // Default: empty. Pipeline overwrites this with the collected dict
656        // before returning the result.
657        perlvar_dict: crate::perlvar_dict::PerlvarDict::new(),
658        preprocessor: pp,
659        stats,
660    }))
661}
662
663/// 宣言から構造体名を抽出
664fn extract_struct_names(decl: &ExternalDecl) -> Option<Vec<InternedStr>> {
665    let declaration = match decl {
666        ExternalDecl::Declaration(d) => d,
667        _ => return None,
668    };
669
670    let mut names = Vec::new();
671
672    for type_spec in &declaration.specs.type_specs {
673        match type_spec {
674            TypeSpec::Struct(spec) | TypeSpec::Union(spec) => {
675                // メンバーリストを持つ定義のみ(前方宣言は除外)
676                if spec.members.is_some() {
677                    if let Some(name) = spec.name {
678                        names.push(name);
679                    }
680                }
681            }
682            _ => {}
683        }
684    }
685
686    if names.is_empty() {
687        None
688    } else {
689        Some(names)
690    }
691}
692
693/// MacroCallWatcher の呼び出しフラグをチェック
694fn check_macro_called(pp: &Preprocessor, macro_id: InternedStr) -> bool {
695    pp.get_macro_called_callback(macro_id)
696        .and_then(|cb| cb.as_any().downcast_ref::<MacroCallWatcher>())
697        .is_some_and(|w| w.was_called())
698}
699
700/// MacroCallWatcher のフラグをリセット
701fn reset_macro_called(pp: &Preprocessor, macro_id: InternedStr) {
702    // take_called() は Cell を使っているので &self で動作する
703    if let Some(cb) = pp.get_macro_called_callback(macro_id) {
704        if let Some(w) = cb.as_any().downcast_ref::<MacroCallWatcher>() {
705            w.take_called(); // フラグを消費してリセット
706        }
707    }
708}
709
710/// 宣言から関数宣言を収集
711fn collect_function_declarations(
712    declaration: &crate::ast::Declaration,
713    dict: &mut CFnDeclDict,
714    is_thx: bool,
715    loc: &crate::source::SourceLocation,
716    path: &std::path::Path,
717    interner: &crate::intern::StringInterner,
718) {
719    // 各宣言子を処理
720    for init_decl in &declaration.declarators {
721        let declarator = &init_decl.declarator;
722
723        // 関数宣言かどうかをチェック(DerivedDecl::Function があるか)
724        let param_list = declarator.derived.iter().find_map(|d| {
725            if let DerivedDecl::Function(params) = d {
726                Some(params)
727            } else {
728                None
729            }
730        });
731
732        if let Some(param_list) = param_list {
733            if let Some(name) = declarator.name {
734                // パラメータを抽出
735                let params: Vec<CParam> = param_list.params.iter().map(|param| {
736                    let param_name = param.declarator.as_ref().and_then(|d| d.name);
737                    let ty = type_specs_to_string(&param.specs, interner);
738                    CParam { name: param_name, ty }
739                }).collect();
740
741                // 戻り値の型を抽出
742                let ret_ty = type_specs_to_string(&declaration.specs, interner);
743
744                let c_fn_decl = CFnDecl {
745                    name,
746                    params,
747                    ret_ty,
748                    is_thx,
749                    is_target: declaration.is_target,
750                    location: Some(format!("{}:{}", path.display(), loc.line)),
751                };
752                dict.insert(c_fn_decl);
753            }
754        }
755    }
756}
757
758/// DeclSpecs から型の文字列表現を生成(シンプルな実装)
759fn type_specs_to_string(specs: &crate::ast::DeclSpecs, interner: &crate::intern::StringInterner) -> String {
760    use crate::ast::TypeSpec;
761
762    let mut parts = Vec::new();
763
764    for type_spec in &specs.type_specs {
765        match type_spec {
766            TypeSpec::Void => parts.push("void".to_string()),
767            TypeSpec::Char => parts.push("char".to_string()),
768            TypeSpec::Short => parts.push("short".to_string()),
769            TypeSpec::Int => parts.push("int".to_string()),
770            TypeSpec::Long => parts.push("long".to_string()),
771            TypeSpec::Float => parts.push("float".to_string()),
772            TypeSpec::Double => parts.push("double".to_string()),
773            TypeSpec::Signed => parts.push("signed".to_string()),
774            TypeSpec::Unsigned => parts.push("unsigned".to_string()),
775            TypeSpec::Bool => parts.push("bool".to_string()),
776            TypeSpec::Complex => parts.push("_Complex".to_string()),
777            TypeSpec::TypedefName(name) => parts.push(interner.get(*name).to_string()),
778            TypeSpec::Struct(spec) => {
779                if let Some(name) = spec.name {
780                    parts.push(format!("struct {}", interner.get(name)));
781                } else {
782                    parts.push("struct".to_string());
783                }
784            }
785            TypeSpec::Union(spec) => {
786                if let Some(name) = spec.name {
787                    parts.push(format!("union {}", interner.get(name)));
788                } else {
789                    parts.push("union".to_string());
790                }
791            }
792            TypeSpec::Enum(spec) => {
793                if let Some(name) = spec.name {
794                    parts.push(format!("enum {}", interner.get(name)));
795                } else {
796                    parts.push("enum".to_string());
797                }
798            }
799            TypeSpec::TypeofExpr(_) => parts.push("typeof(...)".to_string()),
800            TypeSpec::Int128 => parts.push("__int128".to_string()),
801            TypeSpec::Float16 => parts.push("_Float16".to_string()),
802            TypeSpec::Float32 => parts.push("_Float32".to_string()),
803            TypeSpec::Float64 => parts.push("_Float64".to_string()),
804            TypeSpec::Float128 => parts.push("_Float128".to_string()),
805            TypeSpec::Float32x => parts.push("_Float32x".to_string()),
806            TypeSpec::Float64x => parts.push("_Float64x".to_string()),
807        }
808    }
809
810    if parts.is_empty() {
811        "int".to_string() // デフォルトは int
812    } else {
813        parts.join(" ")
814    }
815}