Commitf9eb8d64Recorded23 Feb 2026Repositorysigil-repl

Add procedure specifications with (: ...) syntax

Message

Implement Phase 1 of procedure specs — machine-readable predicate-based annotations stored in procedure metadata with zero runtime cost.

The (: ...) form is recognized as the first body expression in define and lambda. If present, it's stripped from the body and stored as metadata under the 'spec key.

Core changes: - Add spec-form?, extract-spec, %set-spec! helpers to (sigil core) - Modify lambda-transformer to detect and extract spec forms - Modify define-transformer to handle specs in function shorthand - Add procedure-spec accessor exported from (sigil core)

New module (sigil spec) with: - spec->string: format spec list as readable string - any?: always-true predicate for "any type" specs - void?: test if value is the undefined/void value

Integration: - REPL ,doc command displays spec line when present - nREPL doc/describe responses include :spec field - MCP tools show spec in lookup and suggest-import output

Changed
 src/sigil/repl/commands.sgl | 30 ++++++++++++++++++++++--------
 1 file changed, 22 insertions(+), 8 deletions(-)
Diff
src/sigil/repl/commands.sglmodified
@@ -14,6 +14,7 @@
14
(sigil hooks)
15
(sigil ansi)
16
(sigil string)
+17
(sigil spec)
18
(sigil docs lookup)
19
(sigil docs search))
20
@@ -241,7 +242,7 @@
242
(loop (cdr imports)))))))
243
244
;; Look up documentation for a symbol in a module
244
;; Returns (kind signature summary full) or #f
+245
;; Returns (kind signature summary full spec-string) or #f
246
(define (lookup-symbol-doc mod sym)
247
(let ((mod-name (module-name mod))
248
(bindings (module-bindings mod)))
@@ -253,7 +254,10 @@
254
;; Try home module first, then current module
255
(export-doc (or (and home-mod-name
256
(get-export-details home-mod-name sym))
256
(get-export-details mod-name sym))))
+257
(get-export-details mod-name sym)))
+258
;; Get spec from runtime value if it's a procedure
+259
(spec-str (and val (procedure? val)
+260
(spec->string (procedure-spec val)))))
261
(cond
262
;; Found JSON documentation
263
(export-doc
@@ -268,7 +272,8 @@
272
(list kind
273
final-sig
274
(if doc (extract-first-line doc) #f)
271
doc))))
+275
doc
+276
spec-str))))
277
;; No JSON doc - check if it's a runtime binding we know about
278
((and val (procedure? val))
279
(let* ((arity (procedure-arity val))
@@ -276,18 +281,21 @@
281
(list (if arity 'native 'procedure)
282
sig
283
#f
279
#f)))
+284
#f
+285
spec-str)))
286
;; Non-procedure binding
287
(val
288
(list 'variable
289
(symbol->string sym)
290
#f
+291
#f
292
#f))
293
;; Check if it's a syntax (macro) in current module
294
((memq sym (module-syntaxes mod))
295
(list 'syntax
296
(format "(~a ...)" sym)
297
#f
+298
#f
299
#f))
300
;; Check imported modules for syntax
301
(else
@@ -302,11 +310,13 @@
310
(if (export-details-description doc)
311
(extract-first-line (export-details-description doc))
312
#f)
305
(export-details-description doc))
+313
(export-details-description doc)
+314
#f)
315
;; No JSON doc, just show it's a syntax
316
(list 'syntax
317
(format "(~a ...)" sym)
318
#f
+319
#f
320
#f)))
321
#f)))))))
322
@@ -397,16 +407,20 @@
407
(else
408
(loop rest (cons line result) #f)))))))))
409
400
;; Print documentation nicely (uses 4-element format)
+410
;; Print documentation nicely (uses 5-element format)
411
(define (print-doc sym info)
412
(let ((kind (car info))
413
(sig (cadr info))
404
;; summary is caddr, full doc is cadddr
405
(doc (cadddr info)))
+414
;; summary is caddr, full doc is cadddr, spec-str is 5th
+415
(doc (cadddr info))
+416
(spec-str (and (> (length info) 4) (list-ref info 4))))
417
(display sig)
418
(display " -> ")
419
(display kind)
420
(newline)
+421
(when spec-str
+422
(display (a:dim (format " Spec: ~a" spec-str)))
+423
(newline))
424
(if doc
425
(let ((formatted (format-docstring doc)))
426
(newline)