diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 9214b8ed..efe13f8d 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -8160,9 +8160,120 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { return r; }; +// unifyarith — usual-arithmetic-conversion analogue at the AST-tnode +// layer. Mirrors cstage cmd/wcc/check.c:580-596 `unify_arith`. The +// trailing typeeq fallback covers same-typed operands; mismatched typed +// pairs silently return ltn (cstage flags the same shape — wwstage's +// checker stays silent here per existing discipline). +fn unifyarith(c: *checker, ltn: *node, rtn: *node) *node = { + let lu: bool = isuntypedint(ltn) || isuntypedfloat(ltn); + let ru: bool = isuntypedint(rtn) || isuntypedfloat(rtn); + if (lu && ru) { + if (isuntypedfloat(ltn) || isuntypedfloat(rtn)) { + return mktname(c, "untyped_float"); + }; + return mktname(c, "untyped_int"); + }; + let conf: bool = false; + if (lu) { + if (isassignable(c, rtn, ltn, &conf)) { return rtn; }; + }; + if (ru) { + if (isassignable(c, ltn, rtn, &conf)) { return ltn; }; + }; + if (typeeqast(ltn, rtn)) { return ltn; }; + return ltn; +}; + +// binoptype — derive the result tnode of an N_BIN operator expression. +// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes +// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons / +// logicals all reflect cstage's rules. Type-mismatch diagnostics are +// elided here (cstage gates the same shape). +fn binoptype(c: *checker, e: *node) *node = { + let op: tkind = e.op; + let ltn: *node = exprtype(c, e.lhs, nil); + let rtn: *node = exprtype(c, e.rhs, nil); + if (op == tkind.TK_PLUS || op == tkind.TK_MINUS) { + if (ltn != nil && ltn.kind == nkind.N_TPTR && isinttypeast(rtn)) { + return ltn; + }; + }; + if (op == tkind.TK_PLUS) { + if (isinttypeast(ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) { + return rtn; + }; + }; + if (op == tkind.TK_MINUS) { + if (ltn != nil && rtn != nil + && ltn.kind == nkind.N_TPTR && rtn.kind == nkind.N_TPTR) { + return mktname(c, "i64"); + }; + }; + if (op == tkind.TK_PLUS || op == tkind.TK_MINUS || + op == tkind.TK_STAR || op == tkind.TK_SLASH || + op == tkind.TK_PERCENT || op == tkind.TK_AMP || + op == tkind.TK_PIPE || op == tkind.TK_CARET || + op == tkind.TK_LSHIFT || op == tkind.TK_RSHIFT) { + return unifyarith(c, ltn, rtn); + }; + if (op == tkind.TK_EQ || op == tkind.TK_NEQ || + op == tkind.TK_LT || op == tkind.TK_LE || + op == tkind.TK_GT || op == tkind.TK_GE || + op == tkind.TK_AND || op == tkind.TK_OR) { + return mktname(c, "bool"); + }; + return nil; +}; + +// unoptype — derive the result tnode of an N_UN unary expression. Mirrors +// cstage cmd/wcc/check.c:642-687 `cunop`. The slice/str .len/.cap +// pseudo-field address-of widening to *i64 mirrors check.c:672-682 +// directly — its purpose is documented at the cstage site. +fn unoptype(c: *checker, e: *node) *node = { + let op: tkind = e.op; + let opt: *node = exprtype(c, e.lhs, nil); + if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) { return opt; }; + if (op == tkind.TK_NOT) { return mktname(c, "bool"); }; + if (op == tkind.TK_TILDE) { return opt; }; + if (op == tkind.TK_STAR) { + if (opt == nil) { return nil; }; + let u: *node = resolvealias(c, unwrapbang(opt)); + if (u == nil) { return nil; }; + if (u.kind != nkind.N_TPTR) { return nil; }; + return u.lhs; + }; + if (op == tkind.TK_AMP) { + if (e.lhs != nil && e.lhs.kind == nkind.N_DOT) { + let fld: str = e.lhs.str; + if (streq(fld, "len") || streq(fld, "cap")) { + let base: *node = e.lhs.lhs; + if (base != nil && base.type_ != nil) { + let bu: *tinfo = base.type_: *tinfo; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil) { + if (bu.kind == tykind.TY_SLICE || bu.kind == tykind.TY_STR) { + let pp: *node = newnode(nkind.N_TPTR, "", 0, 0); + pp.lhs = mktname(c, "i64"); + return pp; + }; + }; + }; + }; + }; + if (opt == nil) { return nil; }; + let pp: *node = newnode(nkind.N_TPTR, "", 0, 0); + pp.lhs = opt; + return pp; + }; + return nil; +}; + // exprtype — best-effort type-AST inference for an expression -// node. Handles literals, identifiers, calls, and casts; returns -// nil for shapes we don't statically know (binary ops, struct +// node. Handles literals, identifiers, calls, casts, binary/unary +// ops; returns nil for shapes we don't statically know (struct // field access into non-primitive types, etc). // `hint`: optional declared-type AST passed by the caller (let // target, assign target). nil = "no hint, derive from self". Threaded @@ -8240,6 +8351,16 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { }; return t; }; + if (k == nkind.N_BIN) { + let tn: *node = binoptype(c, e); + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; + if (k == nkind.N_UN) { + let tn: *node = unoptype(c, e); + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; if (k == nkind.N_CAST) { // `expr: T` — explicit cast; the type expr is e.rhs. Mirrors // cstage cmd/wcc/check.c:737 `n->type = resolve_type(c, n->rhs)`. diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 1079e68f..d89e8be2 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -1228,9 +1228,120 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { return r; }; +// unifyarith — usual-arithmetic-conversion analogue at the AST-tnode +// layer. Mirrors cstage cmd/wcc/check.c:580-596 `unify_arith`. The +// trailing typeeq fallback covers same-typed operands; mismatched typed +// pairs silently return ltn (cstage flags the same shape — wwstage's +// checker stays silent here per existing discipline). +fn unifyarith(c: *checker, ltn: *node, rtn: *node) *node = { + let lu: bool = isuntypedint(ltn) || isuntypedfloat(ltn); + let ru: bool = isuntypedint(rtn) || isuntypedfloat(rtn); + if (lu && ru) { + if (isuntypedfloat(ltn) || isuntypedfloat(rtn)) { + return mktname(c, "untyped_float"); + }; + return mktname(c, "untyped_int"); + }; + let conf: bool = false; + if (lu) { + if (isassignable(c, rtn, ltn, &conf)) { return rtn; }; + }; + if (ru) { + if (isassignable(c, ltn, rtn, &conf)) { return ltn; }; + }; + if (typeeqast(ltn, rtn)) { return ltn; }; + return ltn; +}; + +// binoptype — derive the result tnode of an N_BIN operator expression. +// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes +// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons / +// logicals all reflect cstage's rules. Type-mismatch diagnostics are +// elided here (cstage gates the same shape). +fn binoptype(c: *checker, e: *node) *node = { + let op: tkind = e.op; + let ltn: *node = exprtype(c, e.lhs, nil); + let rtn: *node = exprtype(c, e.rhs, nil); + if (op == tkind.TK_PLUS || op == tkind.TK_MINUS) { + if (ltn != nil && ltn.kind == nkind.N_TPTR && isinttypeast(rtn)) { + return ltn; + }; + }; + if (op == tkind.TK_PLUS) { + if (isinttypeast(ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) { + return rtn; + }; + }; + if (op == tkind.TK_MINUS) { + if (ltn != nil && rtn != nil + && ltn.kind == nkind.N_TPTR && rtn.kind == nkind.N_TPTR) { + return mktname(c, "i64"); + }; + }; + if (op == tkind.TK_PLUS || op == tkind.TK_MINUS || + op == tkind.TK_STAR || op == tkind.TK_SLASH || + op == tkind.TK_PERCENT || op == tkind.TK_AMP || + op == tkind.TK_PIPE || op == tkind.TK_CARET || + op == tkind.TK_LSHIFT || op == tkind.TK_RSHIFT) { + return unifyarith(c, ltn, rtn); + }; + if (op == tkind.TK_EQ || op == tkind.TK_NEQ || + op == tkind.TK_LT || op == tkind.TK_LE || + op == tkind.TK_GT || op == tkind.TK_GE || + op == tkind.TK_AND || op == tkind.TK_OR) { + return mktname(c, "bool"); + }; + return nil; +}; + +// unoptype — derive the result tnode of an N_UN unary expression. Mirrors +// cstage cmd/wcc/check.c:642-687 `cunop`. The slice/str .len/.cap +// pseudo-field address-of widening to *i64 mirrors check.c:672-682 +// directly — its purpose is documented at the cstage site. +fn unoptype(c: *checker, e: *node) *node = { + let op: tkind = e.op; + let opt: *node = exprtype(c, e.lhs, nil); + if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) { return opt; }; + if (op == tkind.TK_NOT) { return mktname(c, "bool"); }; + if (op == tkind.TK_TILDE) { return opt; }; + if (op == tkind.TK_STAR) { + if (opt == nil) { return nil; }; + let u: *node = resolvealias(c, unwrapbang(opt)); + if (u == nil) { return nil; }; + if (u.kind != nkind.N_TPTR) { return nil; }; + return u.lhs; + }; + if (op == tkind.TK_AMP) { + if (e.lhs != nil && e.lhs.kind == nkind.N_DOT) { + let fld: str = e.lhs.str; + if (streq(fld, "len") || streq(fld, "cap")) { + let base: *node = e.lhs.lhs; + if (base != nil && base.type_ != nil) { + let bu: *tinfo = base.type_: *tinfo; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil) { + if (bu.kind == tykind.TY_SLICE || bu.kind == tykind.TY_STR) { + let pp: *node = newnode(nkind.N_TPTR, "", 0, 0); + pp.lhs = mktname(c, "i64"); + return pp; + }; + }; + }; + }; + }; + if (opt == nil) { return nil; }; + let pp: *node = newnode(nkind.N_TPTR, "", 0, 0); + pp.lhs = opt; + return pp; + }; + return nil; +}; + // exprtype — best-effort type-AST inference for an expression -// node. Handles literals, identifiers, calls, and casts; returns -// nil for shapes we don't statically know (binary ops, struct +// node. Handles literals, identifiers, calls, casts, binary/unary +// ops; returns nil for shapes we don't statically know (struct // field access into non-primitive types, etc). // `hint`: optional declared-type AST passed by the caller (let // target, assign target). nil = "no hint, derive from self". Threaded @@ -1308,6 +1419,16 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { }; return t; }; + if (k == nkind.N_BIN) { + let tn: *node = binoptype(c, e); + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; + if (k == nkind.N_UN) { + let tn: *node = unoptype(c, e); + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; if (k == nkind.N_CAST) { // `expr: T` — explicit cast; the type expr is e.rhs. Mirrors // cstage cmd/wcc/check.c:737 `n->type = resolve_type(c, n->rhs)`. diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index fc2036dd..ed0b2568 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -8160,9 +8160,120 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = { return r; }; +// unifyarith — usual-arithmetic-conversion analogue at the AST-tnode +// layer. Mirrors cstage cmd/wcc/check.c:580-596 `unify_arith`. The +// trailing typeeq fallback covers same-typed operands; mismatched typed +// pairs silently return ltn (cstage flags the same shape — wwstage's +// checker stays silent here per existing discipline). +fn unifyarith(c: *checker, ltn: *node, rtn: *node) *node = { + let lu: bool = isuntypedint(ltn) || isuntypedfloat(ltn); + let ru: bool = isuntypedint(rtn) || isuntypedfloat(rtn); + if (lu && ru) { + if (isuntypedfloat(ltn) || isuntypedfloat(rtn)) { + return mktname(c, "untyped_float"); + }; + return mktname(c, "untyped_int"); + }; + let conf: bool = false; + if (lu) { + if (isassignable(c, rtn, ltn, &conf)) { return rtn; }; + }; + if (ru) { + if (isassignable(c, ltn, rtn, &conf)) { return ltn; }; + }; + if (typeeqast(ltn, rtn)) { return ltn; }; + return ltn; +}; + +// binoptype — derive the result tnode of an N_BIN operator expression. +// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes +// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons / +// logicals all reflect cstage's rules. Type-mismatch diagnostics are +// elided here (cstage gates the same shape). +fn binoptype(c: *checker, e: *node) *node = { + let op: tkind = e.op; + let ltn: *node = exprtype(c, e.lhs, nil); + let rtn: *node = exprtype(c, e.rhs, nil); + if (op == tkind.TK_PLUS || op == tkind.TK_MINUS) { + if (ltn != nil && ltn.kind == nkind.N_TPTR && isinttypeast(rtn)) { + return ltn; + }; + }; + if (op == tkind.TK_PLUS) { + if (isinttypeast(ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) { + return rtn; + }; + }; + if (op == tkind.TK_MINUS) { + if (ltn != nil && rtn != nil + && ltn.kind == nkind.N_TPTR && rtn.kind == nkind.N_TPTR) { + return mktname(c, "i64"); + }; + }; + if (op == tkind.TK_PLUS || op == tkind.TK_MINUS || + op == tkind.TK_STAR || op == tkind.TK_SLASH || + op == tkind.TK_PERCENT || op == tkind.TK_AMP || + op == tkind.TK_PIPE || op == tkind.TK_CARET || + op == tkind.TK_LSHIFT || op == tkind.TK_RSHIFT) { + return unifyarith(c, ltn, rtn); + }; + if (op == tkind.TK_EQ || op == tkind.TK_NEQ || + op == tkind.TK_LT || op == tkind.TK_LE || + op == tkind.TK_GT || op == tkind.TK_GE || + op == tkind.TK_AND || op == tkind.TK_OR) { + return mktname(c, "bool"); + }; + return nil; +}; + +// unoptype — derive the result tnode of an N_UN unary expression. Mirrors +// cstage cmd/wcc/check.c:642-687 `cunop`. The slice/str .len/.cap +// pseudo-field address-of widening to *i64 mirrors check.c:672-682 +// directly — its purpose is documented at the cstage site. +fn unoptype(c: *checker, e: *node) *node = { + let op: tkind = e.op; + let opt: *node = exprtype(c, e.lhs, nil); + if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) { return opt; }; + if (op == tkind.TK_NOT) { return mktname(c, "bool"); }; + if (op == tkind.TK_TILDE) { return opt; }; + if (op == tkind.TK_STAR) { + if (opt == nil) { return nil; }; + let u: *node = resolvealias(c, unwrapbang(opt)); + if (u == nil) { return nil; }; + if (u.kind != nkind.N_TPTR) { return nil; }; + return u.lhs; + }; + if (op == tkind.TK_AMP) { + if (e.lhs != nil && e.lhs.kind == nkind.N_DOT) { + let fld: str = e.lhs.str; + if (streq(fld, "len") || streq(fld, "cap")) { + let base: *node = e.lhs.lhs; + if (base != nil && base.type_ != nil) { + let bu: *tinfo = base.type_: *tinfo; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil && bu.kind == tykind.TY_PTR) { bu = bu.sub; }; + if (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu != nil) { + if (bu.kind == tykind.TY_SLICE || bu.kind == tykind.TY_STR) { + let pp: *node = newnode(nkind.N_TPTR, "", 0, 0); + pp.lhs = mktname(c, "i64"); + return pp; + }; + }; + }; + }; + }; + if (opt == nil) { return nil; }; + let pp: *node = newnode(nkind.N_TPTR, "", 0, 0); + pp.lhs = opt; + return pp; + }; + return nil; +}; + // exprtype — best-effort type-AST inference for an expression -// node. Handles literals, identifiers, calls, and casts; returns -// nil for shapes we don't statically know (binary ops, struct +// node. Handles literals, identifiers, calls, casts, binary/unary +// ops; returns nil for shapes we don't statically know (struct // field access into non-primitive types, etc). // `hint`: optional declared-type AST passed by the caller (let // target, assign target). nil = "no hint, derive from self". Threaded @@ -8240,6 +8351,16 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { }; return t; }; + if (k == nkind.N_BIN) { + let tn: *node = binoptype(c, e); + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; + if (k == nkind.N_UN) { + let tn: *node = unoptype(c, e); + e.type_ = tinfofornode(c, tn): *void; + return tn; + }; if (k == nkind.N_CAST) { // `expr: T` — explicit cast; the type expr is e.rhs. Mirrors // cstage cmd/wcc/check.c:737 `n->type = resolve_type(c, n->rhs)`.