Progress on linting, bugguy unused field detection

This commit is contained in:
Denis Merigoux 2023-03-30 18:52:29 +02:00
parent 9d64150a47
commit 3c364aa1fa
No known key found for this signature in database
GPG Key ID: EE99DCFA365C3EE3
5 changed files with 127 additions and 23 deletions

View File

@ -247,3 +247,28 @@ let free_variables (def : rule RuleName.Map.t) : Pos.t ScopeDefMap.t =
in
add_locs acc locs)
def ScopeDefMap.empty
let fold_exprs ~(f : 'a -> expr -> 'a) ~(init : 'a) (p : program) : 'a =
let acc =
ScopeName.Map.fold
(fun _ scope acc ->
let acc =
ScopeDefMap.fold
(fun _ scope_def acc ->
RuleName.Map.fold
(fun _ rule acc ->
f
(f acc (Expr.unbox rule.rule_just))
(Expr.unbox rule.rule_cons))
scope_def.scope_def_rules acc)
scope.scope_defs acc
in
let acc =
List.fold_left
(fun acc assertion -> f acc (Expr.unbox assertion))
acc scope.scope_assertions
in
acc)
p.program_scopes init
in
TopdefName.Map.fold (fun _ (e, _) acc -> f acc e) p.program_topdefs acc

View File

@ -134,3 +134,9 @@ type program = {
val locations_used : expr -> LocationSet.t
val free_variables : rule RuleName.Map.t -> Pos.t ScopeDefMap.t
val fold_exprs : f:('a -> expr -> 'a) -> init:'a -> program -> 'a
(** Usage: [fold_exprs ~f ~init program] applies ~f to all the expressions
inside rules (justifications and consequences), expressions and top-level
definitions of the program. Note that there may be free variables in these
expressions. *)

View File

@ -19,31 +19,92 @@ open Ast
open Catala_utils
(** If the variable is not an input, then it should be defined somewhere. *)
let detect_empty_definitions
(scope : ScopeName.t)
(def_info : ScopeDef.t)
(def : scope_def) : unit =
if
RuleName.Map.is_empty def.scope_def_rules
&& (not def.scope_def_is_condition)
&&
match Marked.unmark def.scope_def_io.io_input with
| Ast.NoInput -> true
| _ -> false
then
Errors.format_spanned_warning
(ScopeDef.get_position def_info)
"The variable %a is declared but never defined in scope %a"
(Cli.format_with_style [ANSITerminal.yellow])
(Format.asprintf "\"%a\"" Ast.ScopeDef.format_t def_info)
(Cli.format_with_style [ANSITerminal.yellow])
(Format.asprintf "\"%a\"" ScopeName.format_t scope)
let lint_program (p : program) : unit =
let detect_empty_definitions (p : program) : unit =
ScopeName.Map.iter
(fun scope_name scope ->
(fun (scope_name : ScopeName.t) scope ->
ScopeDefMap.iter
(fun scope_def_key scope_def ->
detect_empty_definitions scope_name scope_def_key scope_def)
if
(match scope_def_key with ScopeDef.Var _ -> true | _ -> false)
&& RuleName.Map.is_empty scope_def.scope_def_rules
&& (not scope_def.scope_def_is_condition)
&&
match Marked.unmark scope_def.scope_def_io.io_input with
| Ast.NoInput -> true
| _ -> false
then
Errors.format_spanned_warning
(ScopeDef.get_position scope_def_key)
"The variable %a is declared but never defined in scope %a; did \
you forget something?"
(Cli.format_with_style [ANSITerminal.yellow])
(Format.asprintf "\"%a\"" Ast.ScopeDef.format_t scope_def_key)
(Cli.format_with_style [ANSITerminal.yellow])
(Format.asprintf "\"%a\"" ScopeName.format_t scope_name))
scope.scope_defs)
p.program_scopes
let detect_unused_struct_fields (p : program) : unit =
let struct_fields_used =
Ast.fold_exprs
~f:(fun struct_fields_used e ->
let rec structs_fields_used_expr e struct_fields_used =
match Marked.unmark e with
| EDStructAccess { name_opt = Some name; e = e_struct; field } ->
let field =
try
StructName.Map.find name
(IdentName.Map.find field p.program_ctx.ctx_struct_fields)
with Not_found ->
(* Should not happen after disambiguation *)
Errors.raise_spanned_error
(Expr.mark_pos (Marked.get_mark e))
"Field %a does not belong to structure %a"
(Cli.format_with_style [ANSITerminal.yellow])
field
(Cli.format_with_style [ANSITerminal.yellow])
(Format.asprintf "\"%a\"" StructName.format_t name)
in
StructField.Set.add field
(structs_fields_used_expr e_struct struct_fields_used)
| EStruct { name = _; fields } ->
StructField.Map.fold
(fun field e_field struct_fields_used ->
StructField.Set.add field
(structs_fields_used_expr e_field struct_fields_used))
fields struct_fields_used
| _ ->
Expr.shallow_fold structs_fields_used_expr e StructField.Set.empty
in
structs_fields_used_expr e struct_fields_used)
~init:StructField.Set.empty p
in
let scope_out_structs_fields =
ScopeName.Map.fold
(fun _ out_struct acc ->
ScopeVar.Map.fold
(fun _ field acc -> StructField.Set.add field acc)
out_struct.out_struct_fields acc)
p.program_ctx.ctx_scopes StructField.Set.empty
in
StructName.Map.iter
(fun s_name fields ->
StructField.Map.iter
(fun field _ ->
if
(not (StructField.Set.mem field struct_fields_used))
&& not (StructField.Set.mem field scope_out_structs_fields)
then
Errors.format_spanned_warning
(snd (StructField.get_info field))
"The field %a of struct %a is never used; maybe it's unnecessary?"
(Cli.format_with_style [ANSITerminal.yellow])
(Format.asprintf "\"%a\"" StructField.format_t field)
(Cli.format_with_style [ANSITerminal.yellow])
(Format.asprintf "\"%a\"" StructName.format_t s_name))
fields)
p.program_ctx.ctx_structs
let lint_program (p : program) : unit =
detect_empty_definitions p;
detect_unused_struct_fields p

View File

@ -22,6 +22,12 @@ let map_exprs ~f ~varf { code_items; decl_ctx } =
(fun code_items -> { code_items; decl_ctx })
(Scope.map_exprs ~f ~varf code_items)
let fold_left_exprs ~f ~init { code_items; decl_ctx = _ } =
Scope.fold_left ~f:(fun acc e _ -> f acc e) ~init code_items
let fold_right_exprs ~f ~init { code_items; decl_ctx = _ } =
Scope.fold_right ~f:(fun e _ acc -> f e acc) ~init code_items
let get_scope_body { code_items; _ } scope =
match
Scope.fold_left ~init:None

View File

@ -25,6 +25,12 @@ val map_exprs :
'expr1 program ->
'expr2 program Bindlib.box
val fold_left_exprs :
f:('a -> 'expr code_item -> 'a) -> init:'a -> 'expr program -> 'a
val fold_right_exprs :
f:('expr code_item -> 'a -> 'a) -> init:'a -> 'expr program -> 'a
val get_scope_body :
((_ any, 't) gexpr as 'e) program -> ScopeName.t -> 'e scope_body