Skip to content

Commit 7d5e7d0

Browse files
committed
Type bind! and import! so a missing bang on them is reported
Both are interpreter forms that exist for their effect, but neither carried a type declaration. `metta check` decides an operator is an action by reading the unit return type `(->)` off its signature, so with no signature to read it could not see either of them: a top-level `(bind! &s (new-space))` or `(import! &self lib)` written without its leading `!` was stored as data, the token stayed unbound and the module never loaded, and nothing said so. The first symptom was a later match quietly finding nothing. Declaring the types they already have fixes it, and the declarations are exact: the token and the module name are Atom-typed because neither is reduced, while bind!'s value is %Undefined% because it is, as in (bind! &s (new-space)). The full suite passes unchanged, which is the check that the argument evaluation these declarations imply is the evaluation that was already happening.
1 parent 73a63a9 commit 7d5e7d0

2 files changed

Lines changed: 17 additions & 0 deletions

File tree

packages/core/src/diagnose.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ describe("analyzeSource — top-level actions the interpreter never runs", () =>
214214
expect(actionsOf("(likes Sam pizza)")).toEqual([]);
215215
});
216216

217+
it("flags a bare bind! or import!, which bind nothing and load nothing", () => {
218+
// Both are interpreter forms that exist for their effect. Until they carried a unit return type this
219+
// check could not see them, so a missing `!` left the token unbound or the module unloaded in silence.
220+
expect(actionsOf("(bind! &s (new-space))")).toHaveLength(1);
221+
expect(actionsOf("(import! &self lib)")).toHaveLength(1);
222+
expect(actionsOf("!(bind! &s (new-space))")).toEqual([]);
223+
expect(actionsOf("!(import! &self lib)")).toEqual([]);
224+
});
225+
217226
it("defers to the arity error when the call matches no declared overload", () => {
218227
expect(analyzeSource("(assertEqual 1)", cfg).map((d) => d.code)).toEqual(["arity-mismatch"]);
219228
});

packages/core/src/stdlib.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ export const STDLIB_SRC = `
119119
(= (log! $level $payload)
120120
(if (log-enabled? $level) (println! (Log $level $payload)) ()))
121121
122+
; bind! and import! are interpreter forms that exist for their effect, but neither carried a type, so a
123+
; top-level one written without its bang was stored as data and nothing reported it: the token was never
124+
; bound, the module never loaded, and the first symptom was a later match failing. Declaring the unit
125+
; return type is what lets the checker see them. The token and the module name are Atom-typed because
126+
; neither is reduced; bind!'s value is %Undefined% because it is, as in (bind! &s (new-space)).
127+
(: bind! (-> Atom %Undefined% (->)))
128+
(: import! (-> Atom Atom (->)))
129+
122130
; Partial application is ordinary PeTTa behavior. A known under-applied head becomes
123131
; (partial head args); applying that closure rebuilds the fuller call and evaluates it.
124132
(: partial (-> Atom Expression Atom))

0 commit comments

Comments
 (0)