Skip to content

Fields uses are not tracked through type equations #80

@fantazio

Description

@fantazio

Context

This is a pattern observed in Opam 2.5.1. A record type is defined and a value of that type as well (warning_printer and console), the type is aliased via a type equation (warning_printer), the fields of the value are read internally (here and here), the type (original or alias) is exported (warning_printer and its field are unused externally.

This leads the analyzer to report the fields of the exported type as unused although they are via the internal original type.

Note that in the example found in opam, the original type and its alias have the same name. if we give them different names, we get the following behaviors:

  1. if we export the original type, then all the fields unused before the alias definition are reported unused
  2. if we export the alias, then the fields are correctly reported
  3. if we export both the original and the alias, then the fields are correctly reported for the original (and not reported not for the alias)

I suspect that reusing the same name is equivalent to scenario 1.

Example and reproduction

(* /tmp/type_eq/field.ml *)
type original = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}

let v = {
  used_before_alias = ();
  used_after_alias = ();
  unused = ()
}

let () = v.used_before_alias

type alias = original = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}

let () = v.used_after_alias

The interface is adapted to each scenario.

Exporting the original type

(* /tmp/type_eq/field.mli *)
type original = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}
$ ocamlopt -bin-annot field.mli field.ml 
$ dead_code_analyzer --nothing -T all .
Scanning files...
 [DONE]

.> UNUSED CONSTRUCTORS/RECORD FIELDS:
====================================
/tmp/type_eq/field.mli:4: original.used_after_alias
/tmp/type_eq/field.mli:5: original.unused

Nothing else to report in this section
--------------------------------------------------------------------------------

original.used_after_alias is wrongly reported as unused. original.unused is correctly reported and original.used_before_alias is correctly not reported.

Exporting the alias

(* /tmp/type_eq/field.mli *)
type alias = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}
$ ocamlopt -bin-annot field.mli field.ml 
$ dead_code_analyzer --nothing -T all .
Scanning files...
 [DONE]

.> UNUSED CONSTRUCTORS/RECORD FIELDS:
====================================
/tmp/type_eq/field.mli:5: alias.unused

Nothing else to report in this section
--------------------------------------------------------------------------------

The reports are correct for alias.

Exporting both

Without the type equation

(* /tmp/type_eq/field.mli *)
type original = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}

type alias = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}
$ ocamlopt -bin-annot field.mli field.ml 
$ dead_code_analyzer --nothing -T all .
Scanning files...
 [DONE]

.> UNUSED CONSTRUCTORS/RECORD FIELDS:
====================================
/tmp/type_eq/field.mli:11: original.unused

Nothing else to report in this section
--------------------------------------------------------------------------------

The reports are correct for original.
If we look in more details, the fields of alias are still tracked individually.

$ dead_code_analyzer --nothing -T calls:2 .
Scanning files...
 [DONE]

.> UNUSED CONSTRUCTORS/RECORD FIELDS:
====================================
/tmp/type_eq/field.mli:11: original.unused
--------


.>->  ALMOST UNUSED CONSTRUCTORS/RECORD FIELDS: Called 1 time(s):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/type_eq/field.mli:3: alias.used_before_alias    Call sites:
/tmp/type_eq/field.mli:9:2

/tmp/type_eq/field.mli:5: alias.unused    Call sites:
/tmp/type_eq/field.mli:11:2

/tmp/type_eq/field.mli:9: original.used_before_alias    Call sites:
/tmp/type_eq/field.ml:14:9

/tmp/type_eq/field.mli:10: original.used_after_alias    Call sites:
/tmp/type_eq/field.ml:22:9

--------


.>->  ALMOST UNUSED CONSTRUCTORS/RECORD FIELDS: Called 2 time(s):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/type_eq/field.mli:4: alias.used_after_alias    Call sites:
/tmp/type_eq/field.ml:22:9
/tmp/type_eq/field.mli:10:2


Nothing else to report in this section
--------------------------------------------------------------------------------

With the type equation

(* /tmp/type_eq/field.mli *)
type original = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}

type alias = original = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}
$ ocamlopt -bin-annot field.mli field.ml
$ dead_code_analyzer --nothing -T calls:2 .
Scanning files...
 [DONE]

.> UNUSED CONSTRUCTORS/RECORD FIELDS:
====================================
/tmp/type_eq/field.mli:5: original.unused
--------


.>->  ALMOST UNUSED CONSTRUCTORS/RECORD FIELDS: Called 1 time(s):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/type_eq/field.mli:3: original.used_before_alias    Call sites:
/tmp/type_eq/field.ml:14:9

/tmp/type_eq/field.mli:4: original.used_after_alias    Call sites:
/tmp/type_eq/field.ml:22:9

--------



Nothing else to report in this section
--------------------------------------------------------------------------------

The reports are correct for original and alias is not tracked. They the same as the results without the type equation minus alias.

With the reversed type equation

(* /tmp/type_eq/field.mli *)
type alias = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}

type original = alias = {
  used_before_alias : unit;
  used_after_alias : unit;
  unused : unit
}
$ ocamlopt -bin-annot field.mli field.ml 
$ dead_code_analyzer --nothing -T calls:2 .
Scanning files...
 [DONE]

.> UNUSED CONSTRUCTORS/RECORD FIELDS:
====================================


.>->  ALMOST UNUSED CONSTRUCTORS/RECORD FIELDS: Called 1 time(s):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/type_eq/field.mli:3: alias.used_before_alias    Call sites:
/tmp/type_eq/field.mli:9:2

/tmp/type_eq/field.mli:5: alias.unused    Call sites:
/tmp/type_eq/field.mli:11:2

--------


.>->  ALMOST UNUSED CONSTRUCTORS/RECORD FIELDS: Called 2 time(s):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/tmp/type_eq/field.mli:4: alias.used_after_alias    Call sites:
/tmp/type_eq/field.ml:22:9
/tmp/type_eq/field.mli:10:2


Nothing else to report in this section
--------------------------------------------------------------------------------

The reports are the same as those without the type equation minus original.

Note

The only broken scenario is when exporting the original type alone.
Exporting both types gives the best results.
Exporting the type equation silences the reports for the alias.
Chances are that this is related to DeadType.dependencies

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions