From 2f053b1ba758d2e1a1d9baabfdd0faf753c17bba Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Aug 2025 21:52:01 +0200 Subject: [PATCH] Handle assertion error in TyperState (#23665) #23609 triggers an assertion error in TyperState. The relevant explanation seems to be in ProtoTypes.scala: ```scala // To respect the pre-condition of `mergeConstraintWith` and keep // `protoTyperState` committable we must ensure that it does not // contain any type variable which don't already exist in the passed // TyperState. This is achieved by instantiating any such type // variable. NOTE: this does not suffice to discard type variables // in ancestors of `protoTyperState`, if this situation ever // comes up, an assertion in TyperState will trigger and this code // will need to be generalized. ``` We should go to the bottom of it and fix the assertion. But before that's done this PR offers a temporary hack to catch the exception when it is triggered from a new code path created by PR #23532. This should fix the regression reported in #23609. We should leave the issue open as a reminder that we still need a better fix. Also: handle crash due to missing span in a migration helper. [Cherry-picked 408298d7bf73921303c31c330deae8c618b589e7] --- compiler/src/dotty/tools/dotc/core/TyperState.scala | 10 +++++++--- .../src/dotty/tools/dotc/typer/Migrations.scala | 1 + compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 +++++++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index d4345916ba77..c0db3301b8b5 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -28,6 +28,8 @@ object TyperState { opaque type Snapshot = (Constraint, TypeVars, LevelMap) + class BadTyperStateAssertion(msg: String) extends AssertionError(msg) + extension (ts: TyperState) def snapshot()(using Context): Snapshot = (ts.constraint, ts.ownedVars, ts.upLevels) @@ -43,7 +45,7 @@ object TyperState { } class TyperState() { - import TyperState.LevelMap + import TyperState.{LevelMap, BadTyperStateAssertion} private var myId: Int = uninitialized def id: Int = myId @@ -269,8 +271,10 @@ class TyperState() { */ private def includeVar(tvar: TypeVar)(using Context): Unit = val oldState = tvar.owningState.nn.get - assert(oldState == null || !oldState.isCommittable, - i"$this attempted to take ownership of $tvar which is already owned by committable $oldState") + + if oldState != null && oldState.isCommittable then + throw BadTyperStateAssertion( + i"$this attempted to take ownership of $tvar which is already owned by committable $oldState") tvar.owningState = new WeakReference(this) ownedVars += tvar diff --git a/compiler/src/dotty/tools/dotc/typer/Migrations.scala b/compiler/src/dotty/tools/dotc/typer/Migrations.scala index d811987383c6..31bb29e0e6c5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Migrations.scala +++ b/compiler/src/dotty/tools/dotc/typer/Migrations.scala @@ -162,6 +162,7 @@ trait Migrations: private def checkParentheses(tree: Tree, pt: FunProto)(using Context): Boolean = val ptSpan = pt.args.head.span ptSpan.exists + && tree.span.exists && ctx.source.content .slice(tree.span.end, ptSpan.start) .exists(_ == '(') diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5349cade601e..57221d907011 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4380,11 +4380,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // But in this case we should search with additional arguments typed only if there // is no default argument. + // Try to constrain the result using `pt1`, but back out if a BadTyperStateAssertion + // is thrown. TODO Find out why the bad typer state arises and prevent it. The try-catch + // is a temporary hack to keep projects compiling that would fail otherwise due to + // searching more arguments to instantiate implicits (PR #23532). A failing project + // is described in issue #23609. + def tryConstrainResult(pt: Type): Boolean = + try constrainResult(tree.symbol, wtp, pt) + catch case ex: TyperState.BadTyperStateAssertion => false + arg.tpe match case failed: SearchFailureType if canProfitFromMoreConstraints => val pt1 = pt.deepenProtoTrans - if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && constrainResult(tree.symbol, wtp, pt1) - then return implicitArgs(formals, argIndex, pt1) + if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && tryConstrainResult(pt1) then + return implicitArgs(formals, argIndex, pt1) case _ => arg.tpe match