diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 3a6169b75c6b..34f9a0139142 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -141,18 +141,21 @@ class TyperState() { Stats.record("typerState.commit") assert(isCommittable, s"$this is not committable") assert(!isCommitted, s"$this is already committed") - reporter.flush() - setCommittable(false) val targetState = ctx.typerState - // Committing into an already committed TyperState usually doesn't make - // sense since it means the constraints we're committing won't be propagated - // further, but it can happen if the targetState gets captured in a reported - // Message, because forcing that Message might involve creating and - // committing new TyperStates into the captured one after its been committed. - assert(!targetState.isCommitted || targetState.reporter.hasErrors || targetState.reporter.hasWarnings, + val nothingToCommit = (constraint eq targetState.constraint) && !reporter.hasUnreportedMessages + assert(!targetState.isCommitted || nothingToCommit || + // Committing into an already committed TyperState usually doesn't make + // sense since it means the constraints and messages we're committing won't be propagated + // further, but it can happen if the targetState gets captured in a reported + // Message, because forcing that Message might involve creating and + // committing new TyperStates into the captured one after it's been committed. + targetState.reporter.hasErrors || targetState.reporter.hasWarnings, s"Attempt to commit $this into already committed $targetState") + reporter.flush() + setCommittable(false) + if constraint ne targetState.constraint then Stats.record("typerState.commit.new constraint") constr.println(i"committing $this to $targetState, fromConstr = $constraint, toConstr = ${targetState.constraint}") diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 1d629110a400..a5d64ea11ec6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -244,15 +244,23 @@ abstract class Reporter extends interfaces.ReporterResult { */ def hasUnreportedErrors: Boolean = false + /** Does this reporter contain any message that have yet to be reported by its outer reporter ? + * This includes any warning stored in `unreportedWarnings` which need to be propagated to + * get an accurate count of unreported warnings in the outer reporter. + */ + def hasUnreportedMessages(using Context): Boolean = + pendingMessages.nonEmpty || unreportedWarnings.nonEmpty + /** If this reporter buffers messages, remove and return all buffered messages. */ def removeBufferedMessages(using Context): List[Diagnostic] = Nil - /** Issue all error messages in this reporter to next outer one, or make sure they are written. */ + /** Issue all messages in this reporter to next outer one, or make sure they are written. */ def flush()(using Context): Unit = val msgs = removeBufferedMessages if msgs.nonEmpty then msgs.foreach(ctx.reporter.report) for (key, count) <- unreportedWarnings do ctx.reporter.addUnreported(key, count) + unreportedWarnings = Map.empty /** If this reporter buffers messages, all buffered messages, otherwise Nil */ def pendingMessages(using Context): List[Diagnostic] = Nil diff --git a/tests/pos/i14907.scala b/tests/pos/i14907.scala new file mode 100644 index 000000000000..4a60362d0f73 --- /dev/null +++ b/tests/pos/i14907.scala @@ -0,0 +1,11 @@ +object Module { + class Fun[N <: Int]() + type Fill[N <: Int] = N match { + case 0 => EmptyTuple + case 1 => Any *: Fill[0] + } + extension[N <: Int] (f: Fun[N]) + def apply: Fill[N] => Any = ??? + + Fun[1]()(???) +}