Skip to content

Commit 9f1d4b6

Browse files
authored
Don't capture wildcards if in closure or by-name (#16732)
Fixes scala/bug#9419
2 parents 43d0ec4 + 29dc069 commit 9f1d4b6

File tree

5 files changed

+71
-1
lines changed

5 files changed

+71
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,10 @@ object Inferencing {
542542
case tp: AnnotatedType => tp.derivedAnnotatedType(captureWildcards(tp.parent), tp.annot)
543543
case _ => tp
544544
}
545+
546+
def hasCaptureConversionArg(tp: Type)(using Context): Boolean = tp match
547+
case tp: AppliedType => tp.args.exists(_.typeSymbol == defn.TypeBox_CAP)
548+
case _ => false
545549
}
546550

547551
trait Inferencing { this: Typer =>

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import Decorators._
1313
import Uniques._
1414
import inlines.Inlines
1515
import config.Printers.typr
16+
import Inferencing.*
17+
import ErrorReporting.*
1618
import util.SourceFile
1719
import TypeComparer.necessarySubType
1820

@@ -492,7 +494,23 @@ object ProtoTypes {
492494
val targ = cacheTypedArg(arg,
493495
typer.typedUnadapted(_, wideFormal, locked)(using argCtx),
494496
force = true)
495-
typer.adapt(targ, wideFormal, locked)
497+
val targ1 = typer.adapt(targ, wideFormal, locked)
498+
if wideFormal eq formal then targ1
499+
else checkNoWildcardCaptureForCBN(targ1)
500+
}
501+
502+
def checkNoWildcardCaptureForCBN(targ1: Tree)(using Context): Tree = {
503+
if hasCaptureConversionArg(targ1.tpe) then
504+
stripCast(targ1).tpe match
505+
case tp: AppliedType if tp.hasWildcardArg =>
506+
errorTree(targ1,
507+
em"""argument for by-name parameter is not a value
508+
|and contains wildcard arguments: $tp
509+
|
510+
|Assign it to a val and pass that instead.
511+
|""")
512+
case _ => targ1
513+
else targ1
496514
}
497515

498516
/** The type of the argument `arg`, or `NoType` if `arg` has not been typed before

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
15701570
else if ((tree.tpt `eq` untpd.ContextualEmptyTree) && mt.paramNames.isEmpty)
15711571
// Note implicitness of function in target type since there are no method parameters that indicate it.
15721572
TypeTree(defn.FunctionOf(Nil, mt.resType, isContextual = true, isErased = false))
1573+
else if hasCaptureConversionArg(mt.resType) then
1574+
errorTree(tree,
1575+
em"""cannot turn method type $mt into closure
1576+
|because it has capture conversion skolem types""")
15731577
else
15741578
EmptyTree
15751579
}

tests/neg/t9419.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
trait Magic[S]:
2+
def init: S
3+
def step(s: S): String
4+
5+
object IntMagic extends Magic[Int]:
6+
def init = 0
7+
def step(s: Int): String = (s - 1).toString
8+
9+
object StrMagic extends Magic[String]:
10+
def init = "hi"
11+
def step(s: String): String = s.reverse
12+
13+
object Main:
14+
def onestep[T](m: () => Magic[T]): String = m().step(m().init)
15+
def unostep[T](m: => Magic[T]): String = m.step(m.init)
16+
17+
val iter: Iterator[Magic[?]] = Iterator.tabulate(Int.MaxValue)(i => if i % 2 == 0 then IntMagic else StrMagic)
18+
19+
// was: class java.lang.String cannot be cast to class java.lang.Integer
20+
def main(args: Array[String]): Unit =
21+
onestep(() => iter.next()) // error
22+
unostep(iter.next()) // error
23+
val m = iter.next()
24+
unostep(m) // ok, because m is a value

tests/pos/t9419.jackson.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// from failure in the community project
2+
// jackson-module-scala
3+
// in ScalaAnnotationIntrospectorModule.scala:139:12
4+
5+
import scala.language.implicitConversions
6+
7+
trait EnrichedType[X]:
8+
def value: X
9+
10+
trait ClassW extends EnrichedType[Class[_]]:
11+
def extendsScalaClass = false
12+
13+
class Test:
14+
implicit def mkClassW(c: => Class[_]): ClassW = new ClassW:
15+
lazy val value = c
16+
17+
def test1(c1: Class[_]) = c1.extendsScalaClass // ok: c1 is a value
18+
def test2(c2: Class[_]) = mkClassW(c2).extendsScalaClass // ok: c2 is a value
19+
// c1 in test1 goes throw adapting to find the extension method and gains the wildcard capture cast then
20+
// c2 in test2 goes straight to typedArg, as it's already an arg, so it never gets wildcard captured

0 commit comments

Comments
 (0)