Skip to content

Commit 8a6fc57

Browse files
committed
Make exclusion annotations configurable
1 parent d09144d commit 8a6fc57

File tree

33 files changed

+142
-130
lines changed

33 files changed

+142
-130
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.typesafe.tools.mima.core
2+
3+
private[mima] final case class AnnotInfo(name: String)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private[mima] sealed abstract class ClassInfo(val owner: PackageInfo) extends In
5959
final var _methods: Members[MethodInfo] = NoMembers
6060
final var _flags: Int = 0
6161
final var _scopedPrivate: Boolean = false
62-
final var _experimental: Boolean = false
62+
final var _annotations: List[AnnotInfo] = Nil
6363
final var _implClass: ClassInfo = NoClass
6464
final var _moduleClass: ClassInfo = NoClass
6565
final var _module: ClassInfo = NoClass
@@ -76,7 +76,7 @@ private[mima] sealed abstract class ClassInfo(val owner: PackageInfo) extends In
7676
final def methods: Members[MethodInfo] = afterLoading(_methods)
7777
final def flags: Int = afterLoading(_flags)
7878
final def isScopedPrivate: Boolean = afterLoading(_scopedPrivate)
79-
final def isExperimental: Boolean = afterLoading(_experimental)
79+
final def annotations: List[AnnotInfo] = afterLoading(_annotations)
8080
final def implClass: ClassInfo = { owner.setImplClasses; _implClass } // returns NoClass if this is not a trait
8181
final def moduleClass: ClassInfo = { owner.setModules; if (_moduleClass == NoClass) this else _moduleClass }
8282
final def module: ClassInfo = { owner.setModules; if (_module == NoClass) this else _module }

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ 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
39+
final var _annotations: List[AnnotInfo] = Nil
40+
final def annotations: List[AnnotInfo] = _annotations
4141

4242
def methodString: String = s"$shortMethodString in ${owner.classString}"
4343
def shortMethodString: String = {

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object MimaUnpickler {
77
if (buf.bytes.length == 0) return
88

99
val doPrint = false
10-
//val doPrint = path.contains("v1") && !path.contains("experimental.class") && !path.contains("experimental2.class")
10+
//val doPrint = path.contains("v1") && !path.contains("exclude.class")
1111
if (doPrint) {
1212
println(s"unpickling $path")
1313
PicklePrinter.printPickle(buf)
@@ -215,8 +215,7 @@ object MimaUnpickler {
215215
case TypeRefInfo(_, sym, Nil) => s"$sym"
216216
case _ => "?"
217217
}
218-
cls._experimental |= annotName == "scala.annotation.experimental"
219-
cls._experimental |= annotName == "scala.annotation.experimental2"
218+
cls._annotations :+= AnnotInfo(annotName)
220219
}
221220
}
222221
}

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

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,40 @@ import TastyFormat._, NameTags._, TastyTagOps._, TastyRefs._
99

1010
object TastyUnpickler {
1111
def unpickleClass(in: TastyReader, clazz: ClassInfo, path: String): Unit = {
12-
//val doPrint = false
13-
//val doPrint = path.contains("v1") && !path.contains("experimental2.tasty")
12+
val doPrint = false
13+
//val doPrint = path.contains("v1") && !path.contains("exclude.tasty")
1414
//if (doPrint) TastyPrinter.printClassNames(in.fork, path)
15-
//if (doPrint) TastyPrinter.printPickle(in.fork, path)
16-
unpickle(in, clazz, path)
17-
}
15+
if (doPrint) TastyPrinter.printPickle(in.fork, path)
1816

19-
def unpickle(in: TastyReader, clazz: ClassInfo, path: String): Unit = {
2017
readHeader(in)
2118
val names = readNames(in)
2219
val tree = unpickleTree(getTreeReader(in, names), names)
2320

24-
object trav extends Traverser {
21+
copyAnnotations(tree, clazz)
22+
}
23+
24+
def copyAnnotations(tree: Tree, clazz: ClassInfo): Unit = {
25+
new Traverser {
2526
var pkgNames = List.empty[Name]
2627
var clsNames = List.empty[Name]
2728

28-
def dropHead[A](xs: List[A], head: A) = xs match {
29-
case `head` :: ys => ys
30-
case x :: _ => throw new AssertionError(s"assertion failed: Expected head=$head but it was $x")
31-
case _ => throw new AssertionError(s"assertion failed: Expected head=$head but list was empty")
32-
}
33-
3429
override def traversePkg(pkg: Pkg): Unit = {
35-
val pkgName = pkg.path match {
36-
case TypeRefPkg(fullyQual) => fullyQual
37-
case p: UnknownPath => SimpleName(p.show)
38-
}
39-
pkgNames ::= pkgName
30+
pkgNames ::= getPkgName(pkg)
4031
super.traversePkg(pkg)
41-
pkgNames = dropHead(pkgNames, pkgName)
32+
pkgNames = pkgNames.tail
4233
}
4334

4435
override def traverseClsDef(clsDef: ClsDef): Unit = {
4536
forEachClass(clsDef, pkgNames, clsNames)
4637
clsNames ::= clsDef.name
4738
super.traverseClsDef(clsDef)
48-
clsNames = dropHead(clsNames, clsDef.name)
39+
clsNames = clsNames.tail
4940
}
41+
}.traverse(tree)
42+
43+
def getPkgName(pkg: Pkg) = pkg.path match {
44+
case TypeRefPkg(fullyQual) => fullyQual
45+
case p: UnknownPath => SimpleName(p.show)
5046
}
5147

5248
def forEachClass(clsDef: ClsDef, pkgNames: List[Name], clsNames: List[Name]): Unit = {
@@ -55,22 +51,16 @@ object TastyUnpickler {
5551
val clsName = (clsDef.name :: clsNames).reverseIterator.mkString("$")
5652
val cls = clazz.owner.classes.getOrElse(clsName, NoClass)
5753
if (cls != NoClass) {
58-
cls._experimental |= clsDef.annots.exists(_.tycon.toString == "scala.annotation.experimental")
59-
cls._experimental |= clsDef.annots.exists(_.tycon.toString == "scala.annotation.experimental2")
54+
cls._annotations ++= clsDef.annots.map(annot => AnnotInfo(annot.tycon.toString))
6055

6156
for (defDef <- clsDef.template.meths) {
62-
val isExperimental =
63-
defDef.annots.exists(_.tycon.toString == "scala.annotation.experimental") ||
64-
defDef.annots.exists(_.tycon.toString == "scala.annotation.experimental2")
65-
if (isExperimental)
66-
for (meth <- cls.lookupClassMethods(new MethodInfo(cls, defDef.name.source, 0, "()V")))
67-
meth._experimental = true
57+
val annots = defDef.annots.map(annot => AnnotInfo(annot.tycon.toString))
58+
for (meth <- cls.lookupClassMethods(new MethodInfo(cls, defDef.name.source, 0, "()V")))
59+
meth._annotations ++= annots
6860
}
6961
}
7062
}
7163
}
72-
73-
trav.traverse(tree)
7464
}
7565

7666
def unpickleTree(in: TastyReader, names: Names): Tree = {
@@ -219,7 +209,7 @@ object TastyUnpickler {
219209

220210
sealed trait Path extends Type
221211
final case class UnknownPath(tag: Int) extends Path { def show = s"UnknownPath(${astTagToString(tag)})" }
222-
final case class TypeRefPkg(fullyQual: Name) extends Path { def show = s"$fullyQual" }
212+
final case class TypeRefPkg(fullyQual: Name) extends Path { def show = s"$fullyQual" }
223213

224214
final case class Annot(tycon: Type, fullAnnotation: Tree) extends Tree { def show = s"@$tycon" }
225215

@@ -320,16 +310,16 @@ object TastyUnpickler {
320310

321311
val name = tag match {
322312
case UTF8 => val start = currentAddr; goto(end); SimpleName(new String(bytes.slice(start.index, end.index), "UTF-8"))
323-
case QUALIFIED => QualifiedName(readName(), PathSep, readName().asSimpleName)
324-
case EXPANDED => QualifiedName(readName(), ExpandedSep, readName().asSimpleName)
325-
case EXPANDPREFIX => QualifiedName(readName(), ExpandPrefixSep, readName().asSimpleName)
313+
case QUALIFIED => QualifiedName(readName(), Name.PathSep, readName().asSimpleName)
314+
case EXPANDED => QualifiedName(readName(), Name.ExpandedSep, readName().asSimpleName)
315+
case EXPANDPREFIX => QualifiedName(readName(), Name.ExpandPrefixSep, readName().asSimpleName)
326316

327-
case UNIQUE => UniqueName(sep = readName().asSimpleName, num = readNat(), qual = ifBefore(end)(readName(), Empty))
317+
case UNIQUE => UniqueName(sep = readName().asSimpleName, num = readNat(), qual = ifBefore(end)(readName(), Name.Empty))
328318
case DEFAULTGETTER => DefaultName(readName(), readNat())
329319

330-
case SUPERACCESSOR => PrefixName(SuperPrefix, readName())
331-
case INLINEACCESSOR => PrefixName(InlinePrefix, readName())
332-
case BODYRETAINER => SuffixName(readName(), BodyRetainerSuffix)
320+
case SUPERACCESSOR => PrefixName( Name.SuperPrefix, readName())
321+
case INLINEACCESSOR => PrefixName(Name.InlinePrefix, readName())
322+
case BODYRETAINER => SuffixName(readName(), Name.BodyRetainerSuffix)
333323
case OBJECTCLASS => ObjectName(readName())
334324

335325
case SIGNED => val name = readName(); readSignedRest(name, name)
@@ -375,14 +365,16 @@ object TastyUnpickler {
375365

376366
final case class SignedName(qual: Name, sig: MethodSignature[ErasedTypeRef], target: Name) extends Name
377367

378-
val Empty = SimpleName("")
379-
val PathSep = SimpleName(".")
380-
val ExpandedSep = SimpleName("$$")
381-
val ExpandPrefixSep = SimpleName("$")
382-
val InlinePrefix = SimpleName("inline$")
383-
val SuperPrefix = SimpleName("super$")
384-
val BodyRetainerSuffix = SimpleName("$retainedBody")
385-
val Constructor = SimpleName("<init>")
368+
object Name {
369+
val Empty = SimpleName("")
370+
val PathSep = SimpleName(".")
371+
val ExpandedSep = SimpleName("$$")
372+
val ExpandPrefixSep = SimpleName("$")
373+
val InlinePrefix = SimpleName("inline$")
374+
val SuperPrefix = SimpleName("super$")
375+
val BodyRetainerSuffix = SimpleName("$retainedBody")
376+
val Constructor = SimpleName("<init>")
377+
}
386378

387379
object nme {
388380
def NoSymbol = SimpleName("NoSymbol")
@@ -416,18 +408,18 @@ object TastyUnpickler {
416408

417409
def apply(tname: Name): ErasedTypeRef = {
418410
def name(qual: Name, tname: SimpleName, isModule: Boolean) = {
419-
val qualified = if (qual == Empty) tname else QualifiedName(qual, PathSep, tname)
411+
val qualified = if (qual == Name.Empty) tname else QualifiedName(qual, Name.PathSep, tname)
420412
if (isModule) ObjectName(qualified) else qualified
421413
}
422414
def specialised(qual: Name, terminal: String, isModule: Boolean, arrayDims: Int = 0): ErasedTypeRef = terminal match {
423415
case InnerRegex(inner) => specialised(qual, inner, isModule, arrayDims + 1)
424416
case clazz => ErasedTypeRef(name(qual, SimpleName(clazz), isModule).toTypeName, arrayDims)
425417
}
426418
def transform(name: Name, isModule: Boolean = false): ErasedTypeRef = name match {
427-
case ObjectName(classKind) => transform(classKind, isModule = true)
428-
case SimpleName(raw) => specialised(Empty, raw, isModule) // unqualified in the <empty> package
429-
case QualifiedName(path, PathSep, sel) => specialised(path, sel.raw, isModule)
430-
case _ => throw new AssertionError(s"Unexpected erased type ref ${name.debug}")
419+
case ObjectName(classKind) => transform(classKind, isModule = true)
420+
case SimpleName(raw) => specialised(Name.Empty, raw, isModule) // unqualified in the <empty> package
421+
case QualifiedName(path, Name.PathSep, sel) => specialised(path, sel.raw, isModule)
422+
case _ => throw new AssertionError(s"Unexpected erased type ref ${name.debug}")
431423
}
432424
transform(tname.toTermName)
433425
}
@@ -447,7 +439,7 @@ object TastyUnpickler {
447439

448440
object SourceEncoder extends StringBuilderEncoder {
449441
def traverse(sb: StringBuilder, name: Name): StringBuilder = name match {
450-
case Constructor => sb
442+
case Name.Constructor => sb
451443
case SimpleName(raw) => sb ++= raw
452444
case ObjectName(base) => traverse(sb, base)
453445
case TypeName(base) => traverse(sb, base)

core/src/main/scala/com/typesafe/tools/mima/lib/MiMaLib.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,21 @@ final class MiMaLib(cp: Seq[File], log: Logging = ConsoleLogging) {
2020
pkg
2121
}
2222

23-
private def traversePackages(oldpkg: PackageInfo, newpkg: PackageInfo): List[Problem] = {
23+
private def traversePackages(oldpkg: PackageInfo, newpkg: PackageInfo, excludeAnnots: List[AnnotInfo]): List[Problem] = {
2424
log.verbose(s"traversing $oldpkg")
25-
Analyzer.analyze(oldpkg, newpkg, log) ++ oldpkg.packages.values.toSeq.sortBy(_.name).flatMap { p =>
25+
Analyzer.analyze(oldpkg, newpkg, log, excludeAnnots) ++ oldpkg.packages.values.toSeq.sortBy(_.name).flatMap { p =>
2626
val q = newpkg.packages.getOrElse(p.name, NoPackageInfo)
27-
traversePackages(p, q)
27+
traversePackages(p, q, excludeAnnots)
2828
}
2929
}
3030

3131
/** Return a list of problems for the two versions of the library. */
32-
def collectProblems(oldJarOrDir: File, newJarOrDir: File): List[Problem] = {
32+
def collectProblems(oldJarOrDir: File, newJarOrDir: File, excludeAnnots: List[String]): List[Problem] = {
3333
val oldPackage = createPackage(oldJarOrDir)
3434
val newPackage = createPackage(newJarOrDir)
3535
log.debug(s"[old version in: ${oldPackage.definitions}]")
3636
log.debug(s"[new version in: ${newPackage.definitions}]")
3737
log.debug(s"classpath: ${classpath.asClassPathString}")
38-
traversePackages(oldPackage, newPackage)
38+
traversePackages(oldPackage, newPackage, excludeAnnots.map(AnnotInfo(_)))
3939
}
4040
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ import com.typesafe.tools.mima.lib.analyze.method.MethodChecker
77
import com.typesafe.tools.mima.lib.analyze.template.TemplateChecker
88

99
object Analyzer {
10-
def analyze(oldpkg: PackageInfo, newpkg: PackageInfo, log: Logging): List[Problem] = {
10+
def analyze(oldpkg: PackageInfo, newpkg: PackageInfo, log: Logging, excludeAnnots: List[AnnotInfo]): List[Problem] = {
1111
for {
1212
oldclazz <- oldpkg.accessibleClasses.toList.sortBy(_.bytecodeName)
1313
_ = log.verbose(s"analyzing $oldclazz")
1414
_ = oldclazz.forceLoad
1515
// if it is missing a trait implementation class, then no error should be reported
1616
// since there should be already errors, i.e., missing methods...
1717
if !oldclazz.isImplClass
18-
if !oldclazz.isExperimental
18+
if !excludeAnnots.exists(oldclazz.annotations.contains)
1919
problem <- newpkg.classes.get(oldclazz.bytecodeName) match {
20-
case Some(newclazz) => analyze(oldclazz, newclazz, log)
20+
case Some(newclazz) => analyze(oldclazz, newclazz, log, excludeAnnots)
2121
case None => List(MissingClassProblem(oldclazz))
2222
}
2323
} yield {
@@ -26,7 +26,7 @@ object Analyzer {
2626
}
2727
}
2828

29-
def analyze(oldclazz: ClassInfo, newclazz: ClassInfo, log: Logging): List[Problem] = {
29+
def analyze(oldclazz: ClassInfo, newclazz: ClassInfo, log: Logging, excludeAnnots: List[AnnotInfo]): List[Problem] = {
3030
if ((if (newclazz.isModuleClass) newclazz.module else newclazz).isScopedPrivate) Nil
3131
else {
3232
TemplateChecker.check(oldclazz, newclazz) match {
@@ -37,7 +37,7 @@ object Analyzer {
3737
case maybeProblem =>
3838
maybeProblem.toList :::
3939
FieldChecker.check(oldclazz, newclazz) :::
40-
MethodChecker.check(oldclazz, newclazz)
40+
MethodChecker.check(oldclazz, newclazz, excludeAnnots)
4141
}
4242
}
4343
}

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@ package com.typesafe.tools.mima.lib.analyze.method
33
import com.typesafe.tools.mima.core._
44

55
private[analyze] object MethodChecker {
6-
def check(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] =
7-
checkExisting(oldclazz, newclazz) ::: checkNew(oldclazz, newclazz)
6+
def check(oldclazz: ClassInfo, newclazz: ClassInfo, excludeAnnots: List[AnnotInfo]): List[Problem] =
7+
checkExisting(oldclazz, newclazz, excludeAnnots) ::: checkNew(oldclazz, newclazz, excludeAnnots)
88

99
/** Analyze incompatibilities that may derive from changes in existing methods. */
10-
private def checkExisting(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] = {
11-
for (oldmeth <- oldclazz.methods.value; problem <- checkExisting1(oldmeth, newclazz)) yield problem
10+
private def checkExisting(oldclazz: ClassInfo, newclazz: ClassInfo, excludeAnnots: List[AnnotInfo]): List[Problem] = {
11+
for (oldmeth <- oldclazz.methods.value; problem <- checkExisting1(oldmeth, newclazz, excludeAnnots)) yield problem
1212
}
1313

1414
/** Analyze incompatibilities that may derive from new methods in `newclazz`. */
15-
private def checkNew(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] = {
15+
private def checkNew(oldclazz: ClassInfo, newclazz: ClassInfo, excludeAnnots: List[AnnotInfo]): List[Problem] = {
1616
val problems1 = if (newclazz.isClass) Nil else checkEmulatedConcreteMethodsProblems(oldclazz, newclazz)
17-
val problems2 = checkDeferredMethodsProblems(oldclazz, newclazz)
18-
val problems3 = checkInheritedNewAbstractMethodProblems(oldclazz, newclazz)
17+
val problems2 = checkDeferredMethodsProblems(oldclazz, newclazz, excludeAnnots)
18+
val problems3 = checkInheritedNewAbstractMethodProblems(oldclazz, newclazz, excludeAnnots)
1919
problems1 ::: problems2 ::: problems3
2020
}
2121

22-
private def checkExisting1(oldmeth: MethodInfo, newclazz: ClassInfo): Option[Problem] = {
23-
if (oldmeth.nonAccessible || oldmeth.isExperimental)
22+
private def checkExisting1(oldmeth: MethodInfo, newclazz: ClassInfo, excludeAnnots: List[AnnotInfo]): Option[Problem] = {
23+
if (oldmeth.nonAccessible || excludeAnnots.exists(oldmeth.annotations.contains))
2424
None
2525
else if (newclazz.isClass) {
2626
if (oldmeth.isDeferred)
@@ -135,10 +135,10 @@ private[analyze] object MethodChecker {
135135
} yield problem
136136
}.toList
137137

138-
private def checkDeferredMethodsProblems(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] = {
138+
private def checkDeferredMethodsProblems(oldclazz: ClassInfo, newclazz: ClassInfo, excludeAnnots: List[AnnotInfo]): List[Problem] = {
139139
for {
140140
newmeth <- newclazz.deferredMethods.iterator
141-
if !newmeth.isExperimental
141+
if !excludeAnnots.exists(newmeth.annotations.contains)
142142
problem <- oldclazz.lookupMethods(newmeth).find(_.descriptor == newmeth.descriptor) match {
143143
case None => Some(ReversedMissingMethodProblem(newmeth))
144144
case Some(oldmeth) if newclazz.isClass && oldmeth.isConcrete => Some(ReversedAbstractMethodProblem(newmeth))
@@ -147,7 +147,7 @@ private[analyze] object MethodChecker {
147147
} yield problem
148148
}.toList
149149

150-
private def checkInheritedNewAbstractMethodProblems(oldclazz: ClassInfo, newclazz: ClassInfo): List[Problem] = {
150+
private def checkInheritedNewAbstractMethodProblems(oldclazz: ClassInfo, newclazz: ClassInfo, excludeAnnots: List[AnnotInfo]): List[Problem] = {
151151
def allInheritedTypes(clazz: ClassInfo) = clazz.superClasses ++ clazz.allInterfaces
152152
val diffInheritedTypes = allInheritedTypes(newclazz).diff(allInheritedTypes(oldclazz))
153153

@@ -159,7 +159,7 @@ private[analyze] object MethodChecker {
159159
newInheritedType <- diffInheritedTypes.iterator
160160
// if `newInheritedType` is a trait, then the trait's concrete methods should be counted as deferred methods
161161
newDeferredMethod <- newInheritedType.deferredMethodsInBytecode
162-
if !newDeferredMethod.isExperimental
162+
if !excludeAnnots.exists(newDeferredMethod.annotations.contains)
163163
// checks that the newDeferredMethod did not already exist in one of the oldclazz supertypes
164164
if noInheritedMatchingMethod(oldclazz, newDeferredMethod)(_ => true) &&
165165
// checks that no concrete implementation of the newDeferredMethod is provided by one of the newclazz supertypes

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ object CollectProblemsTest {
1818

1919
def collectProblems(cp: Seq[File], v1: File, v2: File, direction: Direction): List[Problem] = {
2020
val (lhs, rhs) = direction.ordered(v1, v2)
21-
new MiMaLib(cp).collectProblems(lhs, rhs)
21+
new MiMaLib(cp).collectProblems(lhs, rhs, excludeAnnots)
2222
}
2323

2424
def readOracleFile(oracleFile: File): List[String] = {
@@ -51,4 +51,6 @@ object CollectProblemsTest {
5151
Failure(new Exception("CollectProblemsTest failure", null, false, false) {})
5252
}
5353
}
54+
55+
private val excludeAnnots = List("mima.annotation.exclude")
5456
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
object App {
2-
def main(args: Array[String]): Unit = println(new pkg1.pkg2.Foo())
2+
def main(args: Array[String]): Unit = ()
33
}

0 commit comments

Comments
 (0)