From 61d0073d9917b0ae8b864c25ab21905af8462e36 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Aug 2025 19:44:59 +0200 Subject: [PATCH 1/3] 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 7fac8c818a1a..cd5e1c81f667 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5116,11 +5116,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 c31e1b760655ca2f4352d785f066e657a97cc0d2 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Aug 2025 11:39:38 +0200 Subject: [PATCH 2/3] 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 cd5e1c81f667..7fac8c818a1a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -5116,17 +5116,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 4dd0691be96304859ccc251a8a53c0d0c6d5f71d Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Aug 2025 12:04:55 +0200 Subject: [PATCH 3/3] 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] --- .../src/dotty/tools/dotc/typer/Typer.scala | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c45c00845120..e9e3e22342bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4362,11 +4362,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 => @@ -4379,15 +4385,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer 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) - 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, @@ -4398,6 +4396,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