Skip to content

Commit a4bee46

Browse files
authored
Merge pull request #11704 from dotty-staging/default-getter-type-2
Improve our ability to override default parameters
2 parents 66e180d + 5b46ac2 commit a4bee46

File tree

5 files changed

+93
-37
lines changed

5 files changed

+93
-37
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: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,13 +1386,10 @@ class Namer { typer: Typer =>
13861386
}
13871387
end inherited
13881388

1389-
/** The proto-type to be used when inferring the result type from
1390-
* the right hand side. This is `WildcardType` except if the definition
1391-
* is a default getter. In that case, the proto-type is the type of
1392-
* the corresponding parameter where bound parameters are replaced by
1393-
* Wildcards.
1389+
/** If this is a default getter, the type of the corresponding method parameter,
1390+
* otherwise NoType.
13941391
*/
1395-
def rhsProto = sym.asTerm.name collect {
1392+
def defaultParamType = sym.name match
13961393
case DefaultGetterName(original, idx) =>
13971394
val meth: Denotation =
13981395
if (original.isConstructorName && (sym.owner.is(ModuleClass)))
@@ -1401,37 +1398,24 @@ class Namer { typer: Typer =>
14011398
ctx.defContext(sym).denotNamed(original)
14021399
def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match {
14031400
case params :: paramss1 =>
1404-
if (idx < params.length) wildApprox(params(idx))
1401+
if (idx < params.length) params(idx)
14051402
else paramProto(paramss1, idx - params.length)
14061403
case nil =>
1407-
WildcardType
1404+
NoType
14081405
}
14091406
val defaultAlts = meth.altsWith(_.hasDefaultParams)
14101407
if (defaultAlts.length == 1)
14111408
paramProto(defaultAlts.head.info.widen.paramInfoss, idx)
14121409
else
1413-
WildcardType
1414-
} getOrElse WildcardType
1410+
NoType
1411+
case _ =>
1412+
NoType
14151413

14161414
// println(s"final inherited for $sym: ${inherited.toString}") !!!
14171415
// println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}")
14181416
// TODO Scala 3.1: only check for inline vals (no final ones)
14191417
def isInlineVal = sym.isOneOf(FinalOrInline, butNot = Method | Mutable)
14201418

1421-
// Widen rhs type and eliminate `|' but keep ConstantTypes if
1422-
// definition is inline (i.e. final in Scala2) and keep module singleton types
1423-
// instead of widening to the underlying module class types.
1424-
// We also drop the @Repeated annotation here to avoid leaking it in method result types
1425-
// (see run/inferred-repeated-result).
1426-
def widenRhs(tp: Type): Type =
1427-
tp.widenTermRefExpr.simplified match
1428-
case ctp: ConstantType if isInlineVal => ctp
1429-
case tp => TypeComparer.widenInferred(tp, rhsProto)
1430-
1431-
// Replace aliases to Unit by Unit itself. If we leave the alias in
1432-
// it would be erased to BoxedUnit.
1433-
def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp
1434-
14351419
var rhsCtx = ctx.fresh.addMode(Mode.InferringReturnType)
14361420
if sym.isInlineMethod then rhsCtx = rhsCtx.addMode(Mode.InlineableBody)
14371421
if sym.is(ExtensionMethod) then rhsCtx = rhsCtx.addMode(Mode.InExtensionMethod)
@@ -1443,8 +1427,32 @@ class Namer { typer: Typer =>
14431427
rhsCtx.setFreshGADTBounds
14441428
rhsCtx.gadt.addToConstraint(typeParams)
14451429
}
1446-
def rhsType = PrepareInlineable.dropInlineIfError(sym,
1447-
typedAheadExpr(mdef.rhs, (inherited orElse rhsProto).widenExpr)(using rhsCtx)).tpe
1430+
1431+
def typedAheadRhs(pt: Type) =
1432+
PrepareInlineable.dropInlineIfError(sym,
1433+
typedAheadExpr(mdef.rhs, pt)(using rhsCtx))
1434+
1435+
def rhsType =
1436+
// For default getters, we use the corresponding parameter type as an
1437+
// expected type but we run it through `wildApprox` to allow default
1438+
// parameters like in `def mkList[T](value: T = 1): List[T]`.
1439+
val defaultTp = defaultParamType
1440+
val pt = inherited.orElse(wildApprox(defaultTp)).orElse(WildcardType).widenExpr
1441+
val tp = typedAheadRhs(pt).tpe
1442+
if (defaultTp eq pt) && (tp frozen_<:< defaultTp) then
1443+
// When possible, widen to the default getter parameter type to permit a
1444+
// larger choice of overrides (see `default-getter.scala`).
1445+
// For justification on the use of `@uncheckedVariance`, see
1446+
// `default-getter-variance.scala`.
1447+
AnnotatedType(defaultTp, Annotation(defn.UncheckedVarianceAnnot))
1448+
else tp.widenTermRefExpr.simplified match
1449+
case ctp: ConstantType if isInlineVal => ctp
1450+
case tp =>
1451+
TypeComparer.widenInferred(tp, pt)
1452+
1453+
// Replace aliases to Unit by Unit itself. If we leave the alias in
1454+
// it would be erased to BoxedUnit.
1455+
def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp
14481456

14491457
// Approximate a type `tp` with a type that does not contain skolem types.
14501458
val deskolemize = new ApproximatingTypeMap {
@@ -1455,7 +1463,7 @@ class Namer { typer: Typer =>
14551463
}
14561464
}
14571465

1458-
def cookedRhsType = deskolemize(dealiasIfUnit(widenRhs(rhsType)))
1466+
def cookedRhsType = deskolemize(dealiasIfUnit(rhsType))
14591467
def lhsType = fullyDefinedType(cookedRhsType, "right-hand side", mdef.span)
14601468
//if (sym.name.toString == "y") println(i"rhs = $rhsType, cooked = $cookedRhsType")
14611469
if (inherited.exists)
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`

tests/pos/default-getter.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class X
2+
class Y extends X
3+
4+
class A {
5+
def foo(param: X = new Y): X = param
6+
}
7+
8+
class B extends A {
9+
override def foo(param: X = new X): X = param
10+
}

tests/neg/i4659b.scala renamed to tests/pos/i4659c.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ case class SourcePosition(outer: SourcePosition = NoSourcePosition) {
22
assert(outer != null) // crash
33
}
44

5-
object NoSourcePosition extends SourcePosition() // error
5+
object NoSourcePosition extends SourcePosition()

0 commit comments

Comments
 (0)