type-inference #39
Reference in New Issue
Block a user
Delete Branch "type-inference"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two things out of one mechanism. `_' as a type means "work it out from the initializer", so (var n _ (strlen s)) stops needing size-t spelled out; `type-of' hands a macro the type of an expression, so a macro can dispatch on what it was handed rather than on what was declared. Both read the same answers from two sides. Algorithm W's core, intra-procedural, with the extensions C forces: - an unknown type, since (include stdio.h) brings in names we never parsed. Unification is consistency rather than equality, so anything touching an unparsed declaration stops constraining instead of rejecting a program that compiled yesterday; - the usual arithmetic conversions, since `+' is not a function of one type; - checking mode for initializers, since #(0 0) has no type of its own and takes one from its context. #(T : ...) is the way out of that. What it wanted on the way: - what type a *name* has, which neither the typedef nor the tag database recorded. One table serves functions and variables, since a function type already has a surface spelling; - a scope chain, so a (var c int 9) inside a do ends with the block; - form-type, keyed by cons cell, so one form has one type; - macros expanded during the walk rather than before it, so type-of is answered in the scope the macro was written in. Closures take the same machinery: a receiver whose type comes from a call, captures written (name expr) and typed from the expression, and conversion from a bare function wherever a closure is expected. type-match grew `_' on the pattern side, since (closure ((int)) int) and (closure ((float)) int) were separate clauses for one case.Built and ran this on CHICKEN 6.0.0 at
9daaa42.make run-testsis green, includingclosures,fixpoint,wildcards, andinference. The holes below are outside that suite. I compiled each one with./sexc -Cand, where it emitted C, withcc.Closures (inline environment, the receiver evaluated once, conversion from a bare function, the fixpoint forward declaration),
$splicing,static-assert, the(*p).xparentheses, and symbolic array bounds all match the new tests.Before the freeze
_writes a C type that is not the type of the expression.arith-joinkeeps the left operand when the ranks tie.size-tis an unparsed name, so it ties withint. Withstrlendeclaredsize-t, anunsigned int u, anint i, and achar c:ccaccepts the function. In C,1 + nhas typesize_tandi + uhas typeunsigned int; these declarations narrow. The comment onarith-joinalready records the unsigned case.size-tis the same tie, and it is the next line after thestrlenexample.The operators the writer emits are absent from
expression-type. The bool clause still namesc-andandc-or.(var ok _ (&& a b))stops withcannot infer the type of, and so do||,|,&,<<, and++.decayis never called.(var p _ (+ a 1))onint a[4]becomesint p[4] = a + 1.cc:invalid initializer.(var e _ (¤ p 0))on an(* int)stops withcannot infer the type of. Those are the two positions the comment ondecaynames.A toplevel
_is copied into the C.(var n _ 1)emitsstatic _ n = 1.cc:unknown type name '_'. Locals go throughresolve-wildcard.process-global-vardoes not.The body walk rewrites types. With a local
cof closure type,(var fp (fn ((c int)) int) ...)is processed into(fn ((ƛint_int_call c int)) int)and emitted asint (*fp)(c int). The same declaration with no closure namedcemitsint (*fp)(int).cc:expected declaration specifiers or '...' before 'c'.A closure literal in call position is not a call of a closure.
((closure ((x int)) int () (return x)) 5)emitsreturn ƛ0_f_make()(5).cc:called object is not a function or function pointer.((lambda ...) 5)and((make-adder 20) 22)both work. The callee's type is read before the literal is lifted.Can wait
(strlen "hi"), with noextern, reportscannot infer. The walk never builds the unknown type?.inference.sexdescribes a?error. Failing closed is right; the two messages should meet.infer.scmstill opens with "Nothing in the compiler calls this unit yet."I would want 1–6 pinned by tests that fail on this tree. The suite is green because none of these programs are in it.
When the ranks tie this keeps the left operand, so the declared type depends on the order of the source.
With
nasize-t(an unparsed name, rank 1, same asint):ccaccepts both. C types1 + nassize_t, and theintdeclaration narrows. The same tie makes(+ i u)anintand(+ u i)anunsigned int.char + charis declaredchar.size-tnever reaches thelongrank because it is not a typedef Sex has parsed, so thestrlenexample's(+ 1 n)lands here.The forms that reach this walk are
&&,||,|,&,<<,>>,++.c-andandc-orare the names from before those spellings. The writer already emitsa && bfrom(&& a b).(var ok _ (&& a b))and(var x _ (<< a b))and(var x _ (++ a))all stop withcannot infer the type of.An array operand is returned as the array.
decayininfer.scmis the conversion this comment describes, and nothing in the walk calls it.emits
int p[4] = a + 1.cc:invalid initializer.A subscript types only when the base's surface form is already
(¤ ...). An(* int)is not, sostops with
cannot infer the type of. This is the other use named ondecay.resolve-closure-typesruns here andresolve-wildcarddoes not.(var n _ 1)at toplevel emitsstatic _ n = 1.cc:unknown type name '_'. The same form inside a function becomesint n = 1.walk-partswalks the type as well as the initializer. A list whose head is an in-scope closure is rewritten as a closure call, including a parameter name inside afntype.-mshows(var fp (fn ((ƛint_int_call c int)) int) ...), and the C isint (*fp)(c int). With no closure namedc, the same declaration emitsint (*fp)(int).The callee is typed before it is walked, so a closure literal has no type yet and falls through to
convert-arguments. After the walk the form is a call of the constructor:cc:called object is not a function or function pointer.((lambda ((x int)) int (return x)) 5)works, because the lifted function is registered before the call is typed.((make-adder 20) 22)works, because the callee is already a call with a closure return type.Replies to the review, each on the line it concerns. All eight are in
9daaa42..a860da6.@@ -95,3 +98,2 @@SEX_TEST_PROGRAMS = hello-world lists comments unicode serialize features \feature-flags lambdas compound-literalsSEX_TEST_PROGRAMS = c99 \Re 351. One per line, alphabetical.
e87463b.@@ -250,0 +384,4 @@env))(fourth form))))))))((var)Re 347. The type gets what a
fnheader gets -- macro expansion and closure resolution, no walk.fe72a10.@@ -250,0 +431,4 @@(else(let ((closure (receiver-closure-type (car form) env)))(if closure;; the receiver is walked first: `((closure ((x int)) intRe 348. A closure literal answers with its own type. The receiver is walked before the call helper is registered, so the struct is declared ahead of the signature naming it.
fe72a10.@@ -252,0 +602,4 @@(case (car expr);; (¤ pts 1), pts : (¤ struct point 2) -> (struct point);; (¤ p 1), p : (* int) -> int((¤) (let ((base (expression-type (second expr) env)))Re 345.
2b73fcf.@@ -252,0 +630,4 @@((^ |\||) (arithmetic-type expr env));; a shift is the promoted left operand, not a join:;; (<< l b), l : long -> long; (>> c b), c : char -> int((<< >>) (promoted-type (expression-type (second expr) env)))Re 343.
sextest wrote
||escaped, which Sex does not read, so no test could have reached it. Fixed with it.e87463b.@@ -252,0 +656,4 @@#f(cdr expr)))(define (arith-join left right)Re 342. Unsigned takes the tie whichever side it is written on, and a name with no declaration parsed has no rank to compare:
578feb6,e87463b.@@ -252,0 +696,4 @@;;;;;; (var p _ (+ a 1)) a : (¤ int 4) -> int * p = a + 1;;;; not int p[4] = a + 1;(define (decayed written type)Re 344.
decayhas its caller.e87463b.@@ -284,2 +1140,3 @@(define (process-global-var sex-var acc)(cons sex-var acc));; A global is not walked for lambdas, but the writer spells neitherRe 346. Solved from
get-name-typealone, so a global names only what is declared above it.e87463b.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.