Skip to content

Commit 5b46ac2

Browse files
committed
Fix variance checking in default getters
This is needed to compile geny in the community build which does: def count(f: A => Boolean = ((_: Any) => true)) This happened to work before the previous commit because the inferred type of the getter was `Any => Boolean`. Crucially, we can only disable variance checking for default getters whose result type matches the parameter type of the method, see default-getter-variance.scala for a detailed justification of why this is safe. This fixes lets us get rid of an unjustified usage of `@uncheckedVariance` when desugaring case classes.
1 parent 6fde5c1 commit 5b46ac2

File tree

3 files changed

+50
-10
lines changed

3 files changed

+50
-10
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -602,13 +602,8 @@ object desugar {
602602
// def _1: T1 = this.p1
603603
// ...
604604
// def _N: TN = this.pN (unless already given as valdef or parameterless defdef)
605-
// def copy(p1: T1 = p1: @uncheckedVariance, ...,
606-
// pN: TN = pN: @uncheckedVariance)(moreParams) =
605+
// def copy(p1: T1 = p1..., pN: TN = pN)(moreParams) =
607606
// new C[...](p1, ..., pN)(moreParams)
608-
//
609-
// Note: copy default parameters need @uncheckedVariance; see
610-
// neg/t1843-variances.scala for a test case. The test would give
611-
// two errors without @uncheckedVariance, one of them spurious.
612607
val (caseClassMeths, enumScaffolding) = {
613608
def syntheticProperty(name: TermName, tpt: Tree, rhs: Tree) =
614609
DefDef(name, Nil, tpt, rhs).withMods(synthetic)
@@ -638,10 +633,8 @@ object desugar {
638633
}
639634
if (mods.is(Abstract) || hasRepeatedParam) Nil // cannot have default arguments for repeated parameters, hence copy method is not issued
640635
else {
641-
def copyDefault(vparam: ValDef) =
642-
makeAnnotated("scala.annotation.unchecked.uncheckedVariance", refOfDef(vparam))
643636
val copyFirstParams = derivedVparamss.head.map(vparam =>
644-
cpy.ValDef(vparam)(rhs = copyDefault(vparam)))
637+
cpy.ValDef(vparam)(rhs = refOfDef(vparam)))
645638
val copyRestParamss = derivedVparamss.tail.nestedMap(vparam =>
646639
cpy.ValDef(vparam)(rhs = EmptyTree))
647640
DefDef(

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1442,7 +1442,9 @@ class Namer { typer: Typer =>
14421442
if (defaultTp eq pt) && (tp frozen_<:< defaultTp) then
14431443
// When possible, widen to the default getter parameter type to permit a
14441444
// larger choice of overrides (see `default-getter.scala`).
1445-
defaultTp
1445+
// For justification on the use of `@uncheckedVariance`, see
1446+
// `default-getter-variance.scala`.
1447+
AnnotatedType(defaultTp, Annotation(defn.UncheckedVarianceAnnot))
14461448
else tp.widenTermRefExpr.simplified match
14471449
case ctp: ConstantType if isInlineVal => ctp
14481450
case tp =>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
class Foo[+A] {
2+
def count(f: A => Boolean = _ => true): Unit = {}
3+
// The preceding line is valid, even though the generated default getter
4+
// has type `A => Boolean` which wouldn't normally pass variance checks
5+
// because it's equivalent to the following overloads which are valid:
6+
def count2(f: A => Boolean): Unit = {}
7+
def count2(): Unit = count(_ => true)
8+
}
9+
10+
class Bar1[+A] extends Foo[A] {
11+
override def count(f: A => Boolean): Unit = {}
12+
// This reasoning extends to overrides:
13+
override def count2(f: A => Boolean): Unit = {}
14+
}
15+
16+
class Bar2[+A] extends Foo[A] {
17+
override def count(f: A => Boolean = _ => true): Unit = {}
18+
// ... including overrides which also override the default getter:
19+
override def count2(f: A => Boolean): Unit = {}
20+
override def count2(): Unit = count(_ => true)
21+
}
22+
23+
// This can be contrasted with the need for variance checks in
24+
// `protected[this] methods (cf tests/neg/t7093.scala),
25+
// default getters do not have the same problem since they cannot
26+
// appear in arbitrary contexts.
27+
28+
29+
// Crucially, this argument does not apply to situations in which the default
30+
// getter result type is not a subtype of the parameter type, for example (from
31+
// tests/neg/variance.scala):
32+
//
33+
// class Foo[+A: ClassTag](x: A) {
34+
// private[this] val elems: Array[A] = Array(x)
35+
// def f[B](x: Array[B] = elems): Array[B] = x
36+
// }
37+
//
38+
// If we tried to rewrite this with an overload, it would fail
39+
// compilation:
40+
//
41+
// def f[B](): Array[B] = f(elems) // error: Found: Array[A], Expected: Array[B]
42+
//
43+
// So we only disable variance checking for default getters whose
44+
// result type is the method parameter type, this is checked by
45+
// `tests/neg/variance.scala`

0 commit comments

Comments
 (0)