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'.
This commit is contained in:
2026-09-30 14:13:19 +03:00
parent 2da5b5005b
commit 578feb6b73
2 changed files with 50 additions and 7 deletions

View File

@@ -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)