391 lines
14 KiB
Perl
391 lines
14 KiB
Perl
|
|
Ppxlib driver has a variety of ways to disable warnings that can be triggered
|
||
|
|
when using `[@@deriving ...]`. These are all enabled by default but we added
|
||
|
|
flags to let driver users disable them. To allow smooth transition from always
|
||
|
|
adding them to never do so and let individual ppx-es do what they must to avoid
|
||
|
|
triggering warnings, we also added optional arguments to `Deriving.make` so that
|
||
|
|
the ppx themselves can declare whether they need the driver to disable warnings
|
||
|
|
or not.
|
||
|
|
|
||
|
|
The following tests describe the behaviour of flags and features used to control
|
||
|
|
the emission of such warning silencing features.
|
||
|
|
|
||
|
|
One such flag and optional argument pair is the `-unused-code-warnings` flag and
|
||
|
|
`?unused_code_warning` `Deriving.V2.make` argument. Both of those default to
|
||
|
|
`false` and control whether we disable warnings 32, 34 and 60 for generated code
|
||
|
|
and behave as described by the following table:
|
||
|
|
|
||
|
|
Deriver arg | Driver Flag | Unused Code Warnings
|
||
|
|
-------------|-------------|----------------------
|
||
|
|
true | true | Enabled
|
||
|
|
true | false | Disabled*
|
||
|
|
true | force | Enabled
|
||
|
|
false | true | Disabled
|
||
|
|
false | false | Disabled
|
||
|
|
false | force | Enabled
|
||
|
|
* By adding warning silencers like [@@@ocaml.waring "-60"] or producing code like
|
||
|
|
`let _ = zero in...` or `let _ = fun (_ : t) -> ()`.
|
||
|
|
|
||
|
|
We have a driver with 4 derivers linked in:
|
||
|
|
- zero_do_warn
|
||
|
|
- one_no_warn
|
||
|
|
- two_do_warn
|
||
|
|
- alias_warn
|
||
|
|
|
||
|
|
--------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
Let's consider the following ocaml source file using the zero_do_warn deriver
|
||
|
|
|
||
|
|
$ cat > zero_do_warn.ml << EOF
|
||
|
|
> type t = int [@@deriving zero_do_warn]
|
||
|
|
> EOF
|
||
|
|
|
||
|
|
Zero_do_warn is registered with unused_code_warning set to true meaning it allows
|
||
|
|
the driver not to silence unused code and unused module warnings if the
|
||
|
|
-unused-code-warning flag is set to true.
|
||
|
|
|
||
|
|
Let's call the driver with -unused-code-warnings=false:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=false -impl zero_do_warn.ml
|
||
|
|
type t = int[@@deriving zero_do_warn]
|
||
|
|
include struct let _ = fun (_ : t) -> () end[@@ocaml.doc "@inline"][@@merlin.hide
|
||
|
|
]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
module Zero = struct type t =
|
||
|
|
| T0 end
|
||
|
|
let zero = Zero.T0
|
||
|
|
let _ = zero
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
The generated code is wrapped in an include struct ... end to disable unused module
|
||
|
|
warnings, as expected. The derived value zero is followed by a let _ = zero to
|
||
|
|
disable unused value warning, and the type is used by `let _ = fun (_ : t) -> ()`.
|
||
|
|
|
||
|
|
Now if we use -unused-code-warnings=true:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=true -impl zero_do_warn.ml
|
||
|
|
type t = int[@@deriving zero_do_warn]
|
||
|
|
include struct module Zero = struct type t =
|
||
|
|
| T0 end
|
||
|
|
let zero = Zero.T0 end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
Here the warning silencing was disabled as it was both allowed by the driver
|
||
|
|
invocation and the deriver itself. No include wrapping, no warning disabled, no
|
||
|
|
let _.
|
||
|
|
|
||
|
|
Note that this also applies to .mli files.
|
||
|
|
|
||
|
|
Consider:
|
||
|
|
|
||
|
|
$ cat > zero_do_warn.mli << EOF
|
||
|
|
> type t = int [@@deriving zero_do_warn]
|
||
|
|
> EOF
|
||
|
|
|
||
|
|
and compare the result of both driver invocations:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=false -intf zero_do_warn.mli
|
||
|
|
type t = int[@@deriving zero_do_warn]
|
||
|
|
include
|
||
|
|
sig
|
||
|
|
[@@@ocaml.warning "-32-60"]
|
||
|
|
module Zero : sig type t end
|
||
|
|
val zero : Zero.t
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=true -intf zero_do_warn.mli
|
||
|
|
type t = int[@@deriving zero_do_warn]
|
||
|
|
include sig module Zero : sig type t end val zero : Zero.t end[@@ocaml.doc
|
||
|
|
"@inline"]
|
||
|
|
[@@merlin.hide ]
|
||
|
|
|
||
|
|
--------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
The default value of the -unused-code-warnings should be false:
|
||
|
|
|
||
|
|
$ ./driver.exe -impl zero_do_warn.ml
|
||
|
|
type t = int[@@deriving zero_do_warn]
|
||
|
|
include struct let _ = fun (_ : t) -> () end[@@ocaml.doc "@inline"][@@merlin.hide
|
||
|
|
]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
module Zero = struct type t =
|
||
|
|
| T0 end
|
||
|
|
let zero = Zero.T0
|
||
|
|
let _ = zero
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
As we can see here, the warnings were disabled by the driver, as is expected
|
||
|
|
with -unused-code-warnings=false.
|
||
|
|
|
||
|
|
--------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
There is another value possible for the -unused-code-warnings flag: "force".
|
||
|
|
This allows the warnings to be enabled even if the deriver does not allow it. In
|
||
|
|
this example though, using `force` or `true` results in the same output, since
|
||
|
|
the deriver `zero_do_warn` already allows the warning to be enabled.
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=force -impl zero_do_warn.ml
|
||
|
|
type t = int[@@deriving zero_do_warn]
|
||
|
|
include struct module Zero = struct type t =
|
||
|
|
| T0 end
|
||
|
|
let zero = Zero.T0 end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
We'll see below other examples where the `force` flag is actually useful.
|
||
|
|
|
||
|
|
--------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
Let's consider the following ocaml source file using the one_no_warn deriver
|
||
|
|
|
||
|
|
$ cat > one_no_warn.ml << EOF
|
||
|
|
> type t = int [@@deriving one_no_warn]
|
||
|
|
> EOF
|
||
|
|
|
||
|
|
One_no_warn is registered with unused_code_warning set to false, meaning the driver
|
||
|
|
should disable warnings for the generated code, even when the value of the
|
||
|
|
-unused-code-warning is set to true. The following driver invocations have the
|
||
|
|
same output:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=false -impl one_no_warn.ml
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
let _ = fun (_ : t) -> ()
|
||
|
|
module One = struct type 'a t =
|
||
|
|
| T1 of 'a end
|
||
|
|
let one = One.T1 zero
|
||
|
|
let _ = one
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=true -impl one_no_warn.ml
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
let _ = fun (_ : t) -> ()
|
||
|
|
module One = struct type 'a t =
|
||
|
|
| T1 of 'a end
|
||
|
|
let one = One.T1 zero
|
||
|
|
let _ = one
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
Same goes for .mli files:
|
||
|
|
|
||
|
|
$ cat > one_no_warn.mli << EOF
|
||
|
|
> type t = int [@@deriving one_no_warn]
|
||
|
|
> EOF
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=false -intf one_no_warn.mli
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
sig
|
||
|
|
[@@@ocaml.warning "-32-60"]
|
||
|
|
module One : sig type 'a t end
|
||
|
|
val one : Zero.t One.t
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=true -intf one_no_warn.mli
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
sig
|
||
|
|
[@@@ocaml.warning "-32-60"]
|
||
|
|
module One : sig type 'a t end
|
||
|
|
val one : Zero.t One.t
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
--------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
When using a deriving that does not allow the warning to be enabled (such as
|
||
|
|
`one_no_warn` here), it is still possible to force it from the user side. That's
|
||
|
|
what the `force` argument for the driver flag is for. See below:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=force -impl one_no_warn.ml
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
module One = struct type 'a t =
|
||
|
|
| T1 of 'a end
|
||
|
|
let one = One.T1 zero
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
Same goes for .mli files:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=force -intf one_no_warn.mli
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include sig module One : sig type 'a t end val one : Zero.t One.t end
|
||
|
|
[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
--------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
The alias_warn deriver is in fact an alias for two derivers:
|
||
|
|
- alias_do_warn, which is registered with unused_code_warnings=true
|
||
|
|
and derives a single `unit_one : unit` value
|
||
|
|
- alias_no_warn, which is registered with unused_code_warnings=false
|
||
|
|
and derives a single `unit_two : unit` value
|
||
|
|
|
||
|
|
For the following code:
|
||
|
|
|
||
|
|
$ cat > alias_warn.ml << EOF
|
||
|
|
> type t = int [@@deriving alias_warn]
|
||
|
|
> EOF
|
||
|
|
|
||
|
|
We expect that the driver will do the right thing and disable the warning only for
|
||
|
|
unit_one:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=true -impl alias_warn.ml
|
||
|
|
type t = int[@@deriving alias_warn]
|
||
|
|
include struct let _ = fun (_ : t) -> () end[@@ocaml.doc "@inline"][@@merlin.hide
|
||
|
|
]
|
||
|
|
include struct let unit_one = () end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
include struct let unit_two = unit_one
|
||
|
|
let _ = unit_two end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
As expected, there is a let _ = unit_two but nothing for unit_one. Since unit_one does
|
||
|
|
not allow the warning 34 to be enabled, you can see that the combination of both derivers
|
||
|
|
still keeps the `let _ = fun (_ : t) -> ()` construct.
|
||
|
|
|
||
|
|
If we turn off the unused-code-warnings flag, there will be a `let _ = ...` for both:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=false -impl alias_warn.ml
|
||
|
|
type t = int[@@deriving alias_warn]
|
||
|
|
include struct let _ = fun (_ : t) -> () end[@@ocaml.doc "@inline"][@@merlin.hide
|
||
|
|
]
|
||
|
|
include struct let unit_one = ()
|
||
|
|
let _ = unit_one end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
include struct let unit_two = unit_one
|
||
|
|
let _ = unit_two end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
As expected, if we force the unused-code-warnings, there will be no let _ for
|
||
|
|
any of the two values, and no construct to use the type t:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=force -impl alias_warn.ml
|
||
|
|
type t = int[@@deriving alias_warn]
|
||
|
|
include struct let unit_one = () end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
include struct let unit_two = unit_one end[@@ocaml.doc "@inline"][@@merlin.hide
|
||
|
|
]
|
||
|
|
|
||
|
|
Same goes for .mli:
|
||
|
|
|
||
|
|
$ cat > alias_warn.mli << EOF
|
||
|
|
> type t = int [@@deriving alias_warn]
|
||
|
|
> EOF
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=true -intf alias_warn.mli
|
||
|
|
type t = int[@@deriving alias_warn]
|
||
|
|
include sig val unit_one : unit end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
include sig [@@@ocaml.warning "-32"] val unit_two : unit end[@@ocaml.doc
|
||
|
|
"@inline"]
|
||
|
|
[@@merlin.hide ]
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=false -intf alias_warn.mli
|
||
|
|
type t = int[@@deriving alias_warn]
|
||
|
|
include sig [@@@ocaml.warning "-32"] val unit_one : unit end[@@ocaml.doc
|
||
|
|
"@inline"]
|
||
|
|
[@@merlin.hide ]
|
||
|
|
include sig [@@@ocaml.warning "-32"] val unit_two : unit end[@@ocaml.doc
|
||
|
|
"@inline"]
|
||
|
|
[@@merlin.hide ]
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-code-warnings=force -intf alias_warn.mli
|
||
|
|
type t = int[@@deriving alias_warn]
|
||
|
|
include sig val unit_one : unit end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
include sig val unit_two : unit end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
--------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
Whenever a set of types has a [@@deriving ...] attached, ppxlib's driver always
|
||
|
|
generates structure items meant to disable unused type warnings (warning 34) for
|
||
|
|
any of those types.
|
||
|
|
|
||
|
|
Let's consider the following piece of OCaml code:
|
||
|
|
|
||
|
|
$ cat > unused_types.ml << EOF
|
||
|
|
> type t = int
|
||
|
|
> and u = string
|
||
|
|
> [@@deriving zero_do_warn]
|
||
|
|
> EOF
|
||
|
|
|
||
|
|
If we run the driver:
|
||
|
|
|
||
|
|
$ ./driver.exe -impl unused_types.ml
|
||
|
|
type t = int
|
||
|
|
and u = string[@@deriving zero_do_warn]
|
||
|
|
include struct let _ = fun (_ : t) -> ()
|
||
|
|
let _ = fun (_ : u) -> () end[@@ocaml.doc "@inline"][@@merlin.hide
|
||
|
|
]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
module Zero = struct type t =
|
||
|
|
| T0 end
|
||
|
|
let zero = Zero.T0
|
||
|
|
let _ = zero
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
We can see that the driver generated two `let _ = fun (_ : ...`, one for each type
|
||
|
|
in the set.
|
||
|
|
|
||
|
|
As we mentioned before, the driver flag (`-unused-code-warnings`) allows the
|
||
|
|
user to disable all warnings. In addition to this more general flag, we have a
|
||
|
|
flag that disables only this part, and allows unused type warnings to be reported
|
||
|
|
properly. Passing that flag to the driver should remove the two previously
|
||
|
|
mentioned items, without affecting the rest of the generated anti-warning items:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-type-warnings=true -impl unused_types.ml
|
||
|
|
type t = int
|
||
|
|
and u = string[@@deriving zero_do_warn]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
module Zero = struct type t =
|
||
|
|
| T0 end
|
||
|
|
let zero = Zero.T0
|
||
|
|
let _ = zero
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
Similarly to `-unused-code-warnings`, it is possible to force disabling the generation
|
||
|
|
of this construct even when the ppx generator does not allow it.
|
||
|
|
|
||
|
|
For example, consider:
|
||
|
|
|
||
|
|
$ ./driver.exe -impl one_no_warn.ml
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
let _ = fun (_ : t) -> ()
|
||
|
|
module One = struct type 'a t =
|
||
|
|
| T1 of 'a end
|
||
|
|
let one = One.T1 zero
|
||
|
|
let _ = one
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
See how `-unused-type-warnings=true` does not affect the generated code:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-type-warnings=true -impl one_no_warn.ml
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
let _ = fun (_ : t) -> ()
|
||
|
|
module One = struct type 'a t =
|
||
|
|
| T1 of 'a end
|
||
|
|
let one = One.T1 zero
|
||
|
|
let _ = one
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|
||
|
|
|
||
|
|
But if we force it, the driver omits the `let _ = fun (_ : t) -> ()`:
|
||
|
|
|
||
|
|
$ ./driver.exe -unused-type-warnings=force -impl one_no_warn.ml
|
||
|
|
type t = int[@@deriving one_no_warn]
|
||
|
|
include
|
||
|
|
struct
|
||
|
|
[@@@ocaml.warning "-60"]
|
||
|
|
module One = struct type 'a t =
|
||
|
|
| T1 of 'a end
|
||
|
|
let one = One.T1 zero
|
||
|
|
let _ = one
|
||
|
|
end[@@ocaml.doc "@inline"][@@merlin.hide ]
|