@@ -3507,8 +3507,12 @@ impl Analyzer {
35073507 } ) ;
35083508 }
35093509
3510- // Axiom / Parameter without justification — unverified postulates
3511- let axiom_count = code. matches ( "\n Axiom " ) . count ( ) + code. matches ( "\n Parameter " ) . count ( ) ;
3510+ // Axiom / Parameter declarations that are genuine unverified postulates.
3511+ // `count_rocq_unverified_postulates` excludes Section-scoped declarations
3512+ // (discharged at End) and module-level abstraction parameters (carrier
3513+ // types, decidability witnesses, non-Prop function symbols) — those are
3514+ // the legitimate scaffold shape and should not be flagged as postulates.
3515+ let axiom_count = count_rocq_unverified_postulates ( & code) ;
35123516 if axiom_count > 0 {
35133517 weak_points. push ( WeakPoint {
35143518 file : None ,
@@ -5326,6 +5330,146 @@ fn strip_proof_comments(content: &str, line_marker: &str, block: Option<(&str, &
53265330 out
53275331}
53285332
5333+ /// Count Rocq `Axiom`/`Parameter` declarations that represent genuine
5334+ /// unverified postulates, excluding the two legitimate scaffold shapes:
5335+ ///
5336+ /// 1. **Section-scoped declarations.** `Variable`/`Hypothesis` and,
5337+ /// rarely, `Parameter` declarations lexically between `Section name`
5338+ /// and matching `End name` are discharged at `End` — they lambda-
5339+ /// abstract over the lemmas proved inside the Section rather than
5340+ /// adding floating assumptions to the ambient module. This is the
5341+ /// canonical Rocq pattern for parameterised proof development and
5342+ /// should not be flagged.
5343+ /// 2. **Module-level abstraction parameters.** `Parameter` declarations
5344+ /// whose stated type is `Type` / `Set` (carrier types), a
5345+ /// decidability witness of the form `forall ..., { _ = _ } + { _ <> _ }`,
5346+ /// or a function type whose codomain is a concrete non-Prop type
5347+ /// (like `Q -> Q`, `State -> State -> Q`). These are "abstract
5348+ /// function symbols awaiting instantiation" — the standard scaffold
5349+ /// shape for generic proof developments. A closing session supplies
5350+ /// concrete terms; none of them discharge a Prop the scaffold uses
5351+ /// as if proved.
5352+ ///
5353+ /// Everything else is counted, including Prop-valued `Axiom`/`Parameter`
5354+ /// declarations (classical excluded-middle, choice, unresolved
5355+ /// theorem statements) — those are the real ProofDrift signal.
5356+ ///
5357+ /// Heuristic classification: conservative. Unknown shapes default to
5358+ /// counted (postulate).
5359+ fn count_rocq_unverified_postulates ( code : & str ) -> usize {
5360+ let mut section_depth: i32 = 0 ;
5361+ let mut postulate_count: usize = 0 ;
5362+ let mut pending: Option < String > = None ;
5363+
5364+ for raw_line in code. lines ( ) {
5365+ let trimmed = raw_line. trim_start ( ) ;
5366+
5367+ if trimmed. starts_with ( "Section " ) {
5368+ section_depth += 1 ;
5369+ pending = None ;
5370+ continue ;
5371+ }
5372+ if trimmed. starts_with ( "End " ) && trimmed. contains ( '.' ) && section_depth > 0 {
5373+ section_depth -= 1 ;
5374+ pending = None ;
5375+ continue ;
5376+ }
5377+
5378+ // Declarations inside any Section are discharged at End; skip.
5379+ if section_depth > 0 {
5380+ pending = None ;
5381+ continue ;
5382+ }
5383+
5384+ // Multi-line declaration continuation: keep accumulating until
5385+ // we hit a period.
5386+ if let Some ( mut p) = pending. take ( ) {
5387+ p. push ( ' ' ) ;
5388+ p. push_str ( raw_line. trim ( ) ) ;
5389+ if p. contains ( '.' ) {
5390+ if !is_rocq_abstraction_parameter ( & p) {
5391+ postulate_count += 1 ;
5392+ }
5393+ } else {
5394+ pending = Some ( p) ;
5395+ }
5396+ continue ;
5397+ }
5398+
5399+ let is_decl =
5400+ trimmed. starts_with ( "Axiom " ) || trimmed. starts_with ( "Parameter " ) ;
5401+ if !is_decl {
5402+ continue ;
5403+ }
5404+
5405+ if raw_line. trim_end ( ) . ends_with ( '.' ) {
5406+ if !is_rocq_abstraction_parameter ( raw_line) {
5407+ postulate_count += 1 ;
5408+ }
5409+ } else {
5410+ pending = Some ( raw_line. trim ( ) . to_string ( ) ) ;
5411+ }
5412+ }
5413+
5414+ postulate_count
5415+ }
5416+
5417+ /// Classify a Rocq `Axiom`/`Parameter` declaration (full text up to and
5418+ /// including the trailing period) as an abstraction parameter —
5419+ /// carrier type, decidability witness, or non-Prop function symbol —
5420+ /// rather than an unverified postulate. Conservative: unknown shapes
5421+ /// return false (treated as postulate by the caller).
5422+ fn is_rocq_abstraction_parameter ( decl : & str ) -> bool {
5423+ let colon_pos = match decl. find ( ':' ) {
5424+ Some ( i) => i,
5425+ None => return false ,
5426+ } ;
5427+ let typ = decl[ colon_pos + 1 ..] . trim ( ) ;
5428+ let typ = typ. strip_suffix ( '.' ) . unwrap_or ( typ) . trim ( ) ;
5429+
5430+ // 1. Carrier type.
5431+ if typ == "Type" || typ == "Set" {
5432+ return true ;
5433+ }
5434+
5435+ // 2. Decidability witness: `forall ..., { _ = _ } + { _ <> _ }`.
5436+ if typ. contains ( '{' )
5437+ && typ. contains ( '=' )
5438+ && typ. contains ( '}' )
5439+ && typ. contains ( '+' )
5440+ && typ. contains ( "<>" )
5441+ {
5442+ return true ;
5443+ }
5444+
5445+ // 3. Function type with a clearly non-Prop codomain.
5446+ if typ. contains ( "->" ) {
5447+ let return_type = typ. rsplit ( "->" ) . next ( ) . unwrap_or ( "" ) . trim ( ) ;
5448+ if return_type == "Prop" {
5449+ return false ;
5450+ }
5451+ let first_word = return_type. split_whitespace ( ) . next ( ) . unwrap_or ( "" ) ;
5452+ const CONCRETE_RETURNS : & [ & str ] = & [
5453+ "Q" , "R" , "Z" , "N" , "nat" , "bool" , "list" , "option" ,
5454+ "prod" , "sum" , "unit" , "Type" , "Set" ,
5455+ ] ;
5456+ if CONCRETE_RETURNS . contains ( & first_word) {
5457+ return true ;
5458+ }
5459+ // Lowercase identifiers are concrete builtin types; uppercase are
5460+ // (likely) user-level abstractions or Props — we conservatively
5461+ // accept only the explicit allow-list above plus clearly concrete
5462+ // builtins, not arbitrary capital-letter identifiers.
5463+ if let Some ( c) = first_word. chars ( ) . next ( ) {
5464+ if c. is_ascii_lowercase ( ) {
5465+ return true ;
5466+ }
5467+ }
5468+ }
5469+
5470+ false
5471+ }
5472+
53295473/// Rewrite `args` by replacing every `not(test)` group (with
53305474/// whitespace-tolerant matching) with a single space. Used by
53315475/// [`Analyzer::cfg_args_select_test`] so `cfg(not(test))` and
@@ -6217,6 +6361,107 @@ let force_cast (x : 'a) : 'b = Obj.magic x
62176361 ) ;
62186362 }
62196363
6364+ // ---------------------------------------------------------------
6365+ // ProofDrift: Rocq Parameter classification
6366+ // ---------------------------------------------------------------
6367+
6368+ #[ test]
6369+ fn rocq_section_scoped_variables_are_not_postulates ( ) {
6370+ let code = r#"
6371+ Section OrderedField.
6372+ Variable R : Type.
6373+ Variable Rzero : R.
6374+ Hypothesis Rplus_comm : forall x y : R, x = y.
6375+ Parameter Rabs : R -> R.
6376+ End OrderedField.
6377+ "# ;
6378+ assert_eq ! (
6379+ count_rocq_unverified_postulates( code) ,
6380+ 0 ,
6381+ "Section-scoped Variable/Hypothesis/Parameter must discharge at End"
6382+ ) ;
6383+ }
6384+
6385+ #[ test]
6386+ fn rocq_module_level_type_carrier_is_abstraction ( ) {
6387+ let code = "Parameter State : Type.\n " ;
6388+ assert_eq ! (
6389+ count_rocq_unverified_postulates( code) ,
6390+ 0 ,
6391+ "Module-level `Parameter X : Type` is an abstraction, not a postulate"
6392+ ) ;
6393+ }
6394+
6395+ #[ test]
6396+ fn rocq_module_level_decidable_equality_is_abstraction ( ) {
6397+ let code =
6398+ "Parameter state_eq_dec : forall x y : State, {x = y} + {x <> y}.\n " ;
6399+ assert_eq ! (
6400+ count_rocq_unverified_postulates( code) ,
6401+ 0 ,
6402+ "Decidability witness is an abstraction parameter, not a postulate"
6403+ ) ;
6404+ }
6405+
6406+ #[ test]
6407+ fn rocq_module_level_function_with_concrete_return_is_abstraction ( ) {
6408+ let code = r#"
6409+ Parameter log2 : Q -> Q.
6410+ Parameter kernel : State -> State -> Q.
6411+ "# ;
6412+ assert_eq ! (
6413+ count_rocq_unverified_postulates( code) ,
6414+ 0 ,
6415+ "Abstract function symbols with concrete non-Prop codomain are abstractions"
6416+ ) ;
6417+ }
6418+
6419+ #[ test]
6420+ fn rocq_module_level_prop_axiom_is_counted ( ) {
6421+ let code = "Axiom excluded_middle : forall P : Prop, P \\ / ~P.\n " ;
6422+ assert_eq ! (
6423+ count_rocq_unverified_postulates( code) ,
6424+ 1 ,
6425+ "Prop-valued Axiom is a genuine unverified postulate"
6426+ ) ;
6427+ }
6428+
6429+ #[ test]
6430+ fn rocq_module_level_parameter_without_colon_is_counted ( ) {
6431+ // Malformed or unusual declarations default to counted
6432+ // (conservative).
6433+ let code = "Parameter foo.\n " ;
6434+ assert_eq ! (
6435+ count_rocq_unverified_postulates( code) ,
6436+ 1 ,
6437+ "Declarations without a ':' type annotation default to postulate"
6438+ ) ;
6439+ }
6440+
6441+ #[ test]
6442+ fn rocq_scaffold_shape_yields_zero_postulates ( ) {
6443+ // This is the canonical-proof-suite scaffold shape from 007:
6444+ // Section-scoped hypothesis bundle + module-level abstraction
6445+ // parameters. It must not produce any ProofDrift hits.
6446+ let code = r#"
6447+ Parameter Symbol : Type.
6448+ Parameter symbol_eq_dec : forall x y : Symbol, {x = y} + {x <> y}.
6449+ Parameter log2 : Q -> Q.
6450+
6451+ Section ShannonSourceCoding.
6452+ Variable distribution : list (Symbol * Q).
6453+ Hypothesis distribution_sum_one :
6454+ fold_right Qplus 0 (map snd distribution) == 1.
6455+ Parameter abstract_helper : Symbol -> Q.
6456+ End ShannonSourceCoding.
6457+ "# ;
6458+ assert_eq ! (
6459+ count_rocq_unverified_postulates( code) ,
6460+ 0 ,
6461+ "The canonical scaffold shape must not be flagged as ProofDrift"
6462+ ) ;
6463+ }
6464+
62206465 // ---------------------------------------------------------------
62216466 // strip_cfg_test_modules_rs + cfg_args_select_test + strip_not_test_groups
62226467 // ---------------------------------------------------------------
0 commit comments