Skip to content

Commit d6ee5f7

Browse files
committed
Re-enable unused vals and defs
1 parent 134afc6 commit d6ee5f7

20 files changed

+237
-43
lines changed

compiler/src/dotty/tools/dotc/transform/UnusedDecls.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ class UnusedDecls extends MiniPhase with InfoTransformer {
3131

3232
/* Tree transform */
3333

34-
override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree =
34+
override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree)
35+
override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree)
36+
37+
private def transformValOrDefDef(tree: ValOrDefDef)(implicit ctx: Context): Tree =
3538
if (tree.symbol.is(Unused, butNot = Param)) EmptyTree else tree
3639

3740

compiler/src/dotty/tools/dotc/typer/Applications.scala

+2-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import NameKinds.DefaultGetterName
2424
import ProtoTypes._
2525
import EtaExpansion._
2626
import Inferencing._
27+
import UnusedUtil._
2728

2829
import collection.mutable
2930
import config.Printers.{typr, unapp, overload}
@@ -773,12 +774,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
773774
}
774775
app match {
775776
case Apply(fun, args) if fun.tpe.widen.isUnusedMethod =>
776-
val erasedArgs = args.map { arg =>
777-
if (!isPureExpr(arg))
778-
ctx.warning("This argument is given to an unused parameter. This expression will not be evaluated.", arg.pos)
779-
defaultValue(arg.tpe)
780-
}
781-
tpd.cpy.Apply(app)(fun = fun, args = erasedArgs)
777+
tpd.cpy.Apply(app)(fun = fun, args = args.map(arg => normalizeUnusedExpr(arg, "This argument is given to an unused parameter. ")))
782778
case _ => app
783779
}
784780
}

compiler/src/dotty/tools/dotc/typer/Checking.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ object Checking {
379379
}
380380
checkApplicable(UnusedType, !sym.is(UnusedType))
381381
if (sym.is(Unused)) {
382-
checkApplicable(Unused, sym.is(Param) || sym.is(ParamAccessor))
382+
checkApplicable(Unused, !sym.is(MutableOrLazy))
383383
if (sym.info.widen.finalResultType.isBottomType)
384384
fail("unused " + sym.showKind + " cannot have type Nothing")
385385
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import dotty.tools.dotc.transform.Erasure.Boxing
2929
import util.Positions._
3030
import util.common._
3131
import util.{Property, SourcePosition}
32+
import UnusedUtil._
3233

3334
import collection.mutable
3435
import annotation.tailrec
@@ -1255,7 +1256,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
12551256
val tpt1 = checkSimpleKinded(typedType(tpt))
12561257
val rhs1 = vdef.rhs match {
12571258
case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe
1258-
case rhs => typedExpr(rhs, tpt1.tpe)
1259+
case rhs => normalizeUnusedRhs(typedExpr(rhs, tpt1.tpe), sym)
12591260
}
12601261
val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym)
12611262
if (sym.is(Inline, butNot = DeferredOrParamAccessor))
@@ -1313,7 +1314,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
13131314
(tparams1, sym.owner.typeParams).zipped.foreach ((tdef, tparam) =>
13141315
rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef)))
13151316
}
1316-
val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx)
1317+
val rhs1 = normalizeUnusedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym)
13171318

13181319
// Overwrite inline body to make sure it is not evaluated twice
13191320
if (sym.isInlineMethod) Inliner.registerInlineInfo(sym, _ => rhs1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dotty.tools.dotc.typer
2+
3+
import dotty.tools.dotc.ast.tpd._
4+
import dotty.tools.dotc.core.Contexts._
5+
import dotty.tools.dotc.core.Decorators._
6+
import dotty.tools.dotc.core.Symbols._
7+
import dotty.tools.dotc.core.Flags._
8+
9+
/** Util methods for transformation of `unused` expressions
10+
*
11+
* @author Nicolas Stucki
12+
*/
13+
object UnusedUtil {
14+
15+
def normalizeUnusedExpr(tree: Tree, msg: String)(implicit ctx: Context): Tree = {
16+
if (!isPureExpr(tree))
17+
ctx.warning(msg + "This expression will not be evaluated.", tree.pos)
18+
defaultValue(tree.tpe)
19+
}
20+
21+
def normalizeUnusedRhs(tree: Tree, sym: Symbol)(implicit ctx: Context) = {
22+
if (sym.is(Unused) && tree.tpe.exists) normalizeUnusedExpr(tree, "Expression is on the RHS of an `unused` " + sym.showKind + ". ")
23+
else tree
24+
}
25+
26+
}

docs/docs/reference/unused-terms.md

+4-8
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ unused val unusedEvidence: Ev = ...
6666
methodWithUnusedEv(unusedEvidence)
6767
```
6868

69-
7069
What happens with unused values at runtime?
7170
-------------------------------------------
7271
As `unused` are guaranteed not to be used in computations, they can and will be erased.
@@ -79,11 +78,8 @@ def evidence1: Ev = ...
7978
unused def unusedEvidence2: Ev = ... // does not exist at runtime
8079
unused val unusedEvidence3: Ev = ... // does not exist at runtime
8180

82-
// evidence1 is evaluated but the result is not passed to methodWithUnusedEv
81+
// evidence1 is not evaluated and no value is passed to methodWithUnusedEv
8382
methodWithUnusedEv(evidence1)
84-
85-
// unusedEvidence2 is not evaluated and its result is not passed to methodWithUnusedEv
86-
methodWithUnusedEv(unusedEvidence2)
8783
```
8884

8985
State machine with unused evidence example
@@ -111,14 +107,14 @@ final class Off extends State
111107
@implicitNotFound("State is must be Off")
112108
class IsOff[S <: State]
113109
object IsOff {
114-
// def isOff will not exist at runtime
115-
unused implicit def isOff: IsOff[Off] = new IsOff[Off]
110+
// def isOff will not be called at runtime for turnedOn, the compiler will only require that this evidence exists
111+
implicit def isOff: IsOff[Off] = new IsOff[Off]
116112
}
117113

118114
@implicitNotFound("State is must be On")
119115
class IsOn[S <: State]
120116
object IsOn {
121-
// val isOn will not exist at runtime
117+
// def isOn will not exist at runtime, the compiler will only require that this evidence exists at compile time
122118
unused implicit val isOn: IsOn[On] = new IsOn[On]
123119
}
124120

tests/neg/unused-1.scala

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,34 @@
11
object Test {
22
def foo0(a: Int): Int = a
33
def foo1(unused a: Int): Int = {
4-
foo0(a) // error
4+
foo0(
5+
a // error
6+
)
57
foo0({
68
println()
79
a // error
810
})
911
foo1(a) // OK
12+
foo2( // error
13+
a // error
14+
)
15+
foo3( // error
16+
a
17+
)
1018
a // error
11-
a // error
19+
}
20+
unused def foo2(a: Int): Int = {
21+
foo0(a) // OK
22+
foo1(a) // OK
23+
foo2(a) // OK
24+
foo3(a) // OK
25+
a // OK
26+
}
27+
unused def foo3(unused a: Int): Int = {
28+
foo0(a) // OK
29+
foo1(a) // OK
30+
foo2(a) // OK
31+
foo3(a) // OK
32+
a // OK
1233
}
1334
}

tests/neg/unused-2.scala

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
object Test {
2+
def foo0(a: Int): Int = a
3+
def foo1(unused a: Int): Int = {
4+
foo0(
5+
u // error
6+
)
7+
foo1(u) // OK
8+
foo2( // error
9+
u // error
10+
)
11+
foo3( // error
12+
u
13+
)
14+
u // error
15+
u // error
16+
}
17+
unused def foo2(a: Int): Int = {
18+
foo0(u) // OK
19+
foo1(u) // OK
20+
foo2(u) // OK
21+
foo3(u) // OK
22+
u // warn
23+
u // OK
24+
}
25+
unused def foo3(unused a: Int): Int = {
26+
foo0(u) // OK
27+
foo1(u) // OK
28+
foo2(u) // OK
29+
foo3(u) // OK
30+
u // warn
31+
u // OK
32+
}
33+
34+
unused val foo4: Int = {
35+
foo0(u) // OK
36+
foo1(u) // OK
37+
foo2(u) // OK
38+
foo3(u) // OK
39+
u // warn
40+
u // OK
41+
}
42+
43+
unused def u: Int = 42
44+
}

tests/neg/unused-3.scala

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
object Test {
2+
def foo0(a: Int): Int = a
3+
def foo1(unused a: Int): Int = {
4+
foo0(
5+
u() // error
6+
)
7+
foo1(u()) // OK
8+
foo2( // error
9+
u() // error
10+
)
11+
foo3( // error
12+
u()
13+
)
14+
u() // error
15+
u() // error
16+
}
17+
unused def foo2(a: Int): Int = {
18+
foo0(u()) // OK
19+
foo1(u()) // OK
20+
foo2(u()) // OK
21+
foo3(u()) // OK
22+
u() // warn
23+
u() // OK
24+
}
25+
unused def foo3(unused a: Int): Int = {
26+
foo0(u()) // OK
27+
foo1(u()) // OK
28+
foo2(u()) // OK
29+
foo3(u()) // OK
30+
u() // warn
31+
u() // OK
32+
}
33+
34+
unused val foo4: Int = {
35+
foo0(u()) // OK
36+
foo1(u()) // OK
37+
foo2(u()) // OK
38+
foo3(u()) // OK
39+
println()
40+
u() // warn
41+
u() // OK
42+
}
43+
44+
unused def u(): Int = 42
45+
}

tests/neg/unused-6.scala

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
object Test {
2-
def f(unused foo: Foo) = {
3-
foo.x() // error
4-
foo.y // error
5-
foo.z // error
6-
}
2+
unused def foo: Foo = new Foo
3+
foo.x() // error
4+
foo.y // error
5+
foo.z // error
76
}
87

98
class Foo {
109
def x(): String = "abc"
1110
def y: String = "abc"
1211
val z: String = "abc"
13-
}
12+
}

tests/neg/unused-args-lifted.scala

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
object Test {
2-
def f(unused bar: Int => Int) = {
3-
def foo(a: Int)(b: Int, c: Int) = 42
4-
5-
def baz: Int = {
6-
println(1)
7-
2
8-
}
9-
10-
foo(
11-
bar(baz) // error
12-
)(
13-
c = baz, b = baz // force all args to be lifted in vals befor the call
14-
)
2+
def foo(a: Int)(b: Int, c: Int) = 42
3+
unused def bar(i: Int): Int = {
4+
println(1)
5+
42
156
}
7+
def baz: Int = {
8+
println(1)
9+
2
10+
}
11+
foo(
12+
bar(baz) // error
13+
)(
14+
c = baz, b = baz // force all args to be lifted in vals befor the call
15+
)
1616
}

tests/neg/unused-assign.scala

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ object Test {
22
var i: Int = 1
33
def foo(unused a: Int): Int = {
44
i = a // error
5+
unused def r = {
6+
i = a
7+
()
8+
}
59
42
610
}
711
}

tests/neg/unused-implicit.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object Test {
2+
3+
fun // error
4+
5+
def fun(implicit a: Double): Int = 42
6+
7+
unused implicit def doubleImplicit: Double = 42.0
8+
}

tests/run/unused-10.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ object Test {
88
println("pacFun4")
99
}
1010

11-
def inky: Int = {
12-
println("inky")
11+
unused def inky: Int = {
12+
println("inky") // in erased function
1313
42
1414
}
1515

tests/run/unused-13.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
object Test {
22

33
def main(args: Array[String]): Unit = {
4-
lazy val x = {
4+
unused val x = {
55
println("x")
66
42
77
}

tests/run/unused-14.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Foo

tests/run/unused-14.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
object Test {
2+
3+
def main(args: Array[String]): Unit = {
4+
new Foo
5+
}
6+
7+
}
8+
9+
class Foo {
10+
unused val x: Int = {
11+
println("x")
12+
42
13+
}
14+
println("Foo")
15+
}

tests/run/unused-2.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ object Test {
22

33
def main(args: Array[String]): Unit = {
44

5-
def !!! : Null = ???
5+
unused def !!! : Null = ???
66

77
try {
88
fun(!!!)

tests/run/unused-select-prefix.check

Whitespace-only changes.

0 commit comments

Comments
 (0)