Add base class for sem Struct and StructMember

This CL splits the sem::Struct and sem::StructMember classes into having
a base class and a sem variant. The sem variant contains the
`ast::Declaration` for that sem node. This will allow the base classes
to move into the type hierarchy and keep the sem specific requirements
in the sem folder.

Bug: tint:1718
Change-Id: I83294e957a52b87b8536dbb3582daa06a8516253
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112860
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/resolver/atomics_test.cc b/src/tint/resolver/atomics_test.cc
index bb08e83..e3d0ca9 100644
--- a/src/tint/resolver/atomics_test.cc
+++ b/src/tint/resolver/atomics_test.cc
@@ -55,7 +55,7 @@
     ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
     auto* str = TypeOf(g)->UnwrapRef()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    ASSERT_EQ(str->Members().size(), 1u);
+    ASSERT_EQ(str->Members().Length(), 1u);
     auto* atomic = str->Members()[0]->Type()->As<sem::Atomic>();
     ASSERT_NE(atomic, nullptr);
     ASSERT_TRUE(atomic->Type()->Is<sem::I32>());
diff --git a/src/tint/resolver/builtin_test.cc b/src/tint/resolver/builtin_test.cc
index 15bf5de..4b33064 100644
--- a/src/tint/resolver/builtin_test.cc
+++ b/src/tint/resolver/builtin_test.cc
@@ -926,7 +926,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     EXPECT_TRUE(fract->Type()->Is<sem::F32>());
@@ -957,7 +957,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     EXPECT_TRUE(fract->Type()->Is<sem::F16>());
@@ -986,7 +986,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     ASSERT_TRUE(fract->Type()->Is<sem::Vector>());
@@ -1021,7 +1021,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     ASSERT_TRUE(fract->Type()->Is<sem::Vector>());
@@ -1058,7 +1058,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* sig = ty->Members()[0];
     EXPECT_TYPE(sig->Type(), TypeOf(expr));
@@ -1198,7 +1198,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     EXPECT_TRUE(fract->Type()->Is<sem::F32>());
@@ -1229,7 +1229,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     EXPECT_TRUE(fract->Type()->Is<sem::F16>());
@@ -1258,7 +1258,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     ASSERT_TRUE(fract->Type()->Is<sem::Vector>());
@@ -1293,7 +1293,7 @@
     ASSERT_NE(TypeOf(call), nullptr);
     auto* ty = TypeOf(call)->As<sem::Struct>();
     ASSERT_NE(ty, nullptr);
-    ASSERT_EQ(ty->Members().size(), 2u);
+    ASSERT_EQ(ty->Members().Length(), 2u);
 
     auto* fract = ty->Members()[0];
     ASSERT_TRUE(fract->Type()->Is<sem::Vector>());
diff --git a/src/tint/resolver/builtins_validation_test.cc b/src/tint/resolver/builtins_validation_test.cc
index c0452ce..d60b5b1 100644
--- a/src/tint/resolver/builtins_validation_test.cc
+++ b/src/tint/resolver/builtins_validation_test.cc
@@ -892,8 +892,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     EXPECT_TRUE(members[0]->Type()->Is<sem::F32>());
     EXPECT_TRUE(members[1]->Type()->Is<sem::I32>());
 }
@@ -905,8 +905,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
     ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
     EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 2u);
@@ -922,8 +922,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
     ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
     EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 3u);
@@ -939,8 +939,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
     ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
     EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 4u);
@@ -956,8 +956,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     EXPECT_TRUE(members[0]->Type()->Is<sem::F32>());
     EXPECT_TRUE(members[1]->Type()->Is<sem::F32>());
 }
@@ -969,8 +969,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
     ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
     EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 2u);
@@ -986,8 +986,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
     ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
     EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 3u);
@@ -1003,8 +1003,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
     auto* res_ty = TypeOf(builtin)->As<sem::Struct>();
     ASSERT_TRUE(res_ty != nullptr);
-    auto& members = res_ty->Members();
-    ASSERT_EQ(members.size(), 2u);
+    auto members = res_ty->Members();
+    ASSERT_EQ(members.Length(), 2u);
     ASSERT_TRUE(members[0]->Type()->Is<sem::Vector>());
     ASSERT_TRUE(members[1]->Type()->Is<sem::Vector>());
     EXPECT_EQ(members[0]->Type()->As<sem::Vector>()->Width(), 4u);
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index 45ab2de..ce38b1d 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -416,7 +416,7 @@
         conv_els.Reserve(elements.Length());
         std::function<const sem::Type*(size_t idx)> target_el_ty;
         if (auto* str = target_ty->As<sem::Struct>()) {
-            if (str->Members().size() != elements.Length()) {
+            if (str->Members().Length() != elements.Length()) {
                 TINT_ICE(Resolver, builder.Diagnostics())
                     << "const-eval conversion of structure has mismatched element counts";
                 return utils::Failure;
@@ -496,7 +496,7 @@
         [&](const sem::Struct* s) -> const ImplConstant* {
             utils::Hashmap<const sem::Type*, const ImplConstant*, 8> zero_by_type;
             utils::Vector<const sem::Constant*, 4> zeros;
-            zeros.Reserve(s->Members().size());
+            zeros.Reserve(s->Members().Length());
             for (auto* member : s->Members()) {
                 auto* zero = zero_by_type.GetOrCreate(
                     member->Type(), [&] { return ZeroValue(builder, member->Type()); });
@@ -507,7 +507,7 @@
             }
             if (zero_by_type.Count() == 1) {
                 // All members were of the same type, so the zero value is the same for all members.
-                return builder.create<Splat>(type, zeros[0], s->Members().size());
+                return builder.create<Splat>(type, zeros[0], s->Members().Length());
             }
             return CreateComposite(builder, s, std::move(zeros));
         },
diff --git a/src/tint/resolver/const_eval_construction_test.cc b/src/tint/resolver/const_eval_construction_test.cc
index cabc4ab..fe323c2 100644
--- a/src/tint/resolver/const_eval_construction_test.cc
+++ b/src/tint/resolver/const_eval_construction_test.cc
@@ -1625,7 +1625,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 3u);
+    EXPECT_EQ(str->Members().Length(), 3u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AllEqual());
@@ -1670,7 +1670,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 5u);
+    EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
@@ -1723,7 +1723,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 3u);
+    EXPECT_EQ(str->Members().Length(), 3u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AllEqual());
@@ -1777,7 +1777,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 5u);
+    EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
@@ -1849,7 +1849,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 2u);
+    EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AllEqual());
@@ -1892,7 +1892,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 5u);
+    EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
@@ -1950,7 +1950,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 5u);
+    EXPECT_EQ(str->Members().Length(), 5u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
@@ -2024,7 +2024,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 2u);
+    EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
@@ -2064,7 +2064,7 @@
     ASSERT_NE(sem, nullptr);
     auto* str = sem->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 2u);
+    EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(sem->ConstantValue(), nullptr);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_FALSE(sem->ConstantValue()->AllEqual());
diff --git a/src/tint/resolver/const_eval_member_access_test.cc b/src/tint/resolver/const_eval_member_access_test.cc
index 25d1f26..2c5b15c 100644
--- a/src/tint/resolver/const_eval_member_access_test.cc
+++ b/src/tint/resolver/const_eval_member_access_test.cc
@@ -43,7 +43,7 @@
     ASSERT_NE(outer, nullptr);
     auto* str = outer->Type()->As<sem::Struct>();
     ASSERT_NE(str, nullptr);
-    EXPECT_EQ(str->Members().size(), 2u);
+    EXPECT_EQ(str->Members().Length(), 2u);
     ASSERT_NE(outer->ConstantValue(), nullptr);
     EXPECT_TYPE(outer->ConstantValue()->Type(), outer->Type());
     EXPECT_FALSE(outer->ConstantValue()->AllEqual());
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
index ddbc8f8..025ea95 100644
--- a/src/tint/resolver/inferred_type_test.cc
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -150,12 +150,11 @@
     auto* member = Member("x", ty.i32());
     auto* str = Structure("S", utils::Vector{member});
 
-    auto* expected_type =
-        create<sem::Struct>(str, str->source, str->name,
-                            sem::StructMemberList{create<sem::StructMember>(
-                                member, member->source, member->symbol, create<sem::I32>(), 0u, 0u,
-                                0u, 4u, std::nullopt)},
-                            0u, 4u, 4u);
+    auto* expected_type = create<sem::Struct>(
+        str, str->source, str->name,
+        utils::Vector{create<sem::StructMember>(member, member->source, member->symbol,
+                                                create<sem::I32>(), 0u, 0u, 0u, 4u, std::nullopt)},
+        0u, 4u, 4u);
 
     auto* ctor_expr = Construct(ty.Of(str));
 
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index ebd5043..2578881 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -802,18 +802,18 @@
                           std::initializer_list<NameAndType> member_names_and_types) {
     uint32_t offset = 0;
     uint32_t max_align = 0;
-    sem::StructMemberList members;
+    utils::Vector<const sem::StructMember*, 4> members;
     for (auto& m : member_names_and_types) {
         uint32_t align = std::max<uint32_t>(m.type->Align(), 1);
         uint32_t size = m.type->Size();
         offset = utils::RoundUp(align, offset);
         max_align = std::max(max_align, align);
-        members.emplace_back(b.create<sem::StructMember>(
+        members.Push(b.create<sem::StructMember>(
             /* declaration */ nullptr,
             /* source */ Source{},
             /* name */ b.Sym(m.name),
             /* type */ m.type,
-            /* index */ static_cast<uint32_t>(members.size()),
+            /* index */ static_cast<uint32_t>(members.Length()),
             /* offset */ offset,
             /* align */ align,
             /* size */ size,
@@ -826,7 +826,7 @@
         /* declaration */ nullptr,
         /* source */ Source{},
         /* name */ b.Sym(name),
-        /* members */ members,
+        /* members */ std::move(members),
         /* align */ max_align,
         /* size */ size_with_padding,
         /* size_no_padding */ size_without_padding);
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 628bf45..e47f83c 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -2069,7 +2069,7 @@
                     StructInitializerSig{{str, args.Length(), args_stage}},
                     [&]() -> sem::TypeInitializer* {
                         utils::Vector<const sem::Parameter*, 8> params;
-                        params.Resize(std::min(args.Length(), str->Members().size()));
+                        params.Resize(std::min(args.Length(), str->Members().Length()));
                         for (size_t i = 0, n = params.Length(); i < n; i++) {
                             params[i] = builder_->create<sem::Parameter>(
                                 nullptr,                    // declaration
@@ -3097,19 +3097,15 @@
         Mark(attr);
     }
 
-    sem::StructMemberList sem_members;
-    sem_members.reserve(str->members.Length());
+    utils::Vector<const sem::StructMember*, 8> sem_members;
+    sem_members.Reserve(str->members.Length());
 
-    // Calculate the effective size and alignment of each field, and the overall
-    // size of the structure.
-    // For size, use the size attribute if provided, otherwise use the default
-    // size for the type.
-    // For alignment, use the alignment attribute if provided, otherwise use the
-    // default alignment for the member type.
-    // Diagnostic errors are raised if a basic rule is violated.
-    // Validation of storage-class rules requires analyzing the actual variable
-    // usage of the structure, and so is performed as part of the variable
-    // validation.
+    // Calculate the effective size and alignment of each field, and the overall size of the
+    // structure. For size, use the size attribute if provided, otherwise use the default size for
+    // the type. For alignment, use the alignment attribute if provided, otherwise use the default
+    // alignment for the member type. Diagnostic errors are raised if a basic rule is violated.
+    // Validation of storage-class rules requires analyzing the actual variable usage of the
+    // structure, and so is performed as part of the variable validation.
     uint64_t struct_size = 0;
     uint64_t struct_align = 1;
     utils::Hashmap<Symbol, const ast::StructMember*, 8> member_map;
@@ -3274,11 +3270,11 @@
         }
 
         auto* sem_member = builder_->create<sem::StructMember>(
-            member, member->source, member->symbol, type, static_cast<uint32_t>(sem_members.size()),
-            static_cast<uint32_t>(offset), static_cast<uint32_t>(align),
-            static_cast<uint32_t>(size), location);
+            member, member->source, member->symbol, type,
+            static_cast<uint32_t>(sem_members.Length()), static_cast<uint32_t>(offset),
+            static_cast<uint32_t>(align), static_cast<uint32_t>(size), location);
         builder_->Sem().Add(member, sem_member);
-        sem_members.emplace_back(sem_member);
+        sem_members.Push(sem_member);
 
         struct_size = offset + size;
         struct_align = std::max(struct_align, align);
@@ -3299,10 +3295,10 @@
     }
 
     auto* out = builder_->create<sem::Struct>(
-        str, str->source, str->name, sem_members, static_cast<uint32_t>(struct_align),
+        str, str->source, str->name, std::move(sem_members), static_cast<uint32_t>(struct_align),
         static_cast<uint32_t>(struct_size), static_cast<uint32_t>(size_no_padding));
 
-    for (size_t i = 0; i < sem_members.size(); i++) {
+    for (size_t i = 0; i < sem_members.Length(); i++) {
         auto* mem_type = sem_members[i]->Type();
         if (mem_type->Is<sem::Atomic>()) {
             atomic_composite_info_.Add(out, &sem_members[i]->Source());
diff --git a/src/tint/resolver/struct_layout_test.cc b/src/tint/resolver/struct_layout_test.cc
index acfe691..7c50a3e 100644
--- a/src/tint/resolver/struct_layout_test.cc
+++ b/src/tint/resolver/struct_layout_test.cc
@@ -39,7 +39,7 @@
     EXPECT_EQ(sem->Size(), 12u);
     EXPECT_EQ(sem->SizeNoPadding(), 12u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 3u);
+    ASSERT_EQ(sem->Members().Length(), 3u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
@@ -74,7 +74,7 @@
     EXPECT_EQ(sem->Size(), 24u);
     EXPECT_EQ(sem->SizeNoPadding(), 22u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 7u);
+    ASSERT_EQ(sem->Members().Length(), 7u);
     // f32
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
@@ -121,7 +121,7 @@
     EXPECT_EQ(sem->Size(), 8u);
     EXPECT_EQ(sem->SizeNoPadding(), 8u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 2u);
+    ASSERT_EQ(sem->Members().Length(), 2u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
@@ -150,7 +150,7 @@
     EXPECT_EQ(sem->Size(), 52u);
     EXPECT_EQ(sem->SizeNoPadding(), 52u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 4u);
+    ASSERT_EQ(sem->Members().Length(), 4u);
     // array<i32, 3>
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
@@ -190,7 +190,7 @@
     EXPECT_EQ(sem->Size(), 164u);
     EXPECT_EQ(sem->SizeNoPadding(), 164u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 4u);
+    ASSERT_EQ(sem->Members().Length(), 4u);
     // array<i32, 3>, stride = 8
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
@@ -225,7 +225,7 @@
     EXPECT_EQ(sem->Size(), 4u);
     EXPECT_EQ(sem->SizeNoPadding(), 4u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 1u);
+    ASSERT_EQ(sem->Members().Length(), 1u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
@@ -246,7 +246,7 @@
     EXPECT_EQ(sem->Size(), 32u);
     EXPECT_EQ(sem->SizeNoPadding(), 32u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 1u);
+    ASSERT_EQ(sem->Members().Length(), 1u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 32u);
@@ -269,7 +269,7 @@
     EXPECT_EQ(sem->Size(), 384u);
     EXPECT_EQ(sem->SizeNoPadding(), 384u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 1u);
+    ASSERT_EQ(sem->Members().Length(), 1u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 384u);
@@ -296,7 +296,7 @@
     EXPECT_EQ(sem->Size(), 576u);
     EXPECT_EQ(sem->SizeNoPadding(), 576u);
     EXPECT_EQ(sem->Align(), 16u);
-    ASSERT_EQ(sem->Members().size(), 1u);
+    ASSERT_EQ(sem->Members().Length(), 1u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 16u);
     EXPECT_EQ(sem->Members()[0]->Size(), 576u);
@@ -319,7 +319,7 @@
     EXPECT_EQ(sem->Size(), 48u);
     EXPECT_EQ(sem->SizeNoPadding(), 48u);
     EXPECT_EQ(sem->Align(), 16u);
-    ASSERT_EQ(sem->Members().size(), 3u);
+    ASSERT_EQ(sem->Members().Length(), 3u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // vec2
     EXPECT_EQ(sem->Members()[0]->Align(), 8u);
     EXPECT_EQ(sem->Members()[0]->Size(), 8u);
@@ -365,7 +365,7 @@
     EXPECT_EQ(sem->Size(), 576u);
     EXPECT_EQ(sem->SizeNoPadding(), 576u);
     EXPECT_EQ(sem->Align(), 16u);
-    ASSERT_EQ(sem->Members().size(), 18u);
+    ASSERT_EQ(sem->Members().Length(), 18u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);  // mat2x2<f32>
     EXPECT_EQ(sem->Members()[0]->Align(), 8u);
     EXPECT_EQ(sem->Members()[0]->Size(), 16u);
@@ -443,7 +443,7 @@
     EXPECT_EQ(sem->Size(), 80u);
     EXPECT_EQ(sem->SizeNoPadding(), 68u);
     EXPECT_EQ(sem->Align(), 16u);
-    ASSERT_EQ(sem->Members().size(), 3u);
+    ASSERT_EQ(sem->Members().Length(), 3u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
@@ -478,7 +478,7 @@
     EXPECT_EQ(sem->Size(), 76u);
     EXPECT_EQ(sem->SizeNoPadding(), 76u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 4u);
+    ASSERT_EQ(sem->Members().Length(), 4u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
@@ -516,7 +516,7 @@
     EXPECT_EQ(sem->Size(), 96u);
     EXPECT_EQ(sem->SizeNoPadding(), 68u);
     EXPECT_EQ(sem->Align(), 32u);
-    ASSERT_EQ(sem->Members().size(), 4u);
+    ASSERT_EQ(sem->Members().Length(), 4u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
@@ -546,7 +546,7 @@
     EXPECT_EQ(sem->Size(), 1024u);
     EXPECT_EQ(sem->SizeNoPadding(), 4u);
     EXPECT_EQ(sem->Align(), 1024u);
-    ASSERT_EQ(sem->Members().size(), 1u);
+    ASSERT_EQ(sem->Members().Length(), 1u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
     EXPECT_EQ(sem->Members()[0]->Align(), 1024u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
@@ -576,7 +576,7 @@
     EXPECT_EQ(sem->Size(), 132u);
     EXPECT_EQ(sem->SizeNoPadding(), 132u);
     EXPECT_EQ(sem->Align(), 4u);
-    ASSERT_EQ(sem->Members().size(), 5u);
+    ASSERT_EQ(sem->Members().Length(), 5u);
     EXPECT_EQ(sem->Members()[0]->Offset(), 4u);
     EXPECT_EQ(sem->Members()[0]->Align(), 4u);
     EXPECT_EQ(sem->Members()[0]->Size(), 4u);
diff --git a/src/tint/resolver/struct_pipeline_stage_use_test.cc b/src/tint/resolver/struct_pipeline_stage_use_test.cc
index 107b241..c4455b7 100644
--- a/src/tint/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/tint/resolver/struct_pipeline_stage_use_test.cc
@@ -184,7 +184,7 @@
 
     auto* sem = TypeOf(s)->As<sem::Struct>();
     ASSERT_NE(sem, nullptr);
-    ASSERT_EQ(1u, sem->Members().size());
+    ASSERT_EQ(1u, sem->Members().Length());
     EXPECT_EQ(3u, sem->Members()[0]->Location());
 }
 
@@ -214,7 +214,7 @@
 
     auto* sem = TypeOf(s)->As<sem::Struct>();
     ASSERT_NE(sem, nullptr);
-    ASSERT_EQ(1u, sem->Members().size());
+    ASSERT_EQ(1u, sem->Members().Length());
     EXPECT_EQ(3u, sem->Members()[0]->Location());
 }
 
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index dcb9b4c..8da1726 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -440,7 +440,7 @@
     }
 
     if (auto* str = store_ty->As<sem::Struct>()) {
-        for (size_t i = 0; i < str->Members().size(); ++i) {
+        for (size_t i = 0; i < str->Members().Length(); ++i) {
             auto* const m = str->Members()[i];
             uint32_t required_align = required_alignment_of(m->Type());
 
@@ -1724,10 +1724,10 @@
     }
 
     if (ctor->args.Length() > 0) {
-        if (ctor->args.Length() != struct_type->Members().size()) {
-            std::string fm = ctor->args.Length() < struct_type->Members().size() ? "few" : "many";
+        if (ctor->args.Length() != struct_type->Members().Length()) {
+            std::string fm = ctor->args.Length() < struct_type->Members().Length() ? "few" : "many";
             AddError("struct initializer has too " + fm + " inputs: expected " +
-                         std::to_string(struct_type->Members().size()) + ", found " +
+                         std::to_string(struct_type->Members().Length()) + ", found " +
                          std::to_string(ctor->args.Length()),
                      ctor->source);
             return false;
@@ -2019,7 +2019,7 @@
 }
 
 bool Validator::Structure(const sem::Struct* str, ast::PipelineStage stage) const {
-    if (str->Members().empty()) {
+    if (str->Members().IsEmpty()) {
         AddError("structures must have at least one member", str->Source());
         return false;
     }
@@ -2028,7 +2028,7 @@
     for (auto* member : str->Members()) {
         if (auto* r = member->Type()->As<sem::Array>()) {
             if (r->Count()->Is<sem::RuntimeArrayCount>()) {
-                if (member != str->Members().back()) {
+                if (member != str->Members().Back()) {
                     AddError("runtime arrays may only appear as the last member of a struct",
                              member->Source());
                     return false;
diff --git a/src/tint/sem/struct.cc b/src/tint/sem/struct.cc
index c46e310..ecb06a3 100644
--- a/src/tint/sem/struct.cc
+++ b/src/tint/sem/struct.cc
@@ -23,13 +23,15 @@
 #include "src/tint/symbol_table.h"
 #include "src/tint/utils/hash.h"
 
+TINT_INSTANTIATE_TYPEINFO(tint::sem::StructBase);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Struct);
+TINT_INSTANTIATE_TYPEINFO(tint::sem::StructMemberBase);
 TINT_INSTANTIATE_TYPEINFO(tint::sem::StructMember);
 
 namespace tint::sem {
 namespace {
 
-TypeFlags FlagsFrom(const StructMemberList& members) {
+TypeFlags FlagsFrom(utils::VectorRef<const StructMemberBase*> members) {
     TypeFlags flags{
         TypeFlag::kConstructable,
         TypeFlag::kCreationFixedFootprint,
@@ -54,12 +56,21 @@
 Struct::Struct(const ast::Struct* declaration,
                tint::Source source,
                Symbol name,
-               StructMemberList members,
+               utils::VectorRef<const StructMember*> members,
                uint32_t align,
                uint32_t size,
                uint32_t size_no_padding)
+    : Base(source, name, members, align, size, size_no_padding), declaration_(declaration) {}
+
+Struct::~Struct() = default;
+
+StructBase::StructBase(tint::Source source,
+                       Symbol name,
+                       utils::VectorRef<const StructMemberBase*> members,
+                       uint32_t align,
+                       uint32_t size,
+                       uint32_t size_no_padding)
     : Base(FlagsFrom(members)),
-      declaration_(declaration),
       source_(source),
       name_(name),
       members_(std::move(members)),
@@ -67,20 +78,20 @@
       size_(size),
       size_no_padding_(size_no_padding) {}
 
-Struct::~Struct() = default;
+StructBase::~StructBase() = default;
 
-size_t Struct::Hash() const {
+size_t StructBase::Hash() const {
     return utils::Hash(TypeInfo::Of<Struct>().full_hashcode, name_);
 }
 
-bool Struct::Equals(const sem::Type& other) const {
+bool StructBase::Equals(const sem::Type& other) const {
     if (auto* o = other.As<Struct>()) {
         return o->name_ == name_;
     }
     return false;
 }
 
-const StructMember* Struct::FindMember(Symbol name) const {
+const StructMemberBase* StructBase::FindMember(Symbol name) const {
     for (auto* member : members_) {
         if (member->Name() == name) {
             return member;
@@ -89,27 +100,29 @@
     return nullptr;
 }
 
-uint32_t Struct::Align() const {
+uint32_t StructBase::Align() const {
     return align_;
 }
 
-uint32_t Struct::Size() const {
+uint32_t StructBase::Size() const {
     return size_;
 }
 
-std::string Struct::FriendlyName(const SymbolTable& symbols) const {
+std::string StructBase::FriendlyName(const SymbolTable& symbols) const {
     return symbols.NameFor(name_);
 }
 
-std::string Struct::Layout(const tint::SymbolTable& symbols) const {
+std::string StructBase::Layout(const tint::SymbolTable& symbols) const {
     std::stringstream ss;
 
-    auto member_name_of = [&](const sem::StructMember* sm) { return symbols.NameFor(sm->Name()); };
+    auto member_name_of = [&](const sem::StructMemberBase* sm) {
+        return symbols.NameFor(sm->Name());
+    };
 
-    if (Members().empty()) {
+    if (Members().IsEmpty()) {
         return {};
     }
-    const auto* const last_member = Members().back();
+    const auto* const last_member = Members().Back();
     const uint32_t last_member_struct_padding_offset = last_member->Offset() + last_member->Size();
 
     // Compute max widths to align output
@@ -135,7 +148,7 @@
 
     print_struct_begin_line(Align(), Size(), UnwrapRef()->FriendlyName(symbols));
 
-    for (size_t i = 0; i < Members().size(); ++i) {
+    for (size_t i = 0; i < Members().Length(); ++i) {
         auto* const m = Members()[i];
 
         // Output field alignment padding, if any
@@ -176,8 +189,19 @@
                            uint32_t align,
                            uint32_t size,
                            std::optional<uint32_t> location)
-    : declaration_(declaration),
-      source_(source),
+    : Base(source, name, type, index, offset, align, size, location), declaration_(declaration) {}
+
+StructMember::~StructMember() = default;
+
+StructMemberBase::StructMemberBase(tint::Source source,
+                                   Symbol name,
+                                   const sem::Type* type,
+                                   uint32_t index,
+                                   uint32_t offset,
+                                   uint32_t align,
+                                   uint32_t size,
+                                   std::optional<uint32_t> location)
+    : source_(source),
       name_(name),
       type_(type),
       index_(index),
@@ -186,6 +210,6 @@
       size_(size),
       location_(location) {}
 
-StructMember::~StructMember() = default;
+StructMemberBase::~StructMemberBase() = default;
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/struct.h b/src/tint/sem/struct.h
index f878854..5ae2a93 100644
--- a/src/tint/sem/struct.h
+++ b/src/tint/sem/struct.h
@@ -20,13 +20,13 @@
 #include <optional>
 #include <string>
 #include <unordered_set>
-#include <vector>
 
 #include "src/tint/ast/address_space.h"
 #include "src/tint/ast/struct.h"
 #include "src/tint/sem/node.h"
 #include "src/tint/sem/type.h"
 #include "src/tint/symbol.h"
+#include "src/tint/utils/vector.h"
 
 // Forward declarations
 namespace tint::ast {
@@ -34,14 +34,12 @@
 }  // namespace tint::ast
 namespace tint::sem {
 class StructMember;
+class StructMemberBase;
 class Type;
 }  // namespace tint::sem
 
 namespace tint::sem {
 
-/// A vector of StructMember pointers.
-using StructMemberList = std::vector<const StructMember*>;
-
 /// Metadata to capture how a structure is used in a shader module.
 enum class PipelineStageUsage {
     kVertexInput,
@@ -52,11 +50,10 @@
     kComputeOutput,
 };
 
-/// Struct holds the semantic information for structures.
-class Struct final : public Castable<Struct, Type> {
+/// StructBase holds the semantic information for structures.
+class StructBase : public Castable<StructBase, Type> {
   public:
     /// Constructor
-    /// @param declaration the AST structure declaration
     /// @param source the source of the structure
     /// @param name the name of the structure
     /// @param members the structure members
@@ -64,16 +61,15 @@
     /// @param size the byte size of the structure
     /// @param size_no_padding size of the members without the end of structure
     /// alignment padding
-    Struct(const ast::Struct* declaration,
-           tint::Source source,
-           Symbol name,
-           StructMemberList members,
-           uint32_t align,
-           uint32_t size,
-           uint32_t size_no_padding);
+    StructBase(tint::Source source,
+               Symbol name,
+               utils::VectorRef<const StructMemberBase*> members,
+               uint32_t align,
+               uint32_t size,
+               uint32_t size_no_padding);
 
     /// Destructor
-    ~Struct() override;
+    ~StructBase() override;
 
     /// @returns a hash of the type.
     size_t Hash() const override;
@@ -82,9 +78,6 @@
     /// @returns true if the this type is equal to the given type
     bool Equals(const Type& other) const override;
 
-    /// @returns the struct
-    const ast::Struct* Declaration() const { return declaration_; }
-
     /// @returns the source of the structure
     tint::Source Source() const { return source_; }
 
@@ -92,11 +85,11 @@
     Symbol Name() const { return name_; }
 
     /// @returns the members of the structure
-    const StructMemberList& Members() const { return members_; }
+    utils::VectorRef<const StructMemberBase*> Members() const { return members_; }
 
     /// @param name the member name to look for
     /// @returns the member with the given name, or nullptr if it was not found.
-    const StructMember* FindMember(Symbol name) const;
+    const StructMemberBase* FindMember(Symbol name) const;
 
     /// @returns the byte alignment of the structure
     /// @note this may differ from the alignment of a structure member of this
@@ -158,28 +151,131 @@
     std::string Layout(const tint::SymbolTable& symbols) const;
 
     /// @param concrete the conversion-rank ordered concrete versions of this abstract structure.
-    void SetConcreteTypes(utils::VectorRef<const Struct*> concrete) { concrete_types_ = concrete; }
+    void SetConcreteTypes(utils::VectorRef<const StructBase*> concrete) {
+        concrete_types_ = concrete;
+    }
 
     /// @returns the conversion-rank ordered concrete versions of this abstract structure, or an
     /// empty vector if this structure is not abstract.
     /// @note only structures returned by builtins may be abstract (e.g. modf, frexp)
-    utils::VectorRef<const Struct*> ConcreteTypes() const { return concrete_types_; }
+    utils::VectorRef<const StructBase*> ConcreteTypes() const { return concrete_types_; }
 
   private:
-    ast::Struct const* const declaration_;
     const tint::Source source_;
     const Symbol name_;
-    const StructMemberList members_;
+    const utils::Vector<const StructMemberBase*, 4> members_;
     const uint32_t align_;
     const uint32_t size_;
     const uint32_t size_no_padding_;
     std::unordered_set<ast::AddressSpace> address_space_usage_;
     std::unordered_set<PipelineStageUsage> pipeline_stage_uses_;
-    utils::Vector<const Struct*, 2> concrete_types_;
+    utils::Vector<const StructBase*, 2> concrete_types_;
+};
+
+/// Struct holds the semantic information for structures.
+class Struct final : public Castable<Struct, StructBase> {
+  public:
+    /// Constructor
+    /// @param declaration the AST structure declaration
+    /// @param source the source of the structure
+    /// @param name the name of the structure
+    /// @param members the structure members
+    /// @param align the byte alignment of the structure
+    /// @param size the byte size of the structure
+    /// @param size_no_padding size of the members without the end of structure
+    /// alignment padding
+    Struct(const ast::Struct* declaration,
+           tint::Source source,
+           Symbol name,
+           utils::VectorRef<const StructMember*> members,
+           uint32_t align,
+           uint32_t size,
+           uint32_t size_no_padding);
+
+    /// Destructor
+    ~Struct() override;
+
+    /// @returns the struct
+    const ast::Struct* Declaration() const { return declaration_; }
+
+    /// @returns the members of the structure
+    utils::VectorRef<const StructMember*> Members() const {
+        return Base::Members().ReinterpretCast<const StructMember*>();
+    }
+
+  private:
+    ast::Struct const* const declaration_;
+};
+
+/// StructMemberBase holds the semantic information for structure members.
+class StructMemberBase : public Castable<StructMemberBase, Node> {
+  public:
+    /// Constructor
+    /// @param source the source of the struct member
+    /// @param name the name of the structure member
+    /// @param type the type of the member
+    /// @param index the index of the member in the structure
+    /// @param offset the byte offset from the base of the structure
+    /// @param align the byte alignment of the member
+    /// @param size the byte size of the member
+    /// @param location the location attribute, if present
+    StructMemberBase(tint::Source source,
+                     Symbol name,
+                     const sem::Type* type,
+                     uint32_t index,
+                     uint32_t offset,
+                     uint32_t align,
+                     uint32_t size,
+                     std::optional<uint32_t> location);
+
+    /// Destructor
+    ~StructMemberBase() override;
+
+    /// @returns the source the struct member
+    const tint::Source& Source() const { return source_; }
+
+    /// @returns the name of the structure member
+    Symbol Name() const { return name_; }
+
+    /// Sets the owning structure to `s`
+    /// @param s the new structure owner
+    void SetStruct(const sem::StructBase* s) { struct_ = s; }
+
+    /// @returns the structure that owns this member
+    const sem::StructBase* Struct() const { return struct_; }
+
+    /// @returns the type of the member
+    const sem::Type* Type() const { return type_; }
+
+    /// @returns the member index
+    uint32_t Index() const { return index_; }
+
+    /// @returns byte offset from base of structure
+    uint32_t Offset() const { return offset_; }
+
+    /// @returns the alignment of the member in bytes
+    uint32_t Align() const { return align_; }
+
+    /// @returns byte size
+    uint32_t Size() const { return size_; }
+
+    /// @returns the location, if set
+    std::optional<uint32_t> Location() const { return location_; }
+
+  private:
+    const tint::Source source_;
+    const Symbol name_;
+    const sem::StructBase* struct_;
+    const sem::Type* type_;
+    const uint32_t index_;
+    const uint32_t offset_;
+    const uint32_t align_;
+    const uint32_t size_;
+    const std::optional<uint32_t> location_;
 };
 
 /// StructMember holds the semantic information for structure members.
-class StructMember final : public Castable<StructMember, Node> {
+class StructMember final : public Castable<StructMember, StructMemberBase> {
   public:
     /// Constructor
     /// @param declaration the AST declaration node
@@ -207,48 +303,11 @@
     /// @returns the AST declaration node
     const ast::StructMember* Declaration() const { return declaration_; }
 
-    /// @returns the source the struct member
-    const tint::Source& Source() const { return source_; }
-
-    /// @returns the name of the structure member
-    Symbol Name() const { return name_; }
-
-    /// Sets the owning structure to `s`
-    /// @param s the new structure owner
-    void SetStruct(const sem::Struct* s) { struct_ = s; }
-
     /// @returns the structure that owns this member
-    const sem::Struct* Struct() const { return struct_; }
-
-    /// @returns the type of the member
-    const sem::Type* Type() const { return type_; }
-
-    /// @returns the member index
-    uint32_t Index() const { return index_; }
-
-    /// @returns byte offset from base of structure
-    uint32_t Offset() const { return offset_; }
-
-    /// @returns the alignment of the member in bytes
-    uint32_t Align() const { return align_; }
-
-    /// @returns byte size
-    uint32_t Size() const { return size_; }
-
-    /// @returns the location, if set
-    std::optional<uint32_t> Location() const { return location_; }
+    const sem::Struct* Struct() const { return static_cast<const sem::Struct*>(Base::Struct()); }
 
   private:
     const ast::StructMember* const declaration_;
-    const tint::Source source_;
-    const Symbol name_;
-    const sem::Struct* struct_;
-    const sem::Type* type_;
-    const uint32_t index_;
-    const uint32_t offset_;
-    const uint32_t align_;
-    const uint32_t size_;
-    const std::optional<uint32_t> location_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/struct_test.cc b/src/tint/sem/struct_test.cc
index 6a88307..55744d1 100644
--- a/src/tint/sem/struct_test.cc
+++ b/src/tint/sem/struct_test.cc
@@ -26,8 +26,8 @@
     auto name = Sym("S");
     auto* impl = create<ast::Struct>(name, utils::Empty, utils::Empty);
     auto* ptr = impl;
-    auto* s = create<sem::Struct>(impl, impl->source, impl->name, StructMemberList{},
-                                  4u /* align */, 8u /* size */, 16u /* size_no_padding */);
+    auto* s = create<sem::Struct>(impl, impl->source, impl->name, utils::Empty, 4u /* align */,
+                                  8u /* size */, 16u /* size_no_padding */);
     EXPECT_EQ(s->Declaration(), ptr);
     EXPECT_EQ(s->Align(), 4u);
     EXPECT_EQ(s->Size(), 8u);
@@ -36,10 +36,10 @@
 
 TEST_F(StructTest, Hash) {
     auto* a_impl = create<ast::Struct>(Sym("a"), utils::Empty, utils::Empty);
-    auto* a = create<sem::Struct>(a_impl, a_impl->source, a_impl->name, StructMemberList{},
+    auto* a = create<sem::Struct>(a_impl, a_impl->source, a_impl->name, utils::Empty,
                                   4u /* align */, 4u /* size */, 4u /* size_no_padding */);
     auto* b_impl = create<ast::Struct>(Sym("b"), utils::Empty, utils::Empty);
-    auto* b = create<sem::Struct>(b_impl, b_impl->source, b_impl->name, StructMemberList{},
+    auto* b = create<sem::Struct>(b_impl, b_impl->source, b_impl->name, utils::Empty,
                                   4u /* align */, 4u /* size */, 4u /* size_no_padding */);
 
     EXPECT_NE(a->Hash(), b->Hash());
@@ -47,10 +47,10 @@
 
 TEST_F(StructTest, Equals) {
     auto* a_impl = create<ast::Struct>(Sym("a"), utils::Empty, utils::Empty);
-    auto* a = create<sem::Struct>(a_impl, a_impl->source, a_impl->name, StructMemberList{},
+    auto* a = create<sem::Struct>(a_impl, a_impl->source, a_impl->name, utils::Empty,
                                   4u /* align */, 4u /* size */, 4u /* size_no_padding */);
     auto* b_impl = create<ast::Struct>(Sym("b"), utils::Empty, utils::Empty);
-    auto* b = create<sem::Struct>(b_impl, b_impl->source, b_impl->name, StructMemberList{},
+    auto* b = create<sem::Struct>(b_impl, b_impl->source, b_impl->name, utils::Empty,
                                   4u /* align */, 4u /* size */, 4u /* size_no_padding */);
 
     EXPECT_TRUE(a->Equals(*a));
@@ -61,8 +61,8 @@
 TEST_F(StructTest, FriendlyName) {
     auto name = Sym("my_struct");
     auto* impl = create<ast::Struct>(name, utils::Empty, utils::Empty);
-    auto* s = create<sem::Struct>(impl, impl->source, impl->name, StructMemberList{},
-                                  4u /* align */, 4u /* size */, 4u /* size_no_padding */);
+    auto* s = create<sem::Struct>(impl, impl->source, impl->name, utils::Empty, 4u /* align */,
+                                  4u /* size */, 4u /* size_no_padding */);
     EXPECT_EQ(s->FriendlyName(Symbols()), "my_struct");
 }
 
@@ -116,7 +116,7 @@
     ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
 
     auto* sem = p.Sem().Get(st);
-    ASSERT_EQ(2u, sem->Members().size());
+    ASSERT_EQ(2u, sem->Members().Length());
 
     EXPECT_TRUE(sem->Members()[0]->Location().has_value());
     EXPECT_EQ(sem->Members()[0]->Location().value(), 1u);
diff --git a/src/tint/sem/type_test.cc b/src/tint/sem/type_test.cc
index 9148322..90e114f 100644
--- a/src/tint/sem/type_test.cc
+++ b/src/tint/sem/type_test.cc
@@ -47,7 +47,7 @@
     const sem::Struct* str_f32 = create<Struct>(nullptr,
                                                 Source{},
                                                 Sym("str_f32"),
-                                                StructMemberList{
+                                                utils::Vector{
                                                     create<StructMember>(
                                                         /* declaration */ nullptr,
                                                         /* source */ Source{},
@@ -65,7 +65,7 @@
     const sem::Struct* str_f16 = create<Struct>(nullptr,
                                                 Source{},
                                                 Sym("str_f16"),
-                                                StructMemberList{
+                                                utils::Vector{
                                                     create<StructMember>(
                                                         /* declaration */ nullptr,
                                                         /* source */ Source{},
@@ -83,7 +83,7 @@
     sem::Struct* str_af = create<Struct>(nullptr,
                                          Source{},
                                          Sym("str_af"),
-                                         StructMemberList{
+                                         utils::Vector{
                                              create<StructMember>(
                                                  /* declaration */ nullptr,
                                                  /* source */ Source{},
diff --git a/src/tint/transform/array_length_from_uniform.cc b/src/tint/transform/array_length_from_uniform.cc
index 70097f2..e49c215 100644
--- a/src/tint/transform/array_length_from_uniform.cc
+++ b/src/tint/transform/array_length_from_uniform.cc
@@ -145,7 +145,7 @@
             if (auto* str = storage_buffer_type->As<sem::Struct>()) {
                 // The variable is a struct, so subtract the byte offset of the array
                 // member.
-                auto* array_member_sem = str->Members().back();
+                auto* array_member_sem = str->Members().Back();
                 array_type = array_member_sem->Type()->As<sem::Array>();
                 total_size = b.Sub(total_storage_buffer_size, u32(array_member_sem->Offset()));
             } else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
diff --git a/src/tint/transform/calculate_array_length.cc b/src/tint/transform/calculate_array_length.cc
index 9dcdd7b..a87e11d 100644
--- a/src/tint/transform/calculate_array_length.cc
+++ b/src/tint/transform/calculate_array_length.cc
@@ -207,7 +207,7 @@
                                 [&](const sem::Struct* str) {
                                     // The variable is a struct, so subtract the byte offset of
                                     // the array member.
-                                    auto* array_member_sem = str->Members().back();
+                                    auto* array_member_sem = str->Members().Back();
                                     total_size = b.Sub(total_size, u32(array_member_sem->Offset()));
                                     return array_member_sem->Type()->As<sem::Array>();
                                 },
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 671ebc4..1b674ba 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -714,7 +714,7 @@
                 TINT_ASSERT(Transform, str && str->Declaration() == nullptr);
 
                 utils::Vector<const ast::StructMember*, 8> ast_members;
-                ast_members.Reserve(str->Members().size());
+                ast_members.Reserve(str->Members().Length());
                 for (auto& m : str->Members()) {
                     ast_members.Push(
                         b.Member(ctx.Clone(m->Name()), CreateASTTypeFor(ctx, m->Type())));
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
index b6c93b2..c8f8322 100644
--- a/src/tint/transform/num_workgroups_from_uniform.cc
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -117,7 +117,7 @@
                 ctx.Remove(str->Declaration()->members, member->Declaration());
 
                 // If this is the only member, remove the struct and parameter too.
-                if (str->Members().size() == 1) {
+                if (str->Members().Length() == 1) {
                     ctx.Remove(func->params, param->Declaration());
                     ctx.Remove(src->AST().GlobalDeclarations(), str->Declaration());
                 }
diff --git a/src/tint/transform/transform_test.cc b/src/tint/transform/transform_test.cc
index 29a21e1..001d38d 100644
--- a/src/tint/transform/transform_test.cc
+++ b/src/tint/transform/transform_test.cc
@@ -122,8 +122,8 @@
 TEST_F(CreateASTTypeForTest, Struct) {
     auto* str = create([](ProgramBuilder& b) {
         auto* decl = b.Structure("S", {});
-        return b.create<sem::Struct>(decl, decl->source, decl->name, sem::StructMemberList{},
-                                     4u /* align */, 4u /* size */, 4u /* size_no_padding */);
+        return b.create<sem::Struct>(decl, decl->source, decl->name, utils::Empty, 4u /* align */,
+                                     4u /* size */, 4u /* size_no_padding */);
     });
     ASSERT_TRUE(str->Is<ast::TypeName>());
     EXPECT_EQ(ast_type_builder.Symbols().NameFor(str->As<ast::TypeName>()->name), "S");
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 5afaf95..4f86d35 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -295,7 +295,7 @@
         } else if (auto* str = decl->As<ast::Struct>()) {
             auto* sem = builder_.Sem().Get(str);
             bool has_rt_arr = false;
-            if (auto* arr = sem->Members().back()->Type()->As<sem::Array>()) {
+            if (auto* arr = sem->Members().Back()->Type()->As<sem::Array>()) {
                 has_rt_arr = arr->Count()->Is<sem::RuntimeArrayCount>();
             }
             bool is_block =
@@ -2385,7 +2385,7 @@
 
             ScopedParen sp(out);
 
-            for (size_t i = 0; i < s->Members().size(); i++) {
+            for (size_t i = 0; i < s->Members().Length(); i++) {
                 if (i > 0) {
                     out << ", ";
                 }
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 0c3856a..944481c 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -3380,7 +3380,7 @@
 
             auto emit_member_values = [&](std::ostream& o) {
                 o << "{";
-                for (size_t i = 0; i < s->Members().size(); i++) {
+                for (size_t i = 0; i < s->Members().Length(); i++) {
                     if (i > 0) {
                         o << ", ";
                     }
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 8b4da04..1056026 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -1761,8 +1761,8 @@
                 return true;
             }
 
-            auto& members = s->Members();
-            for (size_t i = 0; i < members.size(); i++) {
+            auto members = s->Members();
+            for (size_t i = 0; i < members.Length(); i++) {
                 if (i > 0) {
                     out << ", ";
                 }
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index f2b8f85..b5910ff 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -1706,7 +1706,7 @@
             }
             return composite(count.value());
         },
-        [&](const sem::Struct* s) { return composite(s->Members().size()); },
+        [&](const sem::Struct* s) { return composite(s->Members().Length()); },
         [&](Default) {
             error_ = "unhandled constant type: " + builder_.FriendlyName(ty);
             return 0;
@@ -3922,7 +3922,7 @@
         push_annot(spv::Op::OpDecorate, {Operand(struct_id), U32Operand(SpvDecorationBlock)});
     }
 
-    for (uint32_t i = 0; i < struct_type->Members().size(); ++i) {
+    for (uint32_t i = 0; i < struct_type->Members().Length(); ++i) {
         auto mem_id = GenerateStructMember(struct_id, i, struct_type->Members()[i]);
         if (mem_id == 0) {
             return false;