From 97ef62b7f410966252a866bcc779b797637c096d Mon Sep 17 00:00:00 2001 From: alex-eg Date: Wed, 16 Sep 2026 01:18:20 +0300 Subject: [PATCH] resolve typedefs in get-fields and map-fields Now these macro helpers take in account the possibility of typedefing one type to another, and correctly find the one intended --- tests/modules/Makefile | 2 +- tests/modules/greet-app.sex | 3 +++ tests/modules/greet.sex | 2 ++ tests/types.scm | 36 ++++++++++++++++++++++++++++++ types.scm | 44 ++++++++++++++++++++++++++++--------- 5 files changed, 76 insertions(+), 11 deletions(-) diff --git a/tests/modules/Makefile b/tests/modules/Makefile index 1d11462..1821967 100644 --- a/tests/modules/Makefile +++ b/tests/modules/Makefile @@ -14,7 +14,7 @@ SEXC ?= ../../sexc -EXPECTED = hello, world\nhello, sex\n2 greetings\ntext times \nmood 1 +EXPECTED = hello, world\nhello, sex\n2 greetings\ntext times \ntext times \nmood 1 check: @$(SEXC) greet.sex -c -o greet.o diff --git a/tests/modules/greet-app.sex b/tests/modules/greet-app.sex index c7775ed..6229e91 100644 --- a/tests/modules/greet-app.sex +++ b/tests/modules/greet-app.sex @@ -21,6 +21,9 @@ (printf "%d greetings\n" greet-count) (describe-fields greeting) (printf "\n") + ;; ...and through the imported typedef for it + (describe-fields greeting-t) + (printf "\n") (var m (enum mood) grumpy) (printf "mood %d\n" m) (return 0)) diff --git a/tests/modules/greet.sex b/tests/modules/greet.sex index 382fb36..887b295 100644 --- a/tests/modules/greet.sex +++ b/tests/modules/greet.sex @@ -13,6 +13,8 @@ (pub enum mood (cheerful grumpy)) +(pub typedef greeting-t (struct greeting)) + ;;; Compile-time reflection across the module boundary: both the macro ;;; and the struct it asks about are exported, and the importing unit ;;; has to know the struct's fields to expand this. diff --git a/tests/types.scm b/tests/types.scm index 4d30ee0..d9bfc8a 100644 --- a/tests/types.scm +++ b/tests/types.scm @@ -54,6 +54,42 @@ #f (get-underlying-type 't-point)) + ;; Reflection through an alias. Both spellings of the target occur: + ;; (typedef point-t point) and (typedef point-t (struct point)). + (add-typedef 't-point-t '(typedef t-point-t t-point)) + (test "a typedef to a struct has the struct's fields" + '((x int) (y int)) + (get-fields 't-point-t)) + (add-typedef 't-point-s '(typedef t-point-s (struct t-point))) + (test "written the other way round too" + '((x int) (y int)) + (get-fields 't-point-s)) + (add-typedef 't-point-2 '(typedef t-point-2 t-point-t)) + (test "and through a chain of them" + '((x int) (y int)) + (get-fields 't-point-2)) + (test "map-fields follows an alias as well" + '((x int) (y int)) + (map-fields 't-point-t (lambda (name type) (list name type)))) + (add-typedef 't-color-t '(typedef t-color-t t-color)) + (test "an aliased enum is still named as it was declared" + '((red (enum t-color)) (green (enum t-color)) (blue (enum t-color))) + (map-fields 't-color-t (lambda (name type) (list name type)))) + (test "a typedef to a primitive has no fields" + #f + (get-fields 't-u8)) + + ;; A typedef can be written to lead back to itself. Resolving it must + ;; stop rather than spin + (add-typedef 't-loop-a '(typedef t-loop-a t-loop-b)) + (add-typedef 't-loop-b '(typedef t-loop-b t-loop-a)) + (test "a typedef cycle terminates" + 't-loop-a + (get-underlying-type 't-loop-a)) + (test "and has no fields" + #f + (get-fields 't-loop-a)) + ;; A #define keeps its value forms -- there can be more than one. (add-define 't-maxn '(define t-maxn 8)) (test "defines are recorded" diff --git a/types.scm b/types.scm index 2654ed2..6e6b4b0 100644 --- a/types.scm +++ b/types.scm @@ -76,21 +76,44 @@ ;;; ((name type) ...) for a struct or union, #f for anything else -- ;;; including a name that was never declared. Callers give the better ;;; error, since they know what they wanted it for. +;;; +;;; A typedef is followed to what it stands for, so reflection over an +;;; alias works exactly as it does over the name it aliases. (define (get-fields name) - (let ((info (get-type-info name))) + (let ((info (resolve-type-info name))) (and info (memq (car info) '(struct union)) (caddr info)))) -;;; Follow a typedef chain to the name it ultimately stands for. #f if -;;; NAME is not a typedef. +;;; Follow a typedef chain to the name it stands for. #f if NAME is +;;; not a typedef. A typedef that leads back to itself stops rather +;;; than spinning: nothing prevents one from being written. (define (get-underlying-type name) + (let follow ((name name) (seen (list))) + (and (not (member name seen)) + (let ((info (get-type-info name))) + (and info + (eq? (car info) 'typedef) + (let ((target (caddr info))) + (or (and (symbol? target) (follow target (cons name seen))) + target))))))) + +;;; The declaration NAME ultimately names. For a typedef that is the +;;; entry of whatever it stands for, and for anything else it is +;;; NAME's own info. +(define (resolve-type-info name) (let ((info (get-type-info name))) (and info - (eq? (car info) 'typedef) - (let ((target (caddr info))) - (or (and (symbol? target) (get-underlying-type target)) - target))))) + (if (eq? (car info) 'typedef) + (let* ((target (get-underlying-type name)) + (tag (cond ((symbol? target) target) + ((and (pair? target) + (pair? (cdr target)) + (symbol? (cadr target))) + (cadr target)) + (else #f)))) + (and tag (get-type-info tag))) + info)))) ;;; Type matcher macro ;;; (type-match type @@ -119,15 +142,16 @@ ;;; ;;; Returns #f if nothing of that name was declared (define (map-fields struct-union-enum fn) - (let ((info (get-type-info struct-union-enum))) + (let ((info (resolve-type-info struct-union-enum))) (and info (case (car info) ((struct union) (map (lambda (field) (fn (car field) (cadr field))) (caddr info))) - ;; An enumerator's type is the enum itself. + ;; An enumerator's type is the enum itself -- named as it was + ;; declared, since `enum some-typedef' is not a C type. ((enum) - (let ((type (list 'enum struct-union-enum))) + (let ((type (list 'enum (cadr info)))) (map (lambda (value) (fn value type)) (caddr info)))) (else #f)))))