From 488165dfb235be397d1fd5af8b188f9a70ea74ab Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 5 Jan 2023 13:23:55 +0100 Subject: [PATCH] Fix #16405 - wildcards prematurely resolving to Nothing This was a problem because it could it get in the way of some metaprogramming techniques. The main issue was the fact that when typing functions, the type inference would first look at the types from the source method (resolving type wildcards to Nothing) and only after that, it could look at the target method. Now, in the case of wildcards we delay the resolution from the source method until later, after which it is resolved according to the target method. We also modify the target type resolution method a bit, as just applying the above procedure would fail some of the tests, eg. val y1: Function1[_, Nothing] = x => x would be typed as Function1[Any, Nothing], which was incorrect. --- .../dotty/tools/dotc/typer/Inferencing.scala | 9 +++--- .../src/dotty/tools/dotc/typer/Typer.scala | 24 +++++++++++--- tests/run/16405.scala | 31 +++++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 tests/run/16405.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 656a1266d5ab..9d5615ab21e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -22,13 +22,14 @@ object Inferencing { /** Is type fully defined, meaning the type does not contain wildcard types * or uninstantiated type variables. As a side effect, this will minimize - * any uninstantiated type variables, according to the given force degree, - * but only if the overall result of `isFullyDefined` is `true`. + * any uninstantiated type variables, according to the given force degree + * and the minimizeSelected setting, but only if the overall result of + * `isFullyDefined` is `true`. * Variables that are successfully minimized do not count as uninstantiated. */ - def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = { + def isFullyDefined(tp: Type, force: ForceDegree.Value, minimizeSelected: Boolean = false)(using Context): Boolean = { val nestedCtx = ctx.fresh.setNewTyperState() - val result = new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp) + val result = new IsFullyDefinedAccumulator(force, minimizeSelected)(using nestedCtx).process(tp) if (result) nestedCtx.typerState.commit() result } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e65eba4d5dd8..06ae0c3a282b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1234,7 +1234,22 @@ 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))) + // However for ? >: Nothing <: Any types (type wildcards), + // instead of approximating to Nothing or Any, we change them to bounded + // TypeVars to resolve later. + val init = pt1.argInfos.init.map { + case a @ TypeBounds(nt, at) if nt == defn.NothingType && at == defn.AnyType => + interpolateWildcards(WildcardType(a)) + case other => other.loBound + } + + val last = pt1.argInfos.last match { + case a @ TypeBounds(nt, at) if nt == defn.NothingType && at == defn.AnyType => + WildcardType(a) + case other => other.hiBound + } + + (init, typeTree(interpolateWildcards(last))) 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)))) @@ -1259,8 +1274,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer * The inference makes two attempts: * * 1. Compute the target type `T` and make it known that `S <: T`. - * If the expected type `S` can be fully defined under ForceDegree.flipBottom, - * pick this one (this might use the fact that S <: T for an upper approximation). + * If the expected type `S` can be fully defined under ForceDegree.flipBottom + * and with minimizedSelected option set as true, pick this one + * (this might use the fact that S <: T for an upper approximation). * 2. Otherwise, if the target type `T` can be fully defined under ForceDegree.flipBottom, * pick this one. * @@ -1280,7 +1296,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 isFullyDefined(formal, ForceDegree.flipBottom, minimizeSelected = true) then formal else if target.exists && isFullyDefined(target, ForceDegree.flipBottom) then target else NoType diff --git a/tests/run/16405.scala b/tests/run/16405.scala new file mode 100644 index 000000000000..fa0681683c42 --- /dev/null +++ b/tests/run/16405.scala @@ -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) + } +} \ No newline at end of file