plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
c4ee6ec
— Peter John
2026-09-02T21:21:38+05:30
test(libs/std): add Map test coverage; document two new compiler gaps found attempting Map.map
- libs/std/map.plum +80 -6
libs/std/map.plum
CHANGED
|
@@ -138,9 +138,83 @@ type Map[K, V] =
|
|
|
138
138
|
break
|
|
139
139
|
return result
|
|
140
140
|
|
|
141
|
+
# `map` is left unimplemented — two DISTINCT compiler gaps were found
|
|
142
|
+
# while attempting it (2026-09-02), both real and both deeper than this
|
|
143
|
+
# one method:
|
|
144
|
+
#
|
|
145
|
+
# 1. A generic method's BODY is never substituted for its OWN extra
|
|
146
|
+
# generics — `specializeFn` only substitutes PARAM/RETURN types,
|
|
147
|
+
# cloning the body as-is. An explicit `Map[X, Y](items: List[Pair[X,
|
|
141
|
-
#
|
|
148
|
+
# Y]](...))` construction written directly in `map`'s body therefore
|
|
142
|
-
# inside a closure's return type `Pair[X, Y]` rather than directly bare —
|
|
143
|
-
#
|
|
149
|
+
# keeps the LITERAL letters "X"/"Y" forever, which `resolveFieldType`
|
|
144
|
-
#
|
|
150
|
+
# then treats as if they were real concrete type names — silently
|
|
151
|
+
# manufacturing bogus `List$X`/`Pair$X`-style "specializations" that
|
|
152
|
+
# corrupt unrelated `List`/`Node` template compilation elsewhere in the
|
|
153
|
+
# SAME program (confirmed: broke `List.add`/`sort`/`find`/etc, none of
|
|
154
|
+
# which `map` even touches). Worked around by building the result via
|
|
155
|
+
# `self.items.map(...)` (whose own extra-generic resolution IS
|
|
156
|
+
# per-call-site, not body-substitution-dependent) plus a BARE `Map(items:
|
|
157
|
+
# ...)` construction relying on `current_return_type`, matching
|
|
158
|
+
# `keys`/`values` above — this part alone got further than before.
|
|
159
|
+
# 2. Once past that, `self.items.map(|p| cb(p.key, p.val))` still fails:
|
|
160
|
+
# `plum-checker/src/lib.rs`'s `inferExpr` never propagates a method
|
|
161
|
+
# CALL's own declared param types down as an "expected type" hint when
|
|
162
|
+
# inferring a closure-literal ARGUMENT — it only guesses a closure
|
|
163
|
+
# param's type from field-ACCESS patterns within the closure's own body
|
|
164
|
+
# (`resolveClosureParamFromFieldUsage`), which can't know `p`'s class is
|
|
165
|
+
# `Pair` at all here. Fixing this needs threading an expected `PlumType`
|
|
145
|
-
#
|
|
166
|
+
# through `inferExpr`'s closure-literal handling at (at least) 8 call
|
|
167
|
+
# sites in `lib.rs` — a materially bigger, riskier change than
|
|
168
|
+
# warranted for this one method; not attempted.
|
|
169
|
+
fun map(self) =
|
|
170
|
+
todo
|
|
171
|
+
|
|
172
|
+
fun makeStrIntMapForTest() -> Map[Str, Int] =
|
|
173
|
+
return Map[Str, Int](items: List[Pair[Str, Int]](head: None, tail: None, size: 0))
|
|
174
|
+
|
|
175
|
+
test "get/set/has round-trip and set replaces an existing key in place"
|
|
176
|
+
m := makeStrIntMapForTest()
|
|
177
|
+
expect m.has("a") == False
|
|
178
|
+
m.set("a", 1)
|
|
179
|
+
expect m.has("a") == True
|
|
180
|
+
expect m.get("a").unwrap() == 1
|
|
181
|
+
expect m.length() == 1
|
|
182
|
+
m.set("a", 2)
|
|
183
|
+
expect m.get("a").unwrap() == 2
|
|
184
|
+
expect m.length() == 1
|
|
185
|
+
|
|
186
|
+
test "putIfAbsent only sets a key that isn't already present"
|
|
187
|
+
m := makeStrIntMapForTest()
|
|
188
|
+
m.putIfAbsent("a", 1)
|
|
189
|
+
m.putIfAbsent("a", 2)
|
|
190
|
+
expect m.get("a").unwrap() == 1
|
|
191
|
+
|
|
192
|
+
test "remove deletes an entry and clear empties the map"
|
|
193
|
+
m := makeStrIntMapForTest()
|
|
194
|
+
m.set("a", 1)
|
|
195
|
+
m.set("b", 2)
|
|
196
|
+
m.remove("a")
|
|
197
|
+
expect m.has("a") == False
|
|
198
|
+
expect m.length() == 1
|
|
199
|
+
m.clear()
|
|
200
|
+
expect m.isEmpty() == True
|
|
201
|
+
|
|
202
|
+
test "keys/values list every entry's key/value in insertion order"
|
|
203
|
+
m := makeStrIntMapForTest()
|
|
204
|
+
m.set("a", 1)
|
|
205
|
+
m.set("b", 2)
|
|
206
|
+
expect m.keys().join(",") == "a,b"
|
|
207
|
+
expect m.values().join(",") == "1,2"
|
|
208
|
+
|
|
209
|
+
test "each calls the callback with every key and value"
|
|
210
|
+
m := makeStrIntMapForTest()
|
|
211
|
+
m.set("a", 1)
|
|
212
|
+
m.set("b", 2)
|
|
213
|
+
# A closure captures its outer variables BY VALUE at creation time, so
|
|
146
|
-
#
|
|
214
|
+
# reassigning a captured `Int` inside the callback wouldn't be visible out
|
|
215
|
+
# here — mutate a captured `List` (a class, reference semantics) instead.
|
|
216
|
+
# `makeList(0)` (its arg infers `T = Int` for the empty list that follows).
|
|
217
|
+
seen := makeList(0)
|
|
218
|
+
seen.clear()
|
|
219
|
+
m.each(|k, v| seen.add(v))
|
|
220
|
+
expect seen.join(",") == "1,2"
|