Skip to content

Commit c07c83a

Browse files
committed
Ignore @experimental methods too
1 parent ed93586 commit c07c83a

File tree

14 files changed

+129
-46
lines changed

14 files changed

+129
-46
lines changed

core/src/main/scala/com/typesafe/tools/mima/core/MemberInfo.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ private[mima] final class FieldInfo(owner: ClassInfo, bytecodeName: String, flag
3636
private[mima] final class MethodInfo(owner: ClassInfo, bytecodeName: String, flags: Int, descriptor: String)
3737
extends MemberInfo(owner, bytecodeName, flags, descriptor)
3838
{
39+
final var _experimental = false
40+
final def isExperimental = _experimental
41+
3942
def methodString: String = s"$shortMethodString in ${owner.classString}"
4043
def shortMethodString: String = {
4144
val prefix = if (hasSyntheticName) if (isExtensionMethod) "extension " else "synthetic " else ""

core/src/main/scala/com/typesafe/tools/mima/core/TastyPrinter.scala

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ object TastyPrinter {
77
def printPickle(in: TastyReader, path: String): Unit = {
88
println(s"unpickling $path")
99

10-
val header = readHeader(in)
11-
printHeader(header)
10+
//val header =
11+
readHeader(in)
12+
//printHeader(header)
1213

1314
val names = readNames(in)
14-
printNames(names)
15+
//printNames(names)
1516

16-
val sectionReaders = readSectionReaders(in, names)
17-
printTrees(sectionReaders)
17+
printAllTrees(getTreeReader(in, names), names)
1818
}
1919

20-
private def printHeader(header: Header) = {
20+
def printHeader(header: Header) = {
2121
val (h1, h2, h3, h4) = header.header
2222
val (versionMaj, versionMin, versionExp) = header.version
2323
println(s"Header: ${List(h1, h2, h3, h4).map(_.toHexString.toUpperCase).mkString(" ")}")
@@ -26,20 +26,15 @@ object TastyPrinter {
2626
println(s"UUID: ${header.uuid.toString.toUpperCase}")
2727
}
2828

29-
private def printNames(names: Names) = {
29+
def printNames(names: Names) = {
3030
println("Names:")
3131
for ((name, idx) <- names.zipWithIndex)
3232
println(s"${nameStr(f"$idx%4d")}: ${name.debug}")
3333
}
3434

35-
private def printTrees(sectionReaders: SectionReaders) = {
36-
print("Trees:")
37-
sectionReaders.doTrees(printAllTrees)
38-
}
39-
40-
private def printAllTrees(in: TastyReader, names: Names) = {
35+
def printAllTrees(in: TastyReader, names: Names) = {
4136
import in._
42-
print(s" start=${startAddr.index} base=$base current=${currentAddr.index} end=${endAddr.index}; ${endAddr.index - startAddr.index} bytes of AST")
37+
print(s"Trees: start=${startAddr.index} base=$base current=${currentAddr.index} end=${endAddr.index}; ${endAddr.index - startAddr.index} bytes of AST")
4338

4439
var indent = 0
4540
def newLine() = print(s"\n ${treeStr(f"${index(currentAddr) - index(startAddr)}%5d")}:" + " " * indent)

core/src/main/scala/com/typesafe/tools/mima/core/TastyTagOps.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,9 @@ object TastyTagOps {
202202
case MATCHtype => "MATCHtype"
203203
case MATCHtpt => "MATCHtpt"
204204
case MATCHCASEtype => "MATCHCASEtype"
205+
case HOLE => "HOLE"
205206

206-
case HOLE => "HOLE"
207+
case tag => s"BadTag($tag)"
207208
}
208209

209210
sealed abstract class AstCategory(val range: Range.Inclusive)

core/src/main/scala/com/typesafe/tools/mima/core/TastyUnpickler.scala

Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.typesafe.tools.mima.core
22

33
import java.util.UUID
44

5+
import scala.annotation.tailrec
56
import scala.collection.mutable, mutable.{ ArrayBuffer, ListBuffer }
67

78
import TastyFormat._, NameTags._, TastyTagOps._, TastyRefs._
@@ -17,9 +18,8 @@ object TastyUnpickler {
1718

1819
def unpickle(in: TastyReader, clazz: ClassInfo, path: String): Unit = {
1920
readHeader(in)
20-
val names = readNames(in)
21-
val sectionReaders = readSectionReaders(in ,names)
22-
val tree = sectionReaders.doTrees(unpickleTree)
21+
val names = readNames(in)
22+
val tree = unpickleTree(getTreeReader(in, names), names)
2323

2424
object trav extends Traverser {
2525
var pkgNames = List.empty[Name]
@@ -50,6 +50,15 @@ object TastyUnpickler {
5050
if (cls != NoClass) {
5151
cls._experimental |= clsDef.annots.exists(_.tycon.toString == "scala.annotation.experimental")
5252
cls._experimental |= clsDef.annots.exists(_.tycon.toString == "scala.annotation.experimental2")
53+
54+
for (defDef <- clsDef.template.meths) {
55+
val isExperimental =
56+
defDef.annots.exists(_.tycon.toString == "scala.annotation.experimental") ||
57+
defDef.annots.exists(_.tycon.toString == "scala.annotation.experimental2")
58+
if (isExperimental)
59+
for (meth <- cls.lookupClassMethods(new MethodInfo(cls, defDef.name.source, 0, "()V")))
60+
meth._experimental = true
61+
}
5362
}
5463
}
5564
}
@@ -80,12 +89,47 @@ object TastyUnpickler {
8089
val tag = readByte()
8190

8291
def processLengthTree() = {
83-
val end = readEnd()
84-
def readTrees() = until(end)(readTree()).filter(!_.isInstanceOf[UnknownTree])
85-
def readPackage() = Pkg(readPath(), readTrees()) // Path Tree* -- package path { topLevelStats }
92+
def readTrees(end: Addr) = until(end)(readTree()).filter(!_.isInstanceOf[UnknownTree])
93+
def readPackage() = { val end = readEnd(); Pkg(readPath(), readTrees(end)) } // Path Tree* -- package path { topLevelStats }
94+
95+
def nothingButMods(end: Addr) = currentAddr == end || isModifierTag(nextByte)
96+
97+
def readDefDef() = {
98+
// Length NameRef Param* returnType_Term rhs_Term? Modifier* -- modifiers def name [typeparams] paramss : returnType (= rhs)?
99+
// Param = TypeParam | TermParam
100+
val end = readEnd()
101+
val name = readName()
102+
while (nextByte == TYPEPARAM || nextByte == PARAM || nextByte == EMPTYCLAUSE || nextByte == SPLITCLAUSE) skipTree(readByte()) // params
103+
skipTree(readByte()) // returnType
104+
if (!nothingButMods(end)) skipTree(readByte()) // rhs
105+
val annots = readAnnotsInMods(end)
106+
DefDef(name, annots)
107+
}
108+
109+
def readTemplate() = {
110+
// TypeParam* TermParam* parent_Term* Self? Stat* -- [typeparams] paramss extends parents { self => stats }, where Stat* always starts with the primary constructor.
111+
// TypeParam = TYPEPARAM Length NameRef type_Term Modifier* -- modifiers name bounds
112+
// TermParam = PARAM Length NameRef type_Term Modifier* -- modifiers name : type.
113+
// EMPTYCLAUSE -- an empty parameter clause ()
114+
// SPLITCLAUSE -- splits two non-empty parameter clauses of the same kind
115+
// Self = SELFDEF selfName_NameRef selfType_Term -- selfName : selfType
116+
assert(readByte() == TEMPLATE)
117+
val end = readEnd()
118+
while (nextByte == TYPEPARAM) skipTree(readByte()) // vparams
119+
while (nextByte == PARAM || nextByte == EMPTYCLAUSE || nextByte == SPLITCLAUSE) skipTree(readByte()) // tparams
120+
while (nextByte != SELFDEF && nextByte != DEFDEF) skipTree(readByte()) // parents
121+
if (nextByte == SELFDEF) skipTree(readByte()) // self
122+
val meths = new ListBuffer[DefDef]
123+
doUntil(end)(readByte match {
124+
case DEFDEF => meths += readDefDef()
125+
case tag => skipTree(tag)
126+
})
127+
Template(meths.toList)
128+
}
129+
130+
def readClassDef(name: Name, end: Addr) = ClsDef(name.toTypeName, readTemplate(), readAnnotsInMods(end)) // NameRef Template Modifier* -- modifiers class name template
86131

87-
def readClassDef(name: TypeName) = ClsDef(name, readAnnotsInMods(end)) // NameRef Template Modifier* -- modifiers class name template
88-
def readTypeDefAlt() = UnknownTree(TYPEDEF) // NameRef type_Term Modifier* -- modifiers type name (= type | bounds) | modifiers class name template
132+
def readTypeMemberDef(end: Addr) = { goto(end); UnknownTree(TYPEDEF) } // NameRef type_Term Modifier* -- modifiers type name (= type | bounds)
89133

90134
def readAnnotsInMods(end: Addr) = {
91135
val annots = new ListBuffer[Annot]
@@ -98,14 +142,16 @@ object TastyUnpickler {
98142
}
99143

100144
def readTypeDef() = {
101-
val name = readName().toTypeName
102-
if (skipTree(readByte()).tag == TEMPLATE) readClassDef(name) else readTypeDefAlt()
145+
val end = readEnd()
146+
val name = readName()
147+
if (nextByte == TEMPLATE) readClassDef(name, end) else readTypeMemberDef(end)
103148
}
104149

150+
val end = fork.readEnd()
105151
val tree = tag match {
106152
case PACKAGE => readPackage()
107153
case TYPEDEF => readTypeDef()
108-
case _ => goto(end); UnknownTree(tag)
154+
case _ => skipTree(tag)
109155
}
110156
softAssertEnd(end, s" start=$start tag=${astTagToString(tag)}")
111157
tree
@@ -150,7 +196,9 @@ object TastyUnpickler {
150196

151197
final case class Pkg(path: Path, trees: List[Tree]) extends Tree { def show = s"package $path${trees.map("\n " + _).mkString}" }
152198

153-
final case class ClsDef(name: Name, annots: List[Annot] = Nil) extends Tree { def show = s"${annots.map("" + _ + " ").mkString}class $name" }
199+
final case class ClsDef(name: TypeName, template: Template, annots: List[Annot]) extends Tree { def show = s"${annots.map("" + _ + " ").mkString}class $name$template" }
200+
final case class Template(meths: List[DefDef]) extends Tree { def show = s"${meths.map("\n " + _).mkString}" }
201+
final case class DefDef(name: Name, annots: List[Annot] = Nil) extends Tree { def show = s"${annots.map("" + _ + " ").mkString} def $name" }
154202

155203
sealed trait Type extends Tree
156204
final case class UnknownType(tag: Int) extends Type { def show = s"UnknownType(${astTagToString(tag)})" }
@@ -166,6 +214,8 @@ object TastyUnpickler {
166214
def traverse(tree: Tree): Unit = tree match {
167215
case pkg: Pkg => traversePkg(pkg)
168216
case clsDef: ClsDef => traverseClsDef(clsDef)
217+
case tmpl: Template => traverseTemplate(tmpl)
218+
case defDef: DefDef => traverseDefDef(defDef)
169219
case tp: Type => traverseType(tp)
170220
case annot: Annot => traverseAnnot(annot)
171221
case UnknownTree(_) =>
@@ -178,6 +228,8 @@ object TastyUnpickler {
178228

179229
def traversePkg(pkg: Pkg) = { traverse(pkg.path); traverseTrees(pkg.trees) }
180230
def traverseClsDef(clsDef: ClsDef) = { traverseName(clsDef.name); traverseAnnots(clsDef.annots) }
231+
def traverseTemplate(tmpl: Template) = { traverseTrees(tmpl.meths) }
232+
def traverseDefDef(defDef: DefDef) = { traverseName(defDef.name); traverseAnnots(defDef.annots) }
181233
def traverseAnnot(annot: Annot) = { traverse(annot.tycon); traverse(annot.fullAnnotation) }
182234

183235
def traversePath(path: Path) = path match {
@@ -206,25 +258,26 @@ object TastyUnpickler {
206258
override def traverse(tree: Tree): Unit = { pf.runWith(results += _)(tree); super.traverse(tree) }
207259
}
208260

209-
def readSectionReaders(in: TastyReader, names: Names): SectionReaders = {
210-
val readers = new mutable.HashMap[Name, TastyReader]
211-
while (!in.isAtEnd) {
212-
val name = names(in.readNat())
213-
val end = in.readEnd()
214-
val curr = in.currentAddr.index
215-
in.goto(end)
216-
readers(name) = new TastyReader(in.bytes, curr, end.index, curr)
217-
}
218-
new SectionReaders(readers.toMap, names)
261+
def getTreeReader(in: TastyReader, names: Names): TastyReader = {
262+
getSectionReader(in, names, ASTsSection).getOrElse(sys.error(s"No $ASTsSection section?!"))
219263
}
220264

221-
final class SectionReaders(readers: Map[Name, TastyReader], names: Names) {
222-
def doTrees[R](f: (TastyReader, Names) => R) = f(readers(SimpleName(ASTsSection)), names)
223-
def unpickle[R](sec: SectionUnpickler[R]) = readers.get(sec.name).map(sec.unpickle(_, names))
265+
def getSectionReader(in: TastyReader, names: Names, name: String): Option[TastyReader] = {
266+
import in._
267+
@tailrec def loop(): Option[TastyReader] = {
268+
if (isAtEnd) None
269+
else if (names(readNat()).debug == name) Some(nextSectionReader(in))
270+
else { goto(readEnd()); loop() }
271+
}
272+
loop()
224273
}
225274

226-
abstract class SectionUnpickler[R](val name: Name) {
227-
def unpickle(reader: TastyReader, names: Names): R
275+
private def nextSectionReader(in: TastyReader) = {
276+
import in._
277+
val end = readEnd()
278+
val curr = currentAddr
279+
goto(end)
280+
new TastyReader(bytes, curr.index, end.index, curr.index)
228281
}
229282

230283
def readNames(in: TastyReader): Names = {
@@ -443,9 +496,8 @@ object TastyUnpickler {
443496

444497
def doClassNames(in: TastyReader, path: String): Unit = {
445498
readHeader(in)
446-
val names = readNames(in)
447-
val sectionReaders = readSectionReaders(in, names)
448-
val (pkg, nme) = sectionReaders.doTrees(unpicklePkgAndClsName)
499+
val names = readNames(in)
500+
val (pkg, nme) = unpicklePkgAndClsName(getTreeReader(in, names), names)
449501
println(s"$path -> ${pkg.source}.${nme.source}")
450502
}
451503

core/src/main/scala/com/typesafe/tools/mima/lib/analyze/method/MethodChecker.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ private[analyze] object MethodChecker {
2020
}
2121

2222
private def checkExisting1(oldmeth: MethodInfo, newclazz: ClassInfo): Option[Problem] = {
23-
if (oldmeth.nonAccessible)
23+
if (oldmeth.nonAccessible || oldmeth.isExperimental)
2424
None
2525
else if (newclazz.isClass) {
2626
if (oldmeth.isDeferred)

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/TestCase.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ final class TestCase(val baseDir: Directory, val scalaCompiler: ScalaCompiler, v
112112
scalaBinaryVersion match {
113113
case "2.11" => if (p211.exists) p211 else if (p212.exists) p212 else p
114114
case "2.12" => if (p212.exists) p212 else p
115+
case "2.13" => if (p212.exists) p212 else p
115116
case "3" => if (p3.exists) p3 else p
116117
case _ => p
117118
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object App {
2+
def main(args: Array[String]): Unit = println(new pkg1.pkg2.Foo().foo)
3+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Haven't implemented the ScalaSig unpickling part, only TASTy
2+
method foo()Int in class pkg1.pkg2.Foo does not have a correspondent in new version

functional-tests/src/test/changes-in-experimental-methods-are-ok/problems.txt

Whitespace-only changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# The change is breaking, but the problem is suppressed by the annotation
2+
# This is here to not make the app run test fail

0 commit comments

Comments
 (0)