diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2026-03-23 21:29:21 +0100 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2026-03-29 22:32:57 +0200 |
| commit | f0e22ae62c143fbf00bde368c2adbc848cff5ff5 (patch) | |
| tree | c1270b04b087752a1308dfc3e603a1f4d8a1e36a | |
| parent | e3a708ed79d0f3e03aed0aaf044a447df16646ea (diff) | |
records: Warn about shadowing due to inherited field value bindings.
This is a followup to a7c8e68dc51144a6d3981b770aca9c4897fc7c0c: this commit
introduced a new binding in the body of field values, which could silently
shadow outer bindings. This new warning catches potentially unwanted
shadowing.
* guix/records.scm (make-syntactic-constructor)[check-shadowing]: New
procedure.
[wrap-field-value]: Use it.
* tests/records.scm ("define-record-type* & inherited value shadowing"): New
test.
Change-Id: I81ad14cf10da7213e9f8db987c8b0bd4c41acba2
Signed-off-by: Ludovic Courtès <ludo@gnu.org>
Merges: #7424
| -rw-r--r-- | guix/records.scm | 19 | ||||
| -rw-r--r-- | tests/records.scm | 20 |
2 files changed, 39 insertions, 0 deletions
diff --git a/guix/records.scm b/guix/records.scm index bf746d3b5d9..52d03f73942 100644 --- a/guix/records.scm +++ b/guix/records.scm | |||
| @@ -24,6 +24,7 @@ | |||
| 24 | #:use-module (srfi srfi-26) | 24 | #:use-module (srfi srfi-26) |
| 25 | #:use-module (ice-9 match) | 25 | #:use-module (ice-9 match) |
| 26 | #:use-module (ice-9 rdelim) | 26 | #:use-module (ice-9 rdelim) |
| 27 | #:autoload (system syntax) (syntax-local-binding) | ||
| 27 | #:export (define-record-type* | 28 | #:export (define-record-type* |
| 28 | this-record | 29 | this-record |
| 29 | 30 | ||
| @@ -206,6 +207,23 @@ of TYPE matches the expansion-time ABI." | |||
| 206 | index | 207 | index |
| 207 | (loop rest (+ 1 index)))))))) | 208 | (loop rest (+ 1 index)))))))) |
| 208 | 209 | ||
| 210 | (define (check-shadowing identifier) | ||
| 211 | ;; Warn if IDENTIFIER shadows a local binding. | ||
| 212 | ;; Note: not using (guix diagnostics) to remain independent of | ||
| 213 | ;; other Guix modules. | ||
| 214 | (when (eq? 'lexical (syntax-local-binding identifier)) | ||
| 215 | (format (current-warning-port) | ||
| 216 | "~a: inherited field binding '~a' of \ | ||
| 217 | record type '~a' shadows local variable~%" | ||
| 218 | (match (syntax-source identifier) | ||
| 219 | (#f "<unknown-location>") | ||
| 220 | (lst (format #f "~a:~a:~a" | ||
| 221 | (assq-ref lst 'filename) | ||
| 222 | (and=> (assq-ref lst 'line) 1+) | ||
| 223 | (assq-ref lst 'column)))) | ||
| 224 | (syntax->datum identifier) | ||
| 225 | (syntax->datum #'type)))) | ||
| 226 | |||
| 209 | (define* (wrap-field-value f value #:optional parent) | 227 | (define* (wrap-field-value f value #:optional parent) |
| 210 | ;; Wrap VALUE, the value of field F, such that its sanitizer is | 228 | ;; Wrap VALUE, the value of field F, such that its sanitizer is |
| 211 | ;; called and its properties (thunked, delayed) honored. When | 229 | ;; called and its properties (thunked, delayed) honored. When |
| @@ -222,6 +240,7 @@ of TYPE matches the expansion-time ABI." | |||
| 222 | #`((struct-ref #,parent | 240 | #`((struct-ref #,parent |
| 223 | #,(field-index f)) | 241 | #,(field-index f)) |
| 224 | #,this-identifier))) | 242 | #,this-identifier))) |
| 243 | (check-shadowing f) | ||
| 225 | #`(lambda (x) | 244 | #`(lambda (x) |
| 226 | (syntax-parameterize ((#,this-identifier | 245 | (syntax-parameterize ((#,this-identifier |
| 227 | (lambda (s) | 246 | (lambda (s) |
diff --git a/tests/records.scm b/tests/records.scm index 9c071334d50..57a21d2effc 100644 --- a/tests/records.scm +++ b/tests/records.scm | |||
| @@ -302,6 +302,26 @@ | |||
| 302 | => | 302 | => |
| 303 | (,(foo-bar child) ,(foo-baz child)))))) | 303 | (,(foo-bar child) ,(foo-baz child)))))) |
| 304 | 304 | ||
| 305 | (test-assert "define-record-type* & inherited value shadowing" | ||
| 306 | (let ((exp '(begin | ||
| 307 | (define-record-type* <foo> foo make-foo | ||
| 308 | foo? | ||
| 309 | (bar foo-bar) | ||
| 310 | (baz foo-baz (thunked))) | ||
| 311 | |||
| 312 | (let ((x (foo (bar 1) (baz 2))) | ||
| 313 | (baz 123)) | ||
| 314 | ;; Below, the 'baz' binding for the inherited field value | ||
| 315 | ;; shadows the 'baz' above, which should trigger a warning. | ||
| 316 | (foo (inherit x) | ||
| 317 | (baz (* baz 2))))))) | ||
| 318 | (string-contains | ||
| 319 | (call-with-output-string | ||
| 320 | (lambda (port) | ||
| 321 | (parameterize ((current-warning-port port)) | ||
| 322 | (eval exp (test-module))))) | ||
| 323 | "shadows local variable"))) | ||
| 324 | |||
| 305 | (test-assert "define-record-type* & delayed" | 325 | (test-assert "define-record-type* & delayed" |
| 306 | (begin | 326 | (begin |
| 307 | (define-record-type* <foo> foo make-foo | 327 | (define-record-type* <foo> foo make-foo |
