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
lint trivially self-recursive extension methods #13709
Conversation
sorry to take an issue labeled "spree", but Dale and I actually worked through this explicitly as if we were spree-ing, with me using VS Code + Metals (instead of my usual Emacs or IntelliJ) as practice for doing the same thing with a newcomer at the next spree |
CI is failing because |
I was going to push an even broader version of this I have that also covers lazy vals, based on the work we did on our spree ticket. It would fix issues 9880, 10947, 12943, and 13011. Seems like a lot of tickets converged here. |
@griggt heh. did you already handle the |
We were focused on the lazy vals stuff, and I hadn't tried running a bootstrapped test since broadening to cover 9880, so had not seen the It would have come up tonight when opening a PR though, so thanks for catching it first! :-) |
@griggt happy to let your more ambitious effort go forward instead of this one, but let us know if you get stuck or need reviewers/collaborators Dale noticed when he looked at #13011 that |
Dale and I suspect that Martin's version of the check could issue a false-positive warning in a case such as: class Parent:
val p: Parent = new Child
def foo: Unit = p.foo
class Child extends Parent:
override def foo = {} here this is similar to the well-known thing where but is the false positive in this case actually a concern? maybe not but perhaps the trickiness of reasoning about this argues for the DRYing I suggested in my previous comment. (but we haven't really considered how feasible/hard that DRYing might be) |
I would say yes, any warning on by default should have a vanishingly small false positive rate. |
Martin's original was for given/implicits, where redefining in a subclass is plausible but I couldn't think of a reasonable real-world example of that. Extension methods can't be redefined, so they're ok. For Tom's lazy vals (IIRC!) can't be redefined either, so still good. But this is relevant if we want to go for the broader approach (which Tom mentioned). |
Extension methods and lazy vals can be overridden. |
Derp scala> class Bar extends Foo { override lazy val foo = 2 + 2 }
// defined class Bar
...
scala> class BarOps extends FooOps:
| extension (c: Circle)
| override def circumference: Double = 0.0
| override def foo = 2
|
// defined class BarOps |
@griggt any update? (Dale and I were thinking of finishing this more modest PR up anyway.) |
Ah, sorry, I've been swamped lately. But please continue your work and don't let me hold up progress, I likely wouldn't have time to look at this until the weekend at the earliest. |
As an aside that might be interesting to some...
In the Scala 2 spec, they are actually spec'ed as taking a by-name parameter; https://scala-lang.org/files/archive/spec/2.13/12-the-scala-standard-library.html has: /** Compares two Boolean expressions and returns `true` if both of them evaluate to true.
*
* `a && b` returns `true` if and only if
* - `a` and `b` are `true`.
*
* @note This method uses 'short-circuit' evaluation and
* behaves as if it was declared as `def &&(x: => Boolean): Boolean`.
* If `a` evaluates to `false`, `false` is returned without evaluating `b`.
*/
def &&(x: Boolean): Boolean but Dale found that there's also this comment from Paul: // Compiler won't build with these seemingly more accurate signatures
// def ||(x: => Boolean): Boolean
// def &&(x: => Boolean): Boolean so something blocked Paul from making that change in Scala 2 at some past point in time, but perhaps the same blocker wouldn't exist in Scala 3. But we choose not to try that experiment at the moment. |
I see that #13749 has appeared — it looks orthogonal to me |
9ee0bfc
to
4751f47
Compare
Broadens Martin's fix for scala#13542 Fixes scala#9880 Also, exempt Boolean's && and || methods, which aren't set up as having by-name parameters. Co-authored-by: Dale Wijnand <dale.wijnand@gmail.com>
rebased. remains ready for review. |
To make sure review requests don't get lost, please both ask for review and assign the reviewer to your PR. |
Is the issue with false positives mentioned in #13709 (comment) now fixed? |
Good question. I only just became aware that you can override extension methods, so I'm not entirely sure... The problem is when you invoke a method on a class and it dispatches onto a subclass, thus making it non-self-recursive at runtime even though it might look like it is at compile time (naively). But I don't think you can do that with extension methods. Here's my attempt: abstract class Foo:
def parent: Foo
class FooOps:
extension (x: Foo)
def doFoo: Int =
val ops = new Foo2Ops
import ops._
x.parent.doFoo
class Foo2Ops extends FooOps:
extension (x: Foo)
override def doFoo: Int = 2 This isn't going to be self-recursive, because it's referencing the subtypes doFoo, not the self-reference doFoo (if I haven't messed up). Can you come up with a reasonable counter-example we should consider? |
Do you mean the issue depends on the same symbol being used in the body? Then I could imagine: val ops: FooOps = new Foo2Ops
import ops._
x.parent.doFoo |
That false positive counter-example seems too farfetched to me. It also doesn't seem like a problem-at-a-distance anymore, with FooOps using Foo2Ops and Foo2Ops extending FooOps. This is, in comparison to the original case where Symbol and its owner is used in an extension method and throwing a false positive. Did you have an easy solution in mind, because otherwise I would wait for a real world case to appear before making this more complex. WDYT? |
@smarter I don't think your concern about false positives should be considered a blocker for merging this PR, because:
|
How do you suppress that warning? |
slap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the danger of false positives is very low in this case, and the possibility of accidental recursion is higher. So LGTM.
broadens Martin's fix for #13542, as suggested by Guillaume in a comment on that PR
fixes #9880