Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Per type warn 4 #1071

Open
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Open

Per type warn 4 #1071

wants to merge 6 commits into from

Conversation

vprevosto
Copy link
Contributor

@vprevosto vprevosto commented Feb 27, 2017

This MR allows to enable (or disable) warning 4 (fragile pattern-matching) on a per-type basis instead of globally. More specifically, if an attribute ocaml.warning with a payload containing +4 (resp -4) is associated to the definition of a sum type t, this attribute will be honored for any pattern-matching over a value of type t, regardless of the status of warning 4 at that point.

The rationale behind that is that writing non-fragile pattern-matching is pretty cumbersome and leads to code that is difficult to read (as a matter of fact, even the OCaml compiler disables it for its own compilation). On the other hand, knowing which patterns are fragile is extremely useful when one is adding a constructor, but only if only the patterns of the type being extended are reported (otherwise, there will very likely be too much noise from fragile matching over irrelevant types), which is exactly what this PR allows.

Additionally, the fragile matching detection code is now called on every matching, which uncovered an existing issue (just launch current ocamlc -w +4 over the code of PR #6394 to see it happen). This is fixed in the second commit of the series.

@vprevosto
Copy link
Contributor Author

Note that I've hesitated to update the manual, as I'd tend to think that the sentence

Note that it is not well-defined which scope is used for a specific warning.

in the documentation for the ocaml.warning attribute is general enough 😉

@Drup
Copy link
Contributor

Drup commented Feb 27, 2017

What about cases where you have warning 4 enabled and you pattern match on pairs of a type with -4 and an unannotated type ? It seems to me you would need some sort of analysis for this, which is much more precise than what is currently done.

@vprevosto
Copy link
Contributor Author

I'm not sure I understand your point. In this case, I indeed expect a warning. In my opinion, this is not different to the current situation with a pair of bool/list/option (types that are specifically excluded from warning 4) and another type, and a warning is indeed issued in such a case when warning 4 is enabled. Maybe I should rephrase the MR description to indicate that as soon as one type involved in the fragile matching has warning 4 enabled (either through a global or a type-definition attribute), the warning will be emitted?

@jberdine
Copy link
Contributor

jberdine commented Mar 17, 2017

See also MPR 7310 (#7310) which is closely related.

@vprevosto
Copy link
Contributor Author

@jberdine thanks for the pointer. I only searched for related PR on github and somehow forgot about mantis 😕 . I guess that gasche's or octachron's suggestion of having different levels for warning 4 annotation at declaration point would probably address @Drup remark. I'll try to make some suggestions in this direction in a not-too-distant future.

The same issue that was reported for typecore.ml in PR#6394 did occur in
fragile pattern-matching detection code. Fix is similar: accept the fact that
in presence of recursive module some incoherence may happen.
Implements Octachron's proposal in PR#7310: warnings can now be in 4
states:

- Always: they are always active
- Implicit: they are active, but can be deactivated through external definitions
- Explicit: they are inactive, but can activated through external definitions
- Never: they are always inactive.

`warn-error` keeps its semantics: if the warning is
triggered (regardless of the activation state), a compilation error
will follow.

Warning modifiers in the warning string are now the following

- `+` put the corresponding warnings in Implicit state
- `-` in Explicit state
- `++` in Always state
- `--` in Never state
- `@` in Implicit state and triggers an error
- `@@` in Always state and triggers an error.

as of now, only warning 4 takes advantage of the four states. For any
other warning, `Always` and `Implicit` merely indicate that the
warning is active, and `Never` and `Explicit` that it is inactive.
@vprevosto
Copy link
Contributor Author

I've pushed a proposal implementing octachron's suggestion in the related mantis issue: a warning can now have 4 statuses:

  • always enabled (aka always)
  • implicitly enabled (but "external" directives can disable it, aka implicit)
  • need to be explicitly enabled (by an external directive, aka explicit)
  • always disabled, aka never

I've also modified the parsing of the warning string to set these various statuses:

  • always is triggered by ++ (i.e. -w ++4)
  • implicit by +
  • explicit by -
  • never by --
  • in addition, @@ means always + warn-error.

Remarks are welcome.

Copy link
Contributor

@jberdine jberdine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the direction of this change. The proposed interface seems to be flexible enough, not cumbersome, and would handily cover the use cases I have encountered.

| Some ext when Path.same ext (get_type_path p.pat_type p.pat_env) ->
extra_pat
| Some ext ->
let path = (get_type_path p.pat_type p.pat_env) in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: extra space after =

Builtin_attributes.with_warning_attribute
decl.Types.type_attributes
(fun () -> Warnings.(is_active warn))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indent 2 spaces less?

| true, true -> Implicit
| false, true -> Explicit
| false, false -> Never
;;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: indent 2 spaces less

let set_all i = active.(i) <- true; error.(i) <- true in
let parse_opt error active flexible flags s =
let set i = flags.(i) <- true; flexible.(i) <- true in
let set_strict i = flags.(i) <- true; flexible.(i) <- false in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe 'rigid' instead of 'strict' if you want an antonym of 'flexible'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This renaming makes sense indeed. Thanks for the suggestion.

(if extendable_path path && warn_fragile_type p.pat_type p.pat_env
then add_path path r else r)
ps
| None -> r)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any hint about the behavior of this line? In particular, it's not obvious to me why to return r instead of ps, or even error already.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r is the accumulator (the list of paths we've seen so far), while ps is a list of patterns : the arguments of the constructor, i.e. if p is Foo (a, _, None) then ps is [Tpat_var "a"; Tpat_any; Tpat_construct "None"].
Returning ps wouldn't make sense (it wouldn't type), while returning r does.

One a similar note one can wonder why the code is not instead:

let r =
  match get_type_path ... with
  | Some p when extendable_path p -> add_path p r
  | _ -> r
in
List.fold_left collect_paths_from_pat r ps

Here my guess would be "well, we already know we're in a nonsensical situation, so we might as well not do any extra work". It would be nice if the author could add a comment in the code clarifying that though.

As for the last question "why not error", get_type_path was previously erroring but was modified not to anymore (and return an option instead), it would be of little benefit if we failed when looking at the result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trefis you're right. The None case is here to avoid a fatal error in a particular corner case (type error within a recursive module, as described in Mantis issue 6394). A proper type error will be generated somewhat later, and we can mostly ignore the patterns related to that type.

Regarding the way the code is written, your guess is wrong 😄 . It's just that the modification done for supporting per-type warn 4 and fixing PR 6394 were done in two separate commits and I forgot to refactor the code at that time. Your proposal is indeed clearer, and I'll fix up the code.

| Tconstr (path,_,_) -> Some path
| _ -> None
(* Same as PR#6394: we have an incoherence due to recursive module. It
will result in a type error later on. *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One does indeed get a fatal error here on the small example from MPR#6394, however I feel like this change should be proposed independently of the current GPR.
The change is indeed somewhat unrelated from the rest of the PR and should IMO be discussed independently.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make my point a bit more convincing I'd like to point out that this bugfix change was written in february, so could have been included in 4.04.X and 4.05.0!

Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I've to admit that I'd hoped this GPR to be reviewed a bit more quickly 😄 . That said, I'm a bit reluctant to extract the corresponding commit (albeit tiny, there will be a conflict somewhere), as I'm pretty sure that current usage of warning 4 is extremely limited due its impracticality on any large scale development as long as you can only enable it at global level.

@bobot
Copy link
Contributor

bobot commented Jul 19, 2017

Is there a link between this PR and #1248?


An example of such warning is Fragile_match, which can be (de)activated on a
per-type basis. See PR#7310 for an extended discussion.
*)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MR#7310 instead of PR#7310?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's either PR or MPR, we never used the MR abbreviation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't there some GPR as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes there is also GPR but it's a different set of PRs. I was just talking about the naming of Mantis Problem Reports.

@vprevosto
Copy link
Contributor Author

@bobot I don't think this PR is related to #1248 that closely: As far as I understand, #1248 is about the syntactical scope of warnings, while here we add another dimension which is the list of types for which we want the warning to trigger. But syntactically, the scope is global.

- fixes indentation and spacing
- small code refactoring
- renaming of local variables
@alainfrisch
Copy link
Contributor

I like the general direction (althrough I'd personnally be more interested to have the equivalent for warning 9). But I don't think one should re-use the existing @@ocaml.warning attribute. Even if it's scope is not specified, the intent is that the attribute controls warning whose location would be within its syntactic scope.

I'd prefer to introduce an ad hoc attribute on the type declaration (e.g. [@@ocaml.no_fragile_match]). It would only suppress the warning, and there should be no way to force issuing the warning if the "use site" disable the warning explicitly.

@damiendoligez damiendoligez added this to the 4.07-or-later milestone Sep 25, 2017
@damiendoligez damiendoligez removed this from the consider-for-4.07 milestone Jun 4, 2018
@gares
Copy link

gares commented Sep 18, 2018

I like the general direction (althrough I'd personnally be more interested to have the equivalent for warning 9).

+1, I couldn't agree more.

@damiendoligez
Copy link
Member

Note that the ++ / -- syntax clashes with what was done for alerts in #1804 :-(

(I'd forgotten about this PR, or I would have raised the issue in #1804 before it was merged)

@damiendoligez
Copy link
Member

cc @alainfrisch

@alainfrisch
Copy link
Contributor

Sorry, I missed that as well.

Personally, I'm not convinced by the 4-level controls over warning. For many warnings, the finer distinction is not useful, and there could be cases where there could be multiple different "sources" for enabling a specific warning, and one might want to provide different controls depending on the source. For instance, for extensible constructors, one could imagine some warnings enabled from the type declaration or from the constructor definition. (I'm not claiming that it necessarily makes sense to use different settings depending on the source, only that it's not impossible that one might want to do so.)

There are already several instances of similar warnings with different numbers, used to distinguish variants of the same situation (e.g. 37/38, 20/26/27/32/35/36, 11/12, 33/66). I believe we could do the same here, and simply introduce a new warning, similar to 9, but triggered by a specific annotation on the type declaration.

@gasche
Copy link
Member

gasche commented May 9, 2020

I had forgotten about this work. I think it is nice and important, and that we should consider trying to get it merged. In addition, since the time at which it originally appeared, I have gotten much more familiar with the pattern-matching analyses, to the point that I could probably review the implementation myself.

On the other hand, I don't like the user interface at all. [@warning "--4"] is inconsistent and particularly obscure, there is no easy way for a user to discover the meaning of this annotation. In the original PR I proposed [@@fragile_pattern {error,warn,ignore,default}]. I don't remember anymore what the various levels would mean, but I think that this is a much better interface than playing with ++, -- and the like -- in addition to the fact that 4 sucks as an identifier to mean "fragile pattern warning".

@gasche
Copy link
Member

gasche commented Apr 28, 2021

Note: Nowadays people can write [@@warning "-fragile-match"], which is much better than -4.

What is the status of this PR? Reading the discussion again, I think that the main problem is that we don't have a clear consensus on what we want. There were complaints about the previous UI ([@warning "-4"]), then @vprevosto put in a fair amount of work to support a more fine-grained setup with [@warning "--4"], and people complained about that even more. There is a proposal to use a dedicated attribute for this feature, but it's not clear that people would like it.

What do we want? Clearly people care about the feature itself. So now we need to agree on how to express it.

Personally, I'm not so sure anymore that I really want a dedicated attribute, because there are various other warnings that could benefit from the same treatment (Alain mentioned missing-record-field-pattern). The original UI proposal type t = ... [@@warning "-fragile-match"] seems unambiguous to me, rather natural, and extensible to other warnings, so I think that in the end I like it. (But I would also be fine with a dedicated attribute.)

stedolan pushed a commit to stedolan/ocaml that referenced this pull request Mar 21, 2023
EmileTrotignon pushed a commit to EmileTrotignon/ocaml that referenced this pull request Jan 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants