Merge pull request #2557 from smarter/simplify/value-class

[BPORT] Simplify value class API handling and fix #2497
This commit is contained in:
eugene yokota 2016-05-04 11:01:40 -04:00
commit 1880173c7c
7 changed files with 33 additions and 88 deletions

View File

@ -45,12 +45,6 @@ abstract class Compat {
val Nullary = global.NullaryMethodType val Nullary = global.NullaryMethodType
val ScalaObjectClass = definitions.ScalaObjectClass 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`. * Traverses given type and collects result of applying a partial function `pf`.
* *
@ -121,10 +115,6 @@ abstract class Compat {
private class WithRootMirror(x: Any) { private class WithRootMirror(x: Any) {
def rootMirror: DummyMirror = new DummyMirror 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 { 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 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 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._ def build(t: Type, typeParams: Array[xsbti.api.TypeParameter], valueParameters: List[xsbti.api.ParameterList]): xsbti.api.Def =
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 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 } 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 { t match {
case PolyType(typeParams0, base) => case PolyType(typeParams0, base) =>
assert(typeParams.isEmpty) assert(typeParams.isEmpty)
assert(valueParameters.isEmpty) assert(valueParameters.isEmpty)
build(base, typeParameters(in, typeParams0), Nil) build(base, typeParameters(in, typeParams0), Nil)
case mType @ MethodType(params, resultType) => case 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) build(resultType, typeParams, parameterList(params) :: valueParameters)
val afterErasure =
if (inspectPostErasure)
build(resultType, typeParams, parameterList(mType.params, erase = true) :: valueParameters)
else
Nil
beforeErasure ++ afterErasure
case Nullary(resultType) => // 2.9 and later case Nullary(resultType) => // 2.9 and later
build(resultType, typeParams, valueParameters) build(resultType, typeParams, valueParameters)
case returnType => case returnType =>
def makeDef(retTpe: xsbti.api.Type): xsbti.api.Def = val retType = processType(in, dropConst(returnType))
new xsbti.api.Def( new xsbti.api.Def(valueParameters.reverse.toArray, retType, typeParams,
valueParameters.reverse.toArray, simpleName(s), getAccess(s), getModifiers(s), annotations(in, s))
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
} }
} }
def parameterS(erase: Boolean)(s: Symbol): xsbti.api.MethodParameter = { def parameterS(s: Symbol): xsbti.api.MethodParameter = {
val tp = if (erase) global.transformedType(s.info) else s.info val tp = s.info
makeParameter(simpleName(s), tp, tp.typeSymbol, s) makeParameter(simpleName(s), tp, tp.typeSymbol, s)
} }
@ -403,22 +345,22 @@ class ExtractAPI[GlobalType <: CallbackGlobal](val global: GlobalType,
defs 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 mkVar = Some(fieldDef(in, sym, false, new xsbti.api.Var(_, _, _, _, _)))
def mkVal = List(fieldDef(in, sym, true, new xsbti.api.Val(_, _, _, _, _))) def mkVal = Some(fieldDef(in, sym, true, new xsbti.api.Val(_, _, _, _, _)))
if (isClass(sym)) 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) else if (sym.isNonClassType)
List(typeDef(in, sym)) Some(typeDef(in, sym))
else if (sym.isVariable) else if (sym.isVariable)
if (isSourceField(sym)) mkVar else Nil if (isSourceField(sym)) mkVar else None
else if (sym.isStable) else if (sym.isStable)
if (isSourceField(sym)) mkVal else Nil if (isSourceField(sym)) mkVal else None
else if (sym.isSourceMethod && !sym.isSetter) else if (sym.isSourceMethod && !sym.isSetter)
if (sym.isGetter) mkVar else defDef(in, sym) if (sym.isGetter) mkVar else Some(defDef(in, sym))
else else
Nil None
} }
private def ignoreClass(sym: Symbol): Boolean = private def ignoreClass(sym: Symbol): Boolean =
sym.isLocalClass || sym.isAnonymousClass || sym.fullName.endsWith(LocalChild.toString) 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