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
Under -Xsource:3
, warn that inherited members no longer take precedence over outer definitions in Scala 3
#10220
Conversation
Scala 3 variant: scala/scala3#16401 |
There must be a word for when you are able to contribute versions of a fix on both 2 and 3, coincidentally using the prefix "ambi-". I propose "ambiversal". |
Spec binding precedence of inherited definition. Use -Ylegacy-binding for old precedence.
The most common pattern leading to spurious errors is this: class C(val name: String)
object Test {
def m(name: String) = new C(name) {
println(name)
}
} I spent some time coming up with a heuristic to see through the most common such pattern [EDIT: but the heuristic was entirely discarded ultimately, so the example above warns]. The difficulty is, I pushed this for now to give it a try. If we keep it, we should try to do something similar for Scala 3. The heuristic currently breaks here: abstract class A(val name: String)
class C(name: String) extends A(name)
object Test {
def m(name: String) = new C(name) {
println(name)
}
} |
I think the last experiment is probably not worth it. The implementation is rather ad-hoc, and it would have to be replicated to more compilers (Scala 3, IntelliJ). Instead I changed it to be a warning (WIP) and made it even more verbose. |
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.
Very nice!
This was on my to-do list yesterday, right after "override message" and "dentist" (for child). There was a cavity. 😢 The code inherited from the other PR ( The amendment to Triggering the warning in typer is especially clever yet simple. Just warning is probably sufficient, since |
Thanks, I should also learn writing supportive review messages. |
For fun, I verified the warning for the linked PR.
The build has other warnings under JDK 19, so if you're inclined to ignore warnings, you might miss it. I also wondered if "Since 3" reads normally. "Since 3 P.M." I thought I'd contributed something to mitigate the JDK19 warnings. |
|
@lrytz this caused https://scala-ci.typesafe.com/job/scala-2.13.x-jdk11-integrate-community-build/4184/artifact/logs/kafka-build.log — the source file is https://github.com/ennru/kafka/blob/eda4a2904a863b04f6ea511a4cf1e2bd4806ad35/core/src/test/scala/unit/kafka/server/ReplicaManagerTest.scala — is it a regression, or does it seem expected to you? |
Seems unexpected. The |
Simple repro (or actually it looks slightly different) (probably won't be able to pursue it at the moment)
compiles previously but
Maybe Lukas knows how to do the alias probe less forcefully. Almost said alien probe. |
Thank you for the minimization @som-snytt!
Hmm, didn't find a way so far... But it's probably a regression we can live with? This fails to compile before my change (recursive method f needs result type): trait T
abstract class C(val r: T)
class D {
val r = new C(null) {
def f = D.this.r
}
} |
Regressions are pretty easy to live with. They don't snore and always clean the sink when they are done in the morning. I argued for erroring by default, so I would welcome more regressions. [Autocomplete suggests "terrorizing".] If there is a place for |
1. Definitions and declarations that are local, inherited, or made | ||
available by a package clause and also defined in the same compilation unit | ||
as the reference to them, have the highest precedence. | ||
1. Definitions and declarations in lexical scope that are not [top-level](09-top-level-definitions.html) |
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.
@som-snytt I assume top-level
is excluded here because that's what's implemented in Scala 3 (scala/scala3#8622). In Scala 3, the following compiles and new X
resolves to C.this.X
package p {
trait T { class X }
class C extends T {
def test = new X
}
class X
}
Changing package p
to object p
makes new X
ambiguous. Do you see a good reason for that? cc @odersky
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.
If T
has a member def name: String = "John Doe"
, then just where it can be inherited, introducing a different definition of name
might be in conflict.
Packagings are not classes and don't suffer from extension, so I should be able to write top-level def name = "Gloria Mundi"
without concern about confusing anyone.
But this is ambiguous:
trait T {
class X
def name: String = "John Doe"
}
class C extends T {
def test = new X
def result = name
}
class X
//def name = "Gloria Mundi"
val name: T = new T {
def result = name
}
I expected it to prefer the inherited name
and not the enclosing definition which is a top-level def.
15 | def result = name
| ^^^^
| Reference to name is ambiguous,
| it is both defined in package object t10220$package
| and inherited subsequently in anonymous class Object with p.T {...}
I don't think they actually got rid of package objects...
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.
So you expect an inherited symbol takes precedence over an outer definition in the same source file, if that outer definition is package-owned? Can you say why? I don't yet see what difference it makes if the outer symbol is top-level or not.
I should be able to write top-level
def name = "Gloria Mundi"
without concern about confusing anyone
What is the difference in that regard between package p { def name = "..." }
and object p { def name = "..."}
?
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.
For the record, I'm on this topic because I'm testing #10297, and I see warnings that didn't show up in 2.13. These new warnings seem helpful to me.
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 guess the proof is in the pudding, or in golf it's the putting.
I have only a rationalization: in package p
, I know nothing is inherited in this scope (because we do not package object p extends T
). Once someone defines a class, the folks working in that mine must manage that namespace, where stuff is inherited.
I agree there is regularity in seeing object p
as just a way to "associate" some definitions, and I think they speak of "package membership", but if the regularity were borne out, then we would love package object p extends T
. You would be asking, Why is that different from object p extends T
?
But all that is just a rationalization.
What are some examples of pernicious shadowing of top-level defs? I can't wait to see.
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.
An example was:
package p
class Code extends Parent {
def impl = s"The $CommonName"
}
case class CommonName(some: Thing)
A second file:
package p
trait Parent {
val CommonName = "something"
}
It's maybe clear here that we're not intending to refer to the synthetic case class companion, but this could still be a head scratcher for someone looking at the first file. One could for example do a search in the editor for CommonName
and land on the case class.
In this case I moved CommonName
to a separate file instead of changing the reference.
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.
Aha, that does seem lintable, at least with CommonName
in the same file. Conversely, it loses precedence when moved to a different file, but the reference in Parent
does not.
I just realized this feature is not released until 2.13.11. Let me take a moment to clear my fever-addled brain.
I notice toString
is not exempt.
Also I was about to wonder if the use case is really anon classes, esp SAM types. The user expectation is more obviously, "I am focused on implementing that one method, and probably unaware of inheriting anything."
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.
CommonName
in the same file
Yes, I mean to warn only if the outer definition is in the same compilation unit.
I notice
toString
is not exempt.
if the use case is really anon classes
Those are very good points. I certainly saw it pop up a lot with anonymous classes, and one would expect toString
to resolve in the current object. Hmm. Since this is a spec change and not an impl detail (a lint rule), we need to have general and hopefully simple rules. An exception for toString
wouldn't work...
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 we can wait to see if toString
really is problematic. So far (Scala 3, my tests of the new Scala 2 warning) it didn't seem so.
the "under |
-Xsource:3
, inherited members no longer take precedence over outer definitions
@lrytz actually maybe I'm confused and maybe the edit to the issue title I just did is wrong? if so, can you change it back? and what is the appropriate text to include in the 2.13.11 release notes to cover both this PR and #10339? can you suggest an appropriate edit over at scala/scala-dev#843 ? |
-Xsource:3
, inherited members no longer take precedence over outer definitions-Xsource:3
, warn that inherited members no longer take precedence over outer definitions in Scala 3
@SethTisue I took a shot and trivially proposed using the title, which covers both PRs. |
Continuation of @som-snytt's work at #10177.
Backport from Scala 3: an inherited name no longer takes precedence over a definition in an outer scope. The following reference
m
is ambiguous:It's an error under
-Xsource:3
, a warning otherwise.Fixes scala/bug#11921.
Don't issue an ambiguity error if
=:=
)=:=
)Example:
Type example: