-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4373: reject wildcard types in syntactically invalid positions #4422
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
Conversation
@@ -2279,7 +2279,8 @@ object Parsers { | |||
/** ConstrApp ::= SimpleType {ParArgumentExprs} | |||
*/ | |||
val constrApp = () => { | |||
val t = checkWildcard(annotType()) | |||
// Using Ident(nme.ERROR) to avoid causing cascade errors on non-user-written code | |||
val t = checkWildcard(annotType(), fallbackTree = Ident(nme.ERROR)) |
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.
In extends clauses you could use AnyRef
instead of Any
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.
That didn't work, because I end up with error messages like this, even though https://github.com/scala/scala/blob/2.13.x/src/library-aux/scala/AnyRef.scala#L15
talks about a trait AnyRef
:
scala> class A extends AnyRef with AnyRef
1 |class A extends AnyRef with AnyRef
| ^^^^^^
| class Object is not a trait
trait Base | ||
trait TypeConstr[X] | ||
|
||
class X1[A >: _ | X1[_]] // error |
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.
Maybe add some examples involving annotations too? @_
, _ @x
, ...
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.
Done!
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.
Otherwise LGTM
@@ -1114,8 +1117,8 @@ object desugar { | |||
Apply(Select(Apply(Ident(nme.StringContext), strs), id), elems) | |||
case InfixOp(l, op, r) => | |||
if (ctx.mode is Mode.Type) | |||
if (!op.isBackquoted && op.name == tpnme.raw.AMP) AndTypeTree(l, r) // l & r | |||
else if (!op.isBackquoted && op.name == tpnme.raw.BAR) OrTypeTree(l, r) // l | r | |||
if (!op.isBackquoted && op.name == tpnme.raw.AMP) AndTypeTree(checkWildcard(l), checkWildcard(r)) // l & r |
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 wonder if we shouldn't do this in Parser directly. This desugaring is kind of weird anyway, since it's not really about sugar but about how to parse something: we could just emit AndTypeTree or OrTypeTree as appropriate directly in Parser
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.
The reason to do it this way is that op
is an identifier with a position. Converted to And/OrTypeTree, that information is lost.
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.
👍, trying this out.
It took me a while to give up searching OrTypeTree
in the Parser
, and honestly I was very confused that wasn't already the case.
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.
The reason to do it this way is that op is an identifier with a position. Converted to And/OrTypeTree, that information is lost.
But is that information useful for 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.
FWIW, changing this passes CI, see #4424.
* If it is, returns the [[Position]] where the wildcard occurs. | ||
*/ | ||
@tailrec | ||
final def findWildcardType(t: Tree): Option[Position] = t match { |
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.
You could avoid using an Option
here by returning NoPosition
. You can then call exists
on Position
.
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.
Makes sense, but is that a good idea even if the existing code used Option[Position]
?
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.
The existing code is not always right :)
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 it was a defensive move to accommodate the fact that the returned position could be NoPosition. So, yes, I'd leave it as is.
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.
Actually, self-answer: probably yes, that's the point of NoPosition
, plus it avoids allocations.
EDIT: had missed Martin's answer.
I think it would be good to go through #4389 and try all reported issues in Bugs.txt that contain a |
765369d
to
24d6c9c
Compare
@@ -1035,6 +1035,19 @@ object Parsers { | |||
else | |||
rejectWildcard(t, fallbackTree) | |||
|
|||
def checkAndOrArgument(t: Tree): Tree = | |||
findWildcardType(t, true) match { |
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.
At this point, findWildcardType
is a misnomer.
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.
Fixed!
FWIW, there's a bunch of further errors in #4389 which I'm making progress on — it seems some testcases still fail even when the minimizations were fixed. |
Ready for re-review, I've addressed all open action items. I've looked through // ==> 09d8cdb2f7be2b81c318d338626268b7ec28ab12.scala <==
trait x0[x0[_]] {
(??? :x0[_]):x0[Int]
} |
Essentially reviewed in scala#4424.
The new API is required for all use cases in this PR.
Fix scala#4373. The testcase is from examples in scala#4373, variations and tests for types with wildcards.
Without this commit, the testcase produces additional spurious errors: ```scala -- [E104] Syntax Error: tests/neg/i4373.scala:10:0 ----------------------------- 10 |class A2 extends _ with _ // error // error |^ |class Any is not a trait longer explanation available when compiling with `-explain` -- [E104] Syntax Error: tests/neg/i4373.scala:11:21 ---------------------------- 11 |class A3 extends Base with _ // error | ^ | class Any is not a trait longer explanation available when compiling with `-explain` -- Error: tests/neg/i4373.scala:12:24 ------------------------------------------ 12 |class A4 extends _ with Base // error | ^^^^ |illegal trait inheritance: superclass Any does not derive from trait Base's superclass Object ``` These errors make absolutely no sense for a user, since they refer to code the user didn't write, but that was generated by the compiler's error correction mechanism. I haven't checked if we could use `Ident(nme.ERROR)` everywhere. I have also tried using `EmptyTree` and `EmptyTree.withPos(wildcardPos)` and `EmptyTree.clone.withPos(wildcardPos)` as fallback trees, but all of them failed in some way.
This is mostly a refactoring, though `findWildcardType(t, alsoNonValue = true)` is new behavior that belongs later. `rejectWildcard` had leftover tortous abstration, which is removed here.
Those types lead to assertion failures later while building OrTypeTree. Reject them, also for AndTypeTree.
The testcase is from examples in #4373, variations and tests for types with wildcards. I'm unsure for a couple cases.
The strategy I use comes from generalizing existing code.
A separate commit avoids cascade errors. That was the trickiest part of this PR, but pretty instructive. I now sometimes use
Ident(nme.ERROR)
instead ofscalaAny
to replace illegal wildcards inextends
clauses.Action items:
checkWildcard
private to Parsers again_
somewhere.with
too (noticed from A variety of bugs (42 or so). #4389)