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

Add -Wnonunit-statement to warn about discarded values in statement position #9893

Merged
merged 4 commits into from Jul 29, 2022

Conversation

som-snytt
Copy link
Contributor

@som-snytt som-snytt commented Jan 5, 2022

Summary

New compiler option -Wnonunit-statement warns about any interesting expression whose value is ignored because it is followed by another expression.

The new warnings are not included in -Xlint; they must be separately enabled with -Wnonunit-statement. Users of -Wnonunit-statement may also wish to enable a related option, -Wvalue-discard.

Example

scala> :settings -Wnonunit-statement
scala> Some(3); "hello"
           ^
       warning: unused value
val res0: String = hello

How to silence

The warning can be suppressed at a particular site by explicit ascription: e: Unit. This is the same convention used to ignore warnings about "value discard" conversions.

As always, any warning is also suppressible with @nowarn.

Relation to other warnings

This generalizes the current warning for "pure" expressions, which only warns for limited cases where the expression can be proved not to be side-effecting.

-Wvalue-discard remains separate. It warns only when a value is discarded because it's the final expression in a block whose expected type is Unit, as specified by the language.

Details on what warns or doesn't

"Statement position" means statements in templates (i.e., constructors) and statements in blocks (everything in a block except the result expression). If a statement is an expression (and not a definition or assignment), then -Wnonunit-statement warns if the type of the expression is not Unit. There are heuristic exceptions, such as for methods that return this.type, where there is no loss of information. (The method is inherently side-effecting.) There is an additional flag whether to take an if statement as side-effecting, so that its result can be ignored.

By default, warns about if without else. Specify -Wnonunit-if:false to turn that off. (That also turns off -Wvalue-discard for those cases.)

The heuristic for this.type-returning methods intends to accommodate usual idioms updating unstable values.

Motivation

As requested on Discord!

This is especially useful in pure-functional code where it's unusual for values ever to be intentionally discarded.

@scala-jenkins scala-jenkins added this to the 2.13.9 milestone Jan 5, 2022
@som-snytt
Copy link
Contributor Author

Update to handle Future(42): Unit where the attachment is on the user expression, but we're linting the implicit Future(42)(ec) in { Future(42)(ec) ; () }.

Typing the user expression yields the block, so additional work would be needed to copy attachments from the explicit apply to the implicit.

@som-snytt som-snytt marked this pull request as ready for review January 5, 2022 21:35
@som-snytt som-snytt marked this pull request as draft January 5, 2022 22:02
@SethTisue SethTisue added the release-notes worth highlighting in next release notes label Jan 5, 2022
@SethTisue
Copy link
Member

I haven't seen so many yellow thumbs since my last all-day Simpsons-watching marathon. Looks like this will be popular if you can finish it.

@som-snytt
Copy link
Contributor Author

It's a bit niggling, but the warm reception means I can't stop at mile 18 despite the pain in my side.

@SethTisue

This comment was marked as resolved.

@som-snytt

This comment was marked as resolved.

@som-snytt som-snytt changed the title Add -Wnonunit-statement Add -Wnonunit-statement [ci: last-only] Mar 24, 2022
@SethTisue

This comment was marked as resolved.

@som-snytt

This comment was marked as resolved.

@SethTisue

This comment was marked as resolved.

@som-snytt

This comment was marked as resolved.

@SethTisue SethTisue modified the milestones: 2.13.9, 2.13.10 Apr 25, 2022
@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from ac7478d to 28a7770 Compare May 26, 2022 19:52
@som-snytt
Copy link
Contributor Author

It would be nice to provide an audit of Unit ascriptions that suppress a warning (or respectively suppress nothing). Maybe -Y.

Also a sibling option for nowarn. Otherwise, once you suppress, there is no opportunity to revisit. Grepping is harder than for nowarn usages.

I just needed that feature, A method was used a few times but only one warned because I'd previously slapped on the Unit.

What should the output look like? Just turning off suppression could result in many errors. Possibly specify a region of audit using -opt:inline syntax?

Or a general mechanism -Yaudit-nowarn where all suppressions are gathered, then reported by type and location.

@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from 28a7770 to 20bcb8e Compare June 9, 2022 17:07
@som-snytt som-snytt marked this pull request as ready for review June 9, 2022 17:12
@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from 20bcb8e to f33a40e Compare June 9, 2022 18:08
@som-snytt

This comment was marked as resolved.

@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from 2d26b75 to 1cd6506 Compare June 9, 2022 18:41
@som-snytt

This comment was marked as resolved.

@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from 1cd6506 to 4f94491 Compare June 9, 2022 19:25
@som-snytt som-snytt changed the title Add -Wnonunit-statement [ci: last-only] Add -Wnonunit-statement Jun 9, 2022
@som-snytt

This comment was marked as resolved.

@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from 4f94491 to 6b5adcc Compare June 9, 2022 21:26
@som-snytt
Copy link
Contributor Author

som-snytt commented Jun 9, 2022

This will require re-tweaks, as it still yields what I assume are spurious warnings on the standard library.

hashMapBuilder.addAll(xs)

Probably because it doesn't have a getter.

private[this] var hashMapBuilder: HashMapBuilder[K, V] = _

@som-snytt
Copy link
Contributor Author

Som day, Som day.

https://www.youtube.com/watch?v=UiF0FMs16dU

@SethTisue
Copy link
Member

youtube.com/watch?v=UiF0FMs16dU

🎵 you gave me no warnin’ 🎵

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

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

Question: this warns - should it count as a Unit ascription?

class C {
  def f(x: Int): Int = 1 + x
  def g: Unit = f(0)
}

receiver match {
case Apply(_, _) => Precedence(op.decoded).level == 0 // xs(i) += x
case _ => receiver.symbol != null &&
(receiver.symbol.isGetter || receiver.symbol.isField) // xs.addOne(x) for var xs
Copy link
Member

Choose a reason for hiding this comment

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

This part I don't understand... I don't see anything about var or addOne.

IIUC, it catches any method call on a field, no?

How about calls on local values / variables?

Also addOne and += are defined with return type this.type - why do you need to handle them separately?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a value is not stable, in v().addOne(x), I can witness different values in v() and the result. I think that is the gist of why, even if addOne is side-effecting and returns this, I've lost the value produced by v; I can't assume I can recover it by calling v again.

I see a test class Variant. I'll add more variants to ensure this check is not too wide a net. I think it applies only to this-typed methods. But is that even correct? I'm sure I noticed it by testing on this code base. But maybe v.addOne(x) is bad form.

Copy link
Member

Choose a reason for hiding this comment

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

👍 thanks. what i missed is that we're already narrowed down to a method with a this.type / singleton type here.

@armanbilge
Copy link
Contributor

I just took this for a spin on Skunk. Only two (spurious?) warnings!

https://github.com/tpolecat/skunk/blob/14c756d68ba88da4415699bea388149c43e4e962/modules/core/shared/src/main/scala-2/syntax/StringContextOps.scala#L79-L82

[error] /workspace/skunk/modules/core/shared/src/main/scala-2/syntax/StringContextOps.scala:79:36: unused value
[error]       val EncoderType      = typeOf[Encoder[_]]
[error]                                    ^
[error] /workspace/skunk/modules/core/shared/src/main/scala-2/syntax/StringContextOps.scala:81:36: unused value
[error]       val FragmentType     = typeOf[Fragment[_]]
[error]                                    ^
[error] two errors found

Seems like the issue is the _, if I change it to Any then the warning goes away.

@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from 37e12e1 to 68080c2 Compare July 27, 2022 15:25
@som-snytt
Copy link
Contributor Author

The Skunk warning is due to this call in the expansion. I added a commit that tweaks the signature, which may pass MiMA. There are probably other bits of reflect API that deserve similar tweaking.

$u.internal.reificationSupport.setInfo[$u.Symbol](symdef$EncoderType1, $u.NoType);

@som-snytt
Copy link
Contributor Author

@lrytz As to whether explicit result type means "I intend to discard on RHS", I think the answer is no, because -Wvalue-discard warns for this case.

Explicit result types are supplied often: to specify API, or to accommodate recursion.

I like the "transposed" form:

def g = f(42): Unit

which is somewhat equivalent but does show "intention to discard", and also is not redundant.

src/compiler/scala/tools/nsc/typechecker/RefChecks.scala Outdated Show resolved Hide resolved
receiver match {
case Apply(_, _) => Precedence(op.decoded).level == 0 // xs(i) += x
case _ => receiver.symbol != null &&
(receiver.symbol.isGetter || receiver.symbol.isField) // xs.addOne(x) for var xs
Copy link
Member

Choose a reason for hiding this comment

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

👍 thanks. what i missed is that we're already narrowed down to a method with a this.type / singleton type here.

@SethTisue
Copy link
Member

needs rebase

@som-snytt is #9893 (comment) considered resolved?

@som-snytt
Copy link
Contributor Author

som-snytt commented Jul 28, 2022

Since I have to rebase anyway, sigh, I'll add a code comment where lrytz had to read it twice. I had to read it more than that.

Edit: instead of a comment, added self-documenting method name.

@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from d6a03c9 to d261352 Compare July 28, 2022 15:52
@SethTisue
Copy link
Member

SethTisue commented Jul 29, 2022

needs checkfile update, looks like. once CI likes it let's merge :shipit:

@som-snytt
Copy link
Contributor Author

because the other warning jumped the queue, need to update on the first commit

!!    1 - neg/nonunit-if.scala                      [output differs]
% diff /home/jenkins/workspace/scala-2.13.x-validate-main/test/files/neg/nonunit-if.check /home/jenkins/workspace/scala-2.13.x-validate-main/test/files/neg/nonunit-if-neg.log
@@ -1,7 +1,7 @@
-nonunit-if.scala:58: warning: discarded non-Unit value
+nonunit-if.scala:58: warning: discarded non-Unit value of type U
     if (!isEmpty) f(a)      // warn, check is on
                    ^
-nonunit-if.scala:62: warning: discarded non-Unit value
+nonunit-if.scala:62: warning: discarded non-Unit value of type Boolean
       f(a)                  // warn, check is on
        ^
 nonunit-if.scala:13: warning: unused value

Warn about any interesting value in statement position,
for some definition of interest.

Generalizes the current warning for "pure" expressions.

By default, warns about unibranch if, which lack an else
in user code.

Specify -Wnonunit-if:false to turn off warning about
unibranch if. (That also turns off -Wvalue-discard for
those cases.)

Improve explicit Unit check

Accommodate field when method is this.type

Handle Match
@som-snytt som-snytt force-pushed the topic/warn-nonunit-statement branch from d261352 to 9fe44e8 Compare July 29, 2022 01:29
@som-snytt som-snytt merged commit cbc012e into scala:2.13.x Jul 29, 2022
@som-snytt som-snytt deleted the topic/warn-nonunit-statement branch July 29, 2022 04:47
@SethTisue
Copy link
Member

🎉

@sideeffffect
Copy link

Is there a chance that we will have something like this in Dotty?

@SethTisue
Copy link
Member

@sideeffffect thread is https://contributors.scala-lang.org/t/replacement-for-xlint-in-scala3/5241

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release-notes worth highlighting in next release notes typelevel of interest to Typelevel (http://typelevel.org)
Projects
None yet
7 participants