Skip to content

Commit cb61aca

Browse files
Merge pull request #8377 from bishabosha/add-singleton-ops-bitwise
Add bitwise Int compiletime operations
2 parents 84f2e41 + e2b0de0 commit cb61aca

File tree

7 files changed

+191
-24
lines changed

7 files changed

+191
-24
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,7 @@ class Definitions {
954954
tpnme.Plus, tpnme.Minus, tpnme.Times, tpnme.Div, tpnme.Mod,
955955
tpnme.Lt, tpnme.Gt, tpnme.Ge, tpnme.Le,
956956
tpnme.Abs, tpnme.Negate, tpnme.Min, tpnme.Max, tpnme.ToString,
957+
tpnme.Xor, tpnme.BitwiseAnd, tpnme.BitwiseOr, tpnme.ASR, tpnme.LSL, tpnme.LSR
957958
)
958959
private val compiletimePackageBooleanTypes: Set[Name] = Set(tpnme.Not, tpnme.Xor, tpnme.And, tpnme.Or)
959960
private val compiletimePackageStringTypes: Set[Name] = Set(tpnme.Plus)

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

+23-21
Original file line numberDiff line numberDiff line change
@@ -208,27 +208,29 @@ object StdNames {
208208
final val IOOBException: N = "IndexOutOfBoundsException"
209209
final val FunctionXXL: N = "FunctionXXL"
210210

211-
final val Abs: N = "Abs"
212-
final val And: N = "&&"
213-
final val Div: N = "/"
214-
final val Equals: N = "=="
215-
final val Ge: N = ">="
216-
final val Gt: N = ">"
217-
final val Le: N = "<="
218-
final val Lt: N = "<"
219-
final val Max: N = "Max"
220-
final val Min: N = "Min"
221-
final val Minus: N = "-"
222-
final val Mod: N = "%"
223-
final val Negate: N = "Negate"
224-
final val Not: N = "!"
225-
final val NotEquals: N = "!="
226-
final val Or: N = "||"
227-
final val Plus: N = "+"
228-
final val S: N = "S"
229-
final val Times: N = "*"
230-
final val ToString: N = "ToString"
231-
final val Xor: N = "^"
211+
final val Abs: N = "Abs"
212+
final val And: N = "&&"
213+
final val BitwiseAnd: N = "BitwiseAnd"
214+
final val BitwiseOr: N = "BitwiseOr"
215+
final val Div: N = "/"
216+
final val Equals: N = "=="
217+
final val Ge: N = ">="
218+
final val Gt: N = ">"
219+
final val Le: N = "<="
220+
final val Lt: N = "<"
221+
final val Max: N = "Max"
222+
final val Min: N = "Min"
223+
final val Minus: N = "-"
224+
final val Mod: N = "%"
225+
final val Negate: N = "Negate"
226+
final val Not: N = "!"
227+
final val NotEquals: N = "!="
228+
final val Or: N = "||"
229+
final val Plus: N = "+"
230+
final val S: N = "S"
231+
final val Times: N = "*"
232+
final val ToString: N = "ToString"
233+
final val Xor: N = "^"
232234

233235
final val ClassfileAnnotation: N = "ClassfileAnnotation"
234236
final val ClassManifest: N = "ClassManifest"

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

+6
Original file line numberDiff line numberDiff line change
@@ -3835,6 +3835,12 @@ object Types {
38353835
case tpnme.Gt if nArgs == 2 => constantFold2(intValue, _ > _)
38363836
case tpnme.Ge if nArgs == 2 => constantFold2(intValue, _ >= _)
38373837
case tpnme.Le if nArgs == 2 => constantFold2(intValue, _ <= _)
3838+
case tpnme.Xor if nArgs == 2 => constantFold2(intValue, _ ^ _)
3839+
case tpnme.BitwiseAnd if nArgs == 2 => constantFold2(intValue, _ & _)
3840+
case tpnme.BitwiseOr if nArgs == 2 => constantFold2(intValue, _ | _)
3841+
case tpnme.ASR if nArgs == 2 => constantFold2(intValue, _ >> _)
3842+
case tpnme.LSL if nArgs == 2 => constantFold2(intValue, _ << _)
3843+
case tpnme.LSR if nArgs == 2 => constantFold2(intValue, _ >>> _)
38383844
case tpnme.Min if nArgs == 2 => constantFold2(intValue, _ min _)
38393845
case tpnme.Max if nArgs == 2 => constantFold2(intValue, _ max _)
38403846
case _ => None

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

+45-2
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,40 @@ 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 xor of `X` and `Y`.
93+
* ```scala
94+
* val xor: 10 ^ 30 = 20
95+
* ```
96+
*/
97+
@infix type ^[X <: Int, Y <: Int] <: Int
98+
7199
/** Less-than comparison of two `Int` singleton types.
72100
* ```scala
73101
* val lt1: 4 < 2 = false
@@ -100,6 +128,21 @@ package object ops {
100128
*/
101129
@infix type <=[X <: Int, Y <: Int] <: Boolean
102130

131+
/** Bitwise and of `X` and `Y`.
132+
* ```scala
133+
* val and1: BitwiseAnd[4, 4] = 4
134+
* val and2: BitwiseAnd[10, 5] = 0
135+
* ```
136+
*/
137+
type BitwiseAnd[X <: Int, Y <: Int] <: Int
138+
139+
/** Bitwise or of `X` and `Y`.
140+
* ```scala
141+
* val or: BitwiseOr[10, 11] = 11
142+
* ```
143+
*/
144+
type BitwiseOr[X <: Int, Y <: Int] <: Int
145+
103146
/** Absolute value of an `Int` singleton type.
104147
* ```scala
105148
* val abs: Abs[-1] = 1
@@ -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

+30
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: BitwiseOr[1, 2] = 3
82+
val t57: BitwiseOr[10, 12] = 13 // error
83+
val t58: BitwiseOr[-11, 12] = -3
84+
val t59: BitwiseOr[-111, -10] = 0 // error
85+
86+
val t60: BitwiseAnd[1, 1] = 1
87+
val t61: BitwiseAnd[1, 2] = 0
88+
val t62: BitwiseAnd[-1, -3] = 3 // error
89+
val t63: BitwiseAnd[-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
}
+2-1
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: BitwiseOr[100 << 2 >>> 2 >> 2 ^^ 3, BitwiseAnd[7, 7]] = 31
910
}

tests/run/singleton-ops-flags.scala

+84
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)