From 578feb6b73e1dee18ce5f9e3bc2ad14ddc74a948 Mon Sep 17 00:00:00 2001 From: alex-eg Date: Wed, 30 Sep 2026 14:13:19 +0300 Subject: [PATCH] scope and promote the way C does A `while' or `switch' body is a block with no `do' around it, and its declarations landed in the enclosing frame. Two operands of one narrow type skipped the conversions, so `(+ c c)' answered `char'. --- semen.scm | 29 +++++++++++++++++++++++------ tests/sex-programs/wildcards.sex | 28 +++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/semen.scm b/semen.scm index 23081ed..9ddbaf7 100644 --- a/semen.scm +++ b/semen.scm @@ -295,8 +295,13 @@ ;;; The scope chain ;;; ;;; `(do (var c int 9) ...)' declares a `c' that ends with the block, so -;;; a closure-typed `c' outside it is still a closure after it. `do' and -;;; `for' each open a frame; innermost first. +;;; a closure-typed `c' outside it is still a closure after it. Every +;;; form whose body C brackets opens a frame; innermost first. +;;; +;;; One frame per form is enough, rather than one per arm: a `case' label +;;; opens no scope in C either, and a declaration is not a statement, so +;;; the only way to write one in an `if' arm is the `do' that already +;;; brings its own. (define (declare-name! env name type) (hash-table-set! (car (hash-table-ref env :scopes)) name type)) @@ -351,7 +356,8 @@ (cons walk-embed-result (walk-body expansion env))))) (else (case (car form) - ((do for) (with-scope env (lambda () (walk-parts form env)))) + ((do for while if switch) + (with-scope env (lambda () (walk-parts form env)))) ((lambda) (let ((name (aux-name! env make-lambda-name))) @@ -595,15 +601,26 @@ (cond ((not left) right) ((not right) left) - ((equal? left right) left) (else (let ((l (underlying (parse-type left))) (r (underlying (parse-type right)))) (cond ((or (ptr-type? l) (array-type? l)) left) ((or (ptr-type? r) (array-type? r)) right) - ((< (conversion-rank l) (conversion-rank r)) right) - (else left)))))) + ((< (conversion-rank l) (conversion-rank r)) (promoted right r)) + (else (promoted left l))))))) + +;;; Anything narrower than `int' is promoted to one before the +;;; arithmetic happens, so two `char's join as `int' and not as `char'. +;;; Operands of the same type reach here too, which is the whole point: +;;; `(+ c c)' is where the promotion is invisible and the truncation is +;;; not. `unsigned' alone is `unsigned int' and stays as written. +(define (promoted written type) + (if (and (prim-type? type) + (any (lambda (word) (memq word '(char short bool _Bool))) + (prim-name type))) + 'int + written)) ;;; `char' and `short' promote to `int', so the ranks start there (define (conversion-rank type) diff --git a/tests/sex-programs/wildcards.sex b/tests/sex-programs/wildcards.sex index 2fe4c8b..20b7752 100644 --- a/tests/sex-programs/wildcards.sex +++ b/tests/sex-programs/wildcards.sex @@ -6,8 +6,10 @@ "arrays: 30" "loop: 0 1 2" "shadowed: 9 then 42" + "still an int: 200" "partial: 1 2.5" "joined: 43.5 84 49 1" + "promoted: 200 60000 3705032704" "from elements: 4 9 0") (return 0) @@ -55,11 +57,24 @@ (printf " %d" i)) (printf "\n") - ;; a block's declarations end with it + ;; a block's declarations end with it, and so do the declarations of + ;; everything else C brackets -- a `while' body is a block with no + ;; `do' written around it (do (var n _ 9) (printf "shadowed: %d then " n)) (printf "%d\n" n) + (var wide int 200) + (while false (var wide char 1) (printf "%d" wide)) + (if false (do (var wide char 1) (printf "%d" wide))) + ;; a statement before the declaration: a label may not be followed by + ;; one until C23 + (switch a (case 1 (printf "") (var wide char 1) (printf "%d" wide) (break))) + ;; a copy, not a sum: an arithmetic result would be promoted to `int' + ;; whatever leaked, and say nothing + (var copy _ wide) + (printf "still an int: %d\n" copy) + ;; a wildcard inside a written type: only it is solved (var pp2 (* _) (& p)) (printf "partial: %d %g\n" (-> pp2 x) (-> pp2 y)) @@ -74,6 +89,17 @@ (var single _ (+ g g)) (printf "joined: %g %d %ld %g\n" mixed same wider single) + ;; ...including the promotions, which two operands of one narrow type + ;; are exactly where they show: `char' + `char' is an `int' + (var c1 char 100) + (var c2 char 100) + (var h1 short 30000) + (var narrow _ (+ c1 c2)) + (var narrower _ (+ h1 h1)) + (var kept (unsigned int) 4000000000) + (var unpromoted _ (+ kept kept)) + (printf "promoted: %d %d %u\n" narrow narrower unpromoted) + ;; a brace initializer has no type of its own, but its elements solve ;; the hole in the array type around it -- and the length stays as ;; written, whether or not every slot is initialized