Skip to content

Commit 61a5af3

Browse files
committed
Fix scala#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 targettype 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.
1 parent b65b0f2 commit 61a5af3

File tree

3 files changed

+53
-6
lines changed

3 files changed

+53
-6
lines changed

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ object Inferencing {
2626
* but only if the overall result of `isFullyDefined` is `true`.
2727
* Variables that are successfully minimized do not count as uninstantiated.
2828
*/
29-
def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = {
29+
def isFullyDefined(tp: Type, force: ForceDegree.Value, minimizeSelected: Boolean = false)(using Context): Boolean = {
3030
val nestedCtx = ctx.fresh.setNewTyperState()
31-
val result = new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp)
31+
val result = new IsFullyDefinedAccumulator(force, minimizeSelected)(using nestedCtx).process(tp)
3232
if (result) nestedCtx.typerState.commit()
3333
result
3434
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
12341234
// if expected parameter type(s) are wildcards, approximate from below.
12351235
// if expected result type is a wildcard, approximate from above.
12361236
// this can type the greatest set of admissible closures.
1237-
(pt1.argTypesLo.init, typeTree(interpolateWildcards(pt1.argTypesHi.last)))
1237+
// However for ? >: Nothing <: Any types (type wildcards),
1238+
// instead of approximating to Nothing or Any, we change them to bounded
1239+
// TypeVars to resolve later.
1240+
val init = pt1.argInfos.init.map {
1241+
case a @ TypeBounds(nt, at) if nt == defn.NothingType && at == defn.AnyType =>
1242+
interpolateWildcards(WildcardType(a))
1243+
case other => other.loBound
1244+
}
1245+
1246+
val last = pt1.argInfos.last match {
1247+
case a @ TypeBounds(nt, at) if nt == defn.NothingType && at == defn.AnyType =>
1248+
WildcardType(a)
1249+
case other => other.hiBound
1250+
}
1251+
1252+
(init, typeTree(interpolateWildcards(last)))
12381253
case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe))
12391254
if defn.isNonRefinedFunction(parent) && formals.length == defaultArity =>
12401255
(formals, untpd.DependentTypeTree(syms => restpe.substParams(mt, syms.map(_.termRef))))
@@ -1259,8 +1274,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
12591274
* The inference makes two attempts:
12601275
*
12611276
* 1. Compute the target type `T` and make it known that `S <: T`.
1262-
* If the expected type `S` can be fully defined under ForceDegree.flipBottom,
1263-
* pick this one (this might use the fact that S <: T for an upper approximation).
1277+
* If the expected type `S` can be fully defined under ForceDegree.flipBottom
1278+
* and with minimizedSelected option set as true, pick this one
1279+
* (this might use the fact that S <: T for an upper approximation).
12641280
* 2. Otherwise, if the target type `T` can be fully defined under ForceDegree.flipBottom,
12651281
* pick this one.
12661282
*
@@ -1280,7 +1296,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
12801296
else NoType
12811297
case _ => NoType
12821298
if target.exists then formal <:< target
1283-
if isFullyDefined(formal, ForceDegree.flipBottom) then formal
1299+
if isFullyDefined(formal, ForceDegree.flipBottom, minimizeSelected = true) then formal
12841300
else if target.exists && isFullyDefined(target, ForceDegree.flipBottom) then target
12851301
else NoType
12861302

tests/run/16405.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import scala.compiletime.summonInline
2+
3+
case class TypeDesc[T](tpe: String)
4+
object TypeDesc {
5+
given nothing: TypeDesc[Nothing] = TypeDesc("Nothing")
6+
given string: TypeDesc[String] = TypeDesc("String")
7+
given int: TypeDesc[Int] = TypeDesc("Int")
8+
}
9+
10+
def exampleFn(s: String, i: Int): Unit = ()
11+
12+
inline def argumentTypesOf[R](fun: (_, _) => R): (TypeDesc[?], TypeDesc[?]) = {
13+
inline fun match {
14+
case x: ((a, b) => R) =>
15+
(scala.compiletime.summonInline[TypeDesc[a]], scala.compiletime.summonInline[TypeDesc[b]])
16+
}
17+
}
18+
inline def argumentTypesOfNoWildCard[A, B, R](fun: (A, B) => R): (TypeDesc[?], TypeDesc[?]) = argumentTypesOf(fun)
19+
inline def argumentTypesOfAllWildCard(fun: (?, ?) => ?): (TypeDesc[?], TypeDesc[?]) = argumentTypesOf(fun)
20+
21+
object Test {
22+
def main(args: Array[String]): Unit = {
23+
val expected = (TypeDesc.string, TypeDesc.int)
24+
assert(argumentTypesOf(exampleFn) == expected)
25+
assert(argumentTypesOf(exampleFn(_, _)) == expected)
26+
assert(argumentTypesOfNoWildCard(exampleFn) == expected)
27+
assert(argumentTypesOfNoWildCard(exampleFn(_, _)) == expected)
28+
assert(argumentTypesOfAllWildCard(exampleFn) == expected)
29+
assert(argumentTypesOfAllWildCard(exampleFn(_, _)) == expected)
30+
}
31+
}

0 commit comments

Comments
 (0)