Simplify value class API handling and fix #2497

This is a backport of sbt/zinc#95

The previous approach to value class API (introduced by #2261 and
refined by #2413 and #2414) was to store both unerased and
erased signatures so that changes to value classes forced
recompilations.
This is no longer necessary thanks to #2523: if a class type is
used, then it becomes a dependency of the current class and its name is
part of the used names of the current class. Since the name hash of a
class changes if it stops or start extending AnyVal, this is enough to
force recompilation of anything that uses a value class type. If the
underlying type of a value class change, its name hash doesn't change,
but the name hash of `<init>` changes and since every class uses the name
`<init>`, we don't need to do anything special to trigger recompilations
either.
This commit is contained in:
Guillaume Martres 2016-04-14 22:13:36 +02:00
parent cae569b313
commit aab822c595
7 changed files with 33 additions and 88 deletions

View File

@ -45,12 +45,6 @@ abstract class Compat {
val Nullary = global.NullaryMethodType
val ScalaObjectClass = definitions.ScalaObjectClass
// `transformedType` doesn't exist in Scala < 2.10
implicit def withTransformedType(global: Global): WithTransformedType = new WithTransformedType(global)
class WithTransformedType(global: Global) {
def transformedType(tpe: Type): Type = tpe
}
/**
* Traverses given type and collects result of applying a partial function `pf`.
*
@ -121,10 +115,6 @@ abstract class Compat {
private class WithRootMirror(x: Any) {
def rootMirror: DummyMirror = new DummyMirror
}
lazy val AnyValClass = global.rootMirror.getClassIfDefined("scala.AnyVal")
def isDerivedValueClass(sym: Symbol): Boolean =
sym.isNonBottomSubClass(AnyValClass) && !definitions.ScalaValueClasses.contains(sym)
}
object MacroExpansionOf {

View File

@ -199,90 +199,32 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType,
private def viewer(s: Symbol) = (if (s.isModule) s.moduleClass else s).thisType
private def printMember(label: String, in: Symbol, t: Type) = println(label + " in " + in + " : " + t + " (debug: " + debugString(t) + " )")
private def defDef(in: Symbol, s: Symbol): List[xsbti.api.Def] =
private def defDef(in: Symbol, s: Symbol): xsbti.api.Def =
{
import MirrorHelper._
val hasValueClassAsParameter: Boolean = {
import MirrorHelper._
s.asMethod.paramss.flatten map (_.info) exists (t => isDerivedValueClass(t.typeSymbol))
}
def hasValueClassAsReturnType(tpe: Type): Boolean = tpe match {
case PolyType(_, base) => hasValueClassAsReturnType(base)
case MethodType(_, resultType) => hasValueClassAsReturnType(resultType)
case Nullary(resultType) => hasValueClassAsReturnType(resultType)
case resultType => isDerivedValueClass(resultType.typeSymbol)
}
val inspectPostErasure = hasValueClassAsParameter || hasValueClassAsReturnType(viewer(in).memberInfo(s))
def build(t: Type, typeParams: Array[xsbti.api.TypeParameter], valueParameters: List[xsbti.api.ParameterList]): List[xsbti.api.Def] =
def build(t: Type, typeParams: Array[xsbti.api.TypeParameter], valueParameters: List[xsbti.api.ParameterList]): xsbti.api.Def =
{
def parameterList(syms: List[Symbol], erase: Boolean = false): xsbti.api.ParameterList =
def parameterList(syms: List[Symbol]): xsbti.api.ParameterList =
{
val isImplicitList = syms match { case head :: _ => isImplicit(head); case _ => false }
new xsbti.api.ParameterList(syms.map(parameterS(erase)).toArray, isImplicitList)
new xsbti.api.ParameterList(syms.map(parameterS).toArray, isImplicitList)
}
t match {
case PolyType(typeParams0, base) =>
assert(typeParams.isEmpty)
assert(valueParameters.isEmpty)
build(base, typeParameters(in, typeParams0), Nil)
case mType @ MethodType(params, resultType) =>
// The types of a method's parameters change between phases: For instance, if a
// parameter is a subtype of AnyVal, then it won't have the same type before and after
// erasure. Therefore we record the type of parameters before AND after erasure to
// make sure that we don't miss some API changes.
// class A(val x: Int) extends AnyVal
// def foo(a: A): Int = A.x <- has type (LA)I before erasure
// <- has type (I)I after erasure
// If we change A from value class to normal class, we need to recompile all clients
// of def foo.
val beforeErasure =
build(resultType, typeParams, parameterList(params) :: valueParameters)
val afterErasure =
if (inspectPostErasure)
build(resultType, typeParams, parameterList(mType.params, erase = true) :: valueParameters)
else
Nil
beforeErasure ++ afterErasure
case MethodType(params, resultType) =>
build(resultType, typeParams, parameterList(params) :: valueParameters)
case Nullary(resultType) => // 2.9 and later
build(resultType, typeParams, valueParameters)
case returnType =>
def makeDef(retTpe: xsbti.api.Type): xsbti.api.Def =
new xsbti.api.Def(
valueParameters.reverse.toArray,
retTpe,
typeParams,
simpleName(s),
getAccess(s),
getModifiers(s),
annotations(in, s))
// The return type of a method may change before and after erasure. Consider the
// following method:
// class A(val x: Int) extends AnyVal
// def foo(x: Int): A = new A(x) <- has type (I)LA before erasure
// <- has type (I)I after erasure
// If we change A from value class to normal class, we need to recompile all clients
// of def foo.
val beforeErasure = makeDef(processType(in, dropConst(returnType)))
val afterErasure =
if (inspectPostErasure) {
val erasedReturn = dropConst(global.transformedType(viewer(in).memberInfo(s))) map {
case MethodType(_, r) => r
case other => other
}
List(makeDef(processType(in, erasedReturn)))
} else Nil
beforeErasure :: afterErasure
val retType = processType(in, dropConst(returnType))
new xsbti.api.Def(valueParameters.reverse.toArray, retType, typeParams,
simpleName(s), getAccess(s), getModifiers(s), annotations(in, s))
}
}
def parameterS(erase: Boolean)(s: Symbol): xsbti.api.MethodParameter = {
val tp = if (erase) global.transformedType(s.info) else s.info
def parameterS(s: Symbol): xsbti.api.MethodParameter = {
val tp = s.info
makeParameter(simpleName(s), tp, tp.typeSymbol, s)
}
@ -403,22 +345,22 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType,
defs
}
private def definition(in: Symbol, sym: Symbol): List[xsbti.api.Definition] =
private def definition(in: Symbol, sym: Symbol): Option[xsbti.api.Definition] =
{
def mkVar = List(fieldDef(in, sym, false, new xsbti.api.Var(_, _, _, _, _)))
def mkVal = List(fieldDef(in, sym, true, new xsbti.api.Val(_, _, _, _, _)))
def mkVar = Some(fieldDef(in, sym, false, new xsbti.api.Var(_, _, _, _, _)))
def mkVal = Some(fieldDef(in, sym, true, new xsbti.api.Val(_, _, _, _, _)))
if (isClass(sym))
if (ignoreClass(sym)) Nil else List(classLike(in, sym))
if (ignoreClass(sym)) None else Some(classLike(in, sym))
else if (sym.isNonClassType)
List(typeDef(in, sym))
Some(typeDef(in, sym))
else if (sym.isVariable)
if (isSourceField(sym)) mkVar else Nil
if (isSourceField(sym)) mkVar else None
else if (sym.isStable)
if (isSourceField(sym)) mkVal else Nil
if (isSourceField(sym)) mkVal else None
else if (sym.isSourceMethod && !sym.isSetter)
if (sym.isGetter) mkVar else defDef(in, sym)
if (sym.isGetter) mkVar else Some(defDef(in, sym))
else
Nil
None
}
private def ignoreClass(sym: Symbol): Boolean =
sym.isLocalClass || sym.isAnonymousClass || sym.fullName.endsWith(LocalChild.toString)

View File

@ -0,0 +1 @@
class A(val x: Int) extends AnyVal

View File

@ -0,0 +1,3 @@
object B {
def foo: A = new A(0)
}

View File

@ -0,0 +1,5 @@
object C {
def main(args: Array[String]): Unit = {
val x = B.foo
}
}

View File

@ -0,0 +1 @@
class A(val x: Double) extends AnyVal

View File

@ -0,0 +1,3 @@
> run
$ copy-file changes/A2.scala A.scala
> run