From 7c902e591ee49638949c6e7bdddeb7eaf5951229 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Aug 2025 19:44:59 +0200 Subject: [PATCH 1/4] More careful ClassTag instantiation We now use a blend of the new scheme and backwards compatible special case if type variables as ClassTag arguments are constrained by further type variables. Fixes #23611 [Cherry-picked b677f97a1fa26e0c27a4f434e850b4c8214d606c] --- 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 f0ca272cce36..65f7ff227f6d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4985,11 +4985,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 1baf2b858ad2228edc747d83ecb6e45ad3cb7fed Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Aug 2025 11:39:38 +0200 Subject: [PATCH 2/4] Refine criterion when to use fullyDefinedType in ClassTag search [Cherry-picked e4b8f3c7ad6cb727dafee0509db4fb7a44e3a9c2] --- 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 65f7ff227f6d..f0ca272cce36 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4985,17 +4985,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 be5cbda7ef6c0971c1dcd3e610c0819b209f1369 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Aug 2025 12:04:55 +0200 Subject: [PATCH 3/4] Use more context for implicit search only if no default argument After an "implicit not found", we type additional arguments to get more context which might give a larger implicit scope to search. With this commit we do that only if there is no default argument for the implicit. This might fix #23610 --- .../src/dotty/tools/dotc/typer/Typer.scala | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c574b9ff7889..6289f310ac1e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3836,6 +3836,12 @@ 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 => @@ -3851,11 +3857,17 @@ 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) - // more context might constrain type variables which could make implicit scope larger + // 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. arg.tpe match case failed: SearchFailureType if canProfitFromMoreConstraints => @@ -3868,20 +3880,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case failed: AmbiguousImplicits => arg :: implicitArgs(formals1, argIndex + 1, pt) case failed: SearchFailureType => - 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 + if argHasDefault then // 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, @@ -3892,6 +3891,12 @@ 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 282f39386a0cee3dbb4a76d4a4a59a5a33de8f4b Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Tue, 12 Aug 2025 20:30:52 +0200 Subject: [PATCH 4/4] Use more context for implicit search only if no default argument After an "implicit not found", we type additional arguments to get more context which might give a larger implicit scope to search. With this commit we do that only if there is no default argument for the implicit. This might fix #23610 [Cherry-picked 975c988ed23818c033a433fd91e5c12ec909f359][modified]