Skip to content

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

Merged
merged 11 commits into from
May 7, 2018

Conversation

Blaisorblade
Copy link
Contributor

@Blaisorblade Blaisorblade commented Apr 30, 2018

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 of scalaAny to replace illegal wildcards in extends clauses.

Action items:

@@ -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))
Copy link
Member

@smarter smarter Apr 30, 2018

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

Copy link
Contributor Author

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
Copy link
Member

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, ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Copy link
Member

@smarter smarter left a 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
Copy link
Member

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

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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]?

Copy link
Member

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 :)

Copy link
Contributor

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.

Copy link
Contributor Author

@Blaisorblade Blaisorblade Apr 30, 2018

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.

@odersky
Copy link
Contributor

odersky commented May 1, 2018

I think it would be good to go through #4389 and try all reported issues in Bugs.txt that contain a _ somewhere. I fixed all the other ones there but left wildcard bugs alone, awaiting the fixes.

@Blaisorblade Blaisorblade self-assigned this May 1, 2018
@Blaisorblade Blaisorblade force-pushed the fix-4373 branch 3 times, most recently from 765369d to 24d6c9c Compare May 2, 2018 21:43
@@ -1035,6 +1035,19 @@ object Parsers {
else
rejectWildcard(t, fallbackTree)

def checkAndOrArgument(t: Tree): Tree =
findWildcardType(t, true) match {
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

@Blaisorblade
Copy link
Contributor Author

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.

@Blaisorblade
Copy link
Contributor Author

Ready for re-review, I've addressed all open action items.

I've looked through bugs.txt and I think I'm done here with it, the leftovers with wildcards are different issues, namely #4382 (again) and #4453. Non-minimized bugs are another matter.

// ==> 09d8cdb2f7be2b81c318d338626268b7ec28ab12.scala <==
trait x0[x0[_]] {
  (??? :x0[_]):x0[Int]
}

@Blaisorblade Blaisorblade removed their assignment May 3, 2018
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.
@allanrenucci allanrenucci merged commit 25dd1de into scala:master May 7, 2018
@allanrenucci allanrenucci deleted the fix-4373 branch May 7, 2018 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants