Skip to content

Commit 05cb10f

Browse files
committed
add bitwise compiletime operations
1 parent 3e68e93 commit 05cb10f

File tree

6 files changed

+168
-3
lines changed

6 files changed

+168
-3
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,7 @@ class Definitions {
955955
tpnme.Plus, tpnme.Minus, tpnme.Times, tpnme.Div, tpnme.Mod,
956956
tpnme.Lt, tpnme.Gt, tpnme.Ge, tpnme.Le,
957957
tpnme.Abs, tpnme.Negate, tpnme.Min, tpnme.Max, tpnme.ToString,
958+
tpnme.Xor, tpnme.AND, tpnme.OR, tpnme.ASR, tpnme.LSL, tpnme.LSR
958959
)
959960
private val compiletimePackageBooleanTypes: Set[Name] = Set(tpnme.Not, tpnme.Xor, tpnme.And, tpnme.Or)
960961
private val compiletimePackageStringTypes: Set[Name] = Set(tpnme.Plus)

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3842,6 +3842,12 @@ object Types {
38423842
case tpnme.Gt if nArgs == 2 => constantFold2(intValue, _ > _)
38433843
case tpnme.Ge if nArgs == 2 => constantFold2(intValue, _ >= _)
38443844
case tpnme.Le if nArgs == 2 => constantFold2(intValue, _ <= _)
3845+
case tpnme.Xor if nArgs == 2 => constantFold2(intValue, _ ^ _)
3846+
case tpnme.AND if nArgs == 2 => constantFold2(intValue, _ & _)
3847+
case tpnme.OR if nArgs == 2 => constantFold2(intValue, _ | _)
3848+
case tpnme.ASR if nArgs == 2 => constantFold2(intValue, _ >> _)
3849+
case tpnme.LSL if nArgs == 2 => constantFold2(intValue, _ << _)
3850+
case tpnme.LSR if nArgs == 2 => constantFold2(intValue, _ >>> _)
38453851
case tpnme.Min if nArgs == 2 => constantFold2(intValue, _ min _)
38463852
case tpnme.Max if nArgs == 2 => constantFold2(intValue, _ max _)
38473853
case _ => None

library/src/scala/compiletime/ops/package.scala

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,55 @@ package object ops {
6262
@infix type /[X <: Int, Y <: Int] <: Int
6363

6464
/** Remainder of the division of `X` by `Y`.
65-
* ```scala
65+
* ```scala
6666
* val mod: 5 % 2 = 1
6767
* ```
6868
*/
6969
@infix type %[X <: Int, Y <: Int] <: Int
7070

71+
/** Binary left shift of `X` by `Y`.
72+
* ```scala
73+
* val lshift: 1 << 2 = 4
74+
* ```
75+
*/
76+
@infix type <<[X <: Int, Y <: Int] <: Int
77+
78+
/** Binary right shift of `X` by `Y`.
79+
* ```scala
80+
* val rshift: 10 >> 1 = 5
81+
* ```
82+
*/
83+
@infix type >>[X <: Int, Y <: Int] <: Int
84+
85+
/** Binary right shift of `X` by `Y`, filling the left with zeros.
86+
* ```scala
87+
* val rshiftzero: 10 >>> 1 = 5
88+
* ```
89+
*/
90+
@infix type >>>[X <: Int, Y <: Int] <: Int
91+
92+
/** Bitwise and of `X` and `Y`.
93+
* ```scala
94+
* val and1: 4 & 4 = 4
95+
* val and2: 10 & 5 = 0
96+
* ```
97+
*/
98+
@infix type &[X <: Int, Y <: Int] <: Int
99+
100+
/** Bitwise or of `X` and `Y`.
101+
* ```scala
102+
* val or: 10 | 11 = 11
103+
* ```
104+
*/
105+
@infix type |[X <: Int, Y <: Int] <: Int
106+
107+
/** Bitwise xor of `X` and `Y`.
108+
* ```scala
109+
* val xor: 10 ^ 30 = 20
110+
* ```
111+
*/
112+
@infix type ^[X <: Int, Y <: Int] <: Int
113+
71114
/** Less-than comparison of two `Int` singleton types.
72115
* ```scala
73116
* val lt1: 4 < 2 = false
@@ -124,7 +167,7 @@ package object ops {
124167

125168
/** Maximum of two `Int` singleton types.
126169
* ```scala
127-
* val abs: Abs[-1] = 1
170+
* val max: Max[-1, 1] = 1
128171
* ```
129172
*/
130173
type Max[X <: Int, Y <: Int] <: Int

tests/neg/singleton-ops-int.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,34 @@ object Test {
7272
val t49: ToString[-1] = "-1"
7373
val t50: ToString[0] = "-0" // error
7474
val t51: ToString[200] = "100" // error
75+
76+
val t52: 1 ^ 2 = 3
77+
val t53: 1 ^ 3 = 3 // error
78+
val t54: -1 ^ -2 = 1
79+
val t55: -1 ^ -3 = 1 // error
80+
81+
val t56: 1 | 2 = 3
82+
val t57: 10 | 12 = 13 // error
83+
val t58: -11 | 12 = -3
84+
val t59: -111 | -10 = 0 // error
85+
86+
val t60: 1 & 1 = 1
87+
val t61: 1 & 2 = 0
88+
val t62: -1 & -3 = 3 // error
89+
val t63: -1 & -1 = 1 // error
90+
91+
val t64: 1 << 1 = 2
92+
val t65: 1 << 2 = 4
93+
val t66: 1 << 3 = 8
94+
val t67: 1 << 4 = 0 // error
95+
96+
val t68: 100 >> 2 = 25
97+
val t69: 123456789 >> 71 = 964506
98+
val t70: -7 >> 3 = -1
99+
val t71: -7 >> 3 = 0 // error
100+
101+
val t72: -1 >>> 10000 = 65535
102+
val t73: -7 >>> 3 = 536870911
103+
val t74: -7 >>> 3 = -1 // error
104+
75105
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import scala.compiletime.ops.boolean._
2-
import scala.compiletime.ops.int._
2+
import scala.compiletime.ops.int.{^ => ^^,_} // must rename int.^ or get clash with boolean.^
33

44
object Test {
55
val t0: 1 + 2 * 3 = 7
66
val t1: (2 * 7 + 1) % 10 = 5
77
val t3: 1 * 1 + 2 * 2 + 3 * 3 + 4 * 4 = 30
88
val t4: true && false || true && true || false ^ false = true
9+
val t5: 100 << 2 >>> 2 >> 2 ^^ 3 | (7 & 7) = 31
910
}

tests/run/singleton-ops-flags.scala

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package example {
2+
3+
import compiletime.S
4+
import compiletime.ops.int.<<
5+
6+
object TastyFlags:
7+
8+
final val EmptyFlags = baseFlags
9+
final val Erased = EmptyFlags.next
10+
final val Internal = Erased.next
11+
final val Inline = Internal.next
12+
final val InlineProxy = Inline.next
13+
final val Opaque = InlineProxy.next
14+
final val Scala2x = Opaque.next
15+
final val Extension = Scala2x.next
16+
final val Given = Extension.next
17+
final val Exported = Given.next
18+
final val NoInits = Exported.next
19+
final val TastyMacro = NoInits.next
20+
final val Enum = TastyMacro.next
21+
final val Open = Enum.next
22+
23+
type LastFlag = Open.idx.type
24+
25+
def (s: FlagSet).debug: String =
26+
if s == EmptyFlags then "EmptyFlags"
27+
else s.toSingletonSets[LastFlag].map ( [n <: Int] => (flag: SingletonFlagSet[n]) => flag match {
28+
case Erased => "Erased"
29+
case Internal => "Internal"
30+
case Inline => "Inline"
31+
case InlineProxy => "InlineProxy"
32+
case Opaque => "Opaque"
33+
case Scala2x => "Scala2x"
34+
case Extension => "Extension"
35+
case Given => "Given"
36+
case Exported => "Exported"
37+
case NoInits => "NoInits"
38+
case TastyMacro => "TastyMacro"
39+
case Enum => "Enum"
40+
case Open => "Open"
41+
}) mkString(" | ")
42+
43+
object opaques:
44+
45+
opaque type FlagSet = Int
46+
opaque type EmptyFlagSet <: FlagSet = 0
47+
opaque type SingletonFlagSet[N <: Int] <: FlagSet = 1 << N
48+
49+
opaque type SingletonSets[N <: Int] = Int
50+
51+
private def [N <: Int](n: N).shift: 1 << N = ( 1 << n ).asInstanceOf
52+
private def [N <: Int](n: N).succ : S[N] = ( n + 1 ).asInstanceOf
53+
54+
final val baseFlags: EmptyFlagSet = 0
55+
56+
def (s: EmptyFlagSet).next: SingletonFlagSet[0] = 1
57+
def [N <: Int: ValueOf](s: SingletonFlagSet[N]).next: SingletonFlagSet[S[N]] = valueOf[N].succ.shift
58+
def [N <: Int: ValueOf](s: SingletonFlagSet[N]).idx: N = valueOf[N]
59+
def [N <: Int](s: FlagSet).toSingletonSets: SingletonSets[N] = s
60+
def (s: FlagSet) | (t: FlagSet): FlagSet = s | t
61+
62+
def [A, N <: Int: ValueOf](ss: SingletonSets[N]).map(f: [t <: Int] => (s: SingletonFlagSet[t]) => A): List[A] =
63+
val maxFlag = valueOf[N]
64+
val buf = List.newBuilder[A]
65+
var current = 0
66+
while (current <= maxFlag) {
67+
val flag = current.shift
68+
if ((flag & ss) != 0) {
69+
buf += f(flag)
70+
}
71+
current += 1
72+
}
73+
buf.result
74+
75+
end opaques
76+
77+
export opaques._
78+
79+
}
80+
81+
82+
import example.TastyFlags._
83+
84+
@main def Test = assert((Open | Given | Inline | Erased).debug == "Erased | Inline | Given | Open")

0 commit comments

Comments
 (0)