From d6a03cc2be5f585254b1f2d4536c3d52d613bbe2 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 25 Nov 2025 12:06:55 +0100 Subject: [PATCH 1/7] Revert "Merge pull request #555 from scala/backport-lts-3.3-23665" This reverts commit 023e01d572a4a02095a9cdd9367062b0cbe46199, reversing changes made to 8db88af6f98c5b78d6c9cc225a3d9be0d5e370f6. --- compiler/src/dotty/tools/dotc/core/TyperState.scala | 10 +++------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 ++----------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 55a7560d976f..046cfe3e935c 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -27,8 +27,6 @@ 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) @@ -44,7 +42,7 @@ object TyperState { } class TyperState() { - import TyperState.{LevelMap, BadTyperStateAssertion} + import TyperState.LevelMap private var myId: Int = _ def id: Int = myId @@ -270,10 +268,8 @@ class TyperState() { */ private def includeVar(tvar: TypeVar)(using Context): Unit = val oldState = tvar.owningState.nn.get - - if oldState != null && oldState.isCommittable then - throw BadTyperStateAssertion( - i"$this attempted to take ownership of $tvar which is already owned by committable $oldState") + assert(oldState == null || !oldState.isCommittable, + 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/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3012dfa94172..839d3329a068 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3902,20 +3902,11 @@ 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) && tryConstrainResult(pt1) then - return implicitArgs(formals, argIndex, pt1) + if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && constrainResult(tree.symbol, wtp, pt1) + then return implicitArgs(formals, argIndex, pt1) case _ => arg.tpe match From 716e77f7b2e90af5d0537b1d82a2b8aec5f8e709 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 25 Nov 2025 12:07:46 +0100 Subject: [PATCH 2/7] Revert "Use more context for implicit search only if no default argument" This reverts commit be5cbda7ef6c0971c1dcd3e610c0819b209f1369. --- .../src/dotty/tools/dotc/typer/Typer.scala | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 839d3329a068..f0f153ec9461 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3869,12 +3869,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def addImplicitArgs(using Context) = def hasDefaultParams = methPart(tree).symbol.hasDefaultParams - def findDefaultArgument(argIndex: Int): Tree = - def appPart(t: Tree): Tree = t match - case Block(_, expr) => appPart(expr) - case Inlined(_, _, expr) => appPart(expr) - case t => t - defaultArgument(appPart(tree), n = argIndex, testOnly = false) def implicitArgs(formals: List[Type], argIndex: Int, pt: Type): List[Tree] = formals match case Nil => Nil case formal :: formals1 => @@ -3890,17 +3884,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val arg = inferImplicitArg(formal, tree.span.endPos) - lazy val defaultArg = findDefaultArgument(argIndex) - .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) - def argHasDefault = hasDefaultParams && !defaultArg.isEmpty - def canProfitFromMoreConstraints = arg.tpe.isInstanceOf[AmbiguousImplicits] - // Ambiguity could be decided by more constraints - || !isFullyDefined(formal, ForceDegree.none) && !argHasDefault - // More context might constrain type variables which could make implicit scope larger. - // But in this case we should search with additional arguments typed only if there - // is no default argument. + // ambiguity could be decided by more constraints + || !isFullyDefined(formal, ForceDegree.none) + // more context might constrain type variables which could make implicit scope larger arg.tpe match case failed: SearchFailureType if canProfitFromMoreConstraints => @@ -3913,7 +3901,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case failed: AmbiguousImplicits => arg :: implicitArgs(formals1, argIndex + 1, pt) case failed: SearchFailureType => - if argHasDefault then + lazy val defaultArg = + def appPart(t: Tree): Tree = t match + case Block(stats, expr) => appPart(expr) + case Inlined(_, _, expr) => appPart(expr) + case _ => t + defaultArgument(appPart(tree), argIndex, testOnly = false) + .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) + if !hasDefaultParams || defaultArg.isEmpty then + // no need to search further, the adapt fails in any case + // the reason why we continue inferring arguments in case of an AmbiguousImplicits + // is that we need to know whether there are further errors. + // If there are none, we have to propagate the ambiguity to the caller. + arg :: formals1.map(dummyArg) + else // This is tricky. On the one hand, we need the defaultArg to // correctly type subsequent formal parameters in the same using // clause in case there are parameter dependencies. On the other hand, @@ -3924,12 +3925,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // `if propFail.exists` where we re-type the whole using clause with named // arguments for all implicits that were found. arg :: inferArgsAfter(defaultArg) - else - // no need to search further, the adapt fails in any case - // the reason why we continue inferring arguments in case of an AmbiguousImplicits - // is that we need to know whether there are further errors. - // If there are none, we have to propagate the ambiguity to the caller. - arg :: formals1.map(dummyArg) case _ => arg :: inferArgsAfter(arg) end implicitArgs From c893fbe35492dccef58ca02204771c219f8cd882 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 25 Nov 2025 12:08:01 +0100 Subject: [PATCH 3/7] Revert "Refine criterion when to use fullyDefinedType in ClassTag search" This reverts commit 1baf2b858ad2228edc747d83ecb6e45ad3cb7fed. --- compiler/src/dotty/tools/dotc/core/Types.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 41f7c89462c4..29424b3c20e4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4993,11 +4993,17 @@ object Types extends TypeUtils { */ private def currentEntry(using Context): Type = ctx.typerState.constraint.entry(origin) + /** For uninstantiated type variables: the lower bound */ + def lowerBound(using Context): Type = currentEntry.loBound + + /** For uninstantiated type variables: the upper bound */ + def upperBound(using Context): Type = currentEntry.hiBound + /** For uninstantiated type variables: Is the lower bound different from Nothing? */ - def hasLowerBound(using Context): Boolean = !currentEntry.loBound.isExactlyNothing + def hasLowerBound(using Context): Boolean = !lowerBound.isExactlyNothing /** For uninstantiated type variables: Is the upper bound different from Any? */ - def hasUpperBound(using Context): Boolean = !currentEntry.hiBound.isTopOfSomeKind + def hasUpperBound(using Context): Boolean = !upperBound.isTopOfSomeKind /** Unwrap to instance (if instantiated) or origin (if not), until result * is no longer a TypeVar From b63842f5025971d9fc5a189ebbbfe5aebf4f671a Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 25 Nov 2025 12:08:17 +0100 Subject: [PATCH 4/7] Revert "More careful ClassTag instantiation" This reverts commit 7c902e591ee49638949c6e7bdddeb7eaf5951229. --- compiler/src/dotty/tools/dotc/core/Types.scala | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 29424b3c20e4..41f7c89462c4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4993,17 +4993,11 @@ object Types extends TypeUtils { */ private def currentEntry(using Context): Type = ctx.typerState.constraint.entry(origin) - /** For uninstantiated type variables: the lower bound */ - def lowerBound(using Context): Type = currentEntry.loBound - - /** For uninstantiated type variables: the upper bound */ - def upperBound(using Context): Type = currentEntry.hiBound - /** For uninstantiated type variables: Is the lower bound different from Nothing? */ - def hasLowerBound(using Context): Boolean = !lowerBound.isExactlyNothing + def hasLowerBound(using Context): Boolean = !currentEntry.loBound.isExactlyNothing /** For uninstantiated type variables: Is the upper bound different from Any? */ - def hasUpperBound(using Context): Boolean = !upperBound.isTopOfSomeKind + def hasUpperBound(using Context): Boolean = !currentEntry.hiBound.isTopOfSomeKind /** Unwrap to instance (if instantiated) or origin (if not), until result * is no longer a TypeVar From 0dca4616325c3ca6a1e1def083bf2dc156bce7fb Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 25 Nov 2025 13:06:33 +0100 Subject: [PATCH 5/7] Revert "Constrain by deepening results in more implicit searches" This reverts commit a1813d117632f1850f3a180c10f7170ce3081206. --- .../dotty/tools/dotc/typer/Synthesizer.scala | 10 +++++----- compiler/src/dotty/tools/dotc/typer/Typer.scala | 17 +++-------------- tests/neg/i9568.check | 7 +++++-- tests/pos/i23526.scala | 14 -------------- 4 files changed, 13 insertions(+), 35 deletions(-) delete mode 100644 tests/pos/i23526.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 67f2e8b78b4a..bfa3b75a3699 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -29,12 +29,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val synthesizedClassTag: SpecialHandler = (formal, span) => def instArg(tp: Type): Type = tp.dealias match + // Special case to avoid instantiating `Int & S` to `Int & Nothing` in + // i16328.scala. The intersection comes from an earlier instantiation + // to an upper bound. + // The dual situation with unions is harder to trigger because lower + // bounds are usually widened during instantiation. case tp: AndOrType if tp.tp1 =:= tp.tp2 => - // Special case to avoid instantiating `Int & S` to `Int & Nothing` in - // i16328.scala. The intersection comes from an earlier instantiation - // to an upper bound. - // The dual situation with unions is harder to trigger because lower - // bounds are usually widened during instantiation. instArg(tp.tp1) case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => // If tvar has a lower or upper bound: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f0f153ec9461..65ff2427e4a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3883,23 +3883,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer implicitArgs(formals2, argIndex + 1, pt) val arg = inferImplicitArg(formal, tree.span.endPos) - - def canProfitFromMoreConstraints = - arg.tpe.isInstanceOf[AmbiguousImplicits] - // ambiguity could be decided by more constraints - || !isFullyDefined(formal, ForceDegree.none) - // more context might constrain type variables which could make implicit scope larger - arg.tpe match - case failed: SearchFailureType if canProfitFromMoreConstraints => + case failed: AmbiguousImplicits => val pt1 = pt.deepenProtoTrans if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && constrainResult(tree.symbol, wtp, pt1) - then return implicitArgs(formals, argIndex, pt1) - case _ => - - arg.tpe match - case failed: AmbiguousImplicits => - arg :: implicitArgs(formals1, argIndex + 1, pt) + then implicitArgs(formals, argIndex, pt1) + else arg :: implicitArgs(formals1, argIndex + 1, pt1) case failed: SearchFailureType => lazy val defaultArg = def appPart(t: Tree): Tree = t match diff --git a/tests/neg/i9568.check b/tests/neg/i9568.check index 744023714a69..3f318d0b0111 100644 --- a/tests/neg/i9568.check +++ b/tests/neg/i9568.check @@ -4,10 +4,13 @@ | No given instance of type => Monad[F] was found for parameter ev of method blaMonad in object Test. | I found: | - | Test.blaMonad[F², S] + | Test.blaMonad[F², S](Test.blaMonad[F³, S²]) | - | But method blaMonad in object Test does not match type => Monad[F] + | But method blaMonad in object Test does not match type => Monad[F²] | | where: F is a type variable with constraint <: [_] =>> Any | F² is a type variable with constraint <: [_] =>> Any + | F³ is a type variable with constraint <: [_] =>> Any + | S is a type variable + | S² is a type variable | . diff --git a/tests/pos/i23526.scala b/tests/pos/i23526.scala deleted file mode 100644 index e530608435c7..000000000000 --- a/tests/pos/i23526.scala +++ /dev/null @@ -1,14 +0,0 @@ -trait B[-A, +To] { - def addOne(e: A): this.type = this - def res(): To -} - -class Col[A] - -object Factory { - def newB[A](using reflect.ClassTag[A]) = new B[A, Col[A]] { def res(): Col[A] = new Col[A] } -} - -def test = - val a = Factory.newB.addOne(1).res() - val b = collection.immutable.ArraySeq.newBuilder.addOne(1).result() From 1e10525727b9eeeb1060d6a2c7f601b66417dfc7 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 25 Nov 2025 13:07:32 +0100 Subject: [PATCH 6/7] Revert "Don't use FullyDefinedType when synthesizing ClassTags" This reverts commit 445bafffe92f76cde9bcab4f1b148e6fe91678a7. --- .../dotty/tools/dotc/typer/Inferencing.scala | 4 +-- .../dotty/tools/dotc/typer/Synthesizer.scala | 32 ++----------------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 2da75a0748b5..ffef8e0638cd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -377,9 +377,7 @@ object Inferencing { } /** The instantiation decision for given poly param computed from the constraint. */ - enum Decision: - case Min, Max, ToMax, Skip, Fail - + enum Decision { case Min; case Max; case ToMax; case Skip; case Fail } private def instDecision(tvar: TypeVar, v: Int, minimizeSelected: Boolean, ifBottom: IfBottom)(using Context): Decision = import Decision.* val direction = instDirection(tvar.origin) diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index bfa3b75a3699..d22334c9481a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -28,7 +28,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): private type SpecialHandlers = List[(ClassSymbol, SpecialHandler)] val synthesizedClassTag: SpecialHandler = (formal, span) => - def instArg(tp: Type): Type = tp.dealias match + def instArg(tp: Type): Type = tp.stripTypeVar match // Special case to avoid instantiating `Int & S` to `Int & Nothing` in // i16328.scala. The intersection comes from an earlier instantiation // to an upper bound. @@ -36,35 +36,9 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): // bounds are usually widened during instantiation. case tp: AndOrType if tp.tp1 =:= tp.tp2 => instArg(tp.tp1) - case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => - // If tvar has a lower or upper bound: - // 1. If the bound is not another type variable, use this as approximation. - // 2. Otherwise, if the type can be forced to be fully defined, use that type - // as approximation. - // 3. Otherwise leave argument uninstantiated. - // The reason for (2) is that we observed complicated constraints in i23611.scala - // that get better types if a fully defined type is computed than if several type - // variables are approximated incrementally. This is a minimization of some ZIO code. - // So in order to keep backwards compatibility (where before we _only_ did 2) we - // add that special case. - def isGroundConstr(tp: Type): Boolean = tp.dealias match - case tvar: TypeVar if ctx.typerState.constraint.contains(tvar) => false - case pref: TypeParamRef if ctx.typerState.constraint.contains(pref) => false - case tp: AndOrType => isGroundConstr(tp.tp1) && isGroundConstr(tp.tp2) - case _ => true - instArg( - if tvar.hasLowerBound then - if isGroundConstr(fullLowerBound(tvar.origin)) then tvar.instantiate(fromBelow = true) - else if isFullyDefined(tp, ForceDegree.all) then tp - else NoType - else if tvar.hasUpperBound then - if isGroundConstr(fullUpperBound(tvar.origin)) then tvar.instantiate(fromBelow = false) - else if isFullyDefined(tp, ForceDegree.all) then tp - else NoType - else - NoType) case _ => - tp + if isFullyDefined(tp, ForceDegree.all) then tp + else NoType // this happens in tests/neg/i15372.scala val tag = formal.argInfos match case arg :: Nil => From b3e669786093998039ddac6300d9bdca825a9063 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 25 Nov 2025 13:34:27 +0100 Subject: [PATCH 7/7] chore: Add test case for issue 24192 --- tests/pos/i24192.scala | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/pos/i24192.scala diff --git a/tests/pos/i24192.scala b/tests/pos/i24192.scala new file mode 100644 index 000000000000..ff95c3af55b5 --- /dev/null +++ b/tests/pos/i24192.scala @@ -0,0 +1,25 @@ +// https://github.com/scala/scala-collection-contrib/blob/main/src/main/scala/scala/collection/decorators/package.scala +object decorators { + trait IsMap[A] + implicit def mapDecorator[C](coll: C)(implicit map: IsMap[C]): Map[C, map.type] = ??? +} +import decorators.mapDecorator // unused, required to reprouce + +trait Eq[T] +trait Applicative[F[_]] +given Applicative[Option] = ??? + +trait Traverse[F[_]]: + // context bound required to reproduce + def sequence[G[_]: Applicative, A](fga: F[G[A]]): G[F[A]] = ??? + +object Traverse: + def apply[F[_]]: Traverse[F] = ??? + +trait Segment[Element: Eq] + +case class MergeResult[Element: Eq] private (segments: Seq[Segment[Element]]): + def thisFailsToCompile(): Option[MergeResult[Element]] = + Traverse[Seq] + .sequence(Seq.empty[Option[Segment[Element]]]) + .map(MergeResult.apply) // error \ No newline at end of file