Skip to content

Commit 87c97e7

Browse files
committed
Relax assertion in TyperState#commit If there's nothing to commit
If the added test case is run with `-Yforce-sbt-phases`, a LazyRef (created in the LazyRef case of TypeMap#mapOver) captures a Context. Later, that LazyRef is forced, but at that point the TyperState of the captured Context is already committed, so we're not allowed to commit anything in it anymore. But forcing the LazyRef ends up calling `isFullyDefined` which unconditionally creates a temporary TyperState and commit it, thus triggering the assertion. We fix this by relaxing the assertion to allow committing into a TyperState if there's nothing to commit. This should handle all LazyRefs since they never wrap uninstantiated type variables in practice. Fixes #14907.
1 parent a51380c commit 87c97e7

File tree

2 files changed

+22
-8
lines changed

2 files changed

+22
-8
lines changed

compiler/src/dotty/tools/dotc/core/TyperState.scala

+11-8
Original file line numberDiff line numberDiff line change
@@ -141,18 +141,21 @@ class TyperState() {
141141
Stats.record("typerState.commit")
142142
assert(isCommittable, s"$this is not committable")
143143
assert(!isCommitted, s"$this is already committed")
144-
reporter.flush()
145-
setCommittable(false)
146144
val targetState = ctx.typerState
147145

148-
// Committing into an already committed TyperState usually doesn't make
149-
// sense since it means the constraints we're committing won't be propagated
150-
// further, but it can happen if the targetState gets captured in a reported
151-
// Message, because forcing that Message might involve creating and
152-
// committing new TyperStates into the captured one after its been committed.
153-
assert(!targetState.isCommitted || targetState.reporter.hasErrors || targetState.reporter.hasWarnings,
146+
val nothingToCommit = (constraint eq targetState.constraint) && !reporter.hasUnreportedMessages
147+
assert(!targetState.isCommitted || nothingToCommit ||
148+
// Committing into an already committed TyperState usually doesn't make
149+
// sense since it means the constraints and messages we're committing won't be propagated
150+
// further, but it can happen if the targetState gets captured in a reported
151+
// Message, because forcing that Message might involve creating and
152+
// committing new TyperStates into the captured one after it's been committed.
153+
targetState.reporter.hasErrors || targetState.reporter.hasWarnings,
154154
s"Attempt to commit $this into already committed $targetState")
155155

156+
reporter.flush()
157+
setCommittable(false)
158+
156159
if constraint ne targetState.constraint then
157160
Stats.record("typerState.commit.new constraint")
158161
constr.println(i"committing $this to $targetState, fromConstr = $constraint, toConstr = ${targetState.constraint}")

tests/pos/i14907.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
object Module {
2+
class Fun[N <: Int]()
3+
type Fill[N <: Int] = N match {
4+
case 0 => EmptyTuple
5+
case 1 => Any *: Fill[0]
6+
}
7+
extension[N <: Int] (f: Fun[N])
8+
def apply: Fill[N] => Any = ???
9+
10+
Fun[1]()(???)
11+
}

0 commit comments

Comments
 (0)