Skip to content

Commit a356581

Browse files
authored
Restrict captureWildcards to only be used if needed (#16799)
Rather than blindly using the newly wildcard-captured type, check that it's compatible with the proto/formal type. That way values that have wildcard types can be passed, uncast, to extension methods that don't require the capture. For instance in specs2, a value of type `Class[? <: Foo]` needn't become `Class[?1.CAP]` just so it can be applied to `def theValue[T](t: => T)`. For the zio-http case, despite knowing that JFuture is morally covariant, we don't have any way to knowing that - so we must be safe and error.
2 parents 3ef1e73 + 11854bb commit a356581

File tree

5 files changed

+46
-12
lines changed

5 files changed

+46
-12
lines changed

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -721,8 +721,8 @@ trait Applications extends Compatibility {
721721
|| argMatch == ArgMatch.CompatibleCAP
722722
&& {
723723
val argtpe1 = argtpe.widen
724-
val captured = captureWildcards(argtpe1)
725-
(captured ne argtpe1) && isCompatible(captured, formal.widenExpr)
724+
val captured = captureWildcardsCompat(argtpe1, formal.widenExpr)
725+
captured ne argtpe1
726726
}
727727

728728
/** The type of the given argument */
@@ -2422,4 +2422,9 @@ trait Applications extends Compatibility {
24222422
def isApplicableExtensionMethod(methodRef: TermRef, receiverType: Type)(using Context): Boolean =
24232423
methodRef.symbol.is(ExtensionMethod) && !receiverType.isBottomType &&
24242424
tryApplyingExtensionMethod(methodRef, nullLiteral.asInstance(receiverType)).nonEmpty
2425+
2426+
def captureWildcardsCompat(tp: Type, pt: Type)(using Context): Type =
2427+
val captured = captureWildcards(tp)
2428+
if (captured ne tp) && isCompatible(captured, pt) then captured
2429+
else tp
24252430
}

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

+7-9
Original file line numberDiff line numberDiff line change
@@ -501,15 +501,13 @@ object ProtoTypes {
501501

502502
def checkNoWildcardCaptureForCBN(targ1: Tree)(using Context): Tree = {
503503
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
504+
val tp = stripCast(targ1).tpe
505+
errorTree(targ1,
506+
em"""argument for by-name parameter is not a value
507+
|and contains wildcard arguments: $tp
508+
|
509+
|Assign it to a val and pass that instead.
510+
|""")
513511
else targ1
514512
}
515513

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -3962,7 +3962,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
39623962
return adaptConstant(tree, ConstantType(converted))
39633963
case _ =>
39643964

3965-
val captured = captureWildcards(wtp)
3965+
val captured = captureWildcardsCompat(wtp, pt)
39663966
if (captured `ne` wtp)
39673967
return readapt(tree.cast(captured))
39683968

tests/neg/t9419.zio-http.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Minimisation of how the fix for t9419 affected zio-http
2+
import java.util.concurrent.Future as JFuture
3+
4+
trait Test:
5+
def shutdownGracefully(): JFuture[_]
6+
7+
def executedWildcard(jFuture: => JFuture[_]): Unit
8+
def executedGeneric[A](jFuture: => JFuture[A]): Unit
9+
def executedWildGen[A](jFuture: => JFuture[? <: A]): Unit
10+
11+
// Even though JFuture is morally covariant, at least currently,
12+
// there's no definition-side variance, so it's treated as invariant.
13+
// So we have to be concerned that two different values of `JFuture[A]`
14+
// with different types, blowing up together. So error in `fails`.
15+
def works = executedWildcard(shutdownGracefully())
16+
def fails = executedGeneric(shutdownGracefully()) // error
17+
def fixed = executedGeneric(shutdownGracefully().asInstanceOf[JFuture[Any]]) // fix
18+
def best2 = executedWildGen(shutdownGracefully()) // even better, use use-site variance in the method

tests/pos/t9419.specs2.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Minimisation of how the fix for t9419 affected specs2
2+
class MustExpectable[T](tm: () => T):
3+
def must_==(other: => Any) = tm() == other
4+
5+
class Foo
6+
7+
object Main:
8+
implicit def theValue[T](t: => T): MustExpectable[T] = new MustExpectable(() => t)
9+
def main(args: Array[String]): Unit =
10+
val cls = classOf[Foo]
11+
val instance = new Foo()
12+
val works = cls must_== cls
13+
val fails = instance.getClass must_== cls

0 commit comments

Comments
 (0)