Skip to content

Commit 5d16082

Browse files
Simplify core.Renamer to produce smaller symbols (#938)
General idea: instead of building `name_123_109238_190238_109283019283_1092830192838019283` chains, what if we use the automagic `fresh.next` to get just a darn fresh number? This will limit the identifiers to stuff like `b_k2_123_10928310283`, which is--at least to me--a pretty big improvement. We use the full capabilities of a `Symbol`: it has a `name` (which is really a `String`) and a fresh `id` that gets assigned lazily. Instead of changing the name to be `${oldName}_${oldId}` and then putting a fresh `id` on top of that (to get `${oldName}_${oldId}_${newId}`), we just make a new `Symbol` with the same name, which forces a new, _fresher_, `id`. --------- Co-authored-by: Marcial Gaißert <[email protected]>
1 parent fb22f27 commit 5d16082

File tree

11 files changed

+341
-105
lines changed

11 files changed

+341
-105
lines changed

effekt/jvm/src/test/scala/effekt/core/CoreTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ trait CoreTests extends munit.FunSuite {
4646
expected: ModuleDecl,
4747
clue: => Any = "values are not alpha-equivalent",
4848
names: Names = Names(defaultNames))(using Location): Unit = {
49-
val renamer = Renamer(names, "$")
49+
val renamer = TestRenamer(names, "$")
5050
shouldBeEqual(renamer(obtained), renamer(expected), clue)
5151
}
5252
def assertAlphaEquivalentStatements(obtained: Stmt,
5353
expected: Stmt,
5454
clue: => Any = "values are not alpha-equivalent",
5555
names: Names = Names(defaultNames))(using Location): Unit = {
56-
val renamer = Renamer(names, "$")
56+
val renamer = TestRenamer(names, "$")
5757
shouldBeEqual(renamer(obtained), renamer(expected), clue)
5858
}
5959
def parse(input: String,

effekt/jvm/src/test/scala/effekt/core/OptimizerTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class OptimizerTests extends CoreTests {
2222
val pExpected = parse(moduleHeader + transformed, "expected", names)
2323

2424
// the parser is not assigning symbols correctly, so we need to run renamer first
25-
val renamed = Renamer(names).rewrite(pInput)
25+
val renamed = TestRenamer(names).rewrite(pInput)
2626

2727
val obtained = transform(renamed)
2828
assertAlphaEquivalent(obtained, pExpected, "Not transformed to")
Lines changed: 26 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package effekt.core
2-
import effekt.symbols
32

3+
/**
4+
* This is testing the main/core.Renamer using the test/core.TestRenamer.
5+
*/
46
class RenamerTests extends CoreTests {
57

6-
def assertRenamedTo(input: String,
7-
renamed: String,
8-
clue: => Any = "Not renamed to given value",
9-
names: Names = Names(defaultNames))(using munit.Location) = {
8+
/**
9+
* Check that the renamed input preserves alpha-equivalence using [[assertAlphaEquivalent]]
10+
*/
11+
def assertRenamingPreservesAlpha(input: String,
12+
clue: => Any = "Not renamed to given value",
13+
names: Names = Names(defaultNames))(using munit.Location) = {
1014
val pInput = parse(input, "input", names)
11-
val pExpected = parse(renamed, "expected", names)
12-
val renamer = new Renamer(names, "renamed") // use "renamed" as prefix so we can refer to it
15+
val renamer = new Renamer(names, "renamed")
1316
val obtained = renamer(pInput)
14-
shouldBeEqual(obtained, pExpected, clue)
17+
assertAlphaEquivalent(obtained, pInput, clue)
1518
}
1619

1720
test("No bound local variables"){
@@ -22,69 +25,46 @@ class RenamerTests extends CoreTests {
2225
| return (bar: (Int) => Int @ {})(baz:Int)
2326
|}
2427
|""".stripMargin
25-
assertRenamedTo(code, code)
28+
assertRenamingPreservesAlpha(code)
2629
}
2730

2831
test("val binding"){
29-
val input =
32+
val code =
3033
"""module main
3134
|
3235
|def foo = { () =>
3336
| val x = (foo:(Int)=>Int@{})(4) ;
3437
| return x:Int
3538
|}
3639
|""".stripMargin
37-
val expected =
38-
"""module main
39-
|
40-
|def foo = { () =>
41-
| val renamed1 = (foo:(Int)=>Int@{})(4);
42-
| return renamed1:Int
43-
|}
44-
|""".stripMargin
45-
assertRenamedTo(input, expected)
40+
assertRenamingPreservesAlpha(code)
4641
}
4742

4843
test("var binding"){
49-
val input =
44+
val code =
5045
"""module main
5146
|
5247
|def foo = { () =>
5348
| var x @ global = (foo:(Int)=>Int@{})(4) ;
5449
| return x:Int
5550
|}
5651
|""".stripMargin
57-
val expected =
58-
"""module main
59-
|
60-
|def foo = { () =>
61-
| var renamed1 @ global = (foo:(Int)=>Int@{})(4);
62-
| return renamed1:Int
63-
|}
64-
|""".stripMargin
65-
assertRenamedTo(input, expected)
52+
assertRenamingPreservesAlpha(code)
6653
}
6754

6855
test("function (value) parameters"){
69-
val input =
56+
val code =
7057
"""module main
7158
|
7259
|def foo = { (x:Int) =>
7360
| return x:Int
7461
|}
7562
|""".stripMargin
76-
val expected =
77-
"""module main
78-
|
79-
|def foo = { (renamed1:Int) =>
80-
| return renamed1:Int
81-
|}
82-
|""".stripMargin
83-
assertRenamedTo(input, expected)
63+
assertRenamingPreservesAlpha(code)
8464
}
8565

8666
test("match clauses"){
87-
val input =
67+
val code =
8868
"""module main
8969
|
9070
|type Data { X(a:Int, b:Int) }
@@ -94,39 +74,22 @@ class RenamerTests extends CoreTests {
9474
| }
9575
|}
9676
|""".stripMargin
97-
val expected =
98-
"""module main
99-
|
100-
|type Data { X(a:Int, b:Int) }
101-
|def foo = { () =>
102-
| 12 match {
103-
| X : {(renamed1:Int, renamed2:Int) => return renamed1:Int }
104-
| }
105-
|}
106-
|""".stripMargin
107-
assertRenamedTo(input, expected)
77+
assertRenamingPreservesAlpha(code)
10878
}
10979

11080
test("type parameters"){
111-
val input =
81+
val code =
11282
"""module main
11383
|
11484
|def foo = { ['A](a: A) =>
11585
| return a:Identity[A]
11686
|}
11787
|""".stripMargin
118-
val expected =
119-
"""module main
120-
|
121-
|def foo = { ['renamed1](renamed2: renamed1) =>
122-
| return renamed2:Identity[renamed1]
123-
|}
124-
|""".stripMargin
125-
assertRenamedTo(input, expected)
88+
assertRenamingPreservesAlpha(code)
12689
}
12790

12891
test("pseudo recursive"){
129-
val input =
92+
val code =
13093
""" module main
13194
|
13295
| def bar = { () => return 1 }
@@ -136,22 +99,10 @@ class RenamerTests extends CoreTests {
13699
| (foo : () => Unit @ {})()
137100
| }
138101
|""".stripMargin
139-
140-
val expected =
141-
""" module main
142-
|
143-
| def bar = { () => return 1 }
144-
| def main = { () =>
145-
| def renamed1 = { () => (bar : () => Unit @ {})() }
146-
| def renamed2 = { () => return 2 }
147-
| (renamed1 : () => Unit @ {})()
148-
| }
149-
|""".stripMargin
150-
151-
assertRenamedTo(input, expected)
102+
assertRenamingPreservesAlpha(code)
152103
}
153104
test("shadowing let bindings"){
154-
val input =
105+
val code =
155106
""" module main
156107
|
157108
| def main = { () =>
@@ -160,17 +111,6 @@ class RenamerTests extends CoreTests {
160111
| return x:Int
161112
| }
162113
|""".stripMargin
163-
164-
val expected =
165-
""" module main
166-
|
167-
| def main = { () =>
168-
| let renamed1 = 1
169-
| let renamed2 = 2
170-
| return renamed2:Int
171-
| }
172-
|""".stripMargin
173-
174-
assertRenamedTo(input, expected)
114+
assertRenamingPreservesAlpha(code)
175115
}
176116
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package effekt.core
2+
3+
import effekt.{ core, symbols }
4+
import effekt.context.Context
5+
6+
/**
7+
* Freshens bound names in a given term for tests.
8+
* Please use this _only_ for tests. Otherwise, prefer [[effekt.core.Renamer]].
9+
*
10+
* @param names used to look up a reference by name to resolve to the same symbols.
11+
* This is only used by tests to deterministically rename terms and check for
12+
* alpha-equivalence.
13+
* @param prefix if the prefix is empty, the original name will be used as a prefix
14+
*
15+
* @param C the context is used to copy annotations from old symbols to fresh symbols
16+
*/
17+
class TestRenamer(names: Names = Names(Map.empty), prefix: String = "") extends core.Tree.Rewrite {
18+
19+
// list of scopes that map bound symbols to their renamed variants.
20+
private var scopes: List[Map[Id, Id]] = List.empty
21+
22+
// Here we track ALL renamings
23+
var renamed: Map[Id, Id] = Map.empty
24+
25+
private var suffix: Int = 0
26+
27+
def freshIdFor(id: Id): Id =
28+
suffix = suffix + 1
29+
val uniqueName = if prefix.isEmpty then id.name.name + "_" + suffix.toString else prefix + suffix.toString
30+
names.idFor(uniqueName)
31+
32+
def withBindings[R](ids: List[Id])(f: => R): R =
33+
val before = scopes
34+
try {
35+
val newScope = ids.map { x => x -> freshIdFor(x) }.toMap
36+
scopes = newScope :: scopes
37+
renamed = renamed ++ newScope
38+
f
39+
} finally { scopes = before }
40+
41+
/** Alias for withBindings(List(id)){...} */
42+
def withBinding[R](id: Id)(f: => R): R = withBindings(List(id))(f)
43+
44+
// free variables are left untouched
45+
override def id: PartialFunction[core.Id, core.Id] = {
46+
case id => scopes.collectFirst {
47+
case bnds if bnds.contains(id) => bnds(id)
48+
}.getOrElse(id)
49+
}
50+
51+
override def stmt: PartialFunction[Stmt, Stmt] = {
52+
case core.Def(id, block, body) =>
53+
// can be recursive
54+
withBinding(id) { core.Def(rewrite(id), rewrite(block), rewrite(body)) }
55+
56+
case core.Let(id, tpe, binding, body) =>
57+
val resolvedBinding = rewrite(binding)
58+
withBinding(id) { core.Let(rewrite(id), rewrite(tpe), resolvedBinding, rewrite(body)) }
59+
60+
case core.Val(id, tpe, binding, body) =>
61+
val resolvedBinding = rewrite(binding)
62+
withBinding(id) { core.Val(rewrite(id), rewrite(tpe), resolvedBinding, rewrite(body)) }
63+
64+
case core.Alloc(id, init, reg, body) =>
65+
val resolvedInit = rewrite(init)
66+
val resolvedReg = rewrite(reg)
67+
withBinding(id) { core.Alloc(rewrite(id), resolvedInit, resolvedReg, rewrite(body)) }
68+
69+
case core.Var(ref, init, capt, body) =>
70+
val resolvedInit = rewrite(init)
71+
val resolvedCapt = rewrite(capt)
72+
withBinding(ref) { core.Var(rewrite(ref), resolvedInit, resolvedCapt, rewrite(body)) }
73+
74+
case core.Get(id, tpe, ref, capt, body) =>
75+
val resolvedRef = rewrite(ref)
76+
val resolvedCapt = rewrite(capt)
77+
withBinding(id) { core.Get(rewrite(id), rewrite(tpe), resolvedRef, resolvedCapt, rewrite(body)) }
78+
79+
}
80+
81+
override def block: PartialFunction[Block, Block] = {
82+
case Block.BlockLit(tparams, cparams, vparams, bparams, body) =>
83+
withBindings(tparams ++ cparams ++ vparams.map(_.id) ++ bparams.map(_.id)) {
84+
Block.BlockLit(tparams map rewrite, cparams map rewrite, vparams map rewrite, bparams map rewrite,
85+
rewrite(body))
86+
}
87+
}
88+
89+
override def rewrite(o: Operation): Operation = o match {
90+
case Operation(name, tparams, cparams, vparams, bparams, body) =>
91+
withBindings(tparams ++ cparams ++ vparams.map(_.id) ++ bparams.map(_.id)) {
92+
Operation(name,
93+
tparams map rewrite,
94+
cparams map rewrite,
95+
vparams map rewrite,
96+
bparams map rewrite,
97+
rewrite(body))
98+
}
99+
}
100+
101+
def apply(m: core.ModuleDecl): core.ModuleDecl =
102+
suffix = 0
103+
m match {
104+
case core.ModuleDecl(path, includes, declarations, externs, definitions, exports) =>
105+
core.ModuleDecl(path, includes, declarations, externs, definitions map rewrite, exports)
106+
}
107+
108+
def apply(s: Stmt): Stmt = {
109+
suffix = 0
110+
rewrite(s)
111+
}
112+
}
113+
114+
object TestRenamer {
115+
def rename(b: Block): Block = Renamer().rewrite(b)
116+
def rename(b: BlockLit): (BlockLit, Map[Id, Id]) =
117+
val renamer = Renamer()
118+
val res = renamer.rewrite(b)
119+
(res, renamer.renamed)
120+
}

0 commit comments

Comments
 (0)