From a51380c88302b9f5314ae8124de0641860d3b1d2 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Mon, 11 Apr 2022 23:24:51 +0200 Subject: [PATCH 1/2] Reporter: Add hasUnreportedMessages and clear unreported warnings in flush --- compiler/src/dotty/tools/dotc/reporting/Reporter.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 From 87c97e755466deda814f4ca0a5f560ed6623745a Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Mon, 11 Apr 2022 23:24:51 +0200 Subject: [PATCH 2/2] 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. --- .../dotty/tools/dotc/core/TyperState.scala | 19 +++++++++++-------- tests/pos/i14907.scala | 11 +++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 tests/pos/i14907.scala 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/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]()(???) +}