selfhost/cmd/wcc: stamp e.type_ for N_BIN + N_UN (A.6.1.3)

Adds unifyarith / binoptype / unoptype mirroring cstage
cmd/wcc/check.c:580-596, 598-640, 642-687. Two new exprtype
arms stamp e.type_ for N_BIN and N_UN via the same
tinfofornode pattern as A.6.1.1/.2.

unifyarith: untyped+untyped (prefer float), untyped+typed
(typed via isassignable), typed+typed (typeeqast), fallback ltn.
binoptype: ptr arith (ptr±int → ptr, ptr-ptr → i64), bitwise/
arith → unifyarith, comparison + logical → bool.
unoptype: deref (resolvealias+unwrapbang then N_TPTR.lhs),
addrof → *T with slice/str pseudo-field .len/.cap widening
to *i64 (mirrors check.c:672-682), unary +/-/^/!.

Wwstage stays silent on operator-type errors per existing
checker discipline; cstage flags the same shapes.

Verified 132/132 incl. 995_self_rebuild byte-identity.
This commit is contained in:
2026-05-21 15:31:03 +09:00
parent 19095e4b50
commit ada99973ee
3 changed files with 369 additions and 6 deletions

View File

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

View File

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

View File

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