1use 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
23pub type TypedefDict = HashSet<InternedStr>;
25
26#[derive(Debug)]
28pub enum InferError {
29 PerlConfig(PerlConfigError),
31 ApidocResolve(ApidocResolveError),
33 Compile(EnrichedCompileError),
35 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#[derive(Debug, Clone)]
87pub struct InferConfig {
88 pub input_file: PathBuf,
90 pub apidoc_path: Option<PathBuf>,
92 pub bindings_path: Option<PathBuf>,
94 pub apidoc_dir: Option<PathBuf>,
96 pub debug: bool,
98}
99
100impl InferConfig {
101 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 pub fn with_apidoc(mut self, path: PathBuf) -> Self {
114 self.apidoc_path = Some(path);
115 self
116 }
117
118 pub fn with_bindings(mut self, path: PathBuf) -> Self {
120 self.bindings_path = Some(path);
121 self
122 }
123
124 pub fn with_apidoc_dir(mut self, path: PathBuf) -> Self {
126 self.apidoc_dir = Some(path);
127 self
128 }
129
130 pub fn with_debug(mut self, debug: bool) -> Self {
132 self.debug = debug;
133 self
134 }
135}
136
137#[derive(Debug, Clone, Default)]
141pub struct DebugOptions {
142 pub dump_apidoc_after_merge: Option<String>,
145 pub debug_type_inference: Vec<String>,
147}
148
149impl DebugOptions {
150 pub fn new() -> Self {
151 Self::default()
152 }
153
154 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
161struct 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#[derive(Debug, Clone, Default)]
189pub struct InferStats {
190 pub apidoc_from_comments: usize,
192 pub thx_dependent_count: usize,
194 pub c_fn_decl_count: usize,
196 pub c_fn_thx_count: usize,
198}
199
200pub struct InferResult {
202 pub infer_ctx: MacroInferContext,
204 pub fields_dict: FieldsDict,
206 pub enum_dict: EnumDict,
208 pub inline_fn_dict: InlineFnDict,
210 pub apidoc: ApidocDict,
212 pub rust_decl_dict: Option<RustDeclDict>,
214 pub c_fn_decl_dict: CFnDeclDict,
216 pub typedefs: TypedefDict,
218 pub global_const_dict: crate::global_const_dict::GlobalConstDict,
220 pub apidoc_patches: crate::apidoc_patches::ApidocPatchSet,
222 pub perl_build_mode: crate::perl_config::PerlBuildMode,
224 pub perlvar_dict: crate::perlvar_dict::PerlvarDict,
227 pub preprocessor: Preprocessor,
229 pub stats: InferStats,
231}
232
233pub 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 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 let rust_decl_dict = if let Some(path) = bindings_path {
260 Some(RustDeclDict::parse_file(path)?)
261 } else {
262 None
263 };
264
265 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 dict.intern_names(pp.interner_mut());
275 }
276
277 {
280 let explicit_expand = ExplicitExpandSymbols::new(pp.interner_mut());
281 pp.add_explicit_expand_macros(explicit_expand.iter());
282 }
283
284 let mut fields_dict = FieldsDict::new();
286 let mut global_const_dict = crate::global_const_dict::GlobalConstDict::new();
287
288 let mut enum_dict = EnumDict::new();
290
291 pp.set_comment_callback(Box::new(ApidocCollector::new()));
293
294 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 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 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 let mut c_fn_decl_dict = CFnDeclDict::new();
347
348 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 let mut inline_fn_dict = InlineFnDict::new();
356
357 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_const_dict.try_collect(decl, decl.is_target(), interner);
367
368 enum_dict.collect_from_external_decl(decl, decl.is_target(), interner);
370
371 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 if let ExternalDecl::Declaration(declaration) = decl {
380 let is_thx = check_macro_called(pp, pthx_id) || check_macro_called(pp, pthx_no_comma_id);
382
383 collect_function_declarations(
385 declaration,
386 &mut c_fn_decl_dict,
387 is_thx,
388 loc,
389 path,
390 interner,
391 );
392
393 reset_macro_called(pp, pthx_id);
395 reset_macro_called(pp, pthx_no_comma_id);
396 }
397
398 if decl.is_target() {
400 if let Some(struct_names) = extract_struct_names(decl) {
401 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 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 fields_dict.add_sv_family_member_with_type(*name, &type_name);
414 }
415 }
416 }
417 }
418 }
419
420 for ¯o_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 drop(parser);
439 return Err(InferError::Compile(e.with_files(pp.files())));
440 }
441
442 let typedefs = parser.typedefs().clone();
444
445 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 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 fields_dict.build_consistent_type_cache(pp.interner());
462
463 {
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 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(¯o_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 if let Some(ref dict) = rust_decl_dict {
498 fields_dict.build_common_field_rust_types(dict, pp.interner_mut());
499 }
500
501 fields_dict.build_common_macro_sv_family(pp.interner());
505
506 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 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 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.expand_type_macros(pp.macros(), pp.interner());
556
557 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 let mut infer_ctx = MacroInferContext::new();
567
568 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 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 let no_expand = NoExpandSymbols::new(pp.interner_mut());
583
584 {
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 {
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 let thx_dependent_count = infer_ctx.macros.values()
622 .filter(|info| info.is_target && info.is_thx_dependent)
623 .count();
624
625 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 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 perlvar_dict: crate::perlvar_dict::PerlvarDict::new(),
658 preprocessor: pp,
659 stats,
660 }))
661}
662
663fn 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 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
693fn 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
700fn reset_macro_called(pp: &Preprocessor, macro_id: InternedStr) {
702 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(); }
707 }
708}
709
710fn 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 for init_decl in &declaration.declarators {
721 let declarator = &init_decl.declarator;
722
723 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 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(¶m.specs, interner);
738 CParam { name: param_name, ty }
739 }).collect();
740
741 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
758fn 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() } else {
813 parts.join(" ")
814 }
815}