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

Allow F# compiler directives like #nowarn to span less than an entire file. #278

Open
baronfel opened this issue Oct 20, 2016 · 43 comments
Open
Labels

Comments

@baronfel
Copy link
Contributor

baronfel commented Oct 20, 2016

Submitted by Robert Nielsen on 6/22/2014 12:00:00 AM
7 votes on UserVoice prior to migration

Idea posted here:
http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3338653-allow-f-compiler-directives-like-nowarn-to-span
A possibly related issue / suggestion is listed here:
/archive/suggestion-5676502-disable-compiler-warnings-per-line

Original UserVoice Submission
Archived Uservoice Comments

@dsyme dsyme removed the open label Oct 29, 2016
@gusty
Copy link

gusty commented May 3, 2018

Is this approved?

I think it's important not to disable warnings unintentionally.

Maybe something like:

#nowarn "44"
 ...
#endnowarn "44"

or just:

#nowarn "44"
...
#warn "44"    

@abelbraaksma
Copy link
Member

@gusty, I agree, I really miss this feature in everyday development. Something like nowarnonce "44" may be another alternative.

@dsyme
Copy link
Collaborator

dsyme commented May 4, 2018

Is there a corresponding C# feature? (sorry - on mobile, haven't checked...)

If so we should maintain alignment

@abelbraaksma
Copy link
Member

abelbraaksma commented May 4, 2018

Is there a corresponding C# feature? (sorry - on mobile, haven't checked...)
If so we should maintain alignment

@dsyme, I believe we are quite behind with alignment when it comes down to compiler directives and similar statements. We support only a small amount of them. Think of #line, #region, #define and pragma warning disable in conjunction with pragma warning restore.

In C# they don't have #pragma warning disable once, but it would definitely be a nice-to-have. I don't think we need to stick to the #pragma syntax, expaning on #nowarn seems fine with me.

@gusty
Copy link

gusty commented May 4, 2018

Having to disable many potential warnings in order to address a single one is an issue and AFAIK there is no workaround it.

I'm currently being impacted by this issue in a real world project.

@ReedCopsey
Copy link

ReedCopsey commented May 4, 2018 via email

@abelbraaksma
Copy link
Member

abelbraaksma commented May 5, 2018

@ReedCopsey, I agree, with the caveat that we already have the #nowarn syntax and no #pragma syntax. It may be more orthogonal to stay close to the syntax we already have. I'd think of #nowarn once or current syntax plus #warn enable to re-enable a set of warnings.

While we're at it, I'd like to suggest alignment with #warning and #error in C# as well. I really miss the #warning, which can help keep large code bases sane by adding warnings on tricky code paths that require attention in the near future (not the same as, but related to, //TODO, which is currently not recognized automatically either, but is so in C#).

@kkm000
Copy link

kkm000 commented Sep 27, 2018

@abelbraaksma, I am a little concerned about the scope of #nowarn once. What happens if you put the directive before the construct that causes a warning, but then change it in a way the warning is no longer issued? Will this inadvertently suppress the same warning below?

Explicit #nowarn/#warn brackets, akin to C#, or VC++ stack-style exclusions, e. g. #nowarn "42" "44" push/#nowarn pop, look safer in this regard.

@kkm000
Copy link

kkm000 commented Oct 2, 2018

@dsyme, is this ready for a "needs rfc" status?

@dsyme
Copy link
Collaborator

dsyme commented Oct 12, 2018

@kkm000 Yes - For this sort of thing we're pretty much always happy to follow the C# approach to the same problem, as long as it is fully implemented and tested.

@kkm000
Copy link

kkm000 commented Oct 14, 2018

To sum up the C# approach: it is not stack-based like MS C++ #pragma (which makes sense, as C# does not have to deal with include files, and neither does F#), but rather either disables warnings, or restores warnings to their default state:

#pragma warning disable warning-list  
#pragma warning restore warning-list  

warning-list
A comma-separated list of warning numbers. The "CS" prefix is optional.
When no warning numbers are specified, disable disables all warnings and restore enables all warnings.

My take on this (I might try to put up an RFC, but just want to get a preliminary agreement on the approach):

  • Enable more than one warning in F# directive: #nowarn "42" "43".
  • Do not allow an empty list of warnings to disable all warnings, like C# does (#nowarn w/o arguments is invalid).
  • #nowarn has a cumulative effect (as it does now).
  • The new argument restore restores warnings: #nowarn restore "42" "43".
  • #nowarn restore is allowed w/o the list of warnings, and resets all warnings to the compiler command line settings or defaults.
  • #nowarn effects are scoped to file, i. e. an end of module or namespace or any other scope does not automatically restores any warnings previously disabled in that scope.
  • Optional: allow warning numbers without quotes, #nowarn 42? The quotes are somewhat puzzling, looks like they serve no syntactic purpose. C# does not have them (syntax from op. cit., #pragma warning disable 414, CS3021).

It is not exactly clear where the #nowarn directives could be allowed. Controlling warnings deeply inside an expression seems the best from the usability standpoint, since almost everything is an expression in F#, save for top-level module/type bindings. So what if it goes down to this?

let quite_a_long_function () =
 . . .
  let inner_function a b c = 
    let tuple = (let x = (a +
#nowarn "123"
                          (this_produces_a_warning b) ) *
#nowarn restore "123"
                          c in x*x, "foo")
    Some tuple
 . . .

or a less contrived case that does happen in real life

#nowarn "25"  // Because we never call it with a None.
let f a (Some b) = 
#nowarn restore "25"
 (* 20 lines of implementation, where the incomplete match warning is beneficial. *)

Here, the scope of nowarn intersects the scope of an expression or that of the whole binding.

It would be nice to have such a fine degree of control. Unfortunately, I'm not familiar with the parser internal handling of expressions, so I am just purely fantasizing; it might be such a complex thing to implement than its expense/benefit ratio would be questionable. Can anyone familiar with the compiler (@dsyme is perhaps the ultimate authority) please give a ballpark estimate of the complexity involved in handling the above examples?

@dsyme
Copy link
Collaborator

dsyme commented Oct 15, 2018

@kkm000 Great analysis thanks.

Can anyone familiar with the compiler (@dsyme is perhaps the ultimate authority) please give a ballpark estimate of the complexity involved in handling the above example?

I think that depends entirely on your level of motivation and your ability to grok/change/test the somewhat messy code that processes these things for scripts, compiler service and regular processing.

Having #nowarn in the middle of an expression is not a problem, since it's a lexer thing.

@Rickasaurus
Copy link

It would be nice if these pragmas could follow normal scoping rules.

@abelbraaksma
Copy link
Member

abelbraaksma commented Nov 7, 2018

It would be nice if these pragmas could follow normal scoping rules.

I'd agree, but I think scoping is done way later in the process (not sure, just assuming), and pre-processing directives are typically processed as early as possible.

In fact, there are currently severe problems with some preprocessing directives when you accidentally use them scoped (that is, indented). I reported them somewhere, but it has low priority on the long list of todos, I think.

I am a little concerned about the scope of #nowarn once. What happens if you put the directive before the construct that causes a warning, but then change it in a way the warning is no longer issued? Will this inadvertently suppress the same warning below?

@kkm000, yes, it would. But that very point should actually remove your concerns, as the current syntax applies to a whole file, and one alternative approach (#warn on and #warn off) applies to a block of code, both leading to much larger chances that such mistakes are introduced after later editing the code.

The difference being that #warn once XXX is explicitly scoped to one line and therefor has the smallest scope possible, and the least possible chance of "infecting" more code than it needs to.

@dsyme, @kkm000, I think I saw a PR somewhere (and maybe an RFC?) that introduces new syntax, can that be linked here?

@abelbraaksma
Copy link
Member

abelbraaksma commented Feb 6, 2019

Related request on #error: dotnet/fsharp#6178 . Maybe time to create an overarching issue to align the directives with C#.

@OnurGumus
Copy link

Any updates?

@gusty
Copy link

gusty commented Feb 9, 2020

This is approved in principle so I guess someone have to draft an RFC.

@kerams
Copy link

kerams commented Nov 16, 2022

To clarify, what exactly is approved? Just adding #nowarn restore or aligning directives with C#?

@abelbraaksma
Copy link
Member

I hope the latter. I think it’s time to go over the directives in general and align where possible or sensible.

@abelbraaksma
Copy link
Member

I self-assigned this, I hope to get to writing an RFC soon(ish), as this keeps popping up.

@brianrourkeboll
Copy link

One option to consider for the closing/restoring directive for #nowarn could be #warnon "<warning-list>".

Just as the #nowarn directive already corresponds to the --nowarn (<NoWarn></NoWarn>) compiler option, a #warnon directive would correspond to the existing --warnon (<WarnOn></WarnOn>) compiler option.

A #warnon directive could also (optionally) allow for a symmetrical mechanism for selectively enabling warnings for specific scopes; #nowarn could serve as its closing directive.

Scoped disabling with #nowarn … #warnon

#nowarn "25" // Incomplete pattern matches.
let [x] = List.singleton 1
#warnon "25" // Incomplete pattern matches.

Scoped enabling with #warnon … #nowarn

#warnon "1178" // Type does not satisfy comparison/equality constraint.
type T = T of (unit -> unit)
#nowarn "1178" // Type does not satisfy comparison/equality constraint.

@abelbraaksma
Copy link
Member

abelbraaksma commented May 14, 2024

@brianrourkeboll I agree to the general approach you lay out, as that ties in with the general approach that C# has taken, see #278 (comment).

And when used without arguments, #warnon would re-enable all warnings.

I'd have to check how they deal with commandline arguments in the C# case though. I'd assume these are not touched and that the scoped, file-based ones only apply to that particular file, where the ones on the commandline apply to the whole project.

@voronoipotato
Copy link

voronoipotato commented Jul 24, 2024

This was proposed in 2016, it would be huge if we could get this. Is there a way we can pay for a bounty on things like this because I'm tired of having warnings that I have to see every day because a different area of the file might need it. I know the devil is in the details and it's probably not as easy as it looks, and I'm not trying to judge you for not getting around to it, I just wish there were a way to help even though I lack the availability to contribute.

@psfinaki
Copy link

@voronoipotato actually - there is a way to do just that :)

@T-Gro
Copy link

T-Gro commented Jul 26, 2024

and it's probably not as easy as it looks

I will have a look at the feasibility, since with https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1060-nullable-reference-types.md in sight the desire to have this will increase, especially for gradual introduction of this feature into big projects.

But it still is a feature which needs an RFC, documentation updates and an announcement with motivating examples, so any kind of help (even non-programmatical), is welcomed.

@T-Gro
Copy link

T-Gro commented Jul 26, 2024

The place to add this, considering only the #warnon directive and not an immediate full alignment with C#, would be here:

    override _.DiagnosticSink(diagnostic: PhasedDiagnostic, severity) =
        if severity = FSharpDiagnosticSeverity.Error then
            realErrorPresent <- true
            diagnosticsLogger.DiagnosticSink(diagnostic, severity)
        else
            let report =
                let warningNum = diagnostic.Number

                match diagnostic.Range with
                | Some m ->
                    scopedPragmas
                    |> List.exists (fun pragma ->
                        let (ScopedPragma.WarningOff(pragmaRange, warningNumFromPragma)) = pragma

                        warningNum = warningNumFromPragma
                        && (not checkFile || m.FileIndex = pragmaRange.FileIndex)
                        && posGeq m.Start pragmaRange.Start)
                    |> not
                | None -> true

https://github.com/dotnet/fsharp/blob/db4ad94a723bed5a09c63a4a14c28902d1dbfa3c/src/Compiler/Driver/CompilerDiagnostics.fs#L2246-L2259

As you can see, the existing code already checks for range of the "opening" #nowarn via posGeq comparison. The new feature would add a "closing" #warnon check.

If someone wants to introduce this feature, I can help/advice/pair on the implementation and testing.

@voronoipotato
Copy link

I think any support would be great, and then if someone can get full alignment that would also be nice but being able to nowarn a few lines is most critical for my workplace.
two questions

  • We would need a ScopedPragma.WarningOn?
  • What is posGeq?

@Martin521
Copy link

If nobody else has volunteered, I would want to tackle this end-to-end.
@T-Gro I'd appreciate a quick chat on a few questions I have.
@voronoipotato Your post came in while I was writing mine. If your stuff is urgent, please go ahead.

@voronoipotato
Copy link

voronoipotato commented Aug 1, 2024

@Martin521 I've got a lot on my plate so unless I can bargain for some research time at work for this I can't meaningfully take it on. If you have a tip jar though for your efforts I'd be happy to pitch in.

@vzarytovskii
Copy link

@T-Gro I'd appreciate a quick chat on a few questions I have.

@Martin521 I wonder if it will be easier to just post here, because I know Tomáš will be off next week, and might not have time before then, however I or someone else can answer questions here. Or if you'd like to, you can ping me, and we can chat.

@vzarytovskii
Copy link

One option to consider for the closing/restoring directive for #nowarn could be #warnon "<warning-list>".

Just as the #nowarn directive already corresponds to the --nowarn (<NoWarn></NoWarn>) compiler option, a #warnon directive would correspond to the existing --warnon (<WarnOn></WarnOn>) compiler option.

A #warnon directive could also (optionally) allow for a symmetrical mechanism for selectively enabling warnings for specific scopes; #nowarn could serve as its closing directive.

Scoped disabling with #nowarn … #warnon

#nowarn "25" // Incomplete pattern matches.
let [x] = List.singleton 1
#warnon "25" // Incomplete pattern matches.

Scoped enabling with #warnon … #nowarn

#warnon "1178" // Type does not satisfy comparison/equality constraint.
type T = T of (unit -> unit)
#nowarn "1178" // Type does not satisfy comparison/equality constraint.

I do actually agree with this one, it should definitely first align with our own compiler flags, and aligning with C# is orthogonal.

@brianrourkeboll
Copy link

And when used without arguments, #warnon would re-enable all warnings.

I think that #warnon, whether closing a #nowarn block or opening its own block, should require a non-empty list of warnings.

Consider a scenario like this:

#nowarn "25" // Incomplete pattern matches.
let [x] = List.singleton 1
#warnon

Is #warnon only reenabling FS0025? Or is it also reenabling any other warnings that have been disabled… In an outer #nowarn? In the project file? Via compiler flag? Does it enable all available warnings, even optional warnings that haven't been explicitly disabled? What if there are a thousand lines of code between the #nowarn "25" and the bare #warnon?

So I prefer my original suggestion, i.e., that #warnon should always require specific warning identifiers:

#nowarn "25" // Incomplete pattern matches.
let [x] = List.singleton 1
#warnon "25" // Incomplete pattern matches.

@vzarytovskii
Copy link

vzarytovskii commented Aug 1, 2024

Is #warnon only reenabling FS0025? Or is it also reenabling any other warnings that have been disabled…

I would personally expect this to not work (i.e. expect #warnon to be explicit and require a number/set of numbers).

Update: Yep, as you wrote above:

I think that #warnon, whether closing a #nowarn block or opening its own block, should require a non-empty list of warnings.

@Martin521
Copy link

Martin521 commented Aug 1, 2024

I do actually agree with this one, it should definitely first align with our own compiler flags, and aligning with C# is orthogonal.

Yes, that was also what I thought.
My plan would be

  • Take this comment plus this one plus this one as starting point.
  • Create a draft implementation to make sure it is doable.
  • Create, discuss and conclude on the RFC.
  • Finish the implementation, tests, documentation updates.

This is a hobby project for me, so it may take some time.

Here are my questions:

  • The language reference says:
    "The effect of disabling a warning applies to the entire file, including portions of the file that precede the directive."
    (And the spec, using different wording, confirms that.)
    It seems this has been changed at some point to affect the file only from the #nowarn onwards, right?
  • Should / can we jump directly to expression scoped #nowarn (as considered in the scoping comment that I mentioned above)?
  • Where to put the tests for this feature? I did not find anything on Compiler Directives in ComponentTests.
  • Any complications I have to keep in mind (compiler flags, fsi)?
  • Is ServiceLexing relevant? I.e. do I have to do something here regarding lexing/parsing #warnon, or is this place sufficient?
  • While playing with #nowarn, I found that unknown compiler directives (like #XXX) are not reported as errors (they are just ignored right now). Is this what we want, or should I possibly also fix this?

No need to answer all of the questions, I can find out the technical stuff. Advice on the location for the tests and regarding expression scope would be good.

@vzarytovskii
Copy link

I do actually agree with this one, it should definitely first align with our own compiler flags, and aligning with C# is orthogonal.

Yes, that was also what I thought. My plan would be

  • Take this comment plus this one plus this one as starting point.
  • Create a draft implementation to make sure it is doable.
  • Create, discuss and conclude on the RFC.
  • Finish the implementation, tests, documentation updates.

This is a hobby project for me, so it may take some time.

Here are my questions:

  • The language reference says:
    "The effect of disabling a warning applies to the entire file, including portions of the file that precede the directive."
    (And the spec, using different wording, confirms that.)
    It seems this has been changed at some point to affect the file only from the #nowarn onwards, right?

Currently, it doesn't seem like it (nowarn disabled warning above it):
image
However, after the change it should affect only the range in the file.

  • Should / can we jump directly to expression scoped #nowarn (as considered in the scoping comment that I mentioned above)?

I think we should, yes.

  • Where to put the tests for this feature? I did not find anything on Compiler Directives in ComponentTests.

Yeah, I couldn't either, at least not dedicated category. CompilerDirectives can be created just in the root of the ComponentTests, like we do with CompilerOptions.

  • Any complications I have to keep in mind (compiler flags, fsi)?

Probably only one for now - since now #nowarn covers the whole document, the new one will be covering from specific point in the specific expression. We might wanna think about whether we want to make
a. A breaking change
b. A new contextual behaviour, when if only #nowarn is present without #warnon, then it spans on the whole file, otherwise - on expression.

  • Is ServiceLexing relevant? I.e. do I have to do something here regarding lexing/parsing #warnon, or is this place sufficient?

I would ping @auduchinok here, he knows more than I do here.

  • While playing with #nowarn, I found that unknown compiler directives (like #XXX) are not reported as errors (they are just ignored right now). Is this what we want, or should I possibly also fix this?

Probably we shouldn't as part of this PR, since people could've used them as some sort of markers around the code and it might be a breaking change for them.

No need to answer all of the questions, I can find out the technical stuff. Advice on the location for the tests and regarding expression scope would be good.

@Martin521
Copy link

Martin521 commented Aug 1, 2024

Currently, it doesn't seem like it (nowarn disabled warning above it):
image

Interesting. With the current compiler the first line gets a warning.

Edit: Actually, any fsc that I have on my machine (SDK 5 / 6 / 7 / 8 / preview) emits the warning. But it seems the compiler service does not (??). Maybe this is related to my question re ServiceLexing above??

@TheAngryByrd
Copy link

Funny enough, we hit wanting for this yesterday.

Our use case was we have a set of errors, one of which has been obsoleted.

type MyErrors
| CoolError1 of CoolError1
| CoolError2 of CoolError2
| [<Obsolete>] OldError1 of OldError1

And it would be great to do only small sections that we know we want to ignore rather than the whole file like:

let handler e =
    match e with
    | CoolError1 ce -> ...
    | CoolError2 ce -> ...
    #nowarn 44
    | OldError ce -> ...
    #warnon 44

Interesting. With the current compiler the first line gets a warning.

Edit: Actually, any fsc that I have on my machine (SDK 5 / 6 / 7 / 8 / preview) emits the warning. But it seems the compiler service does not (??). Maybe this is related to my question re ServiceLexing above??

Yeah depending on where the #nowarn was in our file, if it happened to be below the place it would warn, tooling would say it's fine but compile time would show the warning.

@voronoipotato
Copy link

voronoipotato commented Aug 2, 2024

@vzarytovskii it's breaking in that behavior is changing, but it is not breaking in that it would interrupt anyone's development process. They would just see a few warnings. If they had the impression that it was working the way we would like (as I suspect most do), they would see warnings that they want to see, and haven't been. The current user experience of "nowarn anywhere in the file causes all warnings to be hidden" is surprising enough that I doubt people who put it in the middle of a file intended to hide warnings above it. In my opinion we should just fix it.

@vzarytovskii
Copy link

@vzarytovskii it's breaking in that behavior is changing, but it is not breaking in that it would interrupt anyone's development process. They would just see a few warnings. If they had the impression that it was working the way we would like (as I suspect most do), they would see warnings that they want to see, and haven't been. The current user experience of "nowarn anywhere in the file causes all warnings to be hidden" is surprising enough that I doubt people who put it in the middle of a file intended to hide warnings above it. In my opinion we should just fix it.

It is breaking in a way that something which was producing no errors in IDE before, might start producing them (given that majority turn warnings as errors).

@T-Gro
Copy link

T-Gro commented Aug 2, 2024

  • We would need a ScopedPragma.WarningOn?
  • What is posGeq?

Yes, a new case for ScopedPragma would be the structured way of adding this. That way, the compiler will naturally guide the places which need extending.

posGeq is "position greater or equal" ;; a comparison between the position (type range, value for it often called m) of the warning and position of the pragma (in the new logic, searching and comparing against ranges of both possible pragmas)

@voronoipotato
Copy link

voronoipotato commented Aug 14, 2024

It is breaking in a way that something which was producing no errors in IDE before, might start producing them (given that majority turn warnings as errors).

The population of people who put nowarn in the middle of a file and had it marked warning as error, and were trying to nowarn the entire file in my intuition is smaller than the population of people who put nowarn in the middle of the file, marked warning as error, and were trying to nowarn a portion of the file. The current way it works is very surprising and I have been burned by it more than once. I understand that it's "technically" still breaking, but my point is that the existing functionality is effectively "not working" in terms of what many people will expect.

@Martin521
Copy link

There is now a RFC proposal for this feature

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests