@@ -41,6 +41,76 @@ let lkj_cov_message =
4141 independent lognormal distribution on the scales, see: \
4242 https://mc-stan.org/docs/reference-manual/deprecations.html#lkj_cov-distribution"
4343
44+ let functions_block_contains_jac_pe (stmts : untyped_statement list ) =
45+ (* tracking if 'jacobian' is a variable in scope *)
46+ let jacobian_scope_id = ref 0 in
47+ let is_jacobian_in_scope () = ! jacobian_scope_id > 0 in
48+ let current_scope_id = ref 1 in
49+ let found_jacobian () =
50+ if not (is_jacobian_in_scope () ) then jacobian_scope_id := ! current_scope_id
51+ in
52+ let push_scope () = current_scope_id := ! current_scope_id + 1 in
53+ let pop_scope () =
54+ current_scope_id := ! current_scope_id - 1 ;
55+ (* if the scope we just left was the one defining jacobian, reset it *)
56+ if ! jacobian_scope_id > ! current_scope_id then jacobian_scope_id := 0 in
57+ (* walk over the tree, looking for usages of jacobian+= where
58+ there is no variable called jacobian already in scope *)
59+ let rec f (s : untyped_statement ) =
60+ match s.stmt with
61+ | FunDef {body; funname; _}
62+ when String. is_suffix funname.name ~suffix: " _jacobian" ->
63+ push_scope () ;
64+ let res = f body in
65+ pop_scope () ;
66+ res
67+ | Block stmts | Profile (_ , stmts ) ->
68+ push_scope () ;
69+ let res = List. exists ~f stmts in
70+ pop_scope () ;
71+ res
72+ | For {loop_body; _} | While (_ , loop_body ) | ForEach (_ , _ , loop_body ) ->
73+ push_scope () ;
74+ let res = f loop_body in
75+ pop_scope () ;
76+ res
77+ | IfThenElse (_ , s1 , s2_opt ) ->
78+ push_scope () ;
79+ let res1 = f s1 in
80+ pop_scope () ;
81+ push_scope () ;
82+ let res2 = match s2_opt with Some s2 -> f s2 | None -> false in
83+ pop_scope () ;
84+ res1 || res2
85+ | JacobianPE _ -> true
86+ | Assignment
87+ { assign_lhs= LValue {lval= LVariable {name; _}; _}
88+ ; assign_op= OperatorAssign Plus
89+ ; _ }
90+ when String. equal name " jacobian" ->
91+ not (is_jacobian_in_scope () )
92+ | VarDecl {variables; _} ->
93+ if
94+ List. exists
95+ ~f: (fun {identifier; _} -> String. equal identifier.name " jacobian" )
96+ variables
97+ then found_jacobian () ;
98+ false
99+ | _ -> false in
100+ let res = List. exists ~f stmts in
101+ (* sanity check that pushes and pops are balanced *)
102+ if ! current_scope_id <> 1 then
103+ Common.ICE. internal_compiler_error
104+ [% message
105+ " functions_block_contains_jac_pe: scope tracking failed"
106+ (! current_scope_id : int )
107+ (! jacobian_scope_id : int )
108+ (stmts : untyped_statement list )];
109+ res
110+
111+ let set_jacobian_compatibility_mode stmts =
112+ Fun_kind. jacobian_compat_mode := not (functions_block_contains_jac_pe stmts)
113+
44114let rec collect_deprecated_expr (acc : (Location_span.t * string) list )
45115 ({expr; emeta} : (typed_expr_meta, fun_kind) expr_with ) :
46116 (Location_span. t * string ) list =
@@ -89,6 +159,22 @@ let rec collect_deprecated_stmt fundefs (acc : (Location_span.t * string) list)
89159 , " Functions do not need to be declared before definition; all user \
90160 defined function names are always in scope regardless of \
91161 definition order." ) ]
162+ | FunDef {funname; body; _}
163+ when ! Fun_kind. jacobian_compat_mode
164+ && String. is_suffix funname.name ~suffix: " _jacobian" ->
165+ let acc =
166+ ( funname.id_loc
167+ , " Functions that end in _jacobian will change meaning in Stan 2.39. \
168+ They will be used for the encapsulating usages of 'jacobian +=', \
169+ and therefore not available to be called in all the same places as \
170+ this function is now. To avoid any issues, please rename this \
171+ function to not end in _jacobian." )
172+ :: acc in
173+ fold_statement collect_deprecated_expr
174+ (collect_deprecated_stmt fundefs)
175+ collect_deprecated_lval
176+ (fun l _ -> l)
177+ acc body.stmt
92178 | Tilde {distribution; _} when String. equal distribution.name " lkj_cov" ->
93179 let acc = (distribution.id_loc, lkj_cov_message) :: acc in
94180 fold_statement collect_deprecated_expr
0 commit comments