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

Report fewer spurious cyclic errors #10626

Merged
merged 2 commits into from
Dec 13, 2023
Merged

Report fewer spurious cyclic errors #10626

merged 2 commits into from
Dec 13, 2023

Conversation

lrytz
Copy link
Member

@lrytz lrytz commented Dec 8, 2023

Fixes scala/bug#12814, fixes scala/bug#12622, fixes scala/bug#10669, fixes scala/bug#9568, fixes scala/bug#8252.

Co-authored by @retronym.

When checking type params / abstract types for cyclic subtyping, don't use symbol.lock to detect cycles.

The same symbol may occur multiple times and expand to a different type, for example a.Id[a.Id[Int]].

Seen types need to be compared with == (not =:=), otherwise a.Id[Int] is matches a.Id[a.Id[Int]].

I tried using symbol.lock in a first step and only adding types to the stack from the second iteration, but some of the new test cases then still fail to compile because the same symbol lock is also used for type completers (detecting cyclic references).

An issue was that diverging types were no longer reported as cyclic, as in neg/t510.scala. I added a recursion depth limit for this case.

abstract class C { type T }
abstract class D[S <: C](val c: S) extends C {
  type T <: c.T
}
abstract class E(e: E) extends D[E](e)
object Test {
  def f(e: E): Unit = {
    def g(t: e.T): Unit = ()
    ()
  }
}

e.T keeps expanding to e.c.T to e.c.c.T and so on.

Note that removing object Test from the example and compiling only the definitions runs into a stack overflow in a later phase, so the compiler is vulnerable to diverging types in other places.

For the record, in Scala 3 catches stack overflows and reports "Recursion limit exceeded" errors.

@scala-jenkins scala-jenkins added this to the 2.13.14 milestone Dec 8, 2023
@lrytz
Copy link
Member Author

lrytz commented Dec 11, 2023

Dotty does not count / limit the recursion depth, the nicer error message is crafted when catching a StackOverflowError. For example, see uses of handleRecursive in https://github.com/lampepfl/dotty/blob/main/tests/pos-with-compiler-cc/dotc/core/Types.scala. I think it's fine to let the stack overflow instead of trying to guard against that, diverging types shouldn't happen often.

In this light, this PR might be the right tradeoff. Some code with diverging types that previously triggered to a cyclic aliasing error now leads to a stack overflow. I'll see if we can catch those and turn them into error reports like Scala 3. In exchange we get rid of spurious cyclic aliasing errors that prevents valid code from compiling.

@retronym
Copy link
Member

Catching SOE is a bit risky because the exception handler can itself trigger another SOE before when it is doing its logging/reporting/throwing of a "better" exception. But perhaps for a compiler its okay.

@@ -425,19 +428,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
}
}

def checkNonCyclic(pos: Position, tp: Type, lockedSym: Symbol): Boolean = try {
if (!lockedSym.lock(CyclicReferenceError(pos, tp, lockedSym))) false
Copy link
Member Author

Choose a reason for hiding this comment

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

lock always succeeded. This overload of checkNonCyclic is invoked right after checkNotLocked.

@lrytz lrytz marked this pull request as ready for review December 12, 2023 13:44
@lrytz
Copy link
Member Author

lrytz commented Dec 12, 2023

Catching SOE is a bit risky

Added a depth limit for checkNonCyclic.

Updated the PR description, ready for review @som-snytt @retronym.

retronym and others added 2 commits December 12, 2023 15:52
When checking type params / abstract types for cyclic subtyping,
don't use `symbol.lock` to detect cycles.

The same symbol may occur multiple times and expand to a different
type, for example `a.Id[a.Id[Int]]`.

Seen types need to be compared with `==` (not `=:=`), otherwise
`a.Id[Int]` is matches `a.Id[a.Id[Int]]`.

I tried using `symbol.lock` in a first step and only adding types
to the stack from the second iteration, but some of the new test
cases then still fail to compile because the same symbol lock is
also used for type completers (detecting cyclic references).

An issue was that diverging types were no longer reported as cyclic,
as in neg/t510.scala. I added a recursion depth limit for this case.

```
abstract class C { type T }
abstract class D[S <: C](val c: S) extends C {
  type T <: c.T
}
abstract class E(e: E) extends D[E](e)
object Test {
  def f(e: E): Unit = {
    def g(t: e.T): Unit = ()
    ()
  }
}
```

`e.T` keeps expanding to `e.c.T` to `e.c.c.T` and so on.

Note that removing `object Test` from the example and compiling
only the definitions runs into a stack overflow in a later phase,
so the compiler is vulnerable to diverging types in other places.

For the record, in Scala 3 catches stack overflows and reports
"Recursion limit exceeded" errors.
Copy link
Member

@retronym retronym left a comment

Choose a reason for hiding this comment

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

Thank you for reviving this. Having a separate recursion counter seems like a good approach.

On the way to a failure, we have a O(N^2) performance with the tps.contains(tp). I think the limit of the list length is small enough to not worry about this.

It is easy to write code that that unwittngly teeters on the edge of the previous implementation's cliff of spuriousness. If that edge slightly shifts, as happened with Scala 2.13, it can be nigh on impossible to refactor the code in such a a way to retreat to safety.

@lrytz lrytz modified the milestones: 2.13.14, 2.13.13 Dec 13, 2023
@lrytz lrytz merged commit 43aa816 into scala:2.13.x Dec 13, 2023
4 checks passed
@SethTisue
Copy link
Member

community build kicked off — I'll report back if there's a problem, otherwise no news is good news

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants