From 445bafffe92f76cde9bcab4f1b148e6fe91678a7 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 15 Jul 2025 17:44:03 +0200 Subject: [PATCH 1/2] Don't use FullyDefinedType when synthesizing ClassTags It's not necessary to instantiate all type variables, deeply, since we are only interested in the outermost shape. Also, that way we do not instantiate to Nothing, which is something difficult to recover from. [Cherry-picked e6bf7b82b6377943ec1098393a3e49a83eca7ecb] --- compiler/src/dotty/tools/dotc/typer/Inferencing.scala | 4 +++- compiler/src/dotty/tools/dotc/typer/Synthesizer.scala | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index ffef8e0638cd..2da75a0748b5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -377,7 +377,9 @@ object Inferencing { } /** The instantiation decision for given poly param computed from the constraint. */ - enum Decision { case Min; case Max; case ToMax; case Skip; case Fail } + enum Decision: + case Min, Max, ToMax, Skip, 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 6a8acc01f257..633b62070645 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -27,7 +27,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.stripTypeVar match + 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. @@ -35,9 +35,13 @@ 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) => + instArg( + if tvar.hasLowerBound then tvar.instantiate(fromBelow = true) + else if tvar.hasUpperBound then tvar.instantiate(fromBelow = false) + else NoType) case _ => - if isFullyDefined(tp, ForceDegree.all) then tp - else NoType // this happens in tests/neg/i15372.scala + tp val tag = formal.argInfos match case arg :: Nil => From a1813d117632f1850f3a180c10f7170ce3081206 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 15 Jul 2025 19:24:08 +0200 Subject: [PATCH 2/2] Constrain by deepening results in more implicit searches We used to look at deep expected type when the result of an implicit was ambiguous. This could add more constraints which could resolve the ambiguity. We now do the same also if the search type has uninstantiated type variables. In that case, consulting more context might further constrain type variables, which might in turn enlarge the implicit scope so that a solution can be found. Fixes #23526 [Cherry-picked 45f7ef6e5d1f9a3b45b77b933470de43760b0c72] --- .../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, 35 insertions(+), 13 deletions(-) create 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 633b62070645..43035c0fd3dc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -28,12 +28,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) => instArg( diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 65049c698738..c574b9ff7889 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3850,12 +3850,23 @@ 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: AmbiguousImplicits => + case failed: SearchFailureType if canProfitFromMoreConstraints => val pt1 = pt.deepenProtoTrans if (pt1 `ne` pt) && (pt1 ne sharpenedPt) && constrainResult(tree.symbol, wtp, pt1) - then implicitArgs(formals, argIndex, pt1) - else arg :: implicitArgs(formals1, argIndex + 1, pt1) + then return implicitArgs(formals, argIndex, pt1) + case _ => + + arg.tpe match + case failed: AmbiguousImplicits => + arg :: implicitArgs(formals1, argIndex + 1, pt) 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 3f318d0b0111..744023714a69 100644 --- a/tests/neg/i9568.check +++ b/tests/neg/i9568.check @@ -4,13 +4,10 @@ | 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 new file mode 100644 index 000000000000..e530608435c7 --- /dev/null +++ b/tests/pos/i23526.scala @@ -0,0 +1,14 @@ +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()