Skip to content

Commit f82b5c9

Browse files
committed
Fixes
- Remove inline-logging as it was already existing - Adjust text for inlining - Add all examples in the reference - Various fixes around
1 parent ede7aa8 commit f82b5c9

File tree

7 files changed

+165
-107
lines changed

7 files changed

+165
-107
lines changed

docs/docs/reference/metaprogramming/inline.md

Lines changed: 46 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ object Logger {
1717

1818
private var indent = 0
1919

20-
inline def log[T](msg: => String)(op: => T): T =
20+
inline def log[T](msg: String, indentMargin: =>Int)(op: => T): T =
2121
if (Config.logging) {
2222
println(s"${" " * indent}start $msg")
23-
indent += 1
23+
indent += indentMargin
2424
val result = op
25-
indent -= 1
25+
indent -= indentMargin
2626
println(s"${" " * indent}$msg = $result")
2727
result
2828
}
@@ -34,24 +34,28 @@ The `Config` object contains a definition of the **inline value** `logging`.
3434
This means that `logging` is treated as a _constant value_, equivalent to its
3535
right-hand side `false`. The right-hand side of such an `inline val` must itself
3636
be a [constant expression](https://scala-lang.org/files/archive/spec/2.12/06-expressions.html#constant-expressions). Used in this
37-
way, `inline` is equivalent to Java and Scala 2's `final`. `final` meaning
38-
_inlined constant_ is still supported in Dotty, but will be phased out.
37+
way, `inline` is equivalent to Java and Scala 2's `final`. Note that `final`, meaning
38+
_inlined constant_, is still supported in Dotty, but will be phased out.
3939

40-
The `Logger` object contains a definition of the **inline method** `log`.
41-
This method will always be inlined at the point of call.
40+
The `Logger` object contains a definition of the **inline method** `log`. This
41+
method will always be inlined at the point of call.
4242

43-
In the inlined code, an if-then-else with a constant condition will be rewritten
44-
to its then- or else-part. Consequently, in the `log` method above
45-
`if (Config.logging)` with `Config.logging == true` will rewritten into its then-part.
43+
In the inlined code, an `if-then-else` with a constant condition will be rewritten
44+
to its `then`- or `else`-part. Consequently, in the `log` method above the
45+
`if (Config.loggi0ng)` with `Config.logging == true` will get rewritten into its
46+
`then`-part.
4647

4748
Here's an example:
4849

4950
```scala
50-
def factorial(n: BigInt): BigInt =
51-
log(s"factorial($n)") {
51+
var indentSetting = 2
52+
53+
def factorial(n: BigInt): BigInt = {
54+
log(s"factorial($n)", indentSetting) {
5255
if (n == 0) 1
5356
else n * factorial(n - 1)
5457
}
58+
}
5559
```
5660

5761
If `Config.logging == false`, this will be rewritten (simplified) to
@@ -63,61 +67,33 @@ def factorial(n: BigInt): BigInt = {
6367
}
6468
```
6569

66-
and if `true` it will be rewritten to the code below:
70+
As you notice, since neither `msg` or `indentMargin` were used, they do not
71+
appear in the generated code for `factorial`. Also note the body of our `log`
72+
method: the `else-` part reduces to just an `op`. In the generated code we do
73+
not generate any closures because we only refer to a by-name parameter *once*.
74+
Consequently, the code was inlined directly and the call was beta-reduced.
75+
76+
In the `true` case the code will be rewritten to:
6777

6878
```scala
6979
def factorial(n: BigInt): BigInt = {
70-
val msgVal = s"factorial($n)"
71-
println(s"${" " * indent}start $msgVal")
72-
Logger.inline$indent += 1
73-
val result = op
74-
Logger.inline$indent -= 1
75-
println(s"${" " * indent}$msgVal = $result")
80+
val msg = s"factorial($n)"
81+
println(s"${" " * indent}start $msg")
82+
Logger.inline$indent += indentSetting
83+
val result =
84+
if (n == 0) 1
85+
else n * factorial(n - 1)
86+
Logger.inline$indent -= indentSetting
87+
println(s"${" " * indent}$msg = $result")
7688
result
7789
}
7890
```
79-
TODO: adapt to real code.
80-
Note (1) that the arguments corresponding to the parameters `msg` and `op` of
81-
the inline method `log` are defined before the inlined body (which is in this
82-
case simply `op` (2)). By-name parameters of the inline method correspond to
83-
`def` bindings whereas by-value parameters correspond to `val` bindings. So if
84-
`log` was defined like this:
85-
86-
```scala
87-
inline def log[T](msg: String)(op: => T): T = ...
88-
```
89-
90-
we'd get
9191

92-
```scala
93-
val msg = s"factorial($n)"
94-
```
95-
96-
instead. This behavior is designed so that calling an inline method is
97-
semantically the same as calling a normal method: By-value arguments are
98-
evaluated before the call whereas by-name arguments are evaluated each time they
99-
are referenced. As a consequence, it is often preferable to make arguments of
100-
inline methods by-name in order to avoid unnecessary evaluations. Additionally,
101-
in the code above, our goal is to print the result after the evaluation of `op`.
102-
Imagine, if we were printing the duration of the evaluation between the two
103-
prints.
104-
105-
For instance, here is how we can define a zero-overhead `foreach` method that
106-
translates into a straightforward while loop without any indirection or
107-
overhead:
108-
109-
```scala
110-
inline def foreach(op: => Int => Unit): Unit = {
111-
var i = from
112-
while (i < end) {
113-
op(i)
114-
i += 1
115-
}
116-
}
117-
```
92+
Note, that the by-value parameter is evaluated only once, per the usual Scala
93+
semantics, by binding the value and reusing the `msg` through the body of
94+
`factorial`.
11895

119-
By contrast, if `op` is a call-by-value parameter, it would be evaluated
120-
separately as a closure.
96+
### Recursive Inline Methods
12197

12298
Inline methods can be recursive. For instance, when called with a constant
12399
exponent `n`, the following method for `power` will be implemented by
@@ -333,30 +309,30 @@ inline def defaultValue[T] = inline erasedValue[T] match {
333309
case _: Double => Some(0.0d)
334310
case _: Boolean => Some(false)
335311
case _: Unit => Some(())
336-
case _: t >: Null => Some(null)
337312
case _ => None
338313
}
339314
```
340315

341316
Then:
342317
```scala
343-
defaultValue[Int] = Some(0)
344-
defaultValue[Boolean] = Some(false)
345-
defaultValue[String | Null] = Some(null)
346-
defaultValue[AnyVal] = None
318+
val dInt: Some[Int] = defaultValue[Int]
319+
val dDouble: Some[Double] = defaultValue[Double]
320+
val dBoolean: Some[Boolean] = defaultValue[Boolean]
321+
val dAny: None.type = defaultValue[Any]
347322
```
348323

349-
As another example, consider the type-level version of `toNat` above: given a
350-
_type_ representing a Peano number, return the integer _value_ corresponding to
351-
it. Here's how this can be defined:
324+
As another example, consider the type-level version of `toNat` above the we call
325+
`toIntT`: given a _type_ representing a Peano number, return the integer _value_
326+
corresponding to it. Consider the definitions of numbers as in the _Inline
327+
Match_ section aboce. Here's how `toIntT` can be defined:
352328

353329
```scala
354-
inline def toInt[N <: Nat] <: Int = inline scala.compiletime.erasedValue[N] match {
355-
case _: Zero => 0
330+
inline def toIntT[N <: Nat] <: Int = inline scala.compiletime.erasedValue[N] match {
331+
case _: Zero.type => 0
356332
case _: Succ[n] => toIntT[n] + 1
357333
}
358334

359-
final val two = toInt[Succ[Succ[Zero]]]
335+
final val two = toIntT[Succ[Succ[Zero.type]]]
360336
```
361337

362338
`erasedValue` is an `erased` method so it cannot be used and has no runtime

tests/pos/inline-logging.scala

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package compiletime
2+
3+
class Test {
4+
import scala.compiletime.{constValue, erasedValue, S}
5+
6+
trait Nat
7+
case object Zero extends Nat
8+
case class Succ[N <: Nat](n: N) extends Nat
9+
10+
inline def toIntC[N] <: Int =
11+
inline constValue[N] match {
12+
case 0 => 0
13+
case _: S[n1] => 1 + toIntC[n1]
14+
}
15+
16+
final val ctwo = toIntC[2]
17+
18+
inline def defaultValue[T] <: Option[Any] = inline erasedValue[T] match {
19+
case _: Byte => Some(0: Byte)
20+
case _: Char => Some(0: Char)
21+
case _: Short => Some(0: Short)
22+
case _: Int => Some(0)
23+
case _: Long => Some(0L)
24+
case _: Float => Some(0.0f)
25+
case _: Double => Some(0.0d)
26+
case _: Boolean => Some(false)
27+
case _: Unit => Some(())
28+
case _ => None
29+
}
30+
31+
val dInt: Some[Int] = defaultValue[Int]
32+
val dDouble: Some[Double] = defaultValue[Double]
33+
val dBoolean: Some[Boolean] = defaultValue[Boolean]
34+
val dAny: None.type = defaultValue[Any]
35+
36+
inline def toIntT[N <: Nat] <: Int = inline scala.compiletime.erasedValue[N] match {
37+
case _: Zero.type => 0
38+
case _: Succ[n] => toIntT[n] + 1
39+
}
40+
41+
final val two = toIntT[Succ[Succ[Zero.type]]]
42+
43+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package implicitmatch
2+
3+
class Test {
4+
import scala.collection.immutable.{TreeSet, HashSet}
5+
6+
inline def setFor[T]: Set[T] = implicit match {
7+
case ord: Ordering[T] => new TreeSet[T]
8+
case _ => new HashSet[T]
9+
}
10+
11+
the[Ordering[String]]
12+
13+
println(setFor[String].getClass) // prints class scala.collection.immutable.TreeSet
14+
15+
class A
16+
implicit val a1: A = new A
17+
implicit val a2: A = new A
18+
19+
inline def f: Any = implicit match {
20+
case _: A => ??? // error: ambiguous implicits
21+
}
22+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package inlinematch
2+
3+
class Test {
4+
5+
inline def g(x: Any) <: Any = inline x match {
6+
case x: String => (x, x) // Tuple2[String, String](x, x)
7+
case x: Double => x
8+
}
9+
10+
val t1: 1.0d = g(1.0d) // Has type 1.0d which is a subtype of Double
11+
val t2: (String, String) = g("test") // Has type (String, String)
12+
13+
trait Nat
14+
case object Zero extends Nat
15+
case class Succ[N <: Nat](n: N) extends Nat
16+
17+
inline def toInt(n: Nat) <: Int = inline n match {
18+
case Zero => 0
19+
case Succ(n1) => toInt(n1) + 1
20+
}
21+
22+
final val natTwo = toInt(Succ(Succ(Zero)))
23+
val intTwo: 2 = natTwo
24+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package inlinespecializing
2+
3+
object Test{
4+
class A
5+
class B extends A {
6+
def meth() = true
7+
}
8+
9+
inline def choose(b: Boolean) <: A = {
10+
if (b) new A()
11+
else new B()
12+
}
13+
14+
val obj1 = choose(true) // static type is A
15+
val obj2 = choose(false) // static type is B
16+
17+
// obj1.meth() // compile-time error
18+
obj2.meth() // OK
19+
}

tests/pos/reference/inlines.scala

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,36 @@
11
package inlines
22

33
object Config {
4-
inline val logging = false
4+
inline val logging = true
55
}
66

77
object Logger {
88

99
private var indent = 0
1010

11-
inline def log[T](msg: String)(op: => T): T =
11+
inline def log[T](msg: String, indentMargin: => Int)(op: => T): T =
1212
if (Config.logging) {
1313
println(s"${" " * indent}start $msg")
14-
indent += 1
14+
indent += indentMargin
1515
val result = op
16-
indent -= 1
16+
indent -= indentMargin
1717
println(s"${" " * indent}$msg = $result")
1818
result
1919
}
2020
else op
2121
}
2222

23-
object Test {
23+
object Test{
2424
import Logger._
25-
def factorial(n: BigInt): BigInt =
26-
log(s"factorial($n)") {
25+
26+
var indentSetting = 2
27+
28+
def factorial(n: BigInt): BigInt = {
29+
log(s"factorial($n)", indentSetting) {
2730
if (n == 0) 1
2831
else n * factorial(n - 1)
2932
}
33+
}
3034

3135
def main(args: Array[String]): Unit =
3236
println(factorial(33))

0 commit comments

Comments
 (0)