Skip to content

Fix #16405 ctd - wildcards prematurely resolving to Nothing #16764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1197,8 +1197,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
)
end typedIf

/** Decompose function prototype into a list of parameter prototypes and a result prototype
* tree, using WildcardTypes where a type is not known.
/** Decompose function prototype into a list of parameter prototypes and a result
* prototype tree, using WildcardTypes where a type is not known.
* Note: parameter prototypes may be TypeBounds.
* For the result type we do this even if the expected type is not fully
* defined, which is a bit of a hack. But it's needed to make the following work
* (see typers.scala and printers/PlainPrinter.scala for examples).
Expand Down Expand Up @@ -1234,7 +1235,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
// if expected parameter type(s) are wildcards, approximate from below.
// if expected result type is a wildcard, approximate from above.
// this can type the greatest set of admissible closures.
(pt1.argTypesLo.init, typeTree(interpolateWildcards(pt1.argTypesHi.last)))

(pt1.argInfos.init, typeTree(interpolateWildcards(pt1.argInfos.last.hiBound)))
case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe))
if defn.isNonRefinedFunction(parent) && formals.length == defaultArity =>
(formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))))
Expand Down Expand Up @@ -1280,7 +1282,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
else NoType
case _ => NoType
if target.exists then formal <:< target
if isFullyDefined(formal, ForceDegree.flipBottom) then formal
if !formal.isExactlyNothing && isFullyDefined(formal, ForceDegree.flipBottom) then formal
else if target.exists && isFullyDefined(target, ForceDegree.flipBottom) then target
else NoType

Expand Down Expand Up @@ -1473,11 +1475,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
}

var desugared: untpd.Tree = EmptyTree
if protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head) then
val isGenericTuple =
protoFormals.head.derivesFrom(defn.TupleClass)
&& !defn.isTupleClass(protoFormals.head.typeSymbol)
desugared = desugar.makeTupledFunction(params, fnBody, isGenericTuple)
if protoFormals.length == 1 && params.length != 1 then
val firstFormal = protoFormals.head.loBound
if ptIsCorrectProduct(firstFormal) then
val isGenericTuple =
firstFormal.derivesFrom(defn.TupleClass)
&& !defn.isTupleClass(firstFormal.typeSymbol)
desugared = desugar.makeTupledFunction(params, fnBody, isGenericTuple)
else if protoFormals.length > 1 && params.length == 1 then
def isParamRef(scrut: untpd.Tree): Boolean = scrut match
case untpd.Annotated(scrut1, _) => isParamRef(scrut1)
Expand All @@ -1499,12 +1503,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
for ((param, i) <- params.zipWithIndex) yield
if (!param.tpt.isEmpty) param
else
val formal = protoFormal(i)
val formalBounds = protoFormal(i)
val formal = formalBounds.loBound
val isBottomFromWildcard = (formalBounds ne formal) && formal.isExactlyNothing
val knownFormal = isFullyDefined(formal, ForceDegree.failBottom)
// If the expected formal is a TypeBounds wildcard argument with Nothing as lower bound,
// try to prioritize inferring from target. See issue 16405 (tests/run/16405.scala)
val paramType =
if knownFormal then formal
else inferredFromTarget(param, formal, calleeType, paramIndex)
.orElse(errorType(AnonymousFunctionMissingParamType(param, tree, formal), param.srcPos))
if knownFormal && !isBottomFromWildcard then
formal
else
inferredFromTarget(param, formal, calleeType, paramIndex).orElse(
if knownFormal then formal
else errorType(AnonymousFunctionMissingParamType(param, tree, formal), param.srcPos)
)
val paramTpt = untpd.TypedSplice(
(if knownFormal then InferredTypeTree() else untpd.TypeTree())
.withType(paramType.translateFromRepeated(toArray = false))
Expand Down
31 changes: 31 additions & 0 deletions tests/run/16405.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import scala.compiletime.summonInline

case class TypeDesc[T](tpe: String)
object TypeDesc {
given nothing: TypeDesc[Nothing] = TypeDesc("Nothing")
given string: TypeDesc[String] = TypeDesc("String")
given int: TypeDesc[Int] = TypeDesc("Int")
}

def exampleFn(s: String, i: Int): Unit = ()

inline def argumentTypesOf[R](fun: (_, _) => R): (TypeDesc[?], TypeDesc[?]) = {
inline fun match {
case x: ((a, b) => R) =>
(scala.compiletime.summonInline[TypeDesc[a]], scala.compiletime.summonInline[TypeDesc[b]])
}
}
inline def argumentTypesOfNoWildCard[A, B, R](fun: (A, B) => R): (TypeDesc[?], TypeDesc[?]) = argumentTypesOf(fun)
inline def argumentTypesOfAllWildCard(fun: (?, ?) => ?): (TypeDesc[?], TypeDesc[?]) = argumentTypesOf(fun)

object Test {
def main(args: Array[String]): Unit = {
val expected = (TypeDesc.string, TypeDesc.int)
assert(argumentTypesOf(exampleFn) == expected)
assert(argumentTypesOf(exampleFn(_, _)) == expected)
assert(argumentTypesOfNoWildCard(exampleFn) == expected)
assert(argumentTypesOfNoWildCard(exampleFn(_, _)) == expected)
assert(argumentTypesOfAllWildCard(exampleFn) == expected)
assert(argumentTypesOfAllWildCard(exampleFn(_, _)) == expected)
}
}