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 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