plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


c4ee6ecPeter John 2026-09-02T21:21:38+05:30
test(libs/std): add Map test coverage; document two new compiler gaps found attempting Map.map
Files changed (1) hide show
  1. 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
- # `map` (which would need TWO new generic params, `X`/`Y`, both nested
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
- # `resolveMethodOwnGenerics`/`methodOwnGenericParams` only handle a
149
+ # keeps the LITERAL letters "X"/"Y" forever, which `resolveFieldType`
144
- # closure-typed param whose return IS directly the bare extra-generic
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
- # letter, like `List.map`'s `cb: fn(T) -> U`) is left unimplemented; same
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
- # limitation noted on `Option`'s `andThen` in `libs/std/option.plum`.
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"