From c418f14a78acb114278bd942adc35be313a4b89e Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 08:50:51 -0700
Subject: [PATCH 01/15] Tree: remove unnecessary use of Type.canonicalize

---
 src/aro/Tree.zig | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/aro/Tree.zig b/src/aro/Tree.zig
index b82cea2f..68e16913 100644
--- a/src/aro/Tree.zig
+++ b/src/aro/Tree.zig
@@ -1363,12 +1363,11 @@ fn dumpNode(
 
             var lhs_ty = tree.nodes.items(.ty)[@intFromEnum(data.member.lhs)];
             if (lhs_ty.isPtr()) lhs_ty = lhs_ty.elemType();
-            lhs_ty = lhs_ty.canonicalize(.standard);
 
             try w.writeByteNTimes(' ', level + 1);
             try w.writeAll("name: ");
             try config.setColor(w, NAME);
-            try w.print("{s}\n", .{mapper.lookup(lhs_ty.data.record.fields[data.member.index].name)});
+            try w.print("{s}\n", .{mapper.lookup(lhs_ty.getRecord().?.fields[data.member.index].name)});
             try config.setColor(w, .reset);
         },
         .array_access_expr => {

From 241d3186706887146967d80a1d5112a53a610e21 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 08:55:49 -0700
Subject: [PATCH 02/15] Type: add  function for getting actual type specifier

---
 src/aro/Parser.zig |  4 ++--
 src/aro/Type.zig   | 56 +++++++++++++++++++++++++---------------------
 2 files changed, 32 insertions(+), 28 deletions(-)

diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig
index c4baeb8c..02bfaf43 100644
--- a/src/aro/Parser.zig
+++ b/src/aro/Parser.zig
@@ -5795,8 +5795,8 @@ pub const Result = struct {
                 .{ .invalid, .fp16 },
                 .{ .complex_float16, .float16 },
             };
-            const a_spec = a.ty.canonicalize(.standard).specifier;
-            const b_spec = b.ty.canonicalize(.standard).specifier;
+            const a_spec = a.ty.base();
+            const b_spec = b.ty.base();
             if (p.comp.target.cTypeBitSize(.longdouble) == 128) {
                 if (try a.floatConversion(b, a_spec, b_spec, p, float_types[0])) return;
             }
diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index ab083942..6f8d47e9 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -146,7 +146,7 @@ pub const Attributed = struct {
     attributes: []Attribute,
     base: Type,
 
-    pub fn create(allocator: std.mem.Allocator, base: Type, existing_attributes: []const Attribute, attributes: []const Attribute) !*Attributed {
+    pub fn create(allocator: std.mem.Allocator, base_ty: Type, existing_attributes: []const Attribute, attributes: []const Attribute) !*Attributed {
         const attributed_type = try allocator.create(Attributed);
         errdefer allocator.destroy(attributed_type);
 
@@ -156,7 +156,7 @@ pub const Attributed = struct {
 
         attributed_type.* = .{
             .attributes = all_attrs,
-            .base = base,
+            .base = base_ty,
         };
         return attributed_type;
     }
@@ -825,8 +825,8 @@ fn realIntegerConversion(a: Type, b: Type, comp: *const Compilation) Type {
 
 pub fn makeIntegerUnsigned(ty: Type) Type {
     // TODO discards attributed/typeof
-    var base = ty.canonicalize(.standard);
-    switch (base.specifier) {
+    var base_ty = ty.canonicalize(.standard);
+    switch (base_ty.specifier) {
         // zig fmt: off
         .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128,
         .complex_uchar, .complex_ushort, .complex_uint, .complex_ulong, .complex_ulong_long, .complex_uint128,
@@ -834,21 +834,21 @@ pub fn makeIntegerUnsigned(ty: Type) Type {
         // zig fmt: on
 
         .char, .complex_char => {
-            base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 2);
-            return base;
+            base_ty.specifier = @enumFromInt(@intFromEnum(base_ty.specifier) + 2);
+            return base_ty;
         },
 
         // zig fmt: off
         .schar, .short, .int, .long, .long_long, .int128,
         .complex_schar, .complex_short, .complex_int, .complex_long, .complex_long_long, .complex_int128 => {
-            base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 1);
-            return base;
+            base_ty.specifier = @enumFromInt(@intFromEnum(base_ty.specifier) + 1);
+            return base_ty;
         },
         // zig fmt: on
 
         .bit_int, .complex_bit_int => {
-            base.data.int.signedness = .unsigned;
-            return base;
+            base_ty.data.int.signedness = .unsigned;
+            return base_ty;
         },
         else => unreachable,
     }
@@ -1137,6 +1137,10 @@ pub const QualHandling = enum {
     preserve_quals,
 };
 
+pub fn base(ty: Type) Type.Specifier {
+    return ty.canonicalize(.standard).specifier;
+}
+
 /// Canonicalize a possibly-typeof() type. If the type is not a typeof() type, simply
 /// return it. Otherwise, determine the actual qualified type.
 /// The `qual_handling` parameter can be used to return the full set of qualifiers
@@ -1332,19 +1336,19 @@ pub fn sameRankDifferentSign(a: Type, b: Type, comp: *const Compilation) bool {
 
 pub fn makeReal(ty: Type) Type {
     // TODO discards attributed/typeof
-    var base = ty.canonicalize(.standard);
-    switch (base.specifier) {
+    var base_ty = ty.canonicalize(.standard);
+    switch (base_ty.specifier) {
         .complex_float16, .complex_float, .complex_double, .complex_long_double, .complex_float128 => {
-            base.specifier = @enumFromInt(@intFromEnum(base.specifier) - 5);
-            return base;
+            base_ty.specifier = @enumFromInt(@intFromEnum(base_ty.specifier) - 5);
+            return base_ty;
         },
         .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128 => {
-            base.specifier = @enumFromInt(@intFromEnum(base.specifier) - 13);
-            return base;
+            base_ty.specifier = @enumFromInt(@intFromEnum(base_ty.specifier) - 13);
+            return base_ty;
         },
         .complex_bit_int => {
-            base.specifier = .bit_int;
-            return base;
+            base_ty.specifier = .bit_int;
+            return base_ty;
         },
         else => return ty,
     }
@@ -1352,19 +1356,19 @@ pub fn makeReal(ty: Type) Type {
 
 pub fn makeComplex(ty: Type) Type {
     // TODO discards attributed/typeof
-    var base = ty.canonicalize(.standard);
-    switch (base.specifier) {
+    var base_ty = ty.canonicalize(.standard);
+    switch (base_ty.specifier) {
         .float, .double, .long_double, .float128 => {
-            base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 5);
-            return base;
+            base_ty.specifier = @enumFromInt(@intFromEnum(base_ty.specifier) + 5);
+            return base_ty;
         },
         .char, .schar, .uchar, .short, .ushort, .int, .uint, .long, .ulong, .long_long, .ulong_long, .int128, .uint128 => {
-            base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 13);
-            return base;
+            base_ty.specifier = @enumFromInt(@intFromEnum(base_ty.specifier) + 13);
+            return base_ty;
         },
         .bit_int => {
-            base.specifier = .complex_bit_int;
-            return base;
+            base_ty.specifier = .complex_bit_int;
+            return base_ty;
         },
         else => return ty,
     }

From ea37d72f2def6a2582ff3e7ac13b98e6b4d68d63 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 08:56:45 -0700
Subject: [PATCH 03/15] Parser: remove unnecessary use of canonicalize in
 addFieldsFromAnonymous

---
 src/aro/Parser.zig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig
index 02bfaf43..31275e44 100644
--- a/src/aro/Parser.zig
+++ b/src/aro/Parser.zig
@@ -167,7 +167,7 @@ record: struct {
     fn addFieldsFromAnonymous(r: @This(), p: *Parser, ty: Type) Error!void {
         for (ty.getRecord().?.fields) |f| {
             if (f.isAnonymousRecord()) {
-                try r.addFieldsFromAnonymous(p, f.ty.canonicalize(.standard));
+                try r.addFieldsFromAnonymous(p, f.ty);
             } else if (f.name_tok != 0) {
                 try r.addField(p, f.name, f.name_tok);
             }

From 89578d00759a6d765c1fb41e505d016a13cfce72 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 08:58:17 -0700
Subject: [PATCH 04/15] Parser: use Type.base instead of Type.canonicalize

---
 src/aro/Parser.zig | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/aro/Parser.zig b/src/aro/Parser.zig
index 31275e44..d3a9f466 100644
--- a/src/aro/Parser.zig
+++ b/src/aro/Parser.zig
@@ -981,7 +981,7 @@ fn decl(p: *Parser) Error!bool {
             (decl_spec.ty.isRecord() and !decl_spec.ty.isAnonymousRecord(p.comp) and
             !decl_spec.ty.isTypeof())) // we follow GCC and clang's behavior here
         {
-            const specifier = decl_spec.ty.canonicalize(.standard).specifier;
+            const specifier = decl_spec.ty.base();
             const attrs = p.attr_buf.items(.attr)[attr_buf_top..];
             const toks = p.attr_buf.items(.tok)[attr_buf_top..];
             for (attrs, toks) |attr, tok| {
@@ -1870,7 +1870,7 @@ fn initDeclarator(p: *Parser, decl_spec: *DeclSpec, attr_buf_top: usize) Error!?
         init_d.d.ty = try Attribute.applyVariableAttributes(p, init_d.d.ty, attr_buf_top, null);
     }
     if (decl_spec.storage_class != .typedef and init_d.d.ty.hasIncompleteSize()) incomplete: {
-        const specifier = init_d.d.ty.canonicalize(.standard).specifier;
+        const specifier = init_d.d.ty.base();
         if (decl_spec.storage_class == .@"extern") switch (specifier) {
             .@"struct", .@"union", .@"enum" => break :incomplete,
             .incomplete_array => {
@@ -3776,8 +3776,8 @@ fn coerceArrayInitExtra(p: *Parser, item: *Result, tok: TokenIndex, target: Type
         return true; // do not do further coercion
     }
 
-    const target_spec = target.elemType().canonicalize(.standard).specifier;
-    const item_spec = item.ty.elemType().canonicalize(.standard).specifier;
+    const target_spec = target.elemType().base();
+    const item_spec = item.ty.elemType().base();
 
     const compatible = target.elemType().eql(item.ty.elemType(), p.comp, false) or
         (is_str_lit and item_spec == .char and (target_spec == .uchar or target_spec == .schar)) or

From 6d7ab2da23edd8dca0eff4ebf59587ee0ad4b494 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 09:01:26 -0700
Subject: [PATCH 05/15] CodeGen: remove unnecessary canonicalize

---
 src/aro/CodeGen.zig | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/aro/CodeGen.zig b/src/aro/CodeGen.zig
index 9dcc6980..90e51d2f 100644
--- a/src/aro/CodeGen.zig
+++ b/src/aro/CodeGen.zig
@@ -185,12 +185,12 @@ fn genType(c: *CodeGen, base_ty: Type) !Interner.Ref {
 
 fn genFn(c: *CodeGen, decl: NodeIndex) Error!void {
     const name = c.tree.tokSlice(c.node_data[@intFromEnum(decl)].decl.name);
-    const func_ty = c.node_ty[@intFromEnum(decl)].canonicalize(.standard);
+    const func_ty = c.node_ty[@intFromEnum(decl)];
     c.ret_nodes.items.len = 0;
 
     try c.builder.startFn();
 
-    for (func_ty.data.func.params) |param| {
+    for (func_ty.params()) |param| {
         // TODO handle calling convention here
         const arg = try c.builder.addArg(try c.genType(param.ty));
 

From 848f0c0e073e0fd746eb0b509ae0779e0732051a Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 09:11:01 -0700
Subject: [PATCH 06/15] Type: implement base with a loop instead of just
 calling canonicalize

---
 src/aro/Type.zig | 71 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 70 insertions(+), 1 deletion(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index 6f8d47e9..4b326696 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -1138,7 +1138,76 @@ pub const QualHandling = enum {
 };
 
 pub fn base(ty: Type) Type.Specifier {
-    return ty.canonicalize(.standard).specifier;
+    var cur = ty;
+    while (true) {
+        switch (cur.specifier) {
+            .invalid,
+            .auto_type,
+            .c23_auto,
+            => unreachable,
+
+            .typeof_type => cur = cur.data.sub_type.*,
+            .typeof_expr => cur = cur.data.expr.ty,
+            .attributed => cur = cur.data.attributed.base,
+
+            .void,
+            .bool,
+            .char,
+            .schar,
+            .uchar,
+            .short,
+            .ushort,
+            .int,
+            .uint,
+            .long,
+            .ulong,
+            .long_long,
+            .ulong_long,
+            .int128,
+            .uint128,
+            .complex_char,
+            .complex_schar,
+            .complex_uchar,
+            .complex_short,
+            .complex_ushort,
+            .complex_int,
+            .complex_uint,
+            .complex_long,
+            .complex_ulong,
+            .complex_long_long,
+            .complex_ulong_long,
+            .complex_int128,
+            .complex_uint128,
+            .bit_int,
+            .complex_bit_int,
+            .fp16,
+            .float16,
+            .float,
+            .double,
+            .long_double,
+            .float128,
+            .complex_float16,
+            .complex_float,
+            .complex_double,
+            .complex_long_double,
+            .complex_float128,
+            .pointer,
+            .unspecified_variable_len_array,
+            .func,
+            .var_args_func,
+            .old_style_func,
+            .array,
+            .static_array,
+            .incomplete_array,
+            .vector,
+            .variable_len_array,
+            .@"struct",
+            .@"union",
+            .@"enum",
+            .nullptr_t,
+            => |s| return s,
+        }
+    }
 }
 
 /// Canonicalize a possibly-typeof() type. If the type is not a typeof() type, simply

From aabbf4c2a5c2eda82c826b3a49a8c2985955f6f1 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 09:12:26 -0700
Subject: [PATCH 07/15] Attribute: remove unnecessary canonicalize

---
 src/aro/Attribute.zig | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/aro/Attribute.zig b/src/aro/Attribute.zig
index fe9770e2..4b35abf8 100644
--- a/src/aro/Attribute.zig
+++ b/src/aro/Attribute.zig
@@ -1014,14 +1014,13 @@ pub fn applyEnumeratorAttributes(p: *Parser, ty: Type, attr_buf_start: usize) !T
 }
 
 fn applyAligned(attr: Attribute, p: *Parser, ty: Type, tag: ?Diagnostics.Tag) !void {
-    const base = ty.canonicalize(.standard);
     if (attr.args.aligned.alignment) |alignment| alignas: {
         if (attr.syntax != .keyword) break :alignas;
 
         const align_tok = attr.args.aligned.__name_tok;
         if (tag) |t| try p.errTok(t, align_tok);
 
-        const default_align = base.alignof(p.comp);
+        const default_align = ty.alignof(p.comp);
         if (ty.isFunc()) {
             try p.errTok(.alignas_on_func, align_tok);
         } else if (alignment.requested < default_align) {

From 86fc12543387f5e8592fb2ea529efeb5d1ea7f4b Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:21:04 -0700
Subject: [PATCH 08/15] Type: use canonicalize instead of recursing for some
 Type functions

---
 src/aro/Type.zig | 209 ++++++++++++++++++++++++-----------------------
 1 file changed, 106 insertions(+), 103 deletions(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index 4b326696..a7c59513 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -520,7 +520,8 @@ pub fn isDecayed(ty: Type) bool {
 }
 
 pub fn isPtr(ty: Type) bool {
-    return switch (ty.specifier) {
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
         .pointer => true,
 
         .array,
@@ -528,16 +529,16 @@ pub fn isPtr(ty: Type) bool {
         .incomplete_array,
         .variable_len_array,
         .unspecified_variable_len_array,
-        => ty.isDecayed(),
-        .typeof_type => ty.isDecayed() or ty.data.sub_type.isPtr(),
-        .typeof_expr => ty.isDecayed() or ty.data.expr.ty.isPtr(),
-        .attributed => ty.isDecayed() or ty.data.attributed.base.isPtr(),
+        => canon.isDecayed(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
 
 pub fn isInt(ty: Type) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         // zig fmt: off
         .@"enum", .bool, .char, .schar, .uchar, .short, .ushort, .int, .uint, .long, .ulong,
         .long_long, .ulong_long, .int128, .uint128, .complex_char, .complex_schar, .complex_uchar,
@@ -545,28 +546,28 @@ pub fn isInt(ty: Type) bool {
         .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128,
         .bit_int, .complex_bit_int => true,
         // zig fmt: on
-        .typeof_type => ty.data.sub_type.isInt(),
-        .typeof_expr => ty.data.expr.ty.isInt(),
-        .attributed => ty.data.attributed.base.isInt(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
 
 pub fn isFloat(ty: Type) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         // zig fmt: off
         .float, .double, .long_double, .complex_float, .complex_double, .complex_long_double,
         .fp16, .float16, .float128, .complex_float128, .complex_float16 => true,
         // zig fmt: on
-        .typeof_type => ty.data.sub_type.isFloat(),
-        .typeof_expr => ty.data.expr.ty.isFloat(),
-        .attributed => ty.data.attributed.base.isFloat(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
 
 pub fn isReal(ty: Type) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         // zig fmt: off
         .complex_float, .complex_double, .complex_long_double,
         .complex_float128, .complex_char, .complex_schar, .complex_uchar, .complex_short,
@@ -574,15 +575,15 @@ pub fn isReal(ty: Type) bool {
         .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128,
         .complex_bit_int, .complex_float16 => false,
         // zig fmt: on
-        .typeof_type => ty.data.sub_type.isReal(),
-        .typeof_expr => ty.data.expr.ty.isReal(),
-        .attributed => ty.data.attributed.base.isReal(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => true,
     };
 }
 
 pub fn isComplex(ty: Type) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         // zig fmt: off
         .complex_float, .complex_double, .complex_long_double,
         .complex_float128, .complex_char, .complex_schar, .complex_uchar, .complex_short,
@@ -590,19 +591,19 @@ pub fn isComplex(ty: Type) bool {
         .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128,
         .complex_bit_int, .complex_float16 => true,
         // zig fmt: on
-        .typeof_type => ty.data.sub_type.isComplex(),
-        .typeof_expr => ty.data.expr.ty.isComplex(),
-        .attributed => ty.data.attributed.base.isComplex(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
 
 pub fn isVoidStar(ty: Type) bool {
-    return switch (ty.specifier) {
-        .pointer => ty.data.sub_type.specifier == .void,
-        .typeof_type => ty.data.sub_type.isVoidStar(),
-        .typeof_expr => ty.data.expr.ty.isVoidStar(),
-        .attributed => ty.data.attributed.base.isVoidStar(),
+    return switch (ty.base()) {
+        .pointer => ty.elemType().base() == .void,
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
@@ -615,12 +616,7 @@ pub fn isTypeof(ty: Type) bool {
 }
 
 pub fn isConst(ty: Type) bool {
-    return switch (ty.specifier) {
-        .typeof_type => ty.qual.@"const" or ty.data.sub_type.isConst(),
-        .typeof_expr => ty.qual.@"const" or ty.data.expr.ty.isConst(),
-        .attributed => ty.data.attributed.base.isConst(),
-        else => ty.qual.@"const",
-    };
+    return ty.canonicalize(.standard).qual.@"const";
 }
 
 pub fn isUnsignedInt(ty: Type, comp: *const Compilation) bool {
@@ -628,51 +624,52 @@ pub fn isUnsignedInt(ty: Type, comp: *const Compilation) bool {
 }
 
 pub fn signedness(ty: Type, comp: *const Compilation) std.builtin.Signedness {
-    return switch (ty.specifier) {
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
         // zig fmt: off
         .char, .complex_char => return comp.getCharSignedness(),
         .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128, .bool, .complex_uchar, .complex_ushort,
         .complex_uint, .complex_ulong, .complex_ulong_long, .complex_uint128 => .unsigned,
         // zig fmt: on
-        .bit_int, .complex_bit_int => ty.data.int.signedness,
-        .typeof_type => ty.data.sub_type.signedness(comp),
-        .typeof_expr => ty.data.expr.ty.signedness(comp),
-        .attributed => ty.data.attributed.base.signedness(comp),
+        .bit_int, .complex_bit_int => canon.data.int.signedness,
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => .signed,
     };
 }
 
 pub fn isEnumOrRecord(ty: Type) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         .@"enum", .@"struct", .@"union" => true,
-        .typeof_type => ty.data.sub_type.isEnumOrRecord(),
-        .typeof_expr => ty.data.expr.ty.isEnumOrRecord(),
-        .attributed => ty.data.attributed.base.isEnumOrRecord(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
 
 pub fn isRecord(ty: Type) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         .@"struct", .@"union" => true,
-        .typeof_type => ty.data.sub_type.isRecord(),
-        .typeof_expr => ty.data.expr.ty.isRecord(),
-        .attributed => ty.data.attributed.base.isRecord(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
 
 pub fn isAnonymousRecord(ty: Type, comp: *const Compilation) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         // anonymous records can be recognized by their names which are in
         // the format "(anonymous TAG at path:line:col)".
         .@"struct", .@"union" => {
             const mapper = comp.string_interner.getSlowTypeMapper();
-            return mapper.lookup(ty.data.record.name)[0] == '(';
+            return mapper.lookup(ty.getRecord().?.name)[0] == '(';
         },
-        .typeof_type => ty.data.sub_type.isAnonymousRecord(comp),
-        .typeof_expr => ty.data.expr.ty.isAnonymousRecord(comp),
-        .attributed => ty.data.attributed.base.isAnonymousRecord(comp),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
@@ -702,22 +699,24 @@ pub fn elemType(ty: Type) Type {
 }
 
 pub fn returnType(ty: Type) Type {
-    return switch (ty.specifier) {
-        .func, .var_args_func, .old_style_func => ty.data.func.return_type,
-        .typeof_type => ty.data.sub_type.returnType(),
-        .typeof_expr => ty.data.expr.ty.returnType(),
-        .attributed => ty.data.attributed.base.returnType(),
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
+        .func, .var_args_func, .old_style_func => canon.data.func.return_type,
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         .invalid => Type.invalid,
         else => unreachable,
     };
 }
 
 pub fn params(ty: Type) []Func.Param {
-    return switch (ty.specifier) {
-        .func, .var_args_func, .old_style_func => ty.data.func.params,
-        .typeof_type => ty.data.sub_type.params(),
-        .typeof_expr => ty.data.expr.ty.params(),
-        .attributed => ty.data.attributed.base.params(),
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
+        .func, .var_args_func, .old_style_func => canon.data.func.params,
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         .invalid => &.{},
         else => unreachable,
     };
@@ -734,11 +733,12 @@ pub fn isInvalidFunc(ty: Type) bool {
 }
 
 pub fn arrayLen(ty: Type) ?u64 {
-    return switch (ty.specifier) {
-        .array, .static_array => ty.data.array.len,
-        .typeof_type => ty.data.sub_type.arrayLen(),
-        .typeof_expr => ty.data.expr.ty.arrayLen(),
-        .attributed => ty.data.attributed.base.arrayLen(),
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
+        .array, .static_array => canon.data.array.len,
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => null,
     };
 }
@@ -766,11 +766,12 @@ pub fn getAttributes(ty: Type) []const Attribute {
 }
 
 pub fn getRecord(ty: Type) ?*const Type.Record {
-    return switch (ty.specifier) {
-        .attributed => ty.data.attributed.base.getRecord(),
-        .typeof_type => ty.data.sub_type.getRecord(),
-        .typeof_expr => ty.data.expr.ty.getRecord(),
-        .@"struct", .@"union" => ty.data.record,
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
+        .@"struct", .@"union" => canon.data.record,
+        .attributed => unreachable,
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
         else => null,
     };
 }
@@ -863,15 +864,16 @@ pub fn integerConversion(a: Type, b: Type, comp: *const Compilation) Type {
 }
 
 pub fn integerPromotion(ty: Type, comp: *Compilation) Type {
-    var specifier = ty.specifier;
+    var canon = ty.canonicalize(.standard);
+    var specifier = canon.specifier;
     switch (specifier) {
         .@"enum" => {
-            if (ty.hasIncompleteSize()) return .{ .specifier = .int };
-            if (ty.data.@"enum".fixed) return ty.data.@"enum".tag_ty.integerPromotion(comp);
+            if (canon.hasIncompleteSize()) return .{ .specifier = .int };
+            if (canon.data.@"enum".fixed) return canon.data.@"enum".tag_ty.integerPromotion(comp);
 
-            specifier = ty.data.@"enum".tag_ty.specifier;
+            specifier = canon.data.@"enum".tag_ty.specifier;
         },
-        .bit_int, .complex_bit_int => return .{ .specifier = specifier, .data = ty.data },
+        .bit_int, .complex_bit_int => return .{ .specifier = specifier, .data = canon.data },
         else => {},
     }
     return switch (specifier) {
@@ -879,15 +881,15 @@ pub fn integerPromotion(ty: Type, comp: *Compilation) Type {
             .specifier = switch (specifier) {
                 // zig fmt: off
                 .bool, .char, .schar, .uchar, .short => .int,
-                .ushort => if (ty.sizeof(comp).? == sizeof(.{ .specifier = .int }, comp)) Specifier.uint else .int,
+                .ushort => if (canon.sizeof(comp).? == sizeof(Type.int, comp)) Specifier.uint else .int,
                 .int, .uint, .long, .ulong, .long_long, .ulong_long, .int128, .uint128, .complex_char,
                 .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int,
                 .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long,
                 .complex_int128, .complex_uint128 => specifier,
                 // zig fmt: on
-                .typeof_type => return ty.data.sub_type.integerPromotion(comp),
-                .typeof_expr => return ty.data.expr.ty.integerPromotion(comp),
-                .attributed => return ty.data.attributed.base.integerPromotion(comp),
+                .typeof_type => unreachable,
+                .typeof_expr => unreachable,
+                .attributed => unreachable,
                 .invalid => .invalid,
                 else => unreachable, // _BitInt, or not an integer type
             },
@@ -914,22 +916,24 @@ pub fn bitfieldPromotion(ty: Type, comp: *Compilation, width: u32) ?Type {
 }
 
 pub fn hasIncompleteSize(ty: Type) bool {
-    if (ty.isDecayed()) return false;
-    return switch (ty.specifier) {
+    const canon = ty.canonicalize(.standard);
+    if (canon.isDecayed()) return false;
+    return switch (canon.specifier) {
         .void, .incomplete_array => true,
-        .@"enum" => ty.data.@"enum".isIncomplete() and !ty.data.@"enum".fixed,
-        .@"struct", .@"union" => ty.data.record.isIncomplete(),
-        .array, .static_array => ty.data.array.elem.hasIncompleteSize(),
-        .typeof_type => ty.data.sub_type.hasIncompleteSize(),
-        .typeof_expr, .variable_len_array => ty.data.expr.ty.hasIncompleteSize(),
-        .unspecified_variable_len_array => ty.data.sub_type.hasIncompleteSize(),
-        .attributed => ty.data.attributed.base.hasIncompleteSize(),
+        .@"enum" => canon.data.@"enum".isIncomplete() and !canon.data.@"enum".fixed,
+        .@"struct", .@"union" => canon.data.record.isIncomplete(),
+        .array, .static_array => canon.data.array.elem.hasIncompleteSize(),
+        .variable_len_array => canon.data.expr.ty.hasIncompleteSize(),
+        .unspecified_variable_len_array => canon.data.sub_type.hasIncompleteSize(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }
 
 pub fn hasUnboundVLA(ty: Type) bool {
-    var cur = ty;
+    var cur = ty.canonicalize(.standard);
     while (true) {
         switch (cur.specifier) {
             .unspecified_variable_len_array => return true,
@@ -938,9 +942,9 @@ pub fn hasUnboundVLA(ty: Type) bool {
             .incomplete_array,
             .variable_len_array,
             => cur = cur.elemType(),
-            .typeof_type => cur = cur.data.sub_type.*,
-            .typeof_expr => cur = cur.data.expr.ty,
-            .attributed => cur = cur.data.attributed.base,
+            .typeof_type => unreachable,
+            .typeof_expr => unreachable,
+            .attributed => unreachable,
             else => return false,
         }
     }
@@ -1028,14 +1032,15 @@ pub fn sizeof(ty: Type, comp: *const Compilation) ?u64 {
 }
 
 pub fn bitSizeof(ty: Type, comp: *const Compilation) ?u64 {
-    return switch (ty.specifier) {
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
         .bool => if (comp.langopts.emulate == .msvc) @as(u64, 8) else 1,
-        .typeof_type => ty.data.sub_type.bitSizeof(comp),
-        .typeof_expr => ty.data.expr.ty.bitSizeof(comp),
-        .attributed => ty.data.attributed.base.bitSizeof(comp),
-        .bit_int => return ty.data.int.bits,
+        .bit_int => canon.data.int.bits,
         .long_double => comp.target.cTypeBitSize(.longdouble),
-        else => 8 * (ty.sizeof(comp) orelse return null),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
+        else => 8 * (canon.sizeof(comp) orelse return null),
     };
 }
 
@@ -1141,15 +1146,13 @@ pub fn base(ty: Type) Type.Specifier {
     var cur = ty;
     while (true) {
         switch (cur.specifier) {
-            .invalid,
-            .auto_type,
-            .c23_auto,
-            => unreachable,
-
             .typeof_type => cur = cur.data.sub_type.*,
             .typeof_expr => cur = cur.data.expr.ty,
             .attributed => cur = cur.data.attributed.base,
 
+            .auto_type,
+            .c23_auto,
+            .invalid,
             .void,
             .bool,
             .char,

From 76288eb3f3182a0eee21d800fcacf6f8e300b216 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:21:14 -0700
Subject: [PATCH 09/15] Type: implement get using canonicalize

---
 src/aro/Type.zig | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index a7c59513..ca79917b 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -1244,13 +1244,14 @@ pub fn canonicalize(ty: Type, qual_handling: QualHandling) Type {
     return cur;
 }
 
-pub fn get(ty: *const Type, specifier: Specifier) ?*const Type {
+pub fn get(ty: Type, specifier: Specifier) ?Type {
     std.debug.assert(specifier != .typeof_type and specifier != .typeof_expr);
-    return switch (ty.specifier) {
-        .typeof_type => ty.data.sub_type.get(specifier),
-        .typeof_expr => ty.data.expr.ty.get(specifier),
-        .attributed => ty.data.attributed.base.get(specifier),
-        else => if (ty.specifier == specifier) ty else null,
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
+        else => if (canon.specifier == specifier) canon else null,
     };
 }
 

From 4bd239ccae2a4494458b16b9af27a0f3ea0235c7 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:25:29 -0700
Subject: [PATCH 10/15] Type: make typeof types unreachable in integerRank

---
 src/aro/Type.zig | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index ca79917b..e74490a0 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -1389,9 +1389,9 @@ pub fn integerRank(ty: Type, comp: *const Compilation) usize {
         .long_long, .ulong_long => 6 + (ty.bitSizeof(comp).? << 3),
         .int128, .uint128 => 7 + (ty.bitSizeof(comp).? << 3),
 
-        .typeof_type => ty.data.sub_type.integerRank(comp),
-        .typeof_expr => ty.data.expr.ty.integerRank(comp),
-        .attributed => ty.data.attributed.base.integerRank(comp),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
 
         .@"enum" => real.data.@"enum".tag_ty.integerRank(comp),
 
@@ -1423,7 +1423,7 @@ pub fn makeReal(ty: Type) Type {
             base_ty.specifier = .bit_int;
             return base_ty;
         },
-        else => return ty,
+        else => return base_ty,
     }
 }
 

From d946fd37d3a1a97e03ca555b71cf11cadaa5d609 Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:27:15 -0700
Subject: [PATCH 11/15] Type: simplify getAttribute implementation

---
 src/aro/Type.zig | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index e74490a0..2251d423 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -2421,17 +2421,10 @@ pub const Builder = struct {
 };
 
 pub fn getAttribute(ty: Type, comptime tag: Attribute.Tag) ?Attribute.ArgumentsForTag(tag) {
-    switch (ty.specifier) {
-        .typeof_type => return ty.data.sub_type.getAttribute(tag),
-        .typeof_expr => return ty.data.expr.ty.getAttribute(tag),
-        .attributed => {
-            for (ty.data.attributed.attributes) |attribute| {
-                if (attribute.tag == tag) return @field(attribute.args, @tagName(tag));
-            }
-            return null;
-        },
-        else => return null,
+    for (ty.getAttributes()) |attribute| {
+        if (attribute.tag == tag) return @field(attribute.args, @tagName(tag));
     }
+    return null;
 }
 
 pub fn hasAttribute(ty: Type, tag: Attribute.Tag) bool {

From d7249d7fed421dca1889c209e5486087e2c58dfb Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:32:39 -0700
Subject: [PATCH 12/15] Type: simplify isFunc and isCallable

---
 src/aro/Type.zig | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index 2251d423..263011eb 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -443,22 +443,29 @@ pub fn withAttributes(self: Type, allocator: std.mem.Allocator, attributes: []co
 }
 
 pub fn isCallable(ty: Type) ?Type {
-    return switch (ty.specifier) {
-        .func, .var_args_func, .old_style_func => ty,
-        .pointer => if (ty.data.sub_type.isFunc()) ty.data.sub_type.* else null,
-        .typeof_type => ty.data.sub_type.isCallable(),
-        .typeof_expr => ty.data.expr.ty.isCallable(),
-        .attributed => ty.data.attributed.base.isCallable(),
+    const canon = ty.canonicalize(.standard);
+    return switch (ty.base()) {
+        .func, .var_args_func, .old_style_func => canon,
+        .pointer => {
+            const child_ty = ty.elemType();
+            if (child_ty.isFunc()) {
+                return child_ty;
+            }
+            return null;
+        },
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => null,
     };
 }
 
 pub fn isFunc(ty: Type) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         .func, .var_args_func, .old_style_func => true,
-        .typeof_type => ty.data.sub_type.isFunc(),
-        .typeof_expr => ty.data.expr.ty.isFunc(),
-        .attributed => ty.data.attributed.base.isFunc(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }

From 61087a3554f605117dfdd69c25a92a1fc66bfd9a Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:34:17 -0700
Subject: [PATCH 13/15] Type: use Type.base to implememnt Type.is

---
 src/aro/Type.zig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index 263011eb..8b8be255 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -433,7 +433,7 @@ pub const invalid = Type{ .specifier = .invalid };
 /// types if necessary.
 pub fn is(ty: Type, specifier: Specifier) bool {
     std.debug.assert(specifier != .typeof_type and specifier != .typeof_expr);
-    return ty.get(specifier) != null;
+    return ty.base() == specifier;
 }
 
 pub fn withAttributes(self: Type, allocator: std.mem.Allocator, attributes: []const Attribute) !Type {

From 64804c08ec9ae9dd47c28e46fce71bc6932f9dbe Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:36:34 -0700
Subject: [PATCH 14/15] Type: use Type.base to implement
 Type.undergoesDefaultArgPromotion

---
 src/aro/Type.zig | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index 8b8be255..616121e8 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -499,16 +499,16 @@ pub fn setIncompleteArrayLen(ty: *Type, len: u64) void {
 
 /// Whether the type is promoted if used as a variadic argument or as an argument to a function with no prototype
 fn undergoesDefaultArgPromotion(ty: Type, comp: *const Compilation) bool {
-    return switch (ty.specifier) {
+    return switch (ty.base()) {
         .bool => true,
         .char, .uchar, .schar => true,
         .short, .ushort => true,
-        .@"enum" => if (comp.langopts.emulate == .clang) ty.data.@"enum".isIncomplete() else false,
+        .@"enum" => if (comp.langopts.emulate == .clang) ty.hasIncompleteSize() else false,
         .float => true,
 
-        .typeof_type => ty.data.sub_type.undergoesDefaultArgPromotion(comp),
-        .typeof_expr => ty.data.expr.ty.undergoesDefaultArgPromotion(comp),
-        .attributed => ty.data.attributed.base.undergoesDefaultArgPromotion(comp),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }

From b42d19d6c408ec98028ed005ba9d3aa225b5206e Mon Sep 17 00:00:00 2001
From: Evan Haas <evan@lagerdata.com>
Date: Fri, 23 Aug 2024 11:41:57 -0700
Subject: [PATCH 15/15] Type: use canonicalize for isArray

---
 src/aro/Type.zig | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/aro/Type.zig b/src/aro/Type.zig
index 616121e8..a65641ef 100644
--- a/src/aro/Type.zig
+++ b/src/aro/Type.zig
@@ -471,11 +471,12 @@ pub fn isFunc(ty: Type) bool {
 }
 
 pub fn isArray(ty: Type) bool {
-    return switch (ty.specifier) {
-        .array, .static_array, .incomplete_array, .variable_len_array, .unspecified_variable_len_array => !ty.isDecayed(),
-        .typeof_type => !ty.isDecayed() and ty.data.sub_type.isArray(),
-        .typeof_expr => !ty.isDecayed() and ty.data.expr.ty.isArray(),
-        .attributed => !ty.isDecayed() and ty.data.attributed.base.isArray(),
+    const canon = ty.canonicalize(.standard);
+    return switch (canon.specifier) {
+        .array, .static_array, .incomplete_array, .variable_len_array, .unspecified_variable_len_array => !canon.isDecayed(),
+        .typeof_type => unreachable,
+        .typeof_expr => unreachable,
+        .attributed => unreachable,
         else => false,
     };
 }