Skip to content

Commit aa3c5ed

Browse files
committed
final vals in traits not handled properly in Scala 2.12 #193
1 parent 81f254a commit aa3c5ed

File tree

4 files changed

+160
-94
lines changed

4 files changed

+160
-94
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
target/
55
project/boot/
66
project/plugins/project/
7+
credentials.sbt
78

89
# Eclipse specific
910
.classpath

scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala

+89-93
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ScoveragePlugin(val global: Global) extends Plugin {
2929
options.excludedSymbols = opt.substring("excludedSymbols:".length).split(";").map(_.trim).filterNot(_.isEmpty)
3030
} else if (opt.startsWith("dataDir:")) {
3131
options.dataDir = opt.substring("dataDir:".length)
32-
} else if (opt.startsWith("extraAfterPhase:") || opt.startsWith("extraBeforePhase:")){
32+
} else if (opt.startsWith("extraAfterPhase:") || opt.startsWith("extraBeforePhase:")) {
3333
// skip here, these flags are processed elsewhere
3434
} else {
3535
error("Unknown option: " + opt)
@@ -82,8 +82,8 @@ class ScoverageOptions {
8282

8383
class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Option[String], extraBeforePhase: Option[String])
8484
extends PluginComponent
85-
with TypingTransformers
86-
with Transform {
85+
with TypingTransformers
86+
with Transform {
8787

8888
import global._
8989

@@ -95,11 +95,11 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
9595
override val runsBefore: List[String] = List("patmat") ::: extraBeforePhase.toList
9696

9797
/**
98-
* Our options are not provided at construction time, but shortly after,
99-
* so they start as None.
100-
* You must call "setOptions" before running any commands that rely on
101-
* the options.
102-
*/
98+
* Our options are not provided at construction time, but shortly after,
99+
* so they start as None.
100+
* You must call "setOptions" before running any commands that rely on
101+
* the options.
102+
*/
103103
private var options: ScoverageOptions = new ScoverageOptions()
104104
private var coverageFilter: CoverageFilter = AllCoverageFilter
105105

@@ -135,14 +135,14 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
135135
import global._
136136

137137
// contains the location of the last node
138-
var location: Location = null
138+
var location: Location = _
139139

140140
/**
141-
* The 'start' of the position, if it is available, else -1
142-
* We cannot use 'isDefined' to test whether pos.start will work, as some
143-
* classes (e.g. scala.reflect.internal.util.OffsetPosition have
144-
* isDefined true, but throw on `start`
145-
*/
141+
* The 'start' of the position, if it is available, else -1
142+
* We cannot use 'isDefined' to test whether pos.start will work, as some
143+
* classes (e.g. scala.reflect.internal.util.OffsetPosition have
144+
* isDefined true, but throw on `start`
145+
*/
146146
def safeStart(tree: Tree): Int = scala.util.Try(tree.pos.start).getOrElse(-1)
147147
def safeEnd(tree: Tree): Int = scala.util.Try(tree.pos.end).getOrElse(-1)
148148
def safeLine(tree: Tree): Int = if (tree.pos.isDefined) tree.pos.line else -1
@@ -250,7 +250,7 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
250250
)
251251
)
252252
case _ =>
253-
reporter.error(c.pos ,"Cannot instrument partial function apply. Please file bug report")
253+
reporter.error(c.pos, "Cannot instrument partial function apply. Please file bug report")
254254
d
255255
}
256256
case other => other
@@ -301,17 +301,17 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
301301
// case a: GenericApply if a.symbol.isConstructor => instrument(a)
302302

303303
/**
304-
* When an apply has no parameters, or is an application of purely literals or idents
305-
* then we can simply instrument the outer call. Ie, we can treat it all as one single statement
306-
* for the purposes of code coverage.
307-
* This will include calls to case apply.
308-
*/
304+
* When an apply has no parameters, or is an application of purely literals or idents
305+
* then we can simply instrument the outer call. Ie, we can treat it all as one single statement
306+
* for the purposes of code coverage.
307+
* This will include calls to case apply.
308+
*/
309309
case a: GenericApply if allConstArgs(a.args) => instrument(a, a)
310310

311311
/**
312-
* Applications of methods with non trivial args means the args themselves
313-
* must also be instrumented
314-
*/
312+
* Applications of methods with non trivial args means the args themselves
313+
* must also be instrumented
314+
*/
315315
//todo remove once scala merges into Apply proper
316316
case a: ApplyToImplicitArgs =>
317317
instrument(treeCopy.Apply(a, traverseApplication(a.fun), transformStatements(a.args)), a)
@@ -370,7 +370,7 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
370370
// this will catch methods defined as macros, eg def test = macro testImpl
371371
// it will not catch macro implementations
372372
case d: DefDef if d.symbol != null
373-
&& d.symbol.annotations.size > 0
373+
&& d.symbol.annotations.nonEmpty
374374
&& d.symbol.annotations.toString() == "macroImpl" =>
375375
tree
376376

@@ -383,31 +383,31 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
383383
case d: DefDef if d.symbol.isPrimaryConstructor => tree
384384

385385
/**
386-
* Case class accessors for vals
387-
* EG for case class CreditReject(req: MarketOrderRequest, client: ActorRef)
388-
* <stable> <caseaccessor> <accessor> <paramaccessor> def req: com.sksamuel.scoverage.samples.MarketOrderRequest
389-
* <stable> <caseaccessor> <accessor> <paramaccessor> def client: akka.actor.ActorRef
390-
*/
386+
* Case class accessors for vals
387+
* EG for case class CreditReject(req: MarketOrderRequest, client: ActorRef)
388+
* <stable> <caseaccessor> <accessor> <paramaccessor> def req: com.sksamuel.scoverage.samples.MarketOrderRequest
389+
* <stable> <caseaccessor> <accessor> <paramaccessor> def client: akka.actor.ActorRef
390+
*/
391391
case d: DefDef if d.symbol.isCaseAccessor => tree
392392

393393
// Compiler generated case apply and unapply. Ignore these
394394
case d: DefDef if d.symbol.isCaseApplyOrUnapply => tree
395395

396396
/**
397-
* Lazy stable DefDefs are generated as the impl for lazy vals.
398-
*/
397+
* Lazy stable DefDefs are generated as the impl for lazy vals.
398+
*/
399399
case d: DefDef if d.symbol.isStable && d.symbol.isGetter && d.symbol.isLazy =>
400400
updateLocation(d)
401401
treeCopy.DefDef(d, d.mods, d.name, d.tparams, d.vparamss, d.tpt, process(d.rhs))
402402

403403
/**
404-
* Stable getters are methods generated for access to a top level val.
405-
* Should be ignored as this is compiler generated code.
406-
*
407-
* Eg
408-
* <stable> <accessor> def MaxCredit: scala.math.BigDecimal = CreditEngine.this.MaxCredit
409-
* <stable> <accessor> def alwaysTrue: String = InstrumentLoader.this.alwaysTrue
410-
*/
404+
* Stable getters are methods generated for access to a top level val.
405+
* Should be ignored as this is compiler generated code.
406+
*
407+
* Eg
408+
* <stable> <accessor> def MaxCredit: scala.math.BigDecimal = CreditEngine.this.MaxCredit
409+
* <stable> <accessor> def alwaysTrue: String = InstrumentLoader.this.alwaysTrue
410+
*/
411411
case d: DefDef if d.symbol.isStable && d.symbol.isGetter => tree
412412

413413
/** Accessors are auto generated setters and getters.
@@ -496,30 +496,30 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
496496
}
497497

498498
/**
499-
* match with syntax `New(tpt)`.
500-
* This AST node corresponds to the following Scala code:
501-
*
502-
* `new` T
503-
*
504-
* This node always occurs in the following context:
505-
*
506-
* (`new` tpt).<init>[targs](args)
507-
*
508-
* For example, an AST representation of:
509-
*
510-
* new Example[Int](2)(3)
511-
*
512-
* is the following code:
513-
*
514-
* Apply(
515-
* Apply(
516-
* TypeApply(
517-
* Select(New(TypeTree(typeOf[Example])), nme.CONSTRUCTOR)
518-
* TypeTree(typeOf[Int])),
519-
* List(Literal(Constant(2)))),
520-
* List(Literal(Constant(3))))
521-
*
522-
*/
499+
* match with syntax `New(tpt)`.
500+
* This AST node corresponds to the following Scala code:
501+
*
502+
* `new` T
503+
*
504+
* This node always occurs in the following context:
505+
*
506+
* (`new` tpt).<init>[targs](args)
507+
*
508+
* For example, an AST representation of:
509+
*
510+
* new Example[Int](2)(3)
511+
*
512+
* is the following code:
513+
*
514+
* Apply(
515+
* Apply(
516+
* TypeApply(
517+
* Select(New(TypeTree(typeOf[Example])), nme.CONSTRUCTOR)
518+
* TypeTree(typeOf[Int])),
519+
* List(Literal(Constant(2)))),
520+
* List(Literal(Constant(3))))
521+
*
522+
*/
523523
case n: New => n
524524

525525
case s@Select(n@New(tpt), name) =>
@@ -547,9 +547,9 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
547547
case s: Select if location == null => tree
548548

549549
/**
550-
* I think lazy selects are the LHS of a lazy assign.
551-
* todo confirm we can ignore
552-
*/
550+
* I think lazy selects are the LHS of a lazy assign.
551+
* todo confirm we can ignore
552+
*/
553553
case s: Select if s.symbol.isLazy => tree
554554

555555
case s: Select => instrument(treeCopy.Select(s, traverseApplication(s.qualifier), s.name), s)
@@ -581,45 +581,41 @@ class ScoverageInstrumentationComponent(val global: Global, extraAfterPhase: Opt
581581

582582
case _: TypeTree => super.transform(tree)
583583

584+
// if the rhs of a val is a literal we can just leave it
585+
case v: ValDef if v.rhs.isInstanceOf[Literal] => tree
586+
584587
/**
585-
* We can ignore lazy val defs as they are implemented by a generated defdef
586-
*/
587-
case v: ValDef if v.symbol.isLazy =>
588-
val w = v
589-
tree
588+
* We can ignore lazy val defs as they are implemented by a generated defdef
589+
*/
590+
case v: ValDef if v.symbol.isLazy => tree
590591

591592
/**
592-
* <synthetic> val default: A1 => B1 =
593-
* <synthetic> val x1: Any = _
594-
*/
595-
case v: ValDef if v.symbol.isSynthetic =>
596-
val w = v
597-
tree
593+
* <synthetic> val default: A1 => B1 =
594+
* <synthetic> val x1: Any = _
595+
*/
596+
case v: ValDef if v.symbol.isSynthetic => tree
598597

599598
/**
600-
* Vals declared in case constructors
601-
*/
602-
case v: ValDef if v.symbol.isParamAccessor && v.symbol.isCaseAccessor =>
603-
val w = v
604-
tree
599+
* Vals declared in case constructors
600+
*/
601+
case v: ValDef if v.symbol.isParamAccessor && v.symbol.isCaseAccessor => tree
605602

606603
// we need to remove the final mod so that we keep the code in order to check its invoked
607604
case v: ValDef if v.mods.isFinal =>
608-
updateLocation(v)
609605
treeCopy.ValDef(v, v.mods.&~(ModifierFlags.FINAL), v.name, v.tpt, process(v.rhs))
610606

611607
/**
612-
* This AST node corresponds to any of the following Scala code:
613-
*
614-
* mods `val` name: tpt = rhs
615-
* mods `var` name: tpt = rhs
616-
* mods name: tpt = rhs // in signatures of function and method definitions
617-
* self: Bar => // self-types
618-
*
619-
* For user defined value statements, we will instrument the RHS.
620-
*
621-
* This includes top level non-lazy vals. Lazy vals are generated as stable defs.
622-
*/
608+
* This AST node corresponds to any of the following Scala code:
609+
*
610+
* mods `val` name: tpt = rhs
611+
* mods `var` name: tpt = rhs
612+
* mods name: tpt = rhs // in signatures of function and method definitions
613+
* self: Bar => // self-types
614+
*
615+
* For user defined value statements, we will instrument the RHS.
616+
*
617+
* This includes top level non-lazy vals. Lazy vals are generated as stable defs.
618+
*/
623619
case v: ValDef =>
624620
updateLocation(v)
625621
treeCopy.ValDef(tree, v.mods, v.name, v.tpt, process(v.rhs))

scalac-scoverage-plugin/src/test/scala/scoverage/PluginASTSupportTest.scala

+67
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,73 @@ class PluginASTSupportTest
1616
assert(!compiler.reporter.hasErrors)
1717
}
1818

19+
// https://github.com/scoverage/sbt-scoverage/issues/203
20+
test("should support final val literals in traits") {
21+
val compiler = ScoverageCompiler.default
22+
compiler.compileCodeSnippet(
23+
"""
24+
|trait TraitWithFinalVal {
25+
| final val FOO = "Bar"
26+
|} """.stripMargin)
27+
compiler.assertNoErrors()
28+
compiler.assertNMeasuredStatements(0)
29+
}
30+
31+
test("should support final val literals in objects") {
32+
val compiler = ScoverageCompiler.default
33+
compiler.compileCodeSnippet(
34+
"""
35+
|object TraitWithFinalVal {
36+
| final val FOO = "Bar"
37+
|} """.stripMargin)
38+
compiler.assertNoErrors()
39+
compiler.assertNMeasuredStatements(0)
40+
}
41+
42+
test("should support final val literals in classes") {
43+
val compiler = ScoverageCompiler.default
44+
compiler.compileCodeSnippet(
45+
"""
46+
|class TraitWithFinalVal {
47+
| final val FOO = "Bar"
48+
|} """.stripMargin)
49+
compiler.assertNoErrors()
50+
compiler.assertNMeasuredStatements(0)
51+
}
52+
53+
test("should support final val blocks in traits") {
54+
val compiler = ScoverageCompiler.default
55+
compiler.compileCodeSnippet(
56+
"""
57+
|trait TraitWithFinalVal {
58+
| final val FOO = { println("boo"); "Bar" }
59+
|} """.stripMargin)
60+
compiler.assertNoErrors()
61+
compiler.assertNMeasuredStatements(2)
62+
}
63+
64+
test("should support final val blocks in objects") {
65+
val compiler = ScoverageCompiler.default
66+
compiler.compileCodeSnippet(
67+
"""
68+
|object TraitWithFinalVal {
69+
| final val FOO = { println("boo"); "Bar" }
70+
|} """.stripMargin)
71+
compiler.assertNoErrors()
72+
compiler.assertNMeasuredStatements(2)
73+
}
74+
75+
test("should support final val blocks in classes") {
76+
val compiler = ScoverageCompiler.default
77+
compiler.compileCodeSnippet(
78+
"""
79+
|class TraitWithFinalVal {
80+
| final val FOO = { println("boo"); "Bar" }
81+
|} """.stripMargin)
82+
compiler.assertNoErrors()
83+
compiler.assertNMeasuredStatements(2)
84+
}
85+
1986
test("scoverage component should ignore basic macros") {
2087
val compiler = ScoverageCompiler.default
2188
compiler.compileCodeSnippet( """

scalac-scoverage-plugin/src/test/scala/scoverage/ScoverageCompiler.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ object ScoverageCompiler {
6060

6161
private def findIvyJar(groupId: String, artifactId: String, version: String, packaging: String = "jar"): File = {
6262
val userHome = System.getProperty("user.home")
63-
val jarPath = s"$userHome/.ivy2/cache/$groupId/$artifactId/${packaging}s/${artifactId}-${version}.jar"
63+
val jarPath = s"$userHome/.ivy2/cache/$groupId/$artifactId/${packaging}s/$artifactId-$version.jar"
6464
val file = new File(jarPath)
6565
if (!file.exists)
6666
throw new FileNotFoundException(s"Could not locate [$jarPath].")
@@ -98,6 +98,8 @@ class ScoverageCompiler(settings: scala.tools.nsc.Settings, reporter: scala.tool
9898
compileSourceFiles(urls.map(_.getFile).map(new File(_)): _*)
9999
}
100100

101+
def assertNoErrors() = assert(!reporter.hasErrors)
102+
101103
def assertNoCoverage() = assert(!testStore.sources.mkString(" ").contains(s"scoverage.Invoker.invoked"))
102104

103105
def assertNMeasuredStatements(n: Int): Unit = {

0 commit comments

Comments
 (0)