Skip to content

Commit 5724cae

Browse files
retronymadriaanm
authored andcommitted
SI-7694 @uncheckedBounds, an opt-out from type bounds checking
Synthetic defs introduced by transforms like named/default arguments, ANF (in scala-async) often introduce a type tree (the tpt of the temporary) that are based on the types of expressions. These types are scrutinized in RefChecks to check that type parameter bounds are satisfied. However, the type of the expression might be based on slack a LUB that fails to capture constraints between type parameters. This slackness is noted in `mergePrefixAndArgs`: // Martin: I removed this, because incomplete. Not sure there is a // good way to fix it. For the moment we just err on the conservative // side, i.e. with a bound that is too high. The synthesizer can now opt out of bounds by annotating the type as follows: val temp: (<expr.tpe> @uncheckedBounds) = expr This facility is now used in named/default arguments for the temporaries used for the reciever and arguments. The annotation is hidden under scala.reflect.internal, rather than in the more prominent scala.annotation.unchecked, to reflect the intention that it should only be used in tree transformers. The library component of this change and test case will be included in the next commit. Why split like this? It shows that the 2.10.3 compiler will work with 2.10.2 scala-reflect.jar.
1 parent e0d487d commit 5724cae

File tree

6 files changed

+74
-13
lines changed

6 files changed

+74
-13
lines changed

bincompat-backward.whitelist.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ filter {
247247
{
248248
matchName="scala.reflect.runtime.SymbolLoaders.isInvalidClassName"
249249
problemName=MissingMethodProblem
250+
},
251+
{
252+
matchName="scala.reflect.internal.Types.uncheckedBounds"
253+
problemName=MissingMethodProblem
250254
}
251255
]
252256
}

bincompat-forward.whitelist.conf

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,22 @@ filter {
531531
{
532532
matchName="scala.reflect.runtime.SymbolLoaders.isInvalidClassName"
533533
problemName=MissingMethodProblem
534+
},
535+
{
536+
matchName="scala.reflect.internal.SymbolTable.uncheckedBounds"
537+
problemName=MissingMethodProblem
538+
},
539+
{
540+
matchName="scala.reflect.internal.Types.uncheckedBounds"
541+
problemName=MissingMethodProblem
542+
},
543+
{
544+
matchName="scala.reflect.internal.Definitions#DefinitionsClass.UncheckedBoundsClass"
545+
problemName=MissingMethodProblem
546+
},
547+
{
548+
matchName="scala.reflect.internal.annotations.uncheckedBounds"
549+
problemName=MissingClassProblem
534550
}
535551
]
536552
}

src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ trait NamesDefaults { self: Analyzer =>
164164

165165
// never used for constructor calls, they always have a stable qualifier
166166
def blockWithQualifier(qual: Tree, selected: Name) = {
167-
val sym = blockTyper.context.owner.newValue(unit.freshTermName("qual$"), qual.pos) setInfo qual.tpe
167+
val sym = blockTyper.context.owner.newValue(unit.freshTermName("qual$"), qual.pos) setInfo uncheckedBounds(qual.tpe)
168168
blockTyper.context.scope enter sym
169169
val vd = atPos(sym.pos)(ValDef(sym, qual) setType NoType)
170170
// it stays in Vegas: SI-5720, SI-5727
@@ -289,9 +289,10 @@ trait NamesDefaults { self: Analyzer =>
289289
// We have to deconst or types inferred from literal arguments will be Constant(_), e.g. pos/z1730.scala.
290290
gen.stableTypeFor(arg).filter(_ <:< paramTpe).getOrElse(arg.tpe).deconst
291291
)
292-
val s = context.owner.newValue(unit.freshTermName("x$"), arg.pos) setInfo (
293-
if (byName) functionType(Nil, argTpe) else argTpe
294-
)
292+
val s = context.owner.newValue(unit.freshTermName("x$"), arg.pos) setInfo {
293+
val tp = if (byName) functionType(Nil, argTpe) else argTpe
294+
uncheckedBounds(tp)
295+
}
295296
Some((context.scope.enter(s), byName, repeated))
296297
})
297298
map2(symPs, args) {

src/compiler/scala/tools/nsc/typechecker/RefChecks.scala

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,17 +1513,35 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
15131513
false
15141514
}
15151515

1516-
private def checkTypeRef(tp: Type, tree: Tree) = tp match {
1516+
private def checkTypeRef(tp: Type, tree: Tree, skipBounds: Boolean) = tp match {
15171517
case TypeRef(pre, sym, args) =>
15181518
checkDeprecated(sym, tree.pos)
15191519
if(sym.isJavaDefined)
15201520
sym.typeParams foreach (_.cookJavaRawInfo())
1521-
if (!tp.isHigherKinded)
1521+
if (!tp.isHigherKinded && !skipBounds)
15221522
checkBounds(tree, pre, sym.owner, sym.typeParams, args)
15231523
case _ =>
15241524
}
15251525

1526-
private def checkAnnotations(tpes: List[Type], tree: Tree) = tpes foreach (tp => checkTypeRef(tp, tree))
1526+
private def checkTypeRefBounds(tp: Type, tree: Tree) = {
1527+
var skipBounds = false
1528+
tp match {
1529+
case AnnotatedType(ann :: Nil, underlying, selfSym) if ann.symbol == UncheckedBoundsClass =>
1530+
skipBounds = true
1531+
underlying
1532+
case TypeRef(pre, sym, args) =>
1533+
if (!tp.isHigherKinded && !skipBounds)
1534+
checkBounds(tree, pre, sym.owner, sym.typeParams, args)
1535+
tp
1536+
case _ =>
1537+
tp
1538+
}
1539+
}
1540+
1541+
private def checkAnnotations(tpes: List[Type], tree: Tree) = tpes foreach { tp =>
1542+
checkTypeRef(tp, tree, skipBounds = false)
1543+
checkTypeRefBounds(tp, tree)
1544+
}
15271545
private def doTypeTraversal(tree: Tree)(f: Type => Unit) = if (!inPattern) tree.tpe foreach f
15281546

15291547
private def applyRefchecksToAnnotations(tree: Tree): Unit = {
@@ -1551,8 +1569,9 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
15511569
}
15521570

15531571
doTypeTraversal(tree) {
1554-
case AnnotatedType(annots, _, _) => applyChecks(annots)
1555-
case _ =>
1572+
case tp @ AnnotatedType(annots, _, _) =>
1573+
applyChecks(annots)
1574+
case tp =>
15561575
}
15571576
case _ =>
15581577
}
@@ -1735,13 +1754,27 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
17351754
}
17361755

17371756
val existentialParams = new ListBuffer[Symbol]
1738-
doTypeTraversal(tree) { // check all bounds, except those that are existential type parameters
1739-
case ExistentialType(tparams, tpe) =>
1757+
var skipBounds = false
1758+
// check all bounds, except those that are existential type parameters
1759+
// or those within typed annotated with @uncheckedBounds
1760+
doTypeTraversal(tree) {
1761+
case tp @ ExistentialType(tparams, tpe) =>
17401762
existentialParams ++= tparams
1741-
case t: TypeRef =>
1742-
checkTypeRef(deriveTypeWithWildcards(existentialParams.toList)(t), tree)
1763+
case ann: AnnotatedType if ann.hasAnnotation(UncheckedBoundsClass) =>
1764+
// SI-7694 Allow code synthetizers to disable checking of bounds for TypeTrees based on inferred LUBs
1765+
// which might not conform to the constraints.
1766+
skipBounds = true
1767+
case tp: TypeRef =>
1768+
val tpWithWildcards = deriveTypeWithWildcards(existentialParams.toList)(tp)
1769+
checkTypeRef(tpWithWildcards, tree, skipBounds)
17431770
case _ =>
17441771
}
1772+
if (skipBounds) {
1773+
tree.tpe = tree.tpe.map {
1774+
_.filterAnnotations(_.symbol != UncheckedBoundsClass)
1775+
}
1776+
}
1777+
17451778
tree
17461779

17471780
case TypeApply(fn, args) =>

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,7 @@ trait Definitions extends api.StandardDefinitions {
983983
lazy val ThrowsClass = requiredClass[scala.throws[_]]
984984
lazy val TransientAttr = requiredClass[scala.transient]
985985
lazy val UncheckedClass = requiredClass[scala.unchecked]
986+
lazy val UncheckedBoundsClass = getClassIfDefined("scala.reflect.internal.annotations.uncheckedBounds")
986987
lazy val UnspecializedClass = requiredClass[scala.annotation.unspecialized]
987988
lazy val VolatileAttr = requiredClass[scala.volatile]
988989

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7314,6 +7314,12 @@ trait Types extends api.Types { self: SymbolTable =>
73147314
else (ps :+ SerializableClass.tpe).toList
73157315
)
73167316

7317+
/** Adds the @uncheckedBound annotation if the given `tp` has type arguments */
7318+
final def uncheckedBounds(tp: Type): Type = {
7319+
if (tp.typeArgs.isEmpty || UncheckedBoundsClass == NoSymbol) tp // second condition for backwards compatibilty with older scala-reflect.jar
7320+
else tp.withAnnotation(AnnotationInfo marker UncheckedBoundsClass.tpe)
7321+
}
7322+
73177323
/** Members of the given class, other than those inherited
73187324
* from Any or AnyRef.
73197325
*/

0 commit comments

Comments
 (0)