From e6bf7b82b6377943ec1098393a3e49a83eca7ecb 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. --- 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 520c8bf62ba4..6f5dc86193a7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -444,7 +444,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 761a24e10474..2e4120128405 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -29,7 +29,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. @@ -37,9 +37,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 45f7ef6e5d1f9a3b45b77b933470de43760b0c72 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 --- .../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 2e4120128405..cb3bba1cf0e4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -30,12 +30,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 819a66b52946..d33cf0883268 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4361,12 +4361,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 = findDefaultArgument(argIndex) .showing(i"default argument: for $formal, $tree, $argIndex = $result", typr) 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()