88
99from .lexer import preprocess_lines
1010from .models import FortranArgument , FortranBlockData , FortranDerivedType , FortranFile , FortranInterface , FortranModule , FortranParseError , FortranProcedureSignature , FortranProgram , FortranProject , FortranSubmodule , FortranUseMapping , FortranVariable
11+ from .pyi_context import PyiReadinessContext , load_pyi_readiness_context
1112from .type_resolver import extract_kind_from_type_spec
1213from .utils import split_csv
1314
@@ -482,9 +483,17 @@ def visit_project(
482483 self ._insert_unique_scope_symbol (project .interfaces , iface .name .lower (), iface , label = "project interface scope" )
483484 return project
484485
485- def visit_wrap_readiness (self , code : str , filename : str | None = None ) -> dict :
486+ def visit_wrap_readiness (
487+ self ,
488+ code : str ,
489+ filename : str | None = None ,
490+ * ,
491+ pyi_files : list [str | Path ] | tuple [str | Path , ...] | None = None ,
492+ pyi_context : PyiReadinessContext | None = None ,
493+ ) -> dict :
486494 lines = self ._preprocessed_lines (code , filename )
487495 parsed_file = self .visit_file (code , filename = filename )
496+ readiness_context = pyi_context or load_pyi_readiness_context (pyi_files )
488497 modules = parsed_file .modules
489498 submodules = parsed_file .submodules
490499 programs = parsed_file .programs
@@ -525,12 +534,19 @@ def visit_wrap_readiness(self, code: str, filename: str | None = None) -> dict:
525534 wrap_target_signatures ,
526535 types ,
527536 modules ,
537+ pyi_context = readiness_context ,
528538 )
529539 unresolved_kind_args , unresolved_kind_fields = self ._collect_unresolved_kind_diagnostics (
530540 wrap_target_signatures ,
531541 types ,
532542 modules ,
533543 module_params ,
544+ pyi_context = readiness_context ,
545+ )
546+ callback_args_requiring_pyi = self ._collect_callback_argument_diagnostics (
547+ wrap_target_signatures ,
548+ interfaces ,
549+ pyi_context = readiness_context ,
534550 )
535551 blockers = self ._build_wrap_blockers (
536552 signatures = signatures ,
@@ -540,6 +556,7 @@ def visit_wrap_readiness(self, code: str, filename: str | None = None) -> dict:
540556 unresolved_derived_fields = unresolved_derived_fields ,
541557 unresolved_kind_args = unresolved_kind_args ,
542558 unresolved_kind_fields = unresolved_kind_fields ,
559+ callback_args_requiring_pyi = callback_args_requiring_pyi ,
543560 )
544561 unit_blockers = self ._build_unit_blockers (
545562 filename = filename ,
@@ -551,6 +568,7 @@ def visit_wrap_readiness(self, code: str, filename: str | None = None) -> dict:
551568 unresolved_derived_fields = unresolved_derived_fields ,
552569 unresolved_kind_args = unresolved_kind_args ,
553570 unresolved_kind_fields = unresolved_kind_fields ,
571+ callback_args_requiring_pyi = callback_args_requiring_pyi ,
554572 )
555573
556574 return {
@@ -566,6 +584,8 @@ def visit_wrap_readiness(self, code: str, filename: str | None = None) -> dict:
566584 "unresolved_derived_type_fields" : unresolved_derived_fields ,
567585 "unresolved_kind_arguments" : unresolved_kind_args ,
568586 "unresolved_kind_fields" : unresolved_kind_fields ,
587+ "callback_arguments_requiring_pyi" : callback_args_requiring_pyi ,
588+ "pyi_context" : readiness_context .to_dict (),
569589 "wrappability_blockers" : blockers ,
570590 "unit_blockers" : unit_blockers ,
571591 "why_not_wrappable" : [b ["message" ] for b in blockers ],
@@ -3717,11 +3737,14 @@ def _kind_symbol_is_known(
37173737 uses : dict [str , list [FortranUseMapping ]],
37183738 local_symbols : set [str ],
37193739 module_params : dict [str , dict [str , str ]],
3740+ pyi_context : PyiReadinessContext | None = None ,
37203741 ) -> bool :
37213742 """Check whether a symbolic kind is declared locally or in parsed imports."""
37223743 lowered = symbol .lower ()
37233744 if lowered in local_symbols :
37243745 return True
3746+ if pyi_context is not None and pyi_context .has_constant (symbol ):
3747+ return True
37253748 if owning_module and lowered in module_params .get (owning_module .lower (), {}):
37263749 return True
37273750 if FortranParser ._kind_symbol_visible_from_module_params (symbol , uses , module_params ):
@@ -3735,6 +3758,8 @@ def _collect_unresolved_derived_type_diagnostics(
37353758 signatures : list [FortranProcedureSignature ],
37363759 types : list [FortranDerivedType ],
37373760 modules : list [FortranModule ],
3761+ * ,
3762+ pyi_context : PyiReadinessContext | None = None ,
37383763 ) -> tuple [list [dict ], list [dict ]]:
37393764 """Find derived-type references that are not defined in the parsed source."""
37403765 defined_types = {dtype .name .lower () for dtype in types }
@@ -3744,7 +3769,9 @@ def _collect_unresolved_derived_type_diagnostics(
37443769
37453770 def _missing_type (kind : str | None ) -> bool :
37463771 base_name = FortranParser ._derived_type_base_name (kind )
3747- return bool (base_name ) and base_name .lower () not in defined_types
3772+ return bool (base_name ) and base_name .lower () not in defined_types and not (
3773+ pyi_context is not None and pyi_context .has_type (base_name )
3774+ )
37483775
37493776 for sig in signatures :
37503777 for arg in sig .arguments :
@@ -3785,6 +3812,8 @@ def _collect_unresolved_kind_diagnostics(
37853812 types : list [FortranDerivedType ],
37863813 modules : list [FortranModule ],
37873814 module_params : dict [str , dict [str , str ]],
3815+ * ,
3816+ pyi_context : PyiReadinessContext | None = None ,
37883817 ) -> tuple [list [dict ], list [dict ]]:
37893818 """Find symbolic intrinsic kind references not declared in parsed source/imports."""
37903819 module_uses = {mod .name .lower (): mod .uses for mod in modules }
@@ -3802,6 +3831,7 @@ def _append_unresolved_arg(arg: FortranArgument) -> None:
38023831 uses = sig .uses ,
38033832 local_symbols = local_symbols ,
38043833 module_params = module_params ,
3834+ pyi_context = pyi_context ,
38053835 ):
38063836 continue
38073837 item = {
@@ -3816,16 +3846,16 @@ def _append_unresolved_arg(arg: FortranArgument) -> None:
38163846 unresolved_args .append (item )
38173847
38183848 for arg in sig .arguments :
3819- if arg .base_type != "derived" :
3849+ if arg .base_type not in { "derived" , "procedure" } :
38203850 _append_unresolved_arg (arg )
3821- if sig .result and sig .result .base_type != "derived" :
3851+ if sig .result and sig .result .base_type not in { "derived" , "procedure" } :
38223852 _append_unresolved_arg (sig .result )
38233853
38243854 for dtype in types :
38253855 uses = module_uses .get (dtype .module .lower (), {}) if dtype .module else {}
38263856 local_symbols : set [str ] = set ()
38273857 for field in dtype .fields :
3828- if field .base_type == "derived" :
3858+ if field .base_type in { "derived" , "procedure" } :
38293859 continue
38303860 for symbol in sorted (FortranParser ._kind_expression_symbols (field .kind )):
38313861 if FortranParser ._kind_symbol_is_known (
@@ -3834,6 +3864,7 @@ def _append_unresolved_arg(arg: FortranArgument) -> None:
38343864 uses = uses ,
38353865 local_symbols = local_symbols ,
38363866 module_params = module_params ,
3867+ pyi_context = pyi_context ,
38373868 ):
38383869 continue
38393870 item = {
@@ -3849,6 +3880,43 @@ def _append_unresolved_arg(arg: FortranArgument) -> None:
38493880
38503881 return unresolved_args , unresolved_fields
38513882
3883+ @staticmethod
3884+ def _collect_callback_argument_diagnostics (
3885+ signatures : list [FortranProcedureSignature ],
3886+ interfaces : list [FortranInterface ],
3887+ * ,
3888+ pyi_context : PyiReadinessContext | None = None ,
3889+ ) -> list [dict ]:
3890+ """Find procedure dummy arguments whose callback signature is not known."""
3891+ parsed_interfaces = {
3892+ iface .name .lower ()
3893+ for iface in interfaces
3894+ if iface .name
3895+ }
3896+ missing_callbacks : list [dict ] = []
3897+
3898+ for sig in signatures :
3899+ for arg in sig .arguments :
3900+ if arg .base_type != "procedure" :
3901+ continue
3902+ if pyi_context is not None and pyi_context .has_callback_argument (sig .name , arg .name ):
3903+ continue
3904+ if arg .kind and arg .kind .lower () in parsed_interfaces :
3905+ continue
3906+ missing_callbacks .append ({
3907+ "procedure" : sig .name ,
3908+ "module" : sig .module ,
3909+ "argument" : arg .name ,
3910+ "interface" : arg .kind or None ,
3911+ "needs" : [
3912+ "callable_signature" ,
3913+ "argument_order" ,
3914+ "return_type" ,
3915+ ],
3916+ })
3917+
3918+ return missing_callbacks
3919+
38523920 @staticmethod
38533921 def _build_wrap_blockers (
38543922 * ,
@@ -3859,6 +3927,7 @@ def _build_wrap_blockers(
38593927 unresolved_derived_fields : list [dict ],
38603928 unresolved_kind_args : list [dict ],
38613929 unresolved_kind_fields : list [dict ],
3930+ callback_args_requiring_pyi : list [dict ],
38623931 ) -> list [dict ]:
38633932 """Create explicit, user-facing reasons why a source is not wrap-ready."""
38643933 blockers : list [dict ] = []
@@ -3904,6 +3973,12 @@ def _build_wrap_blockers(
39043973 "message" : "Some derived-type fields use kind symbols missing from the parsed source/imports." ,
39053974 "items" : unresolved_kind_fields ,
39063975 })
3976+ if callback_args_requiring_pyi :
3977+ blockers .append ({
3978+ "code" : "callback_arguments_requiring_pyi" ,
3979+ "message" : "Some procedure dummy arguments need callback signatures from a .pyi file." ,
3980+ "items" : callback_args_requiring_pyi ,
3981+ })
39073982 return blockers
39083983
39093984 @staticmethod
@@ -3918,6 +3993,7 @@ def _build_unit_blockers(
39183993 unresolved_derived_fields : list [dict ],
39193994 unresolved_kind_args : list [dict ],
39203995 unresolved_kind_fields : list [dict ],
3996+ callback_args_requiring_pyi : list [dict ],
39213997 ) -> list [dict ]:
39223998 """Build unit-scoped blocker records without per-unit readiness flags.
39233999
@@ -3971,6 +4047,7 @@ def derived_type_unit_key(module: str | None, type_owner: str | None) -> tuple[s
39714047 ]
39724048 derived_items = [item for item in unresolved_derived_args if same_unit (item , sig )]
39734049 kind_items = [item for item in unresolved_kind_args if same_unit (item , sig )]
4050+ callback_items = [item for item in callback_args_requiring_pyi if same_unit (item , sig )]
39744051 if missing_items :
39754052 blockers .append ({
39764053 "code" : "unknown_argument_types" ,
@@ -3989,6 +4066,12 @@ def derived_type_unit_key(module: str | None, type_owner: str | None) -> tuple[s
39894066 "message" : "Some procedure arguments use kind symbols missing from the parsed source/imports." ,
39904067 "items" : kind_items ,
39914068 })
4069+ if callback_items :
4070+ blockers .append ({
4071+ "code" : "callback_arguments_requiring_pyi" ,
4072+ "message" : "Some procedure dummy arguments need callback signatures from a .pyi file." ,
4073+ "items" : callback_items ,
4074+ })
39924075 if not blockers :
39934076 continue
39944077 qualified_name = f"{ sig .module } .{ sig .name } " if sig .module else sig .name
@@ -4418,5 +4501,10 @@ def parse_fortran_project(files, *, encoding: str = "utf-8") -> FortranProject:
44184501 return _DEFAULT_PARSER .visit_project (files , encoding = encoding )
44194502
44204503
4421- def assess_wrap_readiness (code : str , filename : str | None = None ) -> dict :
4422- return _DEFAULT_PARSER .visit_wrap_readiness (code , filename = filename )
4504+ def assess_wrap_readiness (
4505+ code : str ,
4506+ filename : str | None = None ,
4507+ * ,
4508+ pyi_files : list [str | Path ] | tuple [str | Path , ...] | None = None ,
4509+ ) -> dict :
4510+ return _DEFAULT_PARSER .visit_wrap_readiness (code , filename = filename , pyi_files = pyi_files )
0 commit comments