Permalink
Browse files

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.
  • Loading branch information...
1 parent e0d487d commit 5724cae9efe168094f5f3d176f2606b9c43de6dc @retronym retronym committed with adriaanm Aug 10, 2013
@@ -247,6 +247,10 @@ filter {
{
matchName="scala.reflect.runtime.SymbolLoaders.isInvalidClassName"
problemName=MissingMethodProblem
+ },
+ {
+ matchName="scala.reflect.internal.Types.uncheckedBounds"
+ problemName=MissingMethodProblem
}
]
}
@@ -531,6 +531,22 @@ filter {
{
matchName="scala.reflect.runtime.SymbolLoaders.isInvalidClassName"
problemName=MissingMethodProblem
+ },
+ {
+ matchName="scala.reflect.internal.SymbolTable.uncheckedBounds"
+ problemName=MissingMethodProblem
+ },
+ {
+ matchName="scala.reflect.internal.Types.uncheckedBounds"
+ problemName=MissingMethodProblem
+ },
+ {
+ matchName="scala.reflect.internal.Definitions#DefinitionsClass.UncheckedBoundsClass"
+ problemName=MissingMethodProblem
+ },
+ {
+ matchName="scala.reflect.internal.annotations.uncheckedBounds"
+ problemName=MissingClassProblem
}
]
}
@@ -164,7 +164,7 @@ trait NamesDefaults { self: Analyzer =>
// never used for constructor calls, they always have a stable qualifier
def blockWithQualifier(qual: Tree, selected: Name) = {
- val sym = blockTyper.context.owner.newValue(unit.freshTermName("qual$"), qual.pos) setInfo qual.tpe
+ val sym = blockTyper.context.owner.newValue(unit.freshTermName("qual$"), qual.pos) setInfo uncheckedBounds(qual.tpe)
blockTyper.context.scope enter sym
val vd = atPos(sym.pos)(ValDef(sym, qual) setType NoType)
// it stays in Vegas: SI-5720, SI-5727
@@ -289,9 +289,10 @@ trait NamesDefaults { self: Analyzer =>
// We have to deconst or types inferred from literal arguments will be Constant(_), e.g. pos/z1730.scala.
gen.stableTypeFor(arg).filter(_ <:< paramTpe).getOrElse(arg.tpe).deconst
)
- val s = context.owner.newValue(unit.freshTermName("x$"), arg.pos) setInfo (
- if (byName) functionType(Nil, argTpe) else argTpe
- )
+ val s = context.owner.newValue(unit.freshTermName("x$"), arg.pos) setInfo {
+ val tp = if (byName) functionType(Nil, argTpe) else argTpe
+ uncheckedBounds(tp)
+ }
Some((context.scope.enter(s), byName, repeated))
})
map2(symPs, args) {
@@ -1513,17 +1513,35 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
false
}
- private def checkTypeRef(tp: Type, tree: Tree) = tp match {
+ private def checkTypeRef(tp: Type, tree: Tree, skipBounds: Boolean) = tp match {
case TypeRef(pre, sym, args) =>
checkDeprecated(sym, tree.pos)
if(sym.isJavaDefined)
sym.typeParams foreach (_.cookJavaRawInfo())
- if (!tp.isHigherKinded)
+ if (!tp.isHigherKinded && !skipBounds)
checkBounds(tree, pre, sym.owner, sym.typeParams, args)
case _ =>
}
- private def checkAnnotations(tpes: List[Type], tree: Tree) = tpes foreach (tp => checkTypeRef(tp, tree))
+ private def checkTypeRefBounds(tp: Type, tree: Tree) = {
+ var skipBounds = false
+ tp match {
+ case AnnotatedType(ann :: Nil, underlying, selfSym) if ann.symbol == UncheckedBoundsClass =>
+ skipBounds = true
+ underlying
+ case TypeRef(pre, sym, args) =>
+ if (!tp.isHigherKinded && !skipBounds)
+ checkBounds(tree, pre, sym.owner, sym.typeParams, args)
+ tp
+ case _ =>
+ tp
+ }
+ }
+
+ private def checkAnnotations(tpes: List[Type], tree: Tree) = tpes foreach { tp =>
+ checkTypeRef(tp, tree, skipBounds = false)
+ checkTypeRefBounds(tp, tree)
+ }
private def doTypeTraversal(tree: Tree)(f: Type => Unit) = if (!inPattern) tree.tpe foreach f
private def applyRefchecksToAnnotations(tree: Tree): Unit = {
@@ -1551,8 +1569,9 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
doTypeTraversal(tree) {
- case AnnotatedType(annots, _, _) => applyChecks(annots)
- case _ =>
+ case tp @ AnnotatedType(annots, _, _) =>
+ applyChecks(annots)
+ case tp =>
}
case _ =>
}
@@ -1735,13 +1754,27 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
}
val existentialParams = new ListBuffer[Symbol]
- doTypeTraversal(tree) { // check all bounds, except those that are existential type parameters
- case ExistentialType(tparams, tpe) =>
+ var skipBounds = false
+ // check all bounds, except those that are existential type parameters
+ // or those within typed annotated with @uncheckedBounds
+ doTypeTraversal(tree) {
+ case tp @ ExistentialType(tparams, tpe) =>
existentialParams ++= tparams
- case t: TypeRef =>
- checkTypeRef(deriveTypeWithWildcards(existentialParams.toList)(t), tree)
+ case ann: AnnotatedType if ann.hasAnnotation(UncheckedBoundsClass) =>
+ // SI-7694 Allow code synthetizers to disable checking of bounds for TypeTrees based on inferred LUBs
+ // which might not conform to the constraints.
+ skipBounds = true
+ case tp: TypeRef =>
+ val tpWithWildcards = deriveTypeWithWildcards(existentialParams.toList)(tp)
+ checkTypeRef(tpWithWildcards, tree, skipBounds)
case _ =>
}
+ if (skipBounds) {
+ tree.tpe = tree.tpe.map {
+ _.filterAnnotations(_.symbol != UncheckedBoundsClass)
+ }
+ }
+
tree
case TypeApply(fn, args) =>
@@ -983,6 +983,7 @@ trait Definitions extends api.StandardDefinitions {
lazy val ThrowsClass = requiredClass[scala.throws[_]]
lazy val TransientAttr = requiredClass[scala.transient]
lazy val UncheckedClass = requiredClass[scala.unchecked]
+ lazy val UncheckedBoundsClass = getClassIfDefined("scala.reflect.internal.annotations.uncheckedBounds")
lazy val UnspecializedClass = requiredClass[scala.annotation.unspecialized]
lazy val VolatileAttr = requiredClass[scala.volatile]
@@ -7314,6 +7314,12 @@ trait Types extends api.Types { self: SymbolTable =>
else (ps :+ SerializableClass.tpe).toList
)
+ /** Adds the @uncheckedBound annotation if the given `tp` has type arguments */
+ final def uncheckedBounds(tp: Type): Type = {
+ if (tp.typeArgs.isEmpty || UncheckedBoundsClass == NoSymbol) tp // second condition for backwards compatibilty with older scala-reflect.jar
+ else tp.withAnnotation(AnnotationInfo marker UncheckedBoundsClass.tpe)
+ }
+
/** Members of the given class, other than those inherited
* from Any or AnyRef.
*/

0 comments on commit 5724cae

Please sign in to comment.